Java转Python:RSA/ECB/PKCS1PADDING签名逻辑实现结果不一致的问题求助
Java转Python:RSA/ECB/PKCS1PADDING签名逻辑实现结果不一致的问题求助
我仔细对比了你的Java和Python代码,发现问题出在RSA加密的输入内容以及加密方式的理解上,不是PKCS1 padding的差异,而是两个语言实现的逻辑步骤有细微但关键的不同:
核心差异分析
先拆解你的Java代码的实际执行流程:
- 对payload生成SHA-1 digest字节数组
- 将digest做Base16(Hex)编码,得到字节数组后转成字符串
encodedDigest(默认用UTF-8编码) - 直接把
encodedDigest的UTF-8字节数组用RSA/ECB/PKCS1Padding加密(注意这是私钥加密,不是标准RSA签名流程) - 最后把加密后的字节做Hex编码得到结果
而你的Python代码犯了两个关键错误:
- 你把Hex编码后的SHA1哈希又解码回了原始digest字节(
sha1_hash_bytes = binascii.unhexlify(sha1_hash_hex)),这和Java的加密输入完全不同——Java加密的是Hex字符串的字节,你却转回了原始哈希值 - 你使用了
private_key.sign()方法,这是标准RSA签名流程,它会自动对输入做哈希(你指定了hashes.SHA1()),还会添加PKCS1v1.5签名要求的ASN.1结构头,而Java里只是单纯对Hex字符串字节做RSA加密,没有这个额外结构
修正后的Python代码
下面是完全对齐Java逻辑的实现,手动模拟Java的私钥加密流程:
import hashlib import binascii import os from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization private_key_pem = """-----BEGIN RSA PRIVATE KEY----- MIIBOQIBAAJAXWRPQyGlEY+SXz8Uslhe+MLjTgWd8lf/nA0hgCm9JFKC1tq1S73c Q9naClNXsMqY7pwPt1bSY8jYRqHHbdoUvwIDAQABAkAfJkz1pCwtfkig8iZSEf2j VUWBiYgUA9vizdJlsAZBLceLrdk8RZF2YOYCWHrpUtZVea37dzZJe99Dr53K0UZx AiEAtyHQBGoCVHfzPM//a+4tv2ba3tx9at+3uzGR86YNMzcCIQCCjWHcLW/+sQTW OXeXRrtxqHPp28ir8AVYuNX0nT1+uQIgJm158PMtufvRlpkux78a6mby1oD98Ecx jp5AOhhF/NECICyHsQN69CJ5mt6/R01wMOt5u9/eubn76rbyhPgk0h7xAiEAjn6m EmLwkIYD9VnZfp9+2UoWSh0qZiTIHyNwFpJH78o= -----END RSA PRIVATE KEY-----""" def pkcs1_v1_5_encrypt_pad(data, key_size_bytes): """模拟Java RSA/ECB/PKCS1PADDING加密模式的填充逻辑""" padding_min_len = 8 max_data_len = key_size_bytes - padding_min_len - 3 if len(data) > max_data_len: raise ValueError("Data length exceeds max allowed for this key size") padding_len = key_size_bytes - len(data) - 3 # 生成随机非零填充字节 padding = b'' while len(padding) < padding_len: byte = os.urandom(1) if byte != b'\x00': padding += byte # PKCS1 v1.5加密模式填充格式:0x00 + 0x02 + 随机非零字节 + 0x00 + 原始数据 return b'\x00\x02' + padding + b'\x00' + data def get_signature(payload): # 1. 加载私钥 private_key = serialization.load_pem_private_key( private_key_pem.encode("utf-8"), password=None, backend=default_backend() ) private_nums = private_key.private_numbers() key_size_bytes = private_key.key_size // 8 # 1024位密钥对应128字节 # 2. Payload预处理(和Java保持一致) escaped_payload = payload.replace("\n", "").replace(" ", "").replace('<?xmlversion="1.0"encoding="UTF-8"?>', "") # 3. 生成SHA-1 digest sha1_hash = hashlib.sha1(escaped_payload.encode("utf-8")).digest() # 4. 做Hex编码(对应Java的Hex.encode(digest) -> new String(encodedBase16)) sha1_hex_str = binascii.hexlify(sha1_hash).decode("utf-8") # 5. 把Hex字符串转成UTF-8字节(对应Java的encodedDigest.getBytes()) sha1_hex_bytes = sha1_hex_str.encode("utf-8") # 6. 执行PKCS1 v1.5填充 padded_data = pkcs1_v1_5_encrypt_pad(sha1_hex_bytes, key_size_bytes) # 7. 用私钥执行RSA加密(数学上等价于:padded_data^d mod n) padded_int = int.from_bytes(padded_data, byteorder='big') encrypted_int = pow(padded_int, private_nums.d, private_nums.n) encrypted_bytes = encrypted_int.to_bytes(key_size_bytes, byteorder='big') # 8. 把加密后的字节转成Hex编码 hex_signature = binascii.hexlify(encrypted_bytes).decode("utf-8") return hex_signature print(get_signature('helloworld'))
为什么这样修改?
- 对齐输入内容:我们保留了SHA1 digest的Hex编码字符串,并直接取其UTF-8字节作为加密输入,完全匹配Java的处理逻辑
- 模拟Java的加密填充:手动实现了PKCS1v1.5加密模式的填充规则,避免了标准签名带来的ASN.1结构差异
- 手动执行RSA私钥加密:通过RSA的数学原理直接完成私钥加密操作,和Java的
Cipher逻辑完全一致
运行这段代码后,你应该能得到和Java完全相同的签名结果。
备注:内容来源于stack exchange,提问作者Vineeth George Abraham
相关产品推荐
相关产品推荐

