at posts/single.html

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 に挑戦

関連する日記