OpenSSL::Cipherでのivとkeyの形式

結論

ivとkeyは(opensslで使える)テキスト形式*1ではなく、オリジナルのバイナリ形式のまま使いましょう。

経緯

$ openssl aes-128-cbc -d -in a.in -out a.out -p -nosalt -iv 00000000000000000000000000000001 -K 9c4ceae9595bcde045cc53c1a214e512

というdecrypt(復号化)をRubyで行いたかったので

def decrypt(str, key, iv)
  decipher = OpenSSL::Cipher.new("aes-128-cbc")
  decipher.decrypt
  decipher.key = key
  decipher.iv = iv
  decipher.update(str) + decipher.final
end

というメソッドを使って

iv  = "00000000000000000000000000000001" # '%032x' % 1
key = "9c4ceae9595bcde045cc53c1a214e512"
a_out = decrypt(a_in, key, iv)

としたら

OpenSSL::Cipher::CipherError: bad decrypt

と怒られました。

解決法

もしもどこかからivやkeyを取ってきた場合にはオリジナルのバイナリのまま使います。
今回のようにopensslで使うようなテキスト形式になっている場合には

iv_bin  = "00000000000000000000000000000001".unpack('a2'*16).map{ |x| x.hex }.pack('C'*16)
# => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
key_bin = "9c4ceae9595bcde045cc53c1a214e512".unpack('a2'*16).map{ |x| x.hex }.pack('C'*16)
# => "\x9CL\xEA\xE9Y[\xCD\xE0E\xCCS\xC1\xA2\x14\xE5\x12"

のように16進数へ変換します*2
なお最後のpack('C'*16)はpack('C*')でもpack('C16')でもかまいません。

参考

Why are the first 16 bytes wrong with Ruby openssl aes 128 cbc? - Stack Overflow
なお、こちらでは最後のpackはpack('c'*16)となっています。unpackで2バイトASCIIに変換→String#hexメソッドで16進数表記と解釈して整数化、なので符号付き整数のほうが適切だと思います。ということで、上ではpack('C'*16)と記載変更しました。

おまけ:逆変換(テキスト変換)

iv_text  = iv_bin .unpack('C'*16).inject(''){ |str, hex| str << '%02x' % hex }
# => "00000000000000000000000000000001"
key_text = key_bin.unpack('C'*16).inject(''){ |str, hex| str << '%02x' % hex }
# => "9c4ceae9595bcde045cc53c1a214e512"

逆変換だから、と思ってunpackしてmap{|x| x.to_s(16) }してpackしたらx.to_s(16)のところで桁数が落ちてしまったので、String#%で二桁確保するようにしました。
もちろんバイナリ変換と対称的にこうやってもOK

iv_text  = iv_bin.unpack('C'*16).map{ |hex| '%02x' % hex }.pack('a2'*16)
# => "00000000000000000000000000000001"
key_text = key_bin.unpack('C'*16).map{ |hex| '%02x' % hex }.pack('a2'*16)
# => "9c4ceae9595bcde045cc53c1a214e512"

ですが、packがあまり意味ないのでinjectを使うほうが好き。

*1:呼び方はテキスト形式でいいのかな?

*2:ただしivは'%032x' % 1のままでもOKでした。なぜかしら。