项目概述
根据NIST SP 800-38D标准实现 AES-GCM
GHASH、IV 处理、计数器生成、认证标签
实现
外部引入
使用 PyCryptodome 提供的 AES 块加密
使用Python标准库hmac
使用os.urandom生成随机比特流(经查询是密码学安全的随机数生成器)
结构
- gf_mul() # 伽罗瓦域运算
- ghash() # GHASH哈希函数
- _derive_j0() # 预计数器块生成
- gcm_encrypt() # 认证加密
- gcm_decrypt() # 认证解密
比特流工具
#utils.py
BLOCK_SIZE = 16
def xor_bytes(a: bytes, b: bytes) -> bytes:return bytes(x ^ y for x, y in zip(a, b))def pad128(b: bytes) -> bytes:if len(b) % BLOCK_SIZE == 0:return breturn b + b'\x00' * (BLOCK_SIZE - (len(b) % BLOCK_SIZE))def int_from_bytes(b: bytes) -> int:return int.from_bytes(b, 'big')def int_to_bytes(i: int) -> bytes:return i.to_bytes(16, 'big')def inc32(counter_block: bytes) -> bytes:#新值 = (旧值 + 1) mod 2^32c = bytearray(counter_block)val = int.from_bytes(c[-4:], 'big')val = (val + 1) & 0xffffffffc[-4:] = val.to_bytes(4, 'big')return bytes(c)
伽罗瓦域\(GF(2^{128})\)
位移和条件异或实现
R_POLY = 0xE1000000000000000000000000000000
def gf_mul(x: int, y: int) -> int:# GF(2^128) multiply (NIST/IEEE 1619)z = 0v = xfor i in range(128):if (y >> (127 - i)) & 1:z ^= vif v & 1:v = (v >> 1) ^ R_POLYelse:v >>= 1return z & ((1 << 128) - 1)
CHASH模块
分别处理AAD和密文,最后添加长度编码
使用块级迭代处理,可进行流式计算
from utils import *
from gf128 import *
def ghash(H: bytes, A: bytes, C: bytes) -> bytes:# H: 16 bytes (AES_K(0^128))H_int = int_from_bytes(H)X = 0A_padded = pad128(A)for i in range(0, len(A_padded), 16):block = int_from_bytes(A_padded[i:i+16])X = gf_mul(X ^ block, H_int)C_padded = pad128(C)for i in range(0, len(C_padded), 16):block = int_from_bytes(C_padded[i:i+16])X = gf_mul(X ^ block, H_int)len_block = (len(A) * 8).to_bytes(8, 'big') + (len(C) * 8).to_bytes(8, 'big')X = gf_mul(X ^ int_from_bytes(len_block), H_int)return int_to_bytes(X)
计数器
生成预计数器块\(J_0\)
def _derive_j0(H: bytes, iv: bytes) -> bytes:if len(iv) == 12:return iv + b'\x00\x00\x00\x01'else:# J0 = GHASH(H, IV)return ghash(H, b'', iv)
加解密
ICB 初始计数器块
CIPH(X) 在密钥 K 下,对数据块 X 应⽤块密码的正向加密函数的输出
GCTR(ICB, X) 对给定的块密码 K 应⽤于⽐特串 X,并使⽤初始计数器块 ICB,GCTR 函数的输出
GHASH(X) 在哈希⼦密钥 H 下,GHASH 函数应⽤于⽐特串 X 的输出
inc(X):新值 = (旧值 + 1) mod 2^32
MSB(X):由 X 的最左边的 s 位组成的位串
加密流程
输入: 密钥(K), 初始化向量(IV), 明文(P), 附加认证数据(AAD)
输出: 密文(C), 认证标签(T)
流程:
- 生成哈希子密钥 \(H = AES_K(0^128)\)
- 生成预计数器块 \(J0 = derive_J0(H, IV)\)
- 计数器模式加密 \(C = GCTR_K(inc32(J0), P)\)
- 计算认证标签 \(T = MSB_t(GCTR_K(J0, GHASH_H(A||C)))\)
预计数器块\(J_0\)
根据定义
if len(IV) == 96:IV = [96位IV] || [32位计数器]J0 = IV || 0x00000001
else:J0 = GHASH_H(IV || 0^s || [len(IV)]64)#s = 128 - (len(IV) mod 128)
CTR
明文分块: \(P_1, P_2, P_3, ..., P_n\)
计数器序列: \(CB_1, CB2, CB_3, ..., CB_n\)
密钥流: \(KS_1, KS_2, KS_3, ..., KS_n\)
密文分块: \(C_1, C_2, C_3, ..., C_n\)
其中:
\(CB_1 = inc32(J_0)\)
\(CB_i = inc32(CB_{i-1})\)
\(KS_i = AES_K(CB_i)\)
\(C_i = P_i\) XOR \(K_Si\)
认证标签生成
\(S = GHASH_H(AAD || C || [len(AAD)]_{64} || [len(C)]_{64})\)
\(T = MSB_t(AES_K(J_0), S)\)
解密流程
输入: 密钥(K), 初始化向量(IV), 密文(C), 附加认证数据(AAD), 认证标签(T)
输出: 明文(P) 或 认证失败
流程:
- 重新生成哈希子密钥 \(H = AES_K(0^{128})\)
- 重新生成预计数器块 \(J0 = derive_J0(H, IV)\)
- 重新计算认证标签 \(T' = MSB_t(GCTR_K(J_0, GHASH_H(AAD||C)))\)
- 验证标签: 比较 \(T'\) 与 \(T\)
- 如果验证通过,\(CTR\)解密: \(P = GCTR_K(inc32(J_0), C)\)
功能实现
"""
NIST SP 800-38D AES-GCM
"""
from Crypto.Cipher import AESimport hmac
import osfrom utils import xor_bytes, pad128, int_from_bytes, int_to_bytes, inc32
from ghash import ghash
MAX_TAG_LEN = 16
def _derive_j0(H: bytes, iv: bytes) -> bytes:if len(iv) == 12:return iv + b'\x00\x00\x00\x01'else:# J0 = GHASH(H, IV)return ghash(H, b'', iv)def _aes_encrypt_block(key: bytes, block: bytes) -> bytes:return AES.new(key, AES.MODE_ECB).encrypt(block)def gcm_encrypt(key: bytes, iv: bytes, plaintext: bytes, aad: bytes = b'', tag_len: int = 16):if tag_len < 4 or tag_len > MAX_TAG_LEN or tag_len % 2 != 0:raise ValueError("tag_len must be even and between 4 and 16")# H = AES_K(0^128)H = _aes_encrypt_block(key, b'\x00' * 16)J0 = _derive_j0(H, iv)counter = inc32(J0)cipher_stream = b''cipher = AES.new(key, AES.MODE_ECB)out = bytearray()for i in range(0, len(plaintext), 16):block = plaintext[i:i+16]s = cipher.encrypt(counter)counter = inc32(counter)out_block = xor_bytes(block, s[:len(block)])out.extend(out_block)C = bytes(out)#tagS = ghash(H, aad, C)E_J0 = _aes_encrypt_block(key, J0)tag_full = xor_bytes(E_J0, S)return C, tag_full[:tag_len]def gcm_decrypt(key: bytes, iv: bytes, ciphertext: bytes, aad: bytes = b'', tag: bytes = b''):if len(tag) == 0:raise ValueError("tag required for decryption")H = _aes_encrypt_block(key, b'\x00' * 16)J0 = _derive_j0(H, iv)counter = inc32(J0)cipher = AES.new(key, AES.MODE_ECB)out = bytearray()for i in range(0, len(ciphertext), 16):block = ciphertext[i:i+16]s = cipher.encrypt(counter)counter = inc32(counter)out_block = xor_bytes(block, s[:len(block)])out.extend(out_block)plaintext = bytes(out)S = ghash(H, aad, ciphertext)E_J0 = _aes_encrypt_block(key, J0)tag_full = xor_bytes(E_J0, S)expected = tag_full[:len(tag)]if not hmac.compare_digest(expected, tag):raise ValueError("authentication failed")return plaintext
调用
if __name__ == "__main__":key = os.urandom(16)iv = os.urandom(12)aad = "1234".encode('utf-8')pt = "明文".encode('utf-8')ct, tg = gcm_encrypt(key, iv, pt, aad, tag_len=16)print("明文:", pt.hex())print("密文:", ct.hex())print("tag:", tg.hex())pt2 = gcm_decrypt(key, iv, ct, aad, tg)print("验证:", pt2 == pt)