为何两种签名方式生成的数据不一致?及Token签名代码优化咨询
问题背景与现象
操作流程
- 使用OpenSSL生成CSR文件和私钥PEM文件:
openssl req -new -utf8 -nameopt multiline,utf8 -config rahat.cnf -newkey rsa:2048 -nodes -keyout rahat.key -out rahat.Csr - 从证书机构获取crt格式证书,转换为cer格式后提取公钥:
openssl x509 -pubkey -noout -in rahat.cer > pubkey.txt - 创建包含证书、公钥和私钥的p12文件:
openssl pkcs12 -export -out rahat.p12 -inkey rahat.key -in rahat.cer - 将p12文件导入ePass3003Auto令牌,提取的cer文件导出公钥与
pubkey.txt内容一致。
核心问题
用令牌对“Hello World”字符串签名,结果可通过证书/公钥验证;但用rahat.key中的Base64私钥通过本地Java代码签名同一字符串,结果与令牌签名不同。无法修改本地私钥签名代码,需调整令牌签名代码使两者签名结果一致。
相关代码
本地私钥签名代码
import java.io.ByteArrayInputStream; import java.io.InputStream; import java.security.KeyStore; import java.security.PrivateKey; import java.security.Signature; import java.security.cert.X509Certificate; import java.security.cert.Certificate; import java.security.Provider; import java.security.Security; import java.util.Base64; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; public byte[] signData(byte[] data, String PRIVATE_KEY) throws Exception { byte[] encoded = Base64.getDecoder().decode(PRIVATE_KEY); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); KeyFactory kf = KeyFactory.getInstance("RSA"); PrivateKey pk = kf.generatePrivate(keySpec); Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(pk); signature.update(data); byte[] signedData = signature.sign(); return signedData; }
令牌签名代码
Provider p = Security.getProvider("SunPKCS11"); p = p.configure("config.cfg"); Security.addProvider(p); KeyStore keyStore = KeyStore.getInstance("PKCS11", p); char[] pin = "4321".toCharArray(); keyStore.load(null, pin); java.util.Enumeration<String> aliases = keyStore.aliases(); String alias = aliases.nextElement(); PrivateKey pk = (PrivateKey)keyStore.getKey(alias, "4321".toCharArray()); Certificate[] chain = keyStore.getCertificateChain(alias); X509Certificate cert = (X509Certificate)chain[0]; Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(pk); signature.update(data); byte[] signedData = signature.sign(); signature.initVerify(cert.getPublicKey()); signature.update(data); boolean verified = signature.verify(signedData); System.out.println("Verified: " + verified); return signedData;
解决方案
原因分析
两种签名结果不一致的核心原因是RSA签名填充方式不匹配:本地代码使用的SHA256withRSA对应标准的PKCS#1 v1.5固定填充,而ePass3003Auto令牌默认可能启用了RSASSA-PSS概率性填充,导致签名结果不同。此外需确保待签名的字节数组完全一致(如字符串转byte时的编码统一)。
修改后的令牌签名代码
需强制指定令牌使用PKCS#1 v1.5填充的签名算法,同时确保输入数据编码与本地代码一致:
Provider p = Security.getProvider("SunPKCS11"); p = p.configure("config.cfg"); Security.addProvider(p); KeyStore keyStore = KeyStore.getInstance("PKCS11", p); char[] pin = "4321".toCharArray(); keyStore.load(null, pin); java.util.Enumeration<String> aliases = keyStore.aliases(); String alias = aliases.nextElement(); PrivateKey pk = (PrivateKey)keyStore.getKey(alias, "4321".toCharArray()); Certificate[] chain = keyStore.getCertificateChain(alias); X509Certificate cert = (X509Certificate)chain[0]; // 强制指定PKCS#1 v1.5填充的SHA256withRSA算法,部分PKCS11驱动需明确填充类型 Signature signature = Signature.getInstance("SHA256withRSA"); // 若上述写法不生效,尝试带填充标识的完整算法名称: // Signature signature = Signature.getInstance("SHA256withRSA/PKCS1Padding", p); // 确保待签名数据编码与本地代码一致(示例:统一用UTF-8转字符串为字节数组) // byte[] data = "Hello World".getBytes(java.nio.charset.StandardCharsets.UTF_8); signature.initSign(pk); signature.update(data); byte[] signedData = signature.sign(); signature.initVerify(cert.getPublicKey()); signature.update(data); boolean verified = signature.verify(signedData); System.out.println("Verified: " + verified); return signedData;
额外检查项
- 算法名称兼容性:若
SHA256withRSA仍不生效,尝试SHA256withRSAEncryption作为算法名称,适配不同PKCS11驱动的命名规则。 - 令牌配置验证:通过ePassManagerAdm_3003.exe检查令牌的签名模板,确认是否默认启用PSS填充,需调整为PKCS#1 v1.5填充。
- 输入数据一致性:确保两种场景下待签名的
data字节数组完全相同(无编码差异、隐藏字符等)。
内容的提问来源于stack exchange,提问作者RAHAT
相关产品推荐
相关产品推荐

