在 Java 和 Java 脚本之间使用 OAEP 进行 RSA 加密

RSA Encryption with OAEP between Java and JavaScript

我正在尝试在 JavaScript 中加密一个短字符串并在 Java 中解密它。解密失败,我认为这是因为两个平台之间的块模式 and/or 填充存在差异。我试过在Java和JavaScript中加密同一个字符串,得到不同的结果,说明确实有区别。这是创建密钥的 Java 代码:

KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair keyPair = kpg.generateKeyPair();

这是我用来测试加密的 Java 代码:

Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
byte[] bytes = cipher.doFinal("asdf".getBytes());

我将 public 键发送到 JavaScript 进程,并将其转换为 ArrayBuffer,变量名称为 publicKey。我已验证 JavaScript 端的密钥与 Java 端的密钥匹配(通过使用 crypto.subtle.exportKey 导出它并检查字节)。这是我用来测试加密的Java脚本代码:

crypto.subtle.importKey('spki', publicKey,
                        {hash: 'SHA-256', name: 'RSA-OAEP'}, true,
                        ['encrypt'])
      .then((key) => {
        crypto.subtle.encrypt({name: 'RSA-OAEP'}, key,
                              new TextEncoder().encode('asdf'))
              .then((buffer) => {

              });
      });

Java中字节数组的内容和JavaScript中的数组缓冲区内容不一样。我不确定的设置是 Java 端 Cipher#getInstance 的参数和 JavaScript 端 importKeyencrypt 的参数。使用内置 类 是否有任何设置可以在 Java 和 Java 脚本之间工作?或者我应该看看第三方库(例如 Bouncy Castle)?

看起来 JavaScript 和 Java 中的内置 encryption/decryption 没有兼容的 RSA 加密设置。一个可行的解决方案似乎是来自 github (forge on github). The key settings are described on the github page as follows (RSA examples) 的伪造库:

// encrypt data with a public key using RSAES-OAEP/SHA-256/MGF1-SHA-1
// compatible with Java's RSA/ECB/OAEPWithSHA-256AndMGF1Padding
var encrypted = publicKey.encrypt(bytes, 'RSA-OAEP', {
  md: forge.md.sha256.create(),
  mgf1: {
    md: forge.md.sha1.create()
  }
});

这是旧的,但如果您想在 java 脚本中使用微妙的加密并控制 java 解密,这里有一个替代解决方案。

假设您使用问题中的原始 JS 代码进行加密,Java 中的解密方式如下:

Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), PSource.PSpecified.DEFAULT);
cipher.init(Cipher.DECRYPT_MODE, privKey, oaepParams);
byte[] decrypted = cipher.doFinal(encryptedBytes)

密码 RSA/ECB/OAEPWithSHA-256AndMGF1Padding 的问题是它默认使用 SHA-1 作为 MGF1 填充。 Java脚本使用 SHA-256,这会导致不匹配。通过指定 MGF1ParamterSpec,我们可以强制 Java 使用与 Java 脚本默认相同的散列算法。

除了@Chip 的回答——这真的很有帮助——我想补充以下情况:

假设您想在 Java 脚本 (webcrypto) 中使用以下内容进行解密:

window.crypto.subtle.decrypt(
        {
            name: "RSA-OAEP",
            hash: { name: "SHA-512" }
            //label: Uint8Array([...]) //optional
        },
        privateRsaKey, //CryptoKey object containing private RSA key
        encdata //ArrayBuffer containing to be decrypted data
    )
    .catch(function(err){
        console.error(err);
    })

然后您必须在 Java 中使用以下 OAEPParameterSpec 进行加密(反之亦然,但我没有尝试):

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

因为@Chip 只提到了 MGF1 Padding I,所以我认为必须使用

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

但显然必须将两个哈希函数更改为 SHA-512,如我的第一个 OAEPParameterSpec 代码块所示。