風柳メモ

ソフトウェア・プログラミング関連の覚書が中心

AES-GCMでの暗号化/復号の例(JavaScript(Web Crypto API)←→Python(PyCryptodome)相互変換可)

JavaScript(Web Crypto API)で記述されたAES-GCMを用いた暗号化/復号処理をPython(PyCryptodome)にデータ互換性をもたせながら移植しようとしたところ、いくつかハマりどころがあったのでメモしておく。




ハマりどころ

Pythonに入れたPyCryptodomeが動作しない!?

Windows 10 Pro上にインストールしてあるPython 3.11に普通に

$ pip install pycryptodome

で入れているのに、

from Crypto.Cipher import AES

とかしても

ModuleNotFoundError: No module named 'Crypto'

となってしまう現象に悩まされてしまった。

結論としては、PyCryptodomeが入ったフォルダ名が

C:\Python311\Lib\site-packages\crypto

になっていたという……これを手動でcrypto→Cryptoに変更したら、無事に動くようになった。
どうも、ときどき発生する現象である模様……以前に入れたり削除したりしたモジュールの中に小文字でフォルダを作ったものがあったからか?

MAC tagはどこ?

PyCryptodomeではcipher.encrypt_and_digest()で暗号化したときの戻り値としてciphertextの他にMAC tagが存在する
ところが、JavaScriptのcrypto.subtle.encrypt()では、戻り値はciphertext(を含むArrayBuffer)のみで、MAC tagが見当たらない。
でも、AesGcmParamsではtagLengthを指定できる(未指定の場合は128ビット)ので、MAC tagの作成自体はしているはず……(実際、得られたciphertextをそのままMAC Tagを指定しないでdecryptしようとしてもうまくいかない)。

いろいろと調べてみた結果、crypto.subtle.encrypt()の戻り値の最終16オクテット(tagLength(128)ビット)がMAC tagに相当し、正味のciphertextはこれを除いたものであるらしい
PyCryptodomeでcipher.decrypt_and_digest()にわたす際にはこの点を留意する必要がある。

nonceはどこ?

PyCryptodomeだと、AES-GCMの場合にはAES.new()でnonceパラメータを指定する必要があるが、JavaScriptのコードを一見したところ、nonceが見当たらない。
どうも、JavaScriptのcrypto.subtle.encrypt()の第1引数(AesGcmParams)にある「iv」というパラメータ(初期化ベクトル(IV=Initialization Vector))が、nonceに相当するらしい。
AES.new()ではivとnonceは別パラメータとして存在し、MODEによってどちらを設定するか決まっている模様(MODE_GCMではnonceを設定)

additionalDataってどう扱えば?

JavaScriptのcrypto.subtle.encrypt()の第1引数(AesGcmParams)にはadditionalDataというパラメータがあるが、これはPyCryptodomeでどう扱えばよいのか?
そもそもadditionalDataは、それ自体は暗号化されないAAD(additional authenticated data、追加認証データ)であるらしい。
こちらも調べてみると、どうやらcipher.update()で指定すればよいらしいことが判明。

その他

今回調査した実装だけの話だけれど、cipher.decrypt_and_verify()を呼び出した際に、"ValueError: MAC check failed"が出たり出なかったり不安定という謎の現象が発生。
かなり悩んだけれど……実はpasswordを複数の候補からタイムスタンプを元に選択するような仕組みになっており、かつ、一連のシーケンス中で得られるデータ中に微妙に異なるタイムスタンプが複数箇所にあるけれど(仕様書もないために)どれを使えばよいのか不明で、間違ったものを使うと誤ったpasswordを選択してしまうことがある(たまたま一致していて通ることもある)というややこしい状況だった(その後、正しいものを特定するのにもかなり時間を要した……仕様書は大事)。

実装例

JavaScriptとPythonの具体的な実装例を掲載しておく。
それぞれで定義しているaes_gcm_encrypt()で暗号化したデータは、お互いのaes_gcm_decrypt()でも復号することが可能となっている(はず)。

JavaScript(Web Crypto API)用

関数仕様

ソースコード

Python(PyCryptodome)用

関数仕様

ソースコード