【问题标题】:Decrypt Base64 string with RSA private key使用 RSA 私钥解密 Base64 字符串
【发布时间】:2019-04-25 21:47:35
【问题描述】:

我有一个用 RSA 公钥加密的字符串,我需要用 RSA 私钥解密一个字符串。

我已经有一个可用的 Java 代码,但我需要用 Node.js 库编写一个类似的代码来解密我的消息。

这是有效的 Java 代码,

public static String getDecrypted(String data, String Key) 
      throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
      Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
      PrivateKey pk = KeyFactory.getInstance("RSA").generatePrivate(
          new PKCS8EncodedKeySpec(Base64.getDecoder().decode(Key.getBytes())));
      cipher.init(Cipher.DECRYPT_MODE, pk);
      byte[] encryptedbytes = cipher.doFinal(Base64.getDecoder().decode(data.getBytes()));
      return new String(encryptedbytes);
}

我找不到相应的 node.js 代码。

我有一个 Base64 编码的字符串,如下所示

S+JnXECfe8zHO69Mp0oh6ux******.......

并且拥有如下的私钥,

MIIJQgIBADANBgkqhkiG9w0BAQEFAASCC*****............

我无法在 node.js 中使用加密模块获得类似的代码。因为,我是这个加密模块的新手,我不确定我尝试的代码是否正确,

//decryption

const crypto = require('crypto');

const PK_HEADER = '\n-----BEGIN RSA PRIVATE KEY-----\n'
const PK_FOOTER = '\n-----END RSA PRIVATE KEY-----\n'

const pkey ='MIIJQgIBADANBgkqhki......'

const privateKey = `${PK_HEADER}${pkey}${PK_FOOTER}`
const privateEncodedKey = Buffer.from(privateKey, 'base64')

const response = "0f8c396c5279a...";
const decrypt = (privateEncodedKey, message) => {
let enc = crypto.privateDecrypt({
key: privateEncodedKey,
padding: crypto.RSA_PKCS1_OAEP_PADDING
}, Buffer.from(message, 'hex'));
return enc.toString();
};

console.log(decrypt(privateEncodedKey, response ))

你能帮我们修改 node.js crypto 中的代码吗?

这里是解密的细节..

算法 RSA (Rivest–Shamir–Adleman) 模式 ECB(电子密码本) 填充 OAEPWithSHA-1AndMGF1Padding

【问题讨论】:

    标签: java node.js


    【解决方案1】:

    Meta:我很惊讶这不是骗局,但我找不到。

    首先,如果您的数据实际上是使用 OAEP 加密的,那么 Java 代码应该工作。 Java(和大多数其他地方)中的PKCS1Padding 专门用于 PKCSv1.5 填充,现在将RSAES-PKCS1-v1_5 用于加密(而RSASSA-PKCS1-v1_5 用于签名,此处不适用)。虽然 OAEP 也在 PKCS1v2.0 up 中定义,但在 Java 中,您应该仅指定 OAEPPadding 作为默认值(即 SHA-1),或为非默认值指定OAEPwith{hash}andMGF1Paddingjavax.crypto.spec.OAEPParameterSpec 类型的第三个 init 参数.

    其次,您展示的 base64 私钥是 PKCS8 未加密格式(也称为编码),而不是 PKCS1 格式。如果您是从 Java 获得的,那么这是意料之中的; Java 加密对所有算法使用PrivateKey 的PKCS8 格式,(PublicKey 使用“X.509”格式)。这记录在一个不太明显的地方,the superinterface Key。未加密的 PKCS8 的正确 PEM 类型是 PRIVATE KEY - 而不是用于 PKCS1 的 RSA PRIVATE KEY

    另外, PEM 文件不只是 BEGIN 行、一个 base64 行和 END 行。它们是 BEGIN 行、一个或多个 base64 行每 64 个字符中断,以及 END 行。 nodejs 使用 OpenSSL,它的旧版本稍微放宽了这个限制;他们允许每行最多 76 个字符。较新的版本允许更多,但仍有一个限制,我相信您的密钥(显然是 4096 位或关闭)可能会超过它,因此如果不添加至少一些换行符,它将无法工作。

    最后,一旦你有了 PEM 格式,not base64-decode 它。这将包括解码中的 BEGIN 和 END 行,完全垃圾结果。 nodejs crypto 的这一部分(以及其他几个部分)传统上接受与 OpenSSL 相同的 PEM 格式;根据 doc 11.6.0 up 可以接受使用 OpenSSL 的“DER”格式的替代形式,这确实对应于 base64 解码实际密钥数据而不是整个 PEM(但我没有新版本要测试)。

    因此,假设您正确使用 OAEP,以下是两种方法的示例(使用 my 键和数据):

    const crypto = require('crypto');
    
    const pk8b64 = (
    "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKNwapOQ6rQJHetP"+
    "HRlJBIh1OsOsUBiXb3rXXE3xpWAxAha0MH+UPRblOko+5T2JqIb+xKf9Vi3oTM3t"+
    "KvffaOPtzKXZauscjq6NGzA3LgeiMy6q19pvkUUOlGYK6+Xfl+B7Xw6+hBMkQuGE"+
    "nUS8nkpR5mK4ne7djIyfHFfMu4ptAgMBAAECgYA+s0PPtMq1osG9oi4xoxeAGikf"+
    "JB3eMUptP+2DYW7mRibc+ueYKhB9lhcUoKhlQUhL8bUUFVZYakP8xD21thmQqnC4"+
    "f63asad0ycteJMLb3r+z26LHuCyOdPg1pyLk3oQ32lVQHBCYathRMcVznxOG16VK"+
    "I8BFfstJTaJu0lK/wQJBANYFGusBiZsJQ3utrQMVPpKmloO2++4q1v6ZR4puDQHx"+
    "TjLjAIgrkYfwTJBLBRZxec0E7TmuVQ9uJ+wMu/+7zaUCQQDDf2xMnQqYknJoKGq+"+
    "oAnyC66UqWC5xAnQS32mlnJ632JXA0pf9pb1SXAYExB1p9Dfqd3VAwQDwBsDDgP6"+
    "HD8pAkEA0lscNQZC2TaGtKZk2hXkdcH1SKru/g3vWTkRHxfCAznJUaza1fx0wzdG"+
    "GcES1Bdez0tbW4llI5By/skZc2eE3QJAFl6fOskBbGHde3Oce0F+wdZ6XIJhEgCP"+
    "iukIcKZoZQzoiMJUoVRrA5gqnmaYDI5uRRl/y57zt6YksR3KcLUIuQJAd242M/WF"+
    "6YAZat3q/wEeETeQq1wrooew+8lHl05/Nt0cCpV48RGEhJ83pzBm3mnwHf8lTBJH"+
    "x6XroMXsmbnsEw=="
    ) .replace(/.{64}/g,"$&\n");
    
    const ctxb64 =
    "QLiNLbAqDPG024Xdtl80OMWCHfPq4pCIduoXKcVyY0211Ji7n6Cvjp+ATyLg95mX"+
    "/xuFdLV6jiR0ayVw1KTb+U3WKwQRsAWzA+gYiDjdfRaCrNxtcCp2Onw92bjdjZke"+
    "O7VrmzDj+8ovDvDgb/pXhAPMcJKSCDUVXgCxpZSnVJE=";
    const ctxbuf = Buffer.from(ctxb64,'base64');
    
    // the traditional way 
    const t1 = crypto.privateDecrypt("-----BEGIN PRIVATE KEY-----\n"+pk8b64+"\n-----END PRIVATE KEY-----\n", ctxbuf);
    console.log(t1);
    
    // should work in 11.6.0 (not tested); can add padding: to specify if other than OAEP 
    const t2 = crypto.privateDecrypt({key:Buffer.from(pk8b64,'base64'),format:'der',type:'pkcs8'}, ctxbuf);
    console.log(t2);
    

    【讨论】:

    • 非常感谢您的帮助。您的代码在最新的节点 js 上运行良好。但是当我使用我的私钥和文本执行时,我收到以下错误。 internal/crypto/cipher.js:53 返回方法(数据、格式、类型、密码、缓冲区、填充); ^ 错误:错误:0406506C:rsa 例程:rsa_ossl_private_decrypt:数据大于 mod len const t2 = crypto.privateDecrypt( { key: Buffer.from(pk8b64, "base64"),格式:“der”,类型:“pkcs8”,填充:crypto.RSA_PKCS1_OAEP_PADDING },ctxbuf); console.log(t2.toString());
    • Sathish:那么要么您的密钥密文错误(可能在复制或传输中损坏),要么您的密文密钥错误。有效的 RSA 密文在数值上总是小于密钥中的模值“n”,并且通常表示为与模长度相同的八位字节字符串,可能以十六进制、base64 或相似的。 (签名也是如此。)
    • 嗨,戴夫。回复晚了非常抱歉。你是完全正确的,关键是我使用的是错误的。您的代码工作正常,但我无法将节点升级到最新版本。所以尝试了代码。
    • @SathishKumarSundaramoorthy - 根据您的 cmets,我认为这应该被标记为正确答案?
    • @dave_thompson_085 感谢您的回答。你有做私人咨询工作吗? (模组,如果这是一个不恰当的问题,请道歉 - 如果是这样,请告诉我,我会删除此评论)
    【解决方案2】:

    这是 node.js 中与上述 Java 代码完全相同的代码。

    var crypto = require("crypto");
    var path = require("path");
    var fs = require("fs");
    
    var encryptStringWithRsaPublicKey = function(toEncrypt, relativeOrAbsolutePathToPublicKey) {
        var absolutePath = path.resolve(relativeOrAbsolutePathToPublicKey);
        var publicKey = fs.readFileSync(absolutePath, "utf8");
        var buffer = Buffer.from(toEncrypt);
        var encrypted = crypto.publicEncrypt(publicKey, buffer);
        return encrypted.toString("base64");
    };
    
    var decryptStringWithRsaPrivateKey = function(toDecrypt, relativeOrAbsolutePathtoPrivateKey) {
        var absolutePath = path.resolve(relativeOrAbsolutePathtoPrivateKey);
        var privateKey = fs.readFileSync(absolutePath, "utf8");
        var buffer = Buffer.from(toDecrypt, "base64");
        console.log("PRIVATE_KEY:",privateKey);
        var decrypted = crypto.privateDecrypt({
            key: privateKey,
            padding:crypto.constants.RSA_PKCS1_OAEP_PADDING
        }, buffer);
    
        return decrypted.toString("utf8");
    };`
    
    cipher= '/**Encrypted TEXT/*'
    
    /*private.pem*/
    -----BEGIN PRIVATE KEY-----
    ............................
    -----ENDPRIVATE KEY-----
    /**/
    const decryptedText = decryptStringWithRsaPrivateKey(cipher,'private.pem')
    ````````
    

    【讨论】: