使用 NodeJS 的 RSA PKCS#1 加密

RSA PKCS#1 encryption with NodeJS

我想用 public 密钥和 RSA PKCS#1 模式加密一些数据。我已经使用 phpseclib 库在 PHP 上完成了这项工作,并且没有任何问题。

这是我的 PHP 代码:

<?php
include(__DIR__ . DIRECTORY_SEPARATOR . 'phpseclib/Crypt/RSA.php');
$apiKey = 'xxxxxxxxxxxxxxxxxxxx';
$publicKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxx';

// request values in JSON format
$Parameters = array(
    "name" => "Example Name",
    "mobile" => "99999999999",
    "remark" => "Product ",
    "email" => "example@example.com",
    "language" => "en",
    "fields" => array (
        "udf1" => "",
        "udf2" => "",
        "udf3" => "",
    ),
);

// Create rsa object
$rsa = new Crypt_RSA();
// Load public key
$rsa->loadKey($publicKey);
// Set the mode to PKCS#1
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);

//Start the loop through the PHP array to encrypt the value
$returnValue = [];
if($Parameters){
    foreach($Parameters as $x => $x_value){
        if($x == "fields"){
            $_Fields = [];
            foreach($x_value as $y => $y_value){
                $ciphertext = $rsa->encrypt($y_value);
                // Convert it into Base64
                $y_value_encrypted = base64_encode($ciphertext);
                // insert the line array into the overall array
                $_Fields[$y] = $y_value_encrypted;  
            }
            $returnValue[$x] = $_Fields;
        }else{
            $ciphertext = $rsa->encrypt($x_value);
            // Convert it into Base64
            $x_value_encrypted = base64_encode($ciphertext);                    
            // insert the line array into the overall array
            $returnValue[$x] = $x_value_encrypted;                  
        }
    }

    print $returnValue;

    $curl = curl_init();
    curl_setopt_array($curl, array(
        CURLOPT_URL => 'https://api.example.com/v1',
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => "",
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => "POST",
        CURLOPT_POSTFIELDS => $returnValue,
        CURLOPT_HTTPHEADER => array(
            "authorization: Bearer $apiKey",
            "content-type: application/json"
        ),
    ));
    $result = curl_exec($curl);
    $err = curl_error($curl);
    curl_close($curl);

    print $result;

}

?>

我想在 NodeJS 上使用相同的方法,但由于 Parameters 数据加密错误,我从 API 收到错误。

这是我的 NodeJs 代码:

var crypto = require('crypto');
var request = require('request');


const apiKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; //User API KEY
const publicKey = `-----BEGIN PUBLIC KEY-----\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n-----END PUBLIC KEY-----`; //User public Key


//Set data to encrypt
const Parameters = {
    'name': 'example name',
    'mobile': '9999999999',
    'remark': 'Product ',
    'email': 'example@example.com',
    'language': 'en',
    'fields': {
        'udf1': '',
        'udf2': '',
        'udf3': '',
    },
};

//Start encrypting "Parameters" values
function encrypt(requestData) {
    var dataToEncrypt = requestData;
    var returnValue = {};
    if (dataToEncrypt) {
        for (var i in dataToEncrypt) {
            //Encrypt 'fields' values
            if (i == 'fields') {
                var fields_array = {};
                var fields = dataToEncrypt[i];
                for (var y in fields) {
                    var yVlaue = fields[y];
                    var result;
                    result += crypto.publicEncrypt({key:publicKey, padding:crypto.constants.RSA_PKCS1_PADDING}, Buffer.from(yVlaue, 'utf8') ).toString('base64');
                    fields_array[y] = result;
                }
                returnValue[i] = fields_array;
            } else {
                //Encrypt other values
                var result;
                result += crypto.publicEncrypt({key:publicKey, padding:crypto.constants.RSA_PKCS1_PADDING}, Buffer.from(dataToEncrypt[i], 'utf8') ).toString('base64');
                returnValue[i] = result;
            }
        }
    }

    Send(returnValue);
    return returnValue;
}

function Send(encryptedData) {

    var options = {
        'method': 'POST',
        'url': 'https://api.example.com/v1',
        'headers': {
          'Authorization': 'Bearer '+ apiKey,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(encryptedData)
      };

      request(options, function (error, response) { 
        if (!error && response.statusCode == 200) {
            console.log("\nResult:\n"+response.body);
        } else {
            console.log(error.message);
        }
      });
}

encrypt(Parameters);

像我在 PHP 上所做的那样,使用 public 密钥加密字符串并在 NodeJs 上使用 RSA PKCS#1 模式的正确方法是什么?

请注意,加密在 NodeJs 上有效,但加密方式存在问题。

我建议如下更改 Node.js 中的 encrypt 函数,我相信这会在 PHP 中正确解密。

function encrypt(requestData) {
    var dataToEncrypt = requestData;
    var returnValue = {};
    if (dataToEncrypt) {
        for (var i in dataToEncrypt) {
            //Encrypt 'fields' values
            if (i == 'fields') {
                var fields_array = {};
                var fields = dataToEncrypt[i];
                for (var y in fields) {
                    fields_array[y] = crypto.publicEncrypt({key:publicKey, padding: crypto.constants.RSA_PKCS1_PADDING}, Buffer.from(fields[y], 'utf8') ).toString('base64');
                }
                returnValue[i] = fields_array;
            } else {
                //Encrypt other values
                returnValue[i] = crypto.publicEncrypt({key:publicKey, padding: crypto.constants.RSA_PKCS1_PADDING}, Buffer.from(dataToEncrypt[i], 'utf8') ).toString('base64');
            }
        }
    }

    Send(returnValue);
    return returnValue;
}

输出如下所示:

{
    "name": "fdz8lUYD0hsmRGz5nqgTvQVZ82rNZylFCQ2It24HHbL1JdKrbFAia9pzLu45AZ0uf45oUo6H8qzVKO/HmJBnWSMI3CYf1HSPcZC4EmtaSE2SIWf7Z29j5lt4vDIwGKDiefMz2IuKcQhnWy3e3/hDi3uhUaKPDczLZ0ZgBAzf9iI=",
    "mobile": "oBYToRCFftSppUNulagXexPmMKag3tXFEQYV2rRq6VobptxkdZB3Thlhrhait+eVP/5lZzkekGgup5o0kTIAPkJdXvr1ttu4dEJNR6oa6TXBZ4YFMx0ndhLS2HJaYbLMQ5JjK7P4iKW6A6wKuEJlTaUjPL1tHXxMwAG5HjdO5zg=",
    "remark": "pXCA406AVprrvcX33zYlrS8jEZ4wsQDc6RJux1mRBb4tj5tp09JgtoLF2rhJ7cjHLEiWpLlHSSBcC5gkOy2eK8prfshQTgJuGvnEqKMMKGwMhKUJebzP+yio4n6SlHqVasU2ejZksDEY7sSj8BIum9JiL8i9ylQ4chBuZNLXGgc=",
    "email": "0g9zm4WlydNfLU0D0V5bOvxS9JiaQ8a/y4qXAlMealmxZegC973g2FttvolgPDUfxU7HfV10bzXbwgfiXJZXodGMrwYiAWDvhQz424I1SrZznhZEE3djHkz6zr69KBR6HV91Yv43AMyAYyHGHI4YIwzs1Pa8fInBnnTUreVvBo8=",
    "language": "yrSJsmCFMi8py6e8iMXiIjn1r/VAiT858ss4GkPPLFOoxtJopC0XLudG9mDh0MQp6bZRIQifiJwsHi07jpSHHOZTCeiO2R0qCF3VI/FCV3YsyUPHSX3ZdKHYqkzIlwhocDKOPt7ZlAlNhW4U8UbBTAq7YsiyPBtkYA/ikxpgsOE=",
    "fields": {
        "udf1": "SdrCwXYnj6zIW51VzQD8TRXScE7lyrr+tn6eglBInoH+yAZE/IRx0R9nNihCUCbvoIL1s6rT5Irl4Y3KB7CGp6b0wIA3itryKhkaVLYUJpqjXen4lqVLPsdsUpAeboJEgcO0aUbhq2ybawAwr5T1IADUyI/5o937zBv2cJkR7dU=",
        "udf2": "Q0FpyOYmOPUju1Xopr2DXTdRgUJKJ4JlmoxwhWoM3ZEYbIkC76UqT7jAyCtJ5yOTpuh9dyX8VMdI0mx8ziT3ytwrjusDcOKPN8D8SNLxszYAabZVPankcB0WlB+u5GN4owyead1NpDzmWvkyHf+iEf7OFHY4FkrEq3h39Vu50TE=",
        "udf3": "fxs3phQu1DFPZbke13ka7veX2DlMD7DcjB98f31G2UqjJW7RTYzBQ8ddNdYWyuRPsG5N9zTGBLvm4+9om2TinAJd+NXJDOjhOf0pihifQJ/hypOjovWCgt5nlaptvd2Gi3vU9ny5LQ0r3CIwU17iTH2w9QCos/Ncta7Ev2VQ83E="
    }
}

我还建议在 PHP 和 Node.js 中创建简单的测试脚本来测试字符串的简单加密和解密。

这很好地隔离了问题。我建议确保 Node.js 加密的输出可以在 PHP 中解密,反之亦然。

simple_encrypt.php

set_include_path(get_include_path() . PATH_SEPARATOR . 'phpseclib');

include(__DIR__  . '/phpseclib/Crypt/RSA.php');

$publicKey = '-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDbVuHaQIctLFZK1oIn2+P2j8z4
1S5kdQSo2AWYRMeo2yd+00ozdYjDpJ9eNpZyk1F7Dx9b+SmU39R0Tz89/IiTL78T
ZTOQ1FFFvktJhKT413QiegBAx09DhX3I+NQwvZWdU9SkptAGz9PD6zIeJTlh5HHA
fC/P+oLUhpQjkd+JBQIDAQAB
-----END PUBLIC KEY-----';

$privateKey = '-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDbVuHaQIctLFZK1oIn2+P2j8z41S5kdQSo2AWYRMeo2yd+00oz
dYjDpJ9eNpZyk1F7Dx9b+SmU39R0Tz89/IiTL78TZTOQ1FFFvktJhKT413QiegBA
x09DhX3I+NQwvZWdU9SkptAGz9PD6zIeJTlh5HHAfC/P+oLUhpQjkd+JBQIDAQAB
AoGAFv4WkuBsyyl4qkOuRSthzDjqzbLbOCCkKmbqvzmGjIbcrwjLIHXTrl6VbjTe
tIgI5JODQArwdvC3vrGH+aF9V27XW4Vl4WllPlBIzvaq1JufPOGupzNmssScFWV5
IL+P1Sx5F22rO+w+lIUdVFgbInITu4Bs2rvKsjAyBmvq7MkCQQD0kQvDWuPWkHSj
xzUjAXl+rT8ZOmANC1x35bLAEUHL2EWCejV9jO48TnwkqOQabT/rEWjrv4g7VHC0
ErNg1bAjAkEA5ZfrSKjyxoEbbK1lQz1GBMpyXGSpbTVJ+eMinBYY4JOnswLLUpmP
/OIJtLrOq38zSzNE2iHoxoGQDt1OlQDgtwJASPG2G3dRe16sm2jALYe0EBdmOYUS
vvFDjDNDhEvhXwZLfSYsLB1LtUsHdfu1xTgOl3Mi4yXGYUPHNb5aKCi0FwJAQGaH
sd7qEnI+jhJpOB4k2M0snOwDdkWfVX+3wo6UNdJVFOpwu9+lOurwjAhmVkaczbg4
1PL0B1JqZTEAjN0tKwJBAMRx0oqN5JNqc3iCrjDxJ9e94t7poVV7if0TfLBVzceR
5mgjPSOMjjkpzVG1P6+37V4whe26u3BhX04PD8Nzl3c=
-----END RSA PRIVATE KEY-----';

$rsa = new Crypt_RSA();
$rsa->loadKey($publicKey);

$plainText = 'abc';

$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$cipherText = $rsa->encrypt($plainText);
$rsa->loadKey($privateKey);

echo "\nEncrypted (base64): " . base64_encode($cipherText);
echo "\nDecrypted: " . $rsa->decrypt($cipherText);

simple_encrypt.js

const crypto = require('crypto');

const publicKey = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDbVuHaQIctLFZK1oIn2+P2j8z4
1S5kdQSo2AWYRMeo2yd+00ozdYjDpJ9eNpZyk1F7Dx9b+SmU39R0Tz89/IiTL78T
ZTOQ1FFFvktJhKT413QiegBAx09DhX3I+NQwvZWdU9SkptAGz9PD6zIeJTlh5HHA
fC/P+oLUhpQjkd+JBQIDAQAB
-----END PUBLIC KEY-----`;

const privateKey = `-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDbVuHaQIctLFZK1oIn2+P2j8z41S5kdQSo2AWYRMeo2yd+00oz
dYjDpJ9eNpZyk1F7Dx9b+SmU39R0Tz89/IiTL78TZTOQ1FFFvktJhKT413QiegBA
x09DhX3I+NQwvZWdU9SkptAGz9PD6zIeJTlh5HHAfC/P+oLUhpQjkd+JBQIDAQAB
AoGAFv4WkuBsyyl4qkOuRSthzDjqzbLbOCCkKmbqvzmGjIbcrwjLIHXTrl6VbjTe
tIgI5JODQArwdvC3vrGH+aF9V27XW4Vl4WllPlBIzvaq1JufPOGupzNmssScFWV5
IL+P1Sx5F22rO+w+lIUdVFgbInITu4Bs2rvKsjAyBmvq7MkCQQD0kQvDWuPWkHSj
xzUjAXl+rT8ZOmANC1x35bLAEUHL2EWCejV9jO48TnwkqOQabT/rEWjrv4g7VHC0
ErNg1bAjAkEA5ZfrSKjyxoEbbK1lQz1GBMpyXGSpbTVJ+eMinBYY4JOnswLLUpmP
/OIJtLrOq38zSzNE2iHoxoGQDt1OlQDgtwJASPG2G3dRe16sm2jALYe0EBdmOYUS
vvFDjDNDhEvhXwZLfSYsLB1LtUsHdfu1xTgOl3Mi4yXGYUPHNb5aKCi0FwJAQGaH
sd7qEnI+jhJpOB4k2M0snOwDdkWfVX+3wo6UNdJVFOpwu9+lOurwjAhmVkaczbg4
1PL0B1JqZTEAjN0tKwJBAMRx0oqN5JNqc3iCrjDxJ9e94t7poVV7if0TfLBVzceR
5mgjPSOMjjkpzVG1P6+37V4whe26u3BhX04PD8Nzl3c=
-----END RSA PRIVATE KEY-----`;

const plainText = "abc";

function encryptWithPublicKey(plainText, publicKey) {
    const buffer = Buffer.from(plainText);
    return crypto.publicEncrypt( { key: publicKey, padding: crypto.constants.RSA_PKCS1_PADDING } , buffer);
}

function decryptWithPrivateKey(cipherTextBuffer, privateKey) {
    const decrypted = crypto.privateDecrypt( { key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING} , cipherTextBuffer);
    return decrypted.toString("utf8");
}

let cipherText = encryptWithPublicKey(plainText, publicKey);
let decrypted = decryptWithPrivateKey(cipherText, privateKey);

console.log("Encrypted (base64):", cipherText.toString("base64"));
console.log("Decrypted:", decrypted)

注意: 我们不会期望两个脚本的结果相同,因为输出是不确定的。