「AESで暗号化した時の文字列の長さってどれくらい変わるの?」という質問に答えようと調べた話
経緯
「AESで暗号化した時の文字列の長さってどれくらい変わるんだろう、って調べているけど全然出てこねえ。実際試してみると1.5倍くらいっぽいが。」
という質問が友人から投げられてきたので調べました。
前提
- AES256
- 平文は 99 文字
調査ログ
padding のせいだよ説
AESはブロック暗号方式であるので、ブロックごとの長さに足りない分をpaddingされるだけではないか?と予想。
AESのブロック長は 128bit = 16byte
なので Math.ceil(99/16) * 16 = 112
で 112 文字になるのでは?
→ 1.5倍だから150文字くらいになっているのだろう……おかしい
99文字とはいったが1文字1byteとは限らないのでは?
→ ascii 範囲内であることが判明。utf-16とかじゃなければ1文字1byteになりそう(´・ω・`) 99文字 → 152文字らしいので差が53byteある。そもそもブロック長以上の差分が出るわけがないのでおかしい。1
padding 説は濃厚だがそれだけではなさそう。
padding って実際なにしてんの
ここでインターネットに本格的に頼りだす。記事とそこで挙げられている参考実装を読む
参考実装だと 128 bit ごとに入れてるのでそれより長い長さの暗号文をどう扱うかが問題。
もう実装読むしかねえという気持ちになり javax.crypto のソースを漁ると Cipher.getInstance("AES/CBC/PKCS5Padding");
という記述を発見。
また、本人から参考にしている Qiita 記事をもらう。
PKCS5Padding
どうも名のある padding 方式らしいので調べたらRFCとかにもある模様。PKCS#5, PKCS#7のバリエーションがあって、基本的に余った文字数が1だったら 0x01
で、2だったら 0x02
でっていうふうに埋めてく模様。
余談ですが、Java の javax.crypto.Cipher にある PKCS5Padding は、名前は PKCS5 ですが、実質は PKCS#7 相当の動きをするようです。
ひどい話だ……。実用上あんまり問題はないんだろうが。やはりこれを読んでても padding でやたら長くなるのはおかしそうだ。
base64 で膨れてる説
Q: もしかして暗号文って最後に==って出てません?
A: 出てるよ
base64 エンコードされていたことが判明。base64エンコードは長さが変わるのでこれっぽい。
平文→暗号化に入るとき(128bit単位にパディングされて暗号化)→暗号化(パディングされたのと長さ同じ)→base64
というわけで 99 bytes を変換したら 152 bytes になるかを検証。
平文: 99 bytes 平文(パディング): 112 bytes 暗号文: 112 bytes base64: 152 bytes ( = Math.ceil(Math.ceil(112 * 8 /6) / 4) * 4 )
ok
そんでもとの質問は「AESで暗号化した時の文字列の長さってどれくらい変わるの?」だったので、ざっくり1.5倍かなーと思いつつ試算
4(n+16)/3 >= 3n/2 ⇔ 128 >= n
ダメじゃん!2
仮に2倍とっておくと 8>=n におさえられて安心。
input の平文の長さが決まってれば 2 倍じゃなくてかなりきれいに扱えそう。
実験
ここまで理解したところで実験。
encrypted len - Google スプレッドシート
たしかに1.5倍だとところどころ抑えられていないことを確認。 とゆーわけで input の長さによるが、2倍あればおおむねいける。固定長ならもっと攻めることができる、という感じでした。
# https://docs.ruby-lang.org/ja/latest/class/OpenSSL=3a=3aCipher.html require 'openssl' require 'base64' class Encryptor def initialize(data) @salt = OpenSSL::Random.random_bytes(8) @pass = "**secret password**" @data = data end def exec # 暗号化器を作成する enc = OpenSSL::Cipher.new("AES-256-CBC") enc.encrypt # 鍵とIV(Initialize Vector)を PKCS#5 に従ってパスワードと salt から生成する key_iv = OpenSSL::PKCS5.pbkdf2_hmac_sha1(@pass, @salt, 2000, enc.key_len + enc.iv_len) key = key_iv[0, enc.key_len] iv = key_iv[enc.key_len, enc.iv_len] # 鍵とIVを設定する enc.key = key enc.iv = iv # 暗号化する encrypted_data = "" encrypted_data << enc.update(@data) encrypted_data << enc.final encrypted_data end end 1.upto(255) do |n| enc = Encryptor.new('a' * n).exec enc_size = enc.size base64_size = Base64.encode64(enc).size puts "#{n},#{enc_size},#{base64_size}" end