GCM(Galois/Counter Mode) 认证加密算法实现

项目概述

根据NIST SP 800-38D标准实现 AES-GCM
GHASH、IV 处理、计数器生成、认证标签

AES-GCM加密过程

AES-GCM (Galois/Counter Mode) :

  • AES-GCM 将 AES 的 CTR 模式用于保密性,用一个不断递增的计数器块经 AES 加密生成流块 E,用 E 与明文异或得到密文
  • 认证则由 GHASH 完成,它在 \(GF(2^{128})\) 域上对 AAD附加认证数据 与密文进行多项式乘法累加
    使用的哈希子密钥 \(H = AES_K(0^{128})\)
  • 最终认证标签 \(T\) = \(MSB\)(\(tlen\), \(S\) xor \(AES_K\)(\(J_0\))),其中 S 是 GHASH 的输出,J0 是初始计数器
  • IV(初始化向量)长度为 96 位(12 字节)时,\(J_0 = IV || 0x00000001\)
    否则对 IV 做填充并用 GHASH 生成 J0

实现要点:

  • GHASH 需要在 \(GF(2^{128})\) 上实现乘法
  • 对于长度不是 16 字节整数倍的数据,需要对 AAD 和密文在 GHASH 前按规范使用零填充
  • 当明文为空(PTlen = 0)时,规范要求仍能正确计算 GHASH 与 Tag;在此实现中会:
    • 在 CTR 流中如果明文为空不产生密文块,记录第一个计数器加密结果(E)用于中间值展示
    • GHASH 仅对 AAD 与(空)密文和长度块计算
  • 解密过程:先按相同方式计算期望的 Tag 并使用常量时间比较(hmac.compare_digest)验证
    验证通过才进行 CTR 解密以防止出错泄露信息

实现

外部引入

使用 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 b
    return 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^32
    c = bytearray(counter_block)
    val = int.from_bytes(c[-4:], 'big')
    val = (val + 1) & 0xffffffff
    c[-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 = 0
    v = x
    for i in range(128):
        if (y >> (127 - i)) & 1:
            z ^= v
        if v & 1:
            v = (v >> 1) ^ R_POLY
        else:
            v >>= 1
    return 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 = 0
    A_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)

加解密

image
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)
流程:

  1. 生成哈希子密钥 \(H = AES_K(0^{128})\)
  2. 生成预计数器块 \(J_0 = derive_{J_0}(H, IV)\)
  3. 计数器模式加密 \(C = GCTR_K(inc_{32}(J_0), P)\)
  4. 计算认证标签 \(T = MSB_t(GCTR_K(J_0, 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) 或 认证失败

流程:

  1. 重新生成哈希子密钥 \(H = AES_K(0^{128})\)
  2. 重新生成预计数器块 \(J0 = derive_J0(H, IV)\)
  3. 重新计算认证标签 \(T' = MSB_t(GCTR_K(J_0, GHASH_H(AAD||C)))\)
  4. 验证标签: 比较 \(T'\)\(T\)
  5. 如果验证通过,\(CTR\)解密: \(P = GCTR_K(inc32(J_0), C)\)

功能实现

"""
NIST SP 800-38D AES-GCM
"""
from Crypto.Cipher import AES

import hmac
import os

from 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)
    #tag
    S = 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)
posted @ 2025-10-22 22:03  lumiere_cloud  阅读(45)  评论(0)    收藏  举报