TLS1.3 RSA-PSS-RSAE-SHA256证书验证算法实现问题求助
TLS 1.3 Certificate Verify (RSA-PSS-RSAE-SHA256) 分步实现与调试指南
核心问题排查点
你当前的验证流程框架正确,但大概率在EMSA-PSS的字节解析细节或RSA解密后的EM格式处理上出现偏差,以下是修正后的标准步骤及可调试代码:
标准验证分步流程
1. 准备基础数据
- 从证书中提取RSA公钥参数:模数
n(大整数)、公钥指数e(通常为65537) - 获取
CertificateVerify消息中的原始签名字节(注意:不是ASN.1编码的签名,是RSA加密后的EM字节) - 计算握手消息的transcript-hash:将
ClientHello到Certificate的所有握手消息(含消息头的类型、长度字段)按顺序拼接,再计算SHA256哈希(32字节)
2. 构造待签名消息M(RFC8446 §4.4.3)
M的字节结构严格为:
64个0x20字节 + b"TLS 1.3, server CertificateVerify" + 1个0x00字节 + transcript-hash(32字节)
总长度为 64 + 31 + 1 + 32 = 128 字节(注:b"TLS 1.3, server CertificateVerify" 的字节长度为31)
3. RSA解密签名得到EM
- 将签名字节转换为大端整数
sig_int - 用公钥解密:
em_int = pow(sig_int, e, n) - 将
em_int转换为字节数组,补前导零至模数长度(如2048位RSA对应256字节),得到编码消息EM
4. EMSA-PSS验证(RFC8017 §9.1.2)
针对SHA256的参数:hLen=32(哈希长度),sLen=32(salt长度,RSASSA-PSS-RSAE-SHA256要求salt长度等于哈希长度)
- 检查
EM长度是否 ≥hLen + sLen + 2,且最后一个字节为0xbc,不满足则验证失败 - 去掉
EM最后一个字节得到EM',取EM'的最后1 + hLen + sLen = 65字节作为DB - 检查
DB的第一个字节是否为0x00,不满足则验证失败 - 从
DB中提取:H:DB[1:33](第2到第33字节,共32字节)salt:DB[33:](第34到第65字节,共32字节)
- 计算
mHash = SHA256(M) - 构造
M' = 8个0x00字节 + mHash + salt,计算H' = SHA256(M') - 对比
H与H',相等则验证成功
可分步调试的Python代码
import hashlib def tls13_cert_verify_rsapss(n, e, signature, transcript_hash): # 步骤1:构造待签名消息M prefix_64_20 = b'\x20' * 64 label = b"TLS 1.3, server CertificateVerify" null_byte = b'\x00' M = prefix_64_20 + label + null_byte + transcript_hash print(f"[Step 1] M length: {len(M)} bytes") print(f"[Step 1] M hex: {M.hex()}") # 步骤2:RSA解密签名得到EM sig_int = int.from_bytes(signature, byteorder='big') em_int = pow(sig_int, e, n) mod_len = (n.bit_length() + 7) // 8 EM = em_int.to_bytes(mod_len, byteorder='big') print(f"\n[Step 2] EM length: {len(EM)} bytes (mod length: {mod_len})") print(f"[Step 2] EM hex: {EM.hex()}") # 步骤3:EMSA-PSS解析与校验 hLen = 32 sLen = hLen emLen = len(EM) if emLen < hLen + sLen + 2: print(f"[Step 3] Error: EM length {emLen} < required {hLen + sLen + 2}") return False if EM[-1] != 0xbc: print(f"[Step 3] Error: EM last byte is {hex(EM[-1])}, expected 0xbc") return False EM_prime = EM[:-1] db_len = 1 + hLen + sLen if len(EM_prime) < db_len: print(f"[Step 3] Error: EM' length {len(EM_prime)} < DB length {db_len}") return False DB = EM_prime[-db_len:] if DB[0] != 0x00: print(f"[Step 3] Error: DB first byte is {hex(DB[0])}, expected 0x00") return False H = DB[1:1+hLen] salt = DB[1+hLen:] print(f"\n[Step 3] Extracted H hex: {H.hex()}") print(f"[Step 3] Extracted salt hex: {salt.hex()}") # 步骤4:计算并对比哈希 mHash = hashlib.sha256(M).digest() print(f"\n[Step 4] mHash hex: {mHash.hex()}") M_prime = b'\x00' * 8 + mHash + salt H_prime = hashlib.sha256(M_prime).digest() print(f"[Step 4] M' hex: {M_prime.hex()}") print(f"[Step 4] H' hex: {H_prime.hex()}") if H == H_prime: print("\n[Result] Verification SUCCESS") return True else: print("\n[Result] Verification FAILED: H != H'") return False
调试建议
- 重点检查
EM的长度是否与模数长度一致,若解密后的字节不足,必须补前导零 - 确认
DB的第一个字节为0x00,这是EMSA-PSS格式的强制要求,很多解析错误都出在这里 - 对比
mHash的计算结果是否与预期一致,排除transcript-hash的拼接错误
内容的提问来源于stack exchange,提问作者FERcsI
相关产品推荐
相关产品推荐

