You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

相同参数下SHA256withRSA签名验证始终失败是什么原因?

问题描述

编写RSA签名验证测试用例时,公私钥均来自同一个Keystore,自认为签名和验签步骤一致,但验签结果始终返回false,怀疑是编码问题导致。
测试代码如下:

@Test
public void authShouldBeOk() throws Exception {
    // 
    String initialMessage = pCrypto.getKey();
    Signature sign = Signature.getInstance("SHA256withRSA");
    String paramPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArWTAz/TDWyrbXGKva+8ankn/USEmUSj1UM3JQkYnX1ilWxd0kCb+PodWcrp/l4rbBef5xyFIIEZmkWNFuIUVKUvPX35Fg4DVvhbZekMRIjh4lRBi3wyXV41OqTNr35eYBiFbHYJwLeUn5eeQqB0h4S7NmPCV1t6Ih5TWOO8vLJLC0wEwEzapopXkISj9FNXHwQHHzQGvSnqxQhWBLYwFJ9ECGNDXzlALHzshM1EGthKe5zddQoRm63bxGuBsxa75qJdmOcL/xLMnad3/FPntNcC573VM83aI0xh2lzQqhG4ZzjNvVUH+NiYwDmZgE9DVk9vP3XwR/1anBz11vTwksQIDAQAB";
    
    Cipher cipher = Cipher.getInstance("RSA");

    byte[] privBytes = pCrypto.getPriv().getEncoded();

    cipher.init(Cipher.ENCRYPT_MODE, pCrypto.getPriv());
    byte[] encrypted = cipher.doFinal(initialMessage.getBytes());
    String encryptedMessage = Base64.getEncoder().encodeToString(encrypted);

    sign.initSign(pCrypto.getPriv());
    sign.update(encrypted);

    byte[] signatureBytes = sign.sign();

    String signatureMessage = Base64.getEncoder().encodeToString(signatureBytes);

    PublicKey certP = pCrypto.getPublic();
    sign.initVerify(certP);
    sign.update(signatureBytes);
    boolean b = sign.verify(signatureBytes);

    // should be true
    assert(b)

    // when
    authController.getAuth(encryptedMessage, signatureMessage, paramPublicKey);
}
根本原因

和编码问题无关,是验签逻辑写反了,两个核心错误直接导致验签失败:

  • 签名阶段sign.update()传入的是待签名的加密内容encrypted字节数组,验签阶段给update()传入的却是签名字节数组signatureBytes,两边参与签名/验签计算的原始内容完全不一致
  • Signature.verify()方法的入参必须是生成的签名值,这里既把签名值当成待校验的原始内容喂给update,又把签名值本身传给verify做校验,逻辑完全不符合签名验签的API规范
修复方法

把验签阶段的逻辑调整为和签名阶段完全对齐即可:

  1. sign.initVerify()初始化后,sign.update()必须传入和签名阶段完全相同的原始待校验内容,也就是encrypted字节数组
  2. 最后调用sign.verify()时,传入生成的签名字节数组signatureBytes做校验

对应修复后的代码片段:

PublicKey certP = pCrypto.getPublic();
sign.initVerify(certP);
// 传入和签名阶段完全一致的待签名内容
sign.update(encrypted);
// 传入签名值完成校验
boolean b = sign.verify(signatureBytes);
额外注意事项

代码里还有几个容易引发后续问题的隐患,建议一并修复:

  • 定义的paramPublicKey在当前验签逻辑里完全没用到,如果后续authController里是用这个传入的公钥做验签,必须先确认这个公钥字符串和Keystore中对应私钥的公钥完全一致,否则依然会验签失败
  • 字符串转字节数组不要用无参的getBytes()方法,必须明确指定字符编码,比如initialMessage.getBytes(StandardCharsets.UTF_8),避免不同运行环境默认编码不一致导致字节内容偏差
  • RSA加密不要用无参的Cipher.getInstance("RSA"),要明确指定加密模式和填充方式,比如Cipher.getInstance("RSA/ECB/PKCS1Padding"),不同JDK版本默认填充规则不一致会直接导致加解密失败
  • assert(b)语句末尾缺少分号,会直接编译报错

内容的提问来源于stack exchange,提问作者Diop Saliou Alpha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 18:42:37