at posts/single.html

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

利用シーンなんだけど、例えば、アリスが遠くにいる相手(ボブ)にファイルを送るシーンを考えてみる。 「鍵(パスワード)はアリスとボブしか知らないこと」が前提条件。

  1. アリスは、送りたいファイルとパスワードで MAC 値を生成する。
  2. アリスは、ファイルと MAC 値をボブへ送る。
  3. ボブは、アリスと同じようにファイルとパスワードで MAC 値を生成する。
  4. ボブは、生成した 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

今日はここまで。

関連する日記