pythonで暗号演算 ~AES-CBC編~

python,暗号演算,pycryptodome,python,暗号演算

今回はAES-CMAC編でも少し触れたAES-CBCについて、pythonのライブラリpycryptodomeを使用して実際に動かしてみます。

AES-CBCとは

AESとは、 Advanced Encryption Standard(高度暗号化標準)の略となり、特定の長さのデータ(ブロック)を単位として処理を行う「ブロック暗号」の一種です。

AESには暗号利用モードと呼ばれる複数の暗号化メカニズムがあり、AES-CBCはそのメカニズムのうちの一つとなります。

AES-CBCの特徴として、16byteのブロック単位で暗号化していく際、1つ前の暗号文ブロックを利用していくということが挙げられます。

これは1つ前の暗号文ブロックと平文ブロックのXOR(排他的論理和)をとり、そのXORの値で暗号化を行っていくためです。

このため、AES-ECBにおける脆弱性(平文と暗号文が一対になる)はAES-CBCには当たりません。

なぜならブロックを暗号化する際に使用するパラメータが一意でないからです。

また、最初の平文ブロックを暗号化するときには1つ前の暗号文ブロックは存在しないため、1つ前の暗号文ブロックの代わりのビット列を1ブロック分用意する必要があります。

この初期値を初期化ベクトル(IV:Initialization Vector)といい、暗号化のたびに異なるランダムな値を使用する必要があります。

WhiteTimberwolf (SVG version) – PNG version, パブリック・ドメイン, https://commons.wikimedia.org/w/index.php?curid=26434096による

実装

ライブラリ (PyCryptodome)

pipから暗号演算が行えるライブラリをインポート。

pip install pycryptodome

詳細についてはこちらを参照してください。

ソースコード

注意点として必ず入力メッセージサイズは16byte単位にしなければなりません。

これはAESがブロック暗号方式(ある特定のビット数のまとまりを一度に処理する)のためです。

もし対象のデータが16byte単位とならない場合はパディングと呼ばれる足りないサイズ分、適当な値を追加することで補う必要があります。

AESのパディング方式についてはいくつか種類があり、代表的なパディング方式であるPKCS#5、PKCS#7については以下確認お願いします。

また、ブロック長に処理が依存しない暗号方式としてストリーム暗号が存在します。

今回の実装ではIVは固定値にしていますが、本来は乱数を使う必要があります。乱数の扱いについてもそのうち取り上げる予定です。

import Crypto.Cipher.AES as AES

# 鍵値
key = bytes.fromhex("00112233445566778899aabbccddeeff")

# メッセージサイズ16byte単位
data = bytes.fromhex("00112233445566778899aabbccddeeff")
iv = bytes.fromhex("00112233445566778899aabbccddeeff")
print("平文:" + data.hex())
decipher =  AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
enc = decipher.encrypt(data)
print("暗号文:" + enc.hex())
decipher =  AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
dec = decipher.decrypt(enc)
print("復号文:" + dec.hex())


# メッセージサイズ48byte単位
data = bytes.fromhex("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff")
iv = bytes.fromhex("00112233445566778899aabbccddeeff")
print("平文:" + data.hex())
decipher = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
enc = decipher.encrypt(data)
print("暗号文:" + enc.hex())
decipher =  AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
dec = decipher.decrypt(enc)
print("復号文:" + dec.hex())

出力結果

出力結果は以下の通り。

平文:00112233445566778899aabbccddeeff
暗号文:fde4fbae4a09e020eff722969f83832b
復号文:00112233445566778899aabbccddeeff
平文:00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff  
暗号文:fde4fbae4a09e020eff722969f83832b101b11c1c99cbdcbf3655e7e5e3979cfb308284c359bea3e1830f66903238fcb
復号文:00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff

着目してほしいのは、二つ目の48byteデータの平文と暗号文です。

これは入力データに全く同じ16byteのデータの連結を使用しているのですが、AES-ECBの時とはことなり、出力された暗号文はブロック単位に分割しても同一ではありません。

平文:00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
暗号文:fde4fbae4a09e020eff722969f83832b101b11c1c99cbdcbf3655e7e5e3979cfb308284c359bea3e1830f66903238fcb

CBCの脆弱性について

AES-ECBの問題が当たらないとしても、AES-CBCにはいくつかの問題が報告されています。

例えば以下では途中の暗号文ブロックを置き換えることにより、メッセージの改ざんが可能であることを示唆しています。

解決法として、AES-CMAC編で説明したような改ざん検知の仕組みを加えることが述べられています。

このように、脆弱性に対してはいくつかの暗号アルゴリズムを組み合わせることで対応することが可能な例があります。

そのため、暗号演算の分野を扱うにはより幅広い知識が必要になってきます。