メッセージダイジェスト(ハッシュ値)の算出ルーチン

メッセージダイジェストとは、 任意の長さの文字列(原文)から、 あるアルゴリズムによって一定の長さのデータを生成する処理を言います。 特にセキュリティ技術の一環として使われる際には、 この生成されたデータは「チェックサム」や「ハッシュ値」とも呼ばれ、 そのアルゴリズムは一般に次のような特徴を持ちます。

  • ハッシュ値から原文を復元することは困難、ほぼ不可能。
  • 同じハッシュ値を生成する全く違う原文を複数作ることは困難、ほぼ不可能。
  • 原文がほんの少し違うだけでも、全く違うハッシュ値を生成する。
これらの特長を持つアルゴリズムを用いたメッセージダイジェストは、 データ通信における改ざんの有無の検出に使うことができ、 広い意味での暗号化通信やユーザ認証、デジタル署名の基盤技術としてよく使われています。
Tcllibの中には、そのようなメッセージダイジェストを行う関数が、 アルゴリズム別に10個くらい登録されています。
ここではその中で著名な4個、cksum、crc32、md5、sha1をご紹介します。 使い方は4個ともほぼ同じで、 文字列またはファイルに対して、ハッシュ値を生成して返す関数が用意されています。
コマンド仕様はcksumとcrc32が一対、md5とsha1が一対になっています。

package require md5

set v1 {abXpGldjagJnag83agkagj;;ashseHGzshZ}
set v2 {abXpGldjagJnag83agkagj;;asnseHGzshZ}
puts [::md5::md5 -hex $v1]
puts [::md5::md5 -hex $v2]

puts "------"

puts [::md5::md5 -hex -file "./testfile1.txt"]
puts [::md5::md5 -hex -file "./testfile2.txt"]
puts [::md5::md5 -hex -file "./testfile3.txt"]

::md5::md5コマンドは、指定された文字列に対するMD5ハッシュ値を返します。 -fileオプションをつけると、引数の文字列ではなく、 その後ろに指定された名前のファイルから内容を読んで、 その内容に対してMD5文字列を算出します。 またMD5文字列のフォーマットですが、デフォルトでは整数値で返りますが、 16進表記も一般的です。そこで(かどうかは分かりませんが…)、 -hexオプションをつけると16進表記で返す機能もついています。

同様に、そのMD5の後継として強化されたSHA1アルゴリズムの実装の例です。

package require sha1

set v1 {abXpGldjagJnag83agkagj;;ashseHGzshZ}
set v2 {abXpGldjagJnag83agkagj;;asnseHGzshZ}
puts [::sha1::sha1 -hex $v1]
puts [::sha1::sha1 -hex $v2]

puts "------"

puts [::sha1::sha1 -hex -file "./testfile1.txt"]
puts [::sha1::sha1 -hex -file "./testfile2.txt"]
puts [::sha1::sha1 -hex -file "./testfile3.txt"]

次に cksumパッケージの::crc::cksumコマンドは、 UNIXシステムコマンドのcksum(1)と互換性のあるチェックサムを算出して返すコマンドです。

package require cksum

set v1 {abXpGldjagJnag83agkagj;;ashseHGzshZ}
set v2 {abXpGldjagJnag83agkagj;;asnseHGzshZ}
puts [::crc::cksum -format %X $v1]
puts [::crc::cksum -format %X $v2]

puts "------"

puts [::crc::cksum -format %X -file "./testfile1.txt"]
puts [::crc::cksum -format %X -file "./testfile2.txt"]
puts [::crc::cksum -format %X -file "./testfile3.txt"]

こちらもmd5コマンドと同様、16進表記ができますがその方法は-format オプションにprintf風の書式を指定(つまり16進なら%xや%X)することで行います。
なお、ファイルを指定するオプションがマニュアルでは-filenameとなっていますが、 実際には-filenameでも-fileでもどちらも認識されます。

最後に、巡回冗長検査(CRC)アルゴリズムを実装した::crc::crc32コマンドです。

package require crc32

set v1 {abXpGldjagJnag83agkagj;;ashseHGzshZ}
set v2 {abXpGldjagJnag83agkagj;;asnseHGzshZ}
puts [::crc::crc32 -format %X $v1]
puts [::crc::crc32 -format %X $v2]

puts "------"

puts [::crc::crc32 -format %X -file "./testfile1.txt"]
puts [::crc::crc32 -format %X -file "./testfile2.txt"]
puts [::crc::crc32 -format %X -file "./testfile3.txt"]

少しは何かの役に立ちそうなサンプルをご紹介しましょう。 以下のスクリプトは、指定された任意の数のディレクトリに含まれるファイルの中から、 内容が完全に一致する2つのファイルを見つけたら、それらを標準出力に出力します。 これは、メッセージダイジェスト(チェックサム)の、 「ほんの少し内容が違うだけでも、全く違う文字列を出力する」性質を使った物です。

package require md5

if {$argc == 0} {
    puts stderr "usage: samefile.tcl directories..."
    exit 1
}
set files {}
foreach dir $argv { lappend files {*}[glob -type f "$dir/*"] }
set fileCount 0
foreach e $files {
    set m [::md5::md5 -hex -file $e]
    if {[info exists arr($m)]} {
        puts $arr($m)
        puts $e
    } else {
        set arr($m) $e
    }
    incr fileCount
}


共通鍵暗号化ルーチン

Tcllibでは、著名な共通鍵暗号化ルーチンが実装されています。 ここでご紹介するのは、RC4、Blowfish、 DES(Data Encrypthon Standard)、そして AES(Advanced Encryption Standard、Rijndaelという名前でも) の4つです。
使い方はどれもほぼ共通で、次の例のように暗号化したい文字列を直接引数に渡すか、 それ以外に-inオプションで既に開かれているファイルチャネルを指定して読ませるか、 -infileオプションでファイル名を指定してその内容を読ませるか、 の3種類の方法は全てのコマンドで共通です。
それでは順番に…、ですが、 共通鍵暗号化アルゴリズムというものは、 新しく現れたものほど強力だというのは一般論として分かるのですが、 同時期に現れたものなどもあり、 特に序列のようなものはないと思います。 私も暗号技術の素人なので、どれがどれだか…という状態なので、 とりあえず目に付いた順に(!!)ご紹介します。

package require rc4

#set message "Hello, World"
set message "お宝は東の山の中腹の寺院より南50mにある楠の根元にあるかもよ。"
set message [encoding convertto euc-jp $message]

set keydata "hirakegoma"
#set keydata [binary format H* 0123456789abcdef]

set edata [::rc4::rc4 -hex -key $keydata $message]
puts $edata
set odata [::rc4::rc4 -key $keydata [binary format H* $edata]]
set odata [encoding convertfrom euc-jp $odata]

puts $odata

まずRC4の暗号化を行う::rc4::rc4です。 -hexは結果を16進数の文字列で返すオプションです。
-keyには暗号化キーを指定しますが、 RC4の場合、上記の"hirakegoma"のように、 特にキーのバイト数に制限はなく、パスワードのような感覚でキーを指定すればOKです。
それで、Tcllibの暗号化ルーチンに共通の注意点として、 暗号化したい文字列が日本語を含んだUnicodeだと、 バイナリに変換しないとうまく暗号化、復号ができず、日本語が化け化けになってしまいます。 (ASCII文字部分は正しく処理できる)
この目的のために、暗号化前にencoding converttoを行い、 復号後にencoding convertfromを行っています。 上の例ではエンコーディングにeuc-jpを指定していますが、 他のエンコーディングでも構いません。
また他の3つに比べたRC4の特徴として、 暗号化なのか、復号なのかという処理方向の指定が不要、 つまり暗号化と復号が対称に行われることがあげられます。

package require blowfish

set message "お宝は南の山の中腹の寺院より西60mにある桜の根元にあるかもよ。"
set message [encoding convertto iso2022-jp $message]

set keydata "hirakegoma"

set edata [::blowfish::blowfish -hex -mode ecb \
 -dir encrypt -key $keydata $message]
puts $edata
set odata [::blowfish::blowfish -mode ecb \
 -dir decrypt -key $keydata [binary format H* $edata]]
set odata [encoding convertfrom iso2022-jp $odata]

puts $odata

次はBlowfishアルゴリズムを実装した::blowfish::blowfishです。
このアルゴリズムには、モードとしてecb(デフォルト)とcbcというものがあり、 また暗号化(encrypt)なのか復号(decrypt)なのかを-dir オプションで指定する必要があります。
ECB(Electronic Code Book)は、原文をブロック単位で暗号化したものを単純に連結して暗号文を作るモードで、 解読への強度が比較的弱めです。短い文の暗号化に向いています。
CBC(Cipher Block Chaining)は、 あるブロックの暗号化結果を次のブロックの暗号化にも波及させる処理を行うことで、 「同じ原文パターンから同じ暗号文パターンが現れる」 ECBの弱点を補強しています。この処理を行うためには、 初期化ベクトル(Initialization Vector)という8バイトのバイナリ値を必要とし、 そのデフォルト値は0x00000000になっています。 初期化ベクトルの値は、 -ivオプションで変えることができます。

次にDES(Data Encryption Standard)です。

package require des

set message "お宝は北の山の中腹の寺院より東80mにある檜の根元にあるかもよ。"
set message [encoding convertto iso2022-jp $message]

set keydata [binary format H* 0123456789abcdef]

set edata [::DES::des -hex -mode ecb \
 -dir encrypt -key $keydata $message]
puts $edata
set odata [::DES::des -mode ecb \
 -dir decrypt -key $keydata [binary format H* $edata]]
set odata [encoding convertfrom iso2022-jp $odata]

puts $odata

DESで注意しないといけないのは、名前空間が大文字なので、 コマンド名は::des::desではなく、::DES::desだということです。
また、DESではRC4やBlowfishと異なり、 -keyオプションで与える暗号化キーは、 必ず8バイト(64ビット)のバイナリ値という制限があります。 上の例ではbinary formatにH*で "0123456789abcdef" の16文字を渡して8バイトのバイナリ文字列を生成しています。 (8文字で8バイトではないですよ! フォーマットがHだと、2文字が1バイトに変換されるので、 16文字必要なのですね)
またDESでは、-modeにecb、cbcのほかにさらに odb(Output Feed Back)、cfb(Cipher Feedback Mode)を加えて4種類が指定可能になっています。 3つめ、4つめは、いずれもECBの 「同じ原文パターンから同じ暗号文パターンが現れる」という弱点と、 CBCの「通信で発生したビットエラーが広範にエラーを発生させる」 という弱点を補うことができるモードで、これらもCBCと同様初期化ベクトルを使います。

最後にAES(Advanced Encryption Standard、Rijndaelとも言ふ)です。

package require aes

set message "お宝は西の山の中腹の寺院より北70mにある柳の根元にあるかもよ。"
set message [encoding convertto euc-jp $message]

set keydata [binary format H* 0123456789abcdef0123456789abcdef]

set edata [::aes::aes -hex -mode ecb \
 -dir encrypt -key $keydata $message]
puts $edata
set odata [::aes::aes -mode ecb \
 -dir decrypt -key $keydata [binary format H* $edata]]
set odata [encoding convertfrom euc-jp $odata]

puts $odata

AESの-modeはecbとcbcだけ(Blowfishと同じ)です。
また、AESの暗号化キーは16、24、32バイト (つまり128、192、256ビット) のいずれかの長さのバイナリ値でなければいけません。 それ以外の長さだとエラーになります。 おさらいですが、16バイトのキーを与えるには、 binary format H*に32桁の16進数を与える必要がある(16桁では駄目) のです。もうお分かりですね?

拡張レビュー分室 top
(first uploaded 2004/03/17 last updated 2011/05/22, MISUMI URANO)