【问题标题】:RSA decrypt failing between Java and browser using web crypto api使用 Web 加密 API 在 Java 和浏览器之间进行 RSA 解密失败
【发布时间】:2021-09-15 03:53:19
【问题描述】:

我正在尝试使用 RSA 公钥从服务器获取加密响应。密码在服务器端生成,但在客户端解码失败。 Web 加密 API 抛出 DOM 异常。

Java 服务器:

byte[] exponentBytes = Base64.getUrlDecoder().decode(body.exponent);
byte[] modulusBytes = Base64.getUrlDecoder().decode(body.modulus);

BigInteger exponent = new BigInteger(1, exponentBytes);
BigInteger modulus = new BigInteger(1, modulusBytes);

RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent);
KeyFactory factory = KeyFactory.getInstance("RSA");
PublicKey publicKey = factory.generatePublic(spec);

Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherBytes = cipher.doFinal("hello".getBytes())
return Base64.getEncoder().encodeToString(cipherBytes);

浏览器:

const key = await window.crypto.subtle.generateKey(
  {
    name: 'RSA-OAEP',
    modulusLength: 512,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: 'SHA-256',
  },
  true,
  ['encrypt', 'decrypt'],
)

const jwk = await window.subtle.exportKey('jwk', key.publicKey);

const response = await fetch('/foo/bar', { method: 'post', body: { exponent: jwk.e, modulus: jwk.n } });

const body = await response.text();

const binary = window.atob(body);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
  bytes[i] = binary.charCodeAt(i);
}

await window.crypto.subtle.decrypt(
  { name: 'RSA-OAEP' },
  key.privateKey,
  bytes.buffer,
); // returns undefined throws the error

编辑:经过进一步研究,我发现:

  1. (显然)web crypto api 生成的密码在解码时没有问题。
  2. 从服务器端返回字节数组本身没有帮助。

【问题讨论】:

  • 确切的错误是什么
  • 它只是抛出 DOMException
  • 看起来 crypto.subtle 不喜欢小于 2048 的“模数长度”
  • 使用hash: 'SHA-1' 你可以有最低modulusLength: 1024 - 使用hash: 'SHA-256' 似乎最低是modulusLength: 2048 - 除了在谷歌可怕的文档中之外,看不到modulusLength: 512正在使用
  • 我建议在这里调试/解密返回的加密内容gchq.github.io/CyberChef/… 只是为了验证您使用的密钥对是否正确

标签: javascript java cryptography rsa webcrypto-api


【解决方案1】:

在您使用的 Webcrypto 端

name: 'RSA-OAEP',
hash: 'SHA-256',
...

实例化算法。在 Java 方面,您“只是”用

实例化密码
Cipher cipher = Cipher.getInstance("RSA");

但那是

的同义词
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");

您需要使用这些行来实例化 Webcrypto 算法:

    Cipher encryptCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
    OAEPParameterSpec oaepParameterSpecJCE = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
    encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParameterSpecJCE);
    ciphertextByte = encryptCipher.doFinal(plaintextByte);

安全注意事项:512 的密钥长度是UNSECURE,请使用至少 2048 位的密钥长度。

【讨论】:

    猜你喜欢
    • 2013-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-02
    • 2012-04-07
    • 1970-01-01
    相关资源
    最近更新 更多