概要
Ruby on Railsを利用したwebシステムの開発において、公式ドキュメントを参照しつつ
OpenSSLライブラリを利用したAES-256-CBC復号化を試みた際に、鍵の設定において少し詰まってしまったためその備忘録となります。
要点
OpenSSLライブラリを利用してAES-256-CBC復号化を実施する際、鍵・IVは16進数の文字列ではなくバイト列として設定する必要があり、バイト列への変換は以下のような手順で可能
irb(main):028:0> key = ['a377a0c1afb45312592296a5be06384b03adae8172248f4ab85e3b281e7b6c3c'].pack("H*")
=> "\xA3w\xA0\xC1\xAF\xB4S\x12Y\"\x96\xA5\xBE\x068K\x03\xAD\xAE\x81r$\x8FJ\xB8^;(\x1E{l<"
経緯と解決
とある開発のさなか、テスト用の鍵をこのような形式で受領しました
(※こちらは記事用に生成した乱数列になります)
a377a0c1afb45312592296a5be06384b03adae8172248f4ab85e3b281e7b6c3c
今回詰まった点はドキュメントの記載に基づいて、復号化用のインスタンスに鍵をセットしようとした際に遭遇しました
irb(main):014:0> dec = OpenSSL::Cipher.new("AES-256-CBC")
=> #<OpenSSL::Cipher:0x0000019d9c7fd100>
irb(main):015:0> dec.decrypt
=> #<OpenSSL::Cipher:0x0000019d9c7fd100>
irb(main):016:0>
irb(main):017:0> dec.key = 'a377a0c1afb45312592296a5be06384b03adae8172248f4ab85e3b281e7b6c3c'
(irb):17:in `key=': key must be 32 bytes (ArgumentError)
from (irb):17:in `<main>'
from C:/Ruby31-x64/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
from C:/Ruby31-x64/bin/irb:33:in `load'
from C:/Ruby31-x64/bin/irb:33:in `<main>'
鍵を設定しようとした際に例外が発生し、メッセージを確認してみると「鍵は32バイトでなければならない」といった内容であるようです。
ここで頂いた鍵をもう一度確認してみます。
irb(main):021:0> 'a377a0c1afb45312592296a5be06384b03adae8172248f4ab85e3b281e7b6c3c'.length
=> 64
irb(main):022:0> 'a377a0c1afb45312592296a5be06384b03adae8172248f4ab85e3b281e7b6c3c'.bytesize
=> 64
改めて見てみると鍵は一見して64文字、64バイトであるように見えます。
私自身AES暗号化に関する知識に乏しかったこともあり、当初は「鍵長に複数の選択肢があり、OpenSSLライブラリでは32バイトしか対応していないのではないか」と考えていました。
しかし暗号化方式AES-"256" の名前の通り、AES-256においてはあくまでも鍵長は256bit(つまり32bytes)固定であるようです。
少し悩んだ末に一度ドキュメントの実装例に示されている鍵・IV生成の手順から復号化までを実際に試してみることにしました。
手順をなぞっていくと、生成された鍵と手元にある鍵に明確な違いがあることがわかりました
# ドキュメントの実装例に従って生成した鍵の例
"\xD1\x8EA\xF9\xB5\xA14Qma'q\xE4\xA0\"\xC7a\x9C\xE0/\xA1:>\"%\xD1V\xC93\xB5@>"
# 手元の鍵
'a377a0c1afb45312592296a5be06384b03adae8172248f4ab85e3b281e7b6c3c'
# なんだか全然違うぞ!!
ここでの疑問はさておき、一旦暗号化→復号化の一連の流れを試すと最後まで問題なく実施することが出来ました。
試し終わった後、やはり鍵の違いがポイントと感じたためしばらく見比べていると、
実装例中の鍵がバイト列であることに対して、手元の鍵は英数字の乱数文字列というよりは16進数であるという点に気が付きました。
そこで16進数の文字列→バイト列への変換を試みました
1文字1バイトで0~15(4bit相当)を表現している状態から、2文字ずつくっつけて1バイトで0~255を表現するようなイメージです
このような変換にはArrayクラスのpackメソッドが使えるようでした
irb(main):028:0> key = ['a377a0c1afb45312592296a5be06384b03adae8172248f4ab85e3b281e7b6c3c'].pack("H*")
=> "\xA3w\xA0\xC1\xAF\xB4S\x12Y\"\x96\xA5\xBE\x068K\x03\xAD\xAE\x81r$\x8FJ\xB8^;(\x1E{l<"
irb(main):029:0> key.bytesize
=> 32
irb(main):030:0> dec.key = key
=> "\xA3w\xA0\xC1\xAF\xB4S\x12Y\"\x96\xA5\xBE\x068K\x03\xAD\xAE\x81r$\x8FJ\xB8^;(\x1E{l<"
仮説をもとに変換を試みたところ、手元の鍵が実装例と同様の形に変化しており32バイトに変化(!!)していました。ドキドキしながら復号化用インスタンスへの設定を試みたところ無事に通り、その後の復号も問題なく行うことができました。