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

WebCrypto RSA-OAEP-SHA-512加密内容Java解密填充错误排查

RSA-OAEP-SHA-512跨WebCrypto与Java解密报Padding Error的解决方法

问题背景

客户端通过WebCrypto API使用RSA-OAEP-SHA-512加密字符串,服务端Java解密时抛出javax.crypto.BadPaddingException: Padding error in decryption。已知两端各自加解密正常,但跨语言操作失败,更换SHA-1、SHA-256等其他哈希算法也无法解决问题。


客户端代码

密钥生成逻辑

async function generateRSAKeyPair() {
    const rsaKeyPair = await window.crypto.subtle.generateKey(
      {
        name: 'RSA-OAEP',
        modulusLength: 4096,
        publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537
        hash: { name: 'SHA-512' },
      },
      true,
      ['encrypt', 'decrypt']
    );
    return rsaKeyPair;
}

加密逻辑

async function encryptWithRSA(jsonString, jsonRSAPublicKey) {
    // Parse JSON keys
    const rsaPublicKey = JSON.parse(jsonRSAPublicKey);

    // Import RSA public key
    const importedRSAPublicKey = await window.crypto.subtle.importKey(
      'jwk',
      rsaPublicKey,
      { name: 'RSA-OAEP', hash: { name: 'SHA-512' } },
      true,
      ['encrypt']
    );

    // Convert JSON String to ArrayBuffer
    const aesKeyBuffer = new TextEncoder().encode(jsonString);

    // Encrypt AES key using RSA public key
    const encryptedAesKeyBuffer = await window.crypto.subtle.encrypt(
      { name: 'RSA-OAEP' },
      importedRSAPublicKey,
      aesKeyBuffer
    );

    // Convert encrypted AES key to base64
      const encryptedAesKeyBase64 = window.btoa(String.fromCharCode(...new Uint8Array(encryptedAesKeyBuffer)));
    return encryptedAesKeyBase64;
}

服务端Java原报错代码

解密逻辑

import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.math.BigInteger;
import java.util.Base64;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;

public void decrypt() {
    try {
        PrivateKey privateKey = loadPrivateKey();

        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-512AndMGF1Padding");

        cipher.init(Cipher.DECRYPT_MODE, privateKey);

        byte[] encryptedBytes = Base64.getDecoder().decode(cipherText);
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);

        this.clearText = new String(decryptedBytes);
        System.out.println("Decrypted clear text: " + clearText);
    } catch (Exception e) {
        e.printStackTrace();
        System.err.println("Decryption failed: " + e.getMessage());
    }
}

private PrivateKey loadPrivateKey() throws Exception {
    // Parse the JWK JSON string
    ObjectMapper mapper = new ObjectMapper();
    Map<String, Object> jwk = mapper.readValue(rsaKey, Map.class);
    System.out.println("JWK parsed: " + jwk);

    // Extract the key components from the JWK
    String nBase64 = (String) jwk.get("n");
    String dBase64 = (String) jwk.get("d");
    String pBase64 = (String) jwk.get("p");
    String qBase64 = (String) jwk.get("q");
    String dpBase64 = (String) jwk.get("dp");
    String dqBase64 = (String) jwk.get("dq");
    String qiBase64 = (String) jwk.get("qi");
    String eBase64 = (String) jwk.get("e");

    // Decode the Base64 URL-encoded components
    BigInteger modulus = new BigInteger(1, Base64.getUrlDecoder().decode(nBase64));
    BigInteger privateExponent = new BigInteger(1, Base64.getUrlDecoder().decode(dBase64));
    BigInteger publicExponent = new BigInteger(1, Base64.getUrlDecoder().decode(eBase64));
    BigInteger primeP = new BigInteger(1, Base64.getUrlDecoder().decode(pBase64));
    BigInteger primeQ = new BigInteger(1, Base64.getUrlDecoder().decode(qBase64));
    BigInteger primeExponentP = new BigInteger(1, Base64.getUrlDecoder().decode(dpBase64));
    BigInteger primeExponentQ = new BigInteger(1, Base64.getUrlDecoder().decode(dqBase64));
    BigInteger crtCoefficient = new BigInteger(1, Base64.getUrlDecoder().decode(qiBase64));

    // Create the RSA private key specification
    RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent, primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient);

    // Generate the private key
    KeyFactory keyFactory = KeyFactory.getInstance(AppConfig.CRYPTO_RSA);
    return keyFactory.generatePrivate(keySpec);
}

报错信息

javax.crypto.BadPaddingException: Padding error in decryption
    at java.base/com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:383)
    at java.base/com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:419)
    at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2244)
    at zkgitclientcli.crypto.RsaEncryptionHandler2.decrypt(RsaEncryptionHandler2.java:48)
    at zkgitclientcli.commands.login.DecryptAesKey.execute(DecryptAesKey.java:23)
    at zkgitclientcli.commands.CommandManager.executeCommand(CommandManager.java:44)
    at zkgitclientcli.ZkGit.main(ZkGit.java:12)
    at org.codehaus.mojo.exec.ExecJavaMojo.doMain(ExecJavaMojo.java:385)
    at org.codehaus.mojo.exec.ExecJavaMojo.doExec(ExecJavaMojo.java:374)
    at org.codehaus.mojo.exec.ExecJavaMojo.lambda$execute$0(ExecJavaMojo.java:296)
    at java.base/java.lang.Thread.run(Thread.java:1583)
Decryption failed: Padding error in decryption

问题原因

WebCrypto API中,RSA-OAEP默认使用与主哈希算法一致的哈希函数作为MGF1(掩码生成函数)的哈希——即主哈希为SHA-512时,MGF1也用SHA-512。但Java默认的OAEPWithSHA-512AndMGF1Padding实现中,MGF1的哈希函数默认是SHA-1,两者参数不匹配导致padding验证失败。


解决方案

修改Java解密代码,通过OAEPParameterSpec明确指定MGF1使用SHA-512哈希算法,与WebCrypto保持参数一致:

import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import javax.crypto.spec.MGF1ParameterSpec;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.math.BigInteger;
import java.util.Base64;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;

public void decrypt() {
    try {
        PrivateKey privateKey = loadPrivateKey();

        // 明确配置OAEP参数:主哈希SHA-512,MGF1哈希SHA-512,无额外参数
        OAEPParameterSpec oaepParams = new OAEPParameterSpec(
            "SHA-512",
            "MGF1",
            new MGF1ParameterSpec("SHA-512"),
            PSource.PSpecified.DEFAULT
        );

        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-512AndMGF1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParams);

        byte[] encryptedBytes = Base64.getDecoder().decode(cipherText);
        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);

        this.clearText = new String(decryptedBytes);
        System.out.println("Decrypted clear text: " + clearText);
    } catch (Exception e) {
        e.printStackTrace();
        System.err.println("Decryption failed: " + e.getMessage());
    }
}

// loadPrivateKey方法保持不变
private PrivateKey loadPrivateKey() throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    Map<String, Object> jwk = mapper.readValue(rsaKey, Map.class);
    System.out.println("JWK parsed: " + jwk);

    String nBase64 = (String) jwk.get("n");
    String dBase64 = (String) jwk.get("d");
    String pBase64 = (String) jwk.get("p");
    String qBase64 = (String) jwk.get("q");
    String dpBase64 = (String) jwk.get("dp");
    String dqBase64 = (String) jwk.get("dq");
    String qiBase64 = (String) jwk.get("qi");
    String eBase64 = (String) jwk.get("e");

    BigInteger modulus = new BigInteger(1, Base64.getUrlDecoder().decode(nBase64));
    BigInteger privateExponent = new BigInteger(1, Base64.getUrlDecoder().decode(dBase64));
    BigInteger publicExponent = new BigInteger(1, Base64.getUrlDecoder().decode(eBase64));
    BigInteger primeP = new BigInteger(1, Base64.getUrlDecoder().decode(pBase64));
    BigInteger primeQ = new BigInteger(1, Base64.getUrlDecoder().decode(qBase64));
    BigInteger primeExponentP = new BigInteger(1, Base64.getUrlDecoder().decode(dpBase64));
    BigInteger primeExponentQ = new BigInteger(1, Base64.getUrlDecoder().decode(dqBase64));
    BigInteger crtCoefficient = new BigInteger(1, Base64.getUrlDecoder().decode(qiBase64));

    RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent, primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient);
    KeyFactory keyFactory = KeyFactory.getInstance(AppConfig.CRYPTO_RSA);
    return keyFactory.generatePrivate(keySpec);
}

注意事项

  • 确保代码中导入javax.crypto.spec.MGF1ParameterSpec类
  • 该适配针对OpenJDK/SunJCE默认加密 provider,若使用第三方provider需调整参数格式
  • 客户端btoa生成的是标准Base64,Java用Base64.getDecoder()解析正确,无需修改编码逻辑

内容的提问来源于stack exchange,提问作者Roggab32

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 12:44:56