irb で覚える HMAC
https://www.amazon.co.jp/dp/4797322977
irb で覚えるハッシュ関数の続きは HMAC に挑戦。 HMAC は MAC (Message Authentication Code) の一種で、さっき覚えたハッシュ関数を使って実現する。 ハッシュ関数を使った MAC だから HMAC ということ。
MAC ってのは、メッセージを認証するためのコードで正しい人からメッセージが送られていることを確認するために使う。 結城さんの暗号技術入門には、アリス銀行からボブ銀行への送金依頼(口座番号と送金金額が書かれている)の例が載っている。 ボブ銀行がアリス銀行からのメッセージを受け取った時に、メッセージが本当にアリス銀行から来たものなのか、そして改ざんされていないかを確認するために MAC を使うという説明だった(要は改ざんと成りすましを防ぐ)。
身近(?)なところでは、最近流行の WebAPI (Webアプリ同士のやりとり)でのシグネチャ生成に使われてる。 JugemKey認証APIとか Amazon の Web サービスとか。
仕組み
HMAC は鍵付きハッシュとも呼ばれる。 大ざっぱに言えば、メッセージからハッシュ値を作る時にパスワードをくっつける感じ。 パスワードを知っている人だけがハッシュ値(MAC値)を作ったり、ハッシュ値の正当性を確認したりできる。 詳しくは WikiPedia を参考…だけど、 Ruby っぽく書くならこんな感じか。
value = sha1(key.xor(IPAD) + message) value = sha1(key.xor(OPAD) + value)
これはあくまでイメージ。このままじゃ動かないので注意。
やっていることはシンプルで、パスワードとメッセージを繋げた文字列(バイト列)をハッシュ関数に2回通しているだけ。 このとき、事前にパスワードは2つに分割しておく(IPAD, OPADとの XOR を取っているところがそれ)。 なんで2回もハッシュ関数を通すのか…だけど、「秘密鍵とハッシュ関数を組み合わせる手法によっては、セキュリティホールが発生する可能性がある。このような穴がないことが証明されている組み合わせ手法が HMAC である」ということらしい。
HMAC で使うハッシュ関数は、 MD5 や SHA1 など、自由に選べるようになっている。 ハッシュ関数の結果が MAC 値になるので、 MAC 値の長さは使うハッシュ関数によって変わる。
irb で動かしてみる
Ruby/OpenSSL のマニュアルには HMAC の説明がないので、ただのにっきのサンプルを参考にして動かしてみる。 hexdigest クラスメソッドに、使用するハッシュ関数と、鍵(パスワード)と、メッセージを渡すだけだった。
irb(main):001:0> require 'openssl' => true irb(main):002:0> OpenSSL::HMAC::hexdigest(OpenSSL::Digest::SHA1.new, "key", 'message') => "2088df74d5f2146b48146caf4965377e9d0be3a4"
インスタンスを作ってみる。 なぜかクラスメソッドの時と引数の順番が逆…。
irb(main):005:0> hmac = OpenSSL::HMAC.new("key", OpenSSL::Digest::SHA1.new) => f42bb0eeb018ebbd4597ae7213711ec60760843f
update メソッドを使って、メッセージを分割して渡すことができる。 最終的に生成される MAC 値は、一気にまとめて渡した場合と同じになる。
irb(main):008:0> hmac.update("messa") => 4663013b3d6259edf2ae06cca4972cad7b253a5c irb(main):009:0> hmac.update("ge") => 2088df74d5f2146b48146caf4965377e9d0be3a4
digest メソッドでは生のバイト列、 hexdigest メソッドでは16進表現の文字列が取得できる。
irb(main):011:0> hmac.digest => "\210(中略)" irb(main):012:0> hmac.hexdigest => "2088df74d5f2146b48146caf4965377e9d0be3a4"
ハッシュ関数として SHA1 を使ったので、 MAC 値の長さは20バイト (160ビット) になる。
irb(main):013:0> hmac.digest.size => 20
hmacsum.rb
昨日の sha1sum.rb を改造して、 hmacsum.rb を作った。
#!/usr/bin/env ruby require 'openssl' key = ARGV.shift sha1 = OpenSSL::Digest::SHA1.new hmac = OpenSSL::HMAC.new(key, sha1) ARGF.each do |line| hmac.update(line) end puts hmac.hexdigest
鍵(パスワード)と、 MAC 値を生成したいファイル名を引数に渡して使う。
$ ruby hmacsum.rb password /var/log/dmesg b9e41a3ebfb73a21404102f3d13340d94f538c3e
利用シーンなんだけど、例えば、アリスが遠くにいる相手(ボブ)にファイルを送るシーンを考えてみる。 「鍵(パスワード)はアリスとボブしか知らないこと」が前提条件。
- アリスは、送りたいファイルとパスワードで MAC 値を生成する。
- アリスは、ファイルと MAC 値をボブへ送る。
- ボブは、アリスと同じようにファイルとパスワードで MAC 値を生成する。
- ボブは、生成した MAC 値がアリスから送られてきた MAC 値と同じかどうかを確認する。
ここでのポイント。
- アリスとボブは事前に鍵(パスワード)を共有しておく。
- 鍵を持っている人だけが、ファイルの正当性を確認できる
- つまり、第三者にはファイルの正当性を証明できない(証明するためにはパスワードを教えないといけない)
ってことで、特定の二者間で使う分には便利だけど、たくさんの人にメッセージやファイルを送る時には使えない。 そういうときにはデジタル署名を使う(例えば、ActiveXを配布する時とかについているアレ)。
myhmac.rb
最初に書いたとおり、 HMAC の仕組みはシンプル。 なので試しに HMAC 関数を自作してみた。 鍵 (key) を2つに分割 (ikey, okey) して、メッセージともに2回ハッシュ値を取っている。
#!/usr/bin/env ruby require 'openssl' IPAD = "\x36" * 64 OPAD = "\x5c" * 64 def hmac_sha1(key, message) ikey = IPAD okey = OPAD key.size.times do |i| ikey[i] = key[i] ^ IPAD[i] okey[i] = key[i] ^ OPAD[i] end value = OpenSSL::Digest::SHA1.digest(ikey + message) value = OpenSSL::Digest::SHA1.digest(okey + value) value.unpack("H*")[0] # digest -> hexdigest end key = ARGV[0] message = ARGV[1] puts hmac_sha1(key, message)
実行結果 (MAC値) は OpenSSL::HMAC の場合と同じになった。
$ ruby myhmac.rb key message 2088df74d5f2146b48146caf4965377e9d0be3a4
今日はここまで。