首页 > 解决方案 > 使用 Java 进行 RSA 加密,使用 JavaScript 进行解密

问题描述

我正在尝试使用 Java 加密(使用javax.crypto.Cipher)并使用 JavaScript 解密(使用crypto.subtle)。我正在做的是,我让 JavaScript 端生成密钥对,然后通过以下方式将公钥发送到 Java 端:

$(window).on("load", function () {

    const enc = new TextEncoder();
    const dec = new TextDecoder();

    crypto.subtle.generateKey({
      name: "RSA-OAEP",
      modulusLength: 1024,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: "SHA-256"
    },
        true,
        ["encrypt", "decrypt"]
    ).then(function ({ privateKey, publicKey }) {

        crypto.subtle.exportKey("spki", publicKey).then(function (spki) {

            const strPublicKey = spkiToString(spki);
            // SEND THE PUBLIC KEY TO THE SERVER (JAVA)
            
        });


    });


});

function spkiToString(keydata) {
    var keydataS = arrayBufferToString(keydata);
    return window.btoa(keydataS);
}

function arrayBufferToString(buffer) {
    var binary = '';
    var bytes = new Uint8Array(buffer);
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return binary;
}

服务器使用公钥进行加密:

 try {
    String publicKey = ""// this will come from the JS side
    String message = "encrypt me"//  
    byte[] publicBytes = Base64.getDecoder().decode(publicKey).getBytes());
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PublicKey pubKey = keyFactory.generatePublic(keySpec);

    Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
    OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"),
            PSource.PSpecified.DEFAULT);
    cipher.init(Cipher.ENCRYPT_MODE, pubKey, oaepParams);

    return Base64.getEncoder().encode(cipher.doFinal(message));

} catch (Exception e) {
    e.printStacktrace();
}

return new byte[0];

然后将结果值返回给 Javascript 端,以便它可以解密消息:

const encryptedToken = "" // this will be obtained from the server
crypto.subtle.decrypt({
    name: "RSA-OAEP",
    hash: { name: "SHA-256" }
},
    privateKey,
    enc.encode(atob(encryptedToken))
).then(function (result) {
    console.log("decrypted", dec.decode(result))
}).catch(function (e) {
  console.log(e);
})

当 Javascript 尝试解密时,它会抛出一个DOMException没有消息(检查附加图像)。

我做错了什么?提前谢谢你。

在此处输入图像描述

标签: javascriptjavarsa

解决方案


问题出在最后一个 JavaScript 代码片段enc.encode(atob(encryptedToken))的方法中。decrypt()UTF-8 编码会破坏数据,从而阻止成功解密。如果更改此设置,则解密工作如下所示。
下面的 JavaScript 代码与问题中的第一个代码片段基本相同,只是以 PKCS8 格式导出私钥:

crypto.subtle.generateKey(
    {
        name: "RSA-OAEP",
        modulusLength: 1024,
        publicExponent: new Uint8Array([1, 0, 1]),
        hash: "SHA-256"
    },
    true,
    ["encrypt", "decrypt"]
).then(function ({ privateKey, publicKey }) {
    crypto.subtle.exportKey("spki", publicKey).then(function (spki) {
        const strPublicKey = spkiToString(spki);  
        console.log(strPublicKey.replace(/(.{56})/g,'$1\n'));
        crypto.subtle.exportKey("pkcs8", privateKey).then(function (pkcs8) {
            const strPrivateKey = spkiToString(pkcs8);  
            console.log(strPrivateKey.replace(/(.{56})/g,'$1\n'));
        });
    });
});             

function spkiToString(keydata) {
    var keydataS = arrayBufferToString(keydata);
    return window.btoa(keydataS);
}

function arrayBufferToString(buffer) {
    var binary = '';
    var bytes = new Uint8Array(buffer);
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return binary;
}

此代码用于生成例如以下 X.509/SPKI 格式的公钥:

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1F8+EvG9XP8jSXItV89QtlYy/5Z+arMegvMwsasS5IIUdr4b4eE2FGoDalaqyAxWOg/pBkzfBWAQkhuKz3i14OsBYQl1QDDm3yfmI498SsE7tZyrENCfTGrPwoCrQmEwTWOCfIBCh+mGRAUTgcsQO/g8pIFglF3QTTzlu3n0KhQIDAQAB

和PKCS8格式的私钥:

MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALUXz4S8b1c/yNJci1Xz1C2VjL/ln5qsx6C8zCxqxLkghR2vhvh4TYUagNqVqrIDFY6D+kGTN8FYBCSG4rPeLXg6wFhCXVAMObfJ+Yjj3xKwTu1nKsQ0J9Mas/CgKtCYTBNY4J8gEKH6YZEBROByxA7+DykgWCUXdBNPOW7efQqFAgMBAAECgYAK6oUFVNCHW15xI8f4ZerH1qh11tMgoUKlU0whb0wtdqLfj7mcl6/gkqDqzDPOaDYv8Y+vzT6CppoVU5YtznpCF4YRLuOfeAkY0kT9C7w62lu1C1aFMDS1Eydv0a10t001sp0W5U8J0LMgPpevPlksv2t9gZa08yGsBnVX9BIXjwJBAOrlsV6LsxNBnSKqXhZf1+uQe1vpPPzF3IXTvJzd4LhamcnImYayrg4Zjgj71+/0BFdWT9qGxtKGwJJGIjrMDG8CQQDFXLIrFMHVpdjrsAaQXvPWTSVIfVayi6Uib1HpXKiLJ53snebsBrBiShbAsJjrgWXzdurky6nGIlp5NV7i//pLAkEA4XaxRfe/XhdXtWNjxgQe41ueHH2GbXWZktbGrqcFwM4t2RHz0ueEy8HZpGPfQ9GrrQ0Kvs0o4AA5rO0mg9tBfwJBAJMWuaaH6spatyc4Yjv4uEuv5Sh4WUPp9WGLi4WbS/Whyf4N1It1lME8LGbhdqaWIrBnoTpxWw9SjREmqJgPZK8CQBAAI6IUkCeE+Lwub+akoBFuqyyIdIpIfXu4ntyxnZemmCNdotEfNL3yp0J3Rw6TpXyPDN/4uOrxt/aY2heXAKM=

在 Java 代码中应用了公钥以生成下面的密文:

jCt9rD/6Q6OsjH+bd1XKB2FhDYTwzupQsFnwjKkrxulC3ztZx0j9/Zr6hBeCbFrdYFtxZi+j8lyyLJCHv0hpN0S5F/O6v/mhMIgCTWCmpWqcLqKC2zDWo180uL+dMysZm2JaBHzWA9VjnVTdVY3aRTWfu1fpEpTK6W9ESTVSS8Y=

下面的 JavaScript 代码本质上与通过导入上面生成的私钥扩展的第三个代码片段相同。另外,应用上面生成的密文:

var pkcs8B64 = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALUXz4S8b1c/yNJci1Xz1C2VjL/ln5qsx6C8zCxqxLkghR2vhvh4TYUagNqVqrIDFY6D+kGTN8FYBCSG4rPeLXg6wFhCXVAMObfJ+Yjj3xKwTu1nKsQ0J9Mas/CgKtCYTBNY4J8gEKH6YZEBROByxA7+DykgWCUXdBNPOW7efQqFAgMBAAECgYAK6oUFVNCHW15xI8f4ZerH1qh11tMgoUKlU0whb0wtdqLfj7mcl6/gkqDqzDPOaDYv8Y+vzT6CppoVU5YtznpCF4YRLuOfeAkY0kT9C7w62lu1C1aFMDS1Eydv0a10t001sp0W5U8J0LMgPpevPlksv2t9gZa08yGsBnVX9BIXjwJBAOrlsV6LsxNBnSKqXhZf1+uQe1vpPPzF3IXTvJzd4LhamcnImYayrg4Zjgj71+/0BFdWT9qGxtKGwJJGIjrMDG8CQQDFXLIrFMHVpdjrsAaQXvPWTSVIfVayi6Uib1HpXKiLJ53snebsBrBiShbAsJjrgWXzdurky6nGIlp5NV7i//pLAkEA4XaxRfe/XhdXtWNjxgQe41ueHH2GbXWZktbGrqcFwM4t2RHz0ueEy8HZpGPfQ9GrrQ0Kvs0o4AA5rO0mg9tBfwJBAJMWuaaH6spatyc4Yjv4uEuv5Sh4WUPp9WGLi4WbS/Whyf4N1It1lME8LGbhdqaWIrBnoTpxWw9SjREmqJgPZK8CQBAAI6IUkCeE+Lwub+akoBFuqyyIdIpIfXu4ntyxnZemmCNdotEfNL3yp0J3Rw6TpXyPDN/4uOrxt/aY2heXAKM=";
const pkcs8StrDER = atob(pkcs8B64);
const pkcs8DER = str2ab(pkcs8StrDER);
crypto.subtle.importKey(
    "pkcs8",
    pkcs8DER,
    {
        name: "RSA-OAEP",
        modulusLength: 1024,
        publicExponent: new Uint8Array([1, 0, 1]),
        hash: "SHA-256",
    },
    true,
    ["decrypt"]
).then(function(privateKey){
    const dec = new TextDecoder();
    const encryptedToken = "jCt9rD/6Q6OsjH+bd1XKB2FhDYTwzupQsFnwjKkrxulC3ztZx0j9/Zr6hBeCbFrdYFtxZi+j8lyyLJCHv0hpN0S5F/O6v/mhMIgCTWCmpWqcLqKC2zDWo180uL+dMysZm2JaBHzWA9VjnVTdVY3aRTWfu1fpEpTK6W9ESTVSS8Y="; 
    crypto.subtle.decrypt(
        {
            name: "RSA-OAEP",
        },
        privateKey,
        str2ab(atob(encryptedToken))
    ).then(function (result) {
        console.log("decrypted:", dec.decode(result))
    }).catch(function (e) {
        console.log(e);
    });             
});

function str2ab(str) {
    const buf = new ArrayBuffer(str.length);
    const bufView = new Uint8Array(buf);
    for (let i = 0, strLen = str.length; i < strLen; i++) {
        bufView[i] = str.charCodeAt(i);
    }
    return buf;
}

代替破坏数据的 UTF-8 编码,str2ab()使用该函数将 Base64 解码的数据转换为ArrayBuffer.
我的评论中建议的替换_base64ToArrayBuffer(encryptedToken)同样可能。与, 不同str2ab()_base64ToArrayBuffer()也执行 base64 解码。
运行代码会导致纯文本加密我的 Java 代码。


推荐阅读