pythonで暗号演算 ~AES-パディング編~
今回はパディングの中でもAESで主に使用されているパディング(PKCS#5、PKCS#7)について取り上げます。
またpythonで簡単なpadding処理の実装を行います。
PKCS#5、PKCS#7におけるパディング
ブロック暗号では、ある特定のビット数のまとまりを一度に処理するため、入力がブロック単位である必要があります。
その際、入力データに対して行う処理がパディング(padding)です。
パディングとは、処理の実施を行うのに足りないデータを補うため、データの前後に意味のないデータを追加する処理のことを言います。
暗号演算以外でも通信など様々なところでパディングは行われております。
RFC1423の記載について
AESで主に使用されているパディングはPKCS#5、PKCS#7に記載されている方法になります。
以下はPKCS#5(RFC1423)の記載からの抜粋です。
The input to the DES CBC encryption process shall be padded to a multiple of 8 octets, in the following manner. Let n be the length in octets of the input. Pad the input by appending 8-(n mod 8) octets to the end of the message, each having the value 8-(n mod 8), the number of octets being added. In hexadecimal, the possible paddings are: 01, 0202, 030303, 04040404, 0505050505, 060606060606, 07070707070707, and 0808080808080808. All input is padded with 1 to 8 octets to produce a multiple of 8 octets in length. The padding can be removed unambiguously after decryption.
DES CBC暗号化プロセスへの入力は、次の方法で8オクテットの倍数にパディングされます。nを入力のオクテット単位の長さとします。最後に8(n mod 8)オクテットを追加して入力をパディングします。メッセージのそれぞれの値は8(n mod 8)で、追加されるオクテットの数です。16進数では、可能なパディングは01、0202、030303、04040404、0505050505、060606060606、07070707070707、および080080808088入力です。 1〜8オクテットでパディングして、8オクテットの倍数の長さを生成します。パディングは、復号化後に明確に削除できます。
https://datatracker.ietf.org/doc/html/rfc1423
要約するとパディングが必要なサイズを、パディング値として連続して末尾に入力します。
仮に16byte単位に対して13byteの入力の場合、パディングサイズ0x03を末尾3byte分付与させます。
パディング例
以下はパディングの例になります。
パディング前(hex) :00112233445566778899aabbcc
パディング後(hex) :00112233445566778899aabbcc030303
PKCS#5では最大8バイトのパディングまでしか規定されていないため、AESの128ビット(16バイト)に対して本規格をそのまま使用することはできません。
一方でPKCS#7(RFC5652)は最大255バイトのパディングが可能です。
そのため、AESのパディングではPKCS#7を使用していることが多いそうです。
ソースコード
上記PKCS#7において、ブロック長が16byteのpadding処理を実装してみました。
def padding_fnc( data ):
padding_data = data
if 0 != (len(data) % 16):
padding_size = 16 - (len(data) % 16)
for i in range(padding_size):
padding_data += padding_size.to_bytes(1, 'little')
return padding_data
data = bytes.fromhex("00112233445566778899aabbccddeeFF")
print(padding_fnc(data).hex())
data = bytes.fromhex("00112233445566778899aabbccddee")
print(padding_fnc(data).hex())
data = bytes.fromhex("00112233445566778899aabbccdd")
print(padding_fnc(data).hex())
data = bytes.fromhex("00112233445566778899aabbcc")
print(padding_fnc(data).hex())
data = bytes.fromhex("00112233445566778899aabb")
print(padding_fnc(data).hex())
data = bytes.fromhex("00112233445566778899aa")
print(padding_fnc(data).hex())
data = bytes.fromhex("00112233445566778899")
print(padding_fnc(data).hex())
data = bytes.fromhex("001122334455667788")
print(padding_fnc(data).hex())
data = bytes.fromhex("0011223344556677")
print(padding_fnc(data).hex())
data = bytes.fromhex("00112233445566")
print(padding_fnc(data).hex())
出力結果
出力結果が以下になります。ちゃんと16byte単位でパディング処理が実施されています。
00112233445566778899aabbccddeeff
00112233445566778899aabbccddee01
00112233445566778899aabbccdd0202
00112233445566778899aabbcc030303
00112233445566778899aabb04040404
00112233445566778899aa0505050505
00112233445566778899060606060606
00112233445566778807070707070707
00112233445566770808080808080808
00112233445566090909090909090909