irb で覚えるハッシュ関数
ハッシュ関数は任意の文字列から固定の文字列(ハッシュ値)を生成する関数。 詳しくは、@ITの記事やWikiPediaを参照。 以下のような特徴がある。
- 元の文字列が変わると生成されるハッシュ値も変わる
- ハッシュ値から元の文字列を推測できない
例えば、パスワードをデータベースに格納するときや、ファイル内容の変更チェックをするときに使われる。後者の例では、大きなファイルの場合、元のファイル同士でDiffを取るよりもハッシュ値を比較したほうが早いというメリットがある。
早速、 irb を起動して使ってみる。
irb(main):001:0> require 'openssl' => true irb(main):002:0> sha1 = OpenSSL::Digest::SHA1.new("abc") => a9993e364706816aba3e25717850c26c9cd0d89d
これで、文字列 abc に対するハッシュ値 (a9993e364706816aba3e25717850c26c9cd0d89d) が生成された。 update メソッドを使うと、入力文字列を更新できる。
irb(main):003:0> sha1.update("def") => 1f8ac10f23c5b5bc1167bda84b833e5c057a77d2
このハッシュ値は、 "abcdef" に対するハッシュ値と同じになる。
irb(main):005:0> OpenSSL::Digest::SHA1.new("abcdef") => 1f8ac10f23c5b5bc1167bda84b833e5c057a77d2
update で文字列を追加できるようになっている理由は、大きなファイル(例えば100MB)のハッシュ値を生成するときに、メモリを大量に確保しなくてもいいようにするため(だと思う)。 大きなファイルを一気に渡したとしても、ハッシュ関数はそれを一定のサイズに分割して処理する(独自MACの危険性を参照)。 だったら分割して渡そうよ、という感じか。
元のデータが1文字違うと、生成されるハッシュ値はまったく違うものになる。
irb(main):019:0> OpenSSL::Digest::SHA1.new("abcdee") => 7a03467d14a694263d9db991f40ebf2e5c6b666b
ハッシュ値を得るには、 hexdigest メソッドを使う(さっきの例のように new や update の戻り値でも得られる)。
irb(main):014:0> sha1.hexdigest => "1f8ac10f23c5b5bc1167bda84b833e5c057a77d2"
これは16進表記された値。もとのバイナリデータを得るには、 digest メソッドを使う。
irb(main):015:0> sha1.digest => "\037\212\301\017#\305\265\274\021g\275\250K\203>\\\005zw\322"
クラスメソッド (SHA1::hexdigest) を使ってハッシュ値を得ることもできる。
irb(main):016:0> OpenSSL::Digest::SHA1::hexdigest("abcdef") => "1f8ac10f23c5b5bc1167bda84b833e5c057a77d2"
ちなみに、 OpenSSL を使わずに Ruby で書かれたハッシュライブラリ (digest/sha1) もある。 使い方は、ほぼ同じ (reset メソッドが無かったりするけど) 。
irb(main):020:0> require 'digest/sha1' => true irb(main):023:0> sha1 = Digest::SHA1.new("abc") => a9993e364706816aba3e25717850c26c9cd0d89d irb(main):025:0> sha1.update("def") => 589c22335a381f122d129225f5c0ba3056ed5811 irb(main):026:0> sha1.hexdigest => "589c22335a381f122d129225f5c0ba3056ed5811"
sha1sum.rb
UNIX には、ファイルのハッシュ値を取ってくれる sha1sum というコマンドがある。
$ sha1sum test 5fa5bc1d8b34b1d48396f970d6f22484579b8f35 test
これと似たコマンド (sha1sum.rb) を Ruby で作る。
#!/usr/bin/env ruby require 'openssl' sha1 = OpenSSL::Digest::SHA1.new ARGF.each do |line| sha1.update(line) end puts sha1.hexdigest
行単位で読み込み、ハッシュ値を update するようにしている。 実行すると sha1sum コマンドと同じハッシュ値が得られる。
$ ruby sha1sum.rb test 5fa5bc1d8b34b1d48396f970d6f22484579b8f35
今日はここまで。 次はハッシュ関数を使った HMAC に挑戦。