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

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

今回はAES-CTR編です。pythonのpycryptodomeを使用し、AES-CTRによる暗復号を行ってみます。

AES-CTRとは

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

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

AES-CTRの特徴として、カウンタ値を利用していくということが挙げられます。

カウンタ値とNonceの連結値を暗号化した値と平文ブロックのXOR(排他的論理和)をとり、そのXORの値を暗号文として出力します。

以下図を確認いただけるとわかりますが、実はAES-CTRの暗号化と復号化は全く同じ処理を行っています。

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

カウンタ値はブロック処理を行うごとに1つずつインクリメントさせていきます。

また、カウンタの値が繰り返すと安全性が損なわれるため、同じカウンタの値を使用することはできないという制約があります。

NonceAES-CBCのIVに相当する値です。

合計がブロックサイズであれば、カウンタ値とNonceそれぞれの桁数について特に明確な決まりはありません。

そのためカウンタ値で同じ値を使うことにならないような桁数を検討する必要があります。

その他のAES-CTRの特徴として、メッセージ長がブロック単位でなくてもよいことが挙げられます。

これは鍵を使用してブロック単位で暗号化する値がメッセージではなくカウンタ値とNonceであり、その後のXORにおいて使用するメッセージは必ずしもブロック単位である必要がないからです。

ライブラリ (PyCryptodome)

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

pip install pycryptodome

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

Compatibility with PyCrypto

Classic modes of operation for symmetric block ciphers

ソースコード

二つ目のAES-CTR暗復号についてはメッセージ長をあえて47byteにしております。

import Crypto.Cipher.AES as AES
from Crypto.Util import Counter

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

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


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


# カウンタを指定して途中から復号
ctr = Counter.new(64, prefix=nonce, little_endian=False, initial_value=1)
decipher =  AES.new(key=key, mode=AES.MODE_CTR , counter=ctr)
dec = decipher.decrypt(enc[16:32])
print("復号文(2ブロック目):" + dec.hex())

出力結果

以下のように16byte単位でない場合も暗号化と復号化は問題なく実施出来ました。

平文:00112233445566778899aabbccddeeff
暗号文:11c2946ffd960867dab6c482966dc86a
復号文:00112233445566778899aabbccddeeff
平文:00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddee
暗号文:11c2946ffd960867dab6c482966dc86af7b9d8e251ddb42c91723f9ceb0a61c128790eb40b6078d92b8e854fcb7557
復号文:00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddee
復号文(2ブロック目):00112233445566778899aabbccddeeff

カウンタ値の指定

デフォルト(カウンタ値を指定しない)の場合、カウンタ値は0からブロックごとに順次インクリメントされて使用します。

実際の用法としてカウンタの初期値が0以外だったり、メッセージの途中から暗復号を実施したい時などに、カウンタ値を指定する必要があります。

そのような場合はCounterモジュールを使用します。

from Crypto.Util import Counter

ctr = Counter.new(64, prefix=nonce, little_endian=False, initial_value=1)
decipher =  AES.new(key=key, mode=AES.MODE_CTR , counter=ctr)

第一引数:カウンタサイズ64bit (ここは必ず8bit単位である必要がある)
第二引数:Nonceを指定
第三引数:エンディアン指定
第四引数:カウンタ初期値指定

となっております。

その他詳細については以下を確認してください。