Java 使用 NodeJS 加密解密 <RSA_PKCS1_OAEP_PADDING> 填充和 <sha256> oaepHash

Java Decrypt with NodeJS Encrypt <RSA_PKCS1_OAEP_PADDING> padding and <sha256> oaepHash

我需要解密来自使用 NodeJS 构建的外部服务的一些信息。 此服务要求使用 base64 中 pem 格式的 RSA (2048) public 密钥,以加密信息。

我正在 Java 中创建密钥对:

    KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
    kpg.initialize(2048);
    KeyPair kp = kpg.generateKeyPair();
    PublicKey pubkey = kp.getPublic();
    PrivateKey privkey = kp.getPrivate();

    String pemPublicString = "-----BEGIN PUBLIC KEY-----\n";
    pemPublicString = pemPublicString+Base64.getEncoder().encodeToString(pubkey.getEncoded())+"\n";
    pemPublicString = pemPublicString+"-----END PUBLIC KEY-----\n";
    
    String pemPrivateString = "-----BEGIN RSA PRIVATE KEY-----\n";
    pemPrivateString = pemPrivateString+Base64.getEncoder().encodeToString(privkey.getEncoded())+"\n";
    pemPrivateString = pemPrivateString+"-----END RSA PRIVATE KEY-----\n";
    
    //Send to node js service
    String base64publickey = Base64.getEncoder().encodeToString(pemPublicString.getBytes());

    //Store for decrypting
    String base64privatekey = Base64.getEncoder().encodeToString(pemPrivateString.getBytes());

外部服务正在按如下方式加密信息并返回字节:

  crypto.publicEncrypt(
  {
    key: publicKey,
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
    oaepHash: "sha256",
  },
    dataToEncrypt
  );

我正在尝试解密Java中的信息如下:

    public String decrypt(String payload, String privateKey){
      byte [] ciphertext = payload.getBytes(StandardCharsets.UTF_8);
      Cipher oaepFromInit = Cipher.getInstance("RSA/ECB/OAEPPadding");
      OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new 
      MGF1ParameterSpec("SHA-1"), PSource.PSpecified.DEFAULT);
      oaepFromInit.init(Cipher.DECRYPT_MODE, getRSAPrivateKeyFrom(privateKey), oaepParams);
      byte[] pt = oaepFromInit.doFinal(ciphertext);
      return new String(pt, StandardCharsets.UTF_8);
    }

    private PrivateKey getRSAPrivateKeyFrom(String content) {
      byte[] decodedBytes = Base64.getDecoder().decode(content);
      String decodedString = new String(decodedBytes);
      Security.addProvider(new BouncyCastleProvider());
      PEMParser pemParser = new PEMParser(new StringReader(decodedString));
      JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
      Object object = pemParser.readObject();
      PrivateKey k = converter.getPrivateKey((PrivateKeyInfo) object);
      return k;
   }

现在我收到 BadPadding 异常,知道可能是什么问题吗? 提前致谢。

发布的代码没有显示 NodeJS 代码如何导出密文。 Java 代码的 decrypt() 中的以下行:

 byte[] ciphertext = payload.getBytes(StandardCharsets.UTF_8);

表示您使用了 Utf-8 编码。这是破坏密文的常见错误(见here). Instead of Utf-8, apply a binary-to-text enncoding,如Base64.

密文的导出将在 NodeJS 中完成:

var chiphertextBase64 = ciphertext.toString('base64');

并在 Java 中导入:

import java.util.Base64;
...
byte[] ciphertext = Base64.getDecoder().decode(payload);  

在 NodeJS 代码中,OAEP(RSAES-OAEP) is specified as padding. crypto.publicEncrypt() 应用参数 oaepHash 相同 摘要,OAEP 和 MGF1 摘要。oaepHash: "sha256" 因此为 both 摘要指定 SHA256。
相反,Java 允许单独(和不同)规范 OAEP 和 MGF1 摘要。虽然 SHA256 用于 decrypt() 中的 OAEP 摘要,但 SHA1 用于 MGF1 摘要。后者与NodeJS代码不一致,需要改成SHA256:

OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), PSource.PSpecified.DEFAULT);

Maarten Bodewes 在第一条评论中已经怀疑存在此错误。


在发布的代码中,PEM 编码的 pemPublicStringpemPrivateString 密钥分别被 Base64 编码为 base64publickeybase64privatekey。这不是必需的,因为 PEM 编码密钥的 body 已经是 Base64 编码并且 header 和页脚由 ASCII 字符组成。因此,第二个 Base64 编码没有带来任何优势,但会带来关键数据被不必要地放大的缺点(Base64 开销:33%,参见 here)。另一方面,如果服务需要双重 Base64 编码的 public 密钥,您必须遵守。

生成密钥时,发布的 Java 代码隐式使用 PKCS#8 格式作为私钥。您可以使用 privkey.getFormat() 验证这一点,这将输出 PKCS#8。 PKCS#8 的关联 header 是 -----BEGIN PRIVATE KEY----- 和页脚 -----END PRIVATE KEY-----。您当前使用的 header 和页脚都属于 PEM 编码的 PKCS#1 私钥,因此与密钥数据不一致。 Maarten Bodewes 在第二条评论中已经解决了这个问题。

顺便说一下,无需 BouncyCastle 使用 PKCS8EncodedKeySpec. For this only header, footer and line breaks have to be removed and the rest has to be Base64 decoded (DER encoding), s. e.g. here.

即可轻松导入 PKCS#8 密钥