AESで不正な鍵で復号した時に何が起きるか
AESには間違ったキーを判定する機能はありません
間違えた鍵を与えると、間違えた鍵に基づいて計算して復号します。
require 'openssl'
data = "\x00" * 16
cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
cipher.padding = 0
cipher.encrypt
cipher.key = "\x00" * cipher.key_len
cipher.iv = "\x00" * cipher.key_len
encrypted_data = cipher.update(data) + cipher.final
puts encrypted_data.unpack("H*")[0]
#=> dc95c078a2408989ad48a21492842087
cipher.reset
cipher.decrypt
cipher.padding = 0
cipher.key = "\x00" * cipher.key_len
cipher.iv = "\x00" * cipher.key_len
decrypted_data = cipher.update(encrypted_data) + cipher.final
puts decrypted_data.unpack("H*")[0]
#=> 00000000000000000000000000000000
#正しく復号できた
cipher.reset
cipher.decrypt
cipher.padding = 0
cipher.key = "\x00" * (cipher.key_len - 1) + "\x01"
cipher.iv = "\x00" * cipher.key_len
decrypted_data = cipher.update(encrypted_data) + cipher.final
puts decrypted_data.unpack("H*")[0]
#=> 4b8a9dadd55b82f7da7d776bad7476ff
#間違えた
キーを間違えると例外が起きる?
require 'openssl'
data = "\x00" * 16
cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
cipher.encrypt
key = "\x00" * cipher.key_len
iv = "\x00" * cipher.key_len
cipher.key = key
cipher.iv = iv
encrypted_data = cipher.update(data) + cipher.final
puts encrypted_data.unpack("H*")[0]
#=> dc95c078a2408989ad48a21492842087f3c003ddc4a7b8a94baedffc3d214c38
cipher.reset
cipher.decrypt
key = "\x00" * (cipher.key_len - 1) + "\x01"
cipher.key = key
cipher.iv = iv
decrypted_data = cipher.update(encrypted_data) + cipher.final
puts decrypted_data.unpack("H*")[0]
#=> raise error (bad decrypt (OpenSSL::Cipher::CipherError))
最初のコードからしれっとcipher.padding = 0
を削っています。そうすると、例外が起きるようになりました。
##padding
ブロック暗号は、定められたサイズのブロックごとにサイズを行います。AESの場合128bit(16byte)です。データがブロックサイズで割りきれないサイズだとと困ったことになるので、ブロックサイズで割り切れるようにダミーデータを埋める(padding)処理が必要です。
###PKCS#7パディング
一般に使われるのはPKCS#7パディングという方法です。16byteブロックの場合、
- 1byte足りない→ 0x01で埋める
- 2byte足りない→ 0x02 0x02で埋める
- 3byte足りない→ 0x03 0x03 0x03で埋める
- :
- 15byte足りない→0x0f 15個で埋める
という形でブロックサイズに合わせます。特別に、元データがブロックサイズの区切りと同じ場合、0x10 16個の1ブロックを追加します。上のコードのコメント部を見比べると、32byteになっています。
##なぜ例外が起きたか
上記のパディングがあるので、最終ブロックには特定のパターンがあります。最終バイトの値と同じ個数だけ最終バイトから前に向かって並んでいるはずです。
しかし、不正な鍵で復号すると、この部分のデータも壊れます。最終ブロックだと認識できないのに復号処理を終了させようとしたために、異常となるわけです。
##鍵が違うことと例外が起きることの関係
たとえば、15byteのデータを暗号化するときは、上記の通り0x01がパディングとして追加されます。これを暗号化したデータを、任意のキーで復号すると、最終バイトは0x00~0xffのどれかになります。1/256の確率で、正しいパディングである0x01が出てきます。実際に下のスクリプトで試すと、正しいキーであるi=0の時の他に、i=71でもデータが出てきます。さらに多数のキーで試せば、1/256^2の確率で0x02 0x02が現れて14byteのデータとして復号されたり、1/256^3の確率で0x03 0x03 0x03が現れて13byteのデータが現れて…ということもあるでしょう。
require 'openssl'
data = "\x00" * 1
cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
cipher.encrypt
cipher.key = "\x00" * cipher.key_len
cipher.iv = "\x00" * cipher.key_len
encrypted_data = cipher.update(data) + cipher.final
(0..255).each do |i|
cipher.reset
cipher.decrypt
cipher.key = "\x00" * (cipher.key_len - 1) + [i].pack("c1")
cipher.iv = "\x00" * cipher.key_len
decrypted_data = cipher.update(encrypted_data) + cipher.final rescue next
puts decrypted_data.unpack("H*")[0]
end
ということで、処理が成功すればキーは正しかった、とは言えません。
では、キーが正しければ必ず成功するか、というと、これも間違いです。パディング部が何らかの理由で化けてしまうと、やはり処理には失敗します。データの伝送形態によってはデータサイズが増減してブロックサイズの区切りとずれてしまう、ということもあるでしょう。
#まとめ
- AES自体の機能として、鍵が違ったら復号に失敗する、ということはありません。
- パディングが絡むと復号に失敗しますが、偶然に成功してしまうこともあるので、不正な鍵→復号失敗、ではありまえん
- 正しい鍵であっても、データ側の問題で復号に失敗することもあります。したがって、復号失敗→不正な鍵、でもありません。