精华 Node.js 基于ursa模块的RSA 加密
发布于 8 年前 作者 zhaomaoxin 32068 次浏览 最后一次编辑是 6 年前 来自 分享

前几天调试一个RSA加密,遇到一些问题,在网上找了好久好久,与Node.js相关的资源少得非常可怜,最后还是靠自己一步一步解决了,今天把代码和一些心得拿出来分享一下:

一. 相关知识

这里推介几篇博客,在做加密解密之前先了解一下,我以前没做过,所以上网找了一些,提供大家参考。 算法原理1 算法原理2 关于密钥长度 关于填充 ursa模块 关于RSA的原理什么的,上面的文章有详细的介绍,这里不再赘述,下面开始加密/加密的操作。

二. 生成密钥对

/**
 * 2015-02-03
 * zhaomaoxin
 * generate keys
 */

var fs = require('fs');
var ursa = require('ursa');

var modulusBit = 512;  
var key  = ursa.generatePrivateKey(modulusBit, 65537);

var privatePem = ursa.createPrivateKey(key.toPrivatePem()); //生成私钥
var privateKey = privatePem.toPrivatePem('utf8');
fs.writeFile('private.pem', privateKey, 'utf8', function(error){
    if(error){
        throw error;
    }
    console.log('\n私钥privateKey已经保存\n');
    console.log('\n私钥privateKey:\n' + privateKey);
});


var publicPem = ursa.createPublicKey(key.toPublicPem());   //生成公钥
var publicKey = publicPem.toPublicPem('utf8');
fs.writeFile('public.pub', publicKey, 'utf8', function(error){
    if(error){
        throw error;
    }
    console.log('\n私钥publicKey已经保存\n');
    console.log('\n私钥publicKey:\n' + publicKey);
});

说明: 将密钥写入文件或者直接使用打印出来,需要使用toPublicPem或者toPrivatePem转换成UTF8编码形式。

三. 密钥导入

加密/解密的双方各自生成密钥对,需保证能导入对方的密钥,并使用公钥进行加密 。 key.js

/**
 * 2015-02-03
 * zhaomaoxin
 * import keys
 */

var ursa = require('ursa');


var client_public  =    '-----BEGIN PUBLIC KEY-----\n'+
                        'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ6d7vkyWjQpHDgpcwBxiAoecUZPeKA7\n'+
                        '5cCgZlnYnXNR08yPAzJuBUrTUODloj3OFxN2WE/VpPSQsu2KGpBIO9MCAwEAAQ==\n'+
                        '-----END PUBLIC KEY-----';


var client_private =    '-----BEGIN RSA PRIVATE KEY-----\n'+
                        'MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAnp3u+TJaNCkcOClz\n' +
                        'AHGICh5xRk94oDvlwKBmWdidc1HTzI8DMm4FStNQ4OWiPc4XE3ZYT9Wk9JCy7Yoa\n' +
                        'kEg70wIDAQABAkAZ0wDLVaVWBLNxeV4d3l4Vt6sdlWbAP8BCQlsnmZrY4WIIy1HE\n' +
                        'Jqa2eCdedlHzuEPxsGnmHxO4nQpGFug1IGqBAiEAzIjiNZ3Ej4Ko8XkLkNxPcnbR\n' +
                        'VR/iG2THWtPr2cuTcbMCIQDGh0DfD1l/EGj/OpwDao8Dk0dnUbWXrTskrlfmXSO9\n' +
                        'YQIhAIXnA3kofVuapbHYlgrTQKvmP6tkASn/80dyQBDI5xFjAiEApy22fyBZ6RpU\n' +
                        'kLk2L9pH3Gbltiekl7mVGGqIMsE0G4ECIFb3wKlEXS2q1zDLz58SdLyLh/no19CD\n' +
                        '170XCkCMYq+7\n'+
                        '-----END RSA PRIVATE KEY-----';


var server_public  =    '-----BEGIN PUBLIC KEY-----\n'+
                        'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALFSzfjrIl8Vj0dbUVTvVEXnGMOL3O6I\n'+
                        'OWsK0EJcEk18BktuaIGC0gWb8f9MpL3cqouRaDuS2JM+xIZrocyA5YECAwEAAQ==\n'+
                        '-----END PUBLIC KEY-----';


var server_private  =   '-----BEGIN RSA PRIVATE KEY-----\n'+
                        'MIIBPQIBAAJBALFSzfjrIl8Vj0dbUVTvVEXnGMOL3O6IOWsK0EJcEk18BktuaIGC\n'+
                        '0gWb8f9MpL3cqouRaDuS2JM+xIZrocyA5YECAwEAAQJBAJzBNaIZws3JklqjSFfM\n'+
                        'JnSRIZwkNQ+Mzy1oZshy+h8RznAxD0yQRgHvlU+cUhjLr4znQpyVSZ5686Ay9LI1\n'+
                        'eVECIQDhrcCUzOKsVhEjlotfDpBHixWdJNzt62UcLwdXthW/dQIhAMkl3fDGE7wR\n'+
                        'ZIjRSVOgGU8VgR67WV14DNXD8cqVffhdAiEAkVu8wxsElUQKXgXFV0CmJa6sCT+J\n'+
                        'HaWUxoZ0EEaz01ECIQC++sUOpgJ2vczGWm9Uht2AyNofY6IlrKYDEFeyEN3ZwQIh\n'+
                        'AJ+UaCEDeiFRwSxFYnCpkhTU1ZUVrzo4HaZuAt780KBD\n'+
                        '-----END RSA PRIVATE KEY-----';

var server = {
    pem :ursa.createPrivateKey(server_private),
    pub :ursa.createPublicKey(server_public)
};

var client = {
    pub :ursa.createPublicKey(client_public),
    pem :ursa.createPrivateKey(client_private)
};

exports.server = server;
exports.client = client;

说明: 在使用ursa导入密钥的时候,可能遇到下面的错误,表明密钥无法导入,这个的原因可能是密钥字符串拼接的问题,可以先先用ursa模块生成密钥,然后直接在public.pub和private.pem(这两个文件是用ursa生成密钥后用fs写的文件)中直接拼接好再拷过来重新试试,多半是拼接的问题。

        throw new Error("Not a key.");
              ^
 Error: Not a key.
    at Object.createKey (/home/zhaomaoxin/project/node/xxt-crypt/node_modules/ursa/lib/ursa.js:402:15)
    at Object.<anonymous> (/home/zhaomaoxin/project/node/xxt-crypt/pems/index.js:37:15)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)
    at node.js:906:3

四. 加密,解密方法


/**
 * 2015-02-03
 * zhaomaoxin
 * encrypt and decrypt data
 */
var ursa                = require('ursa');
var encoding            = require('encoding');
var pems                = require('../pems');
var client              = pems.client;              //客户端密钥
var server              = pems.server;              //服务端密钥
var clientPublic        = client.pub;               //客户端公钥
var serverPublic        = server.pub;               //服务端公钥
var clientPrivate       = client.pem;               //客户端私钥
var serverPrivate       = server.pem;               //服务端私钥
var clientModulusBit    = 512;
var serverModulusBit    = 512;
var clientMaxBit        = clientModulusBit/8;
var serverMaxBit        = serverModulusBit/8;
var clientRealBit       = clientMaxBit - 11;
var serverRealBit       = serverMaxBit - 11;
var padding             = ursa.RSA_PKCS1_PADDING;

//加密,使用客户端公钥加密
exports.clientEncrypt = function(plain){
    plain = plain || "";
    return encrypt(plain, clientPublic, clientRealBit, padding);
};

//解密,使用客户端私钥解密
exports.clientDecrypt = function(cipher){
    cipher = cipher || "";
    return decrypt(cipher, clientPrivate, clientMaxBit, padding);
};

//加密,使用服务端公钥加密
exports.serverEncrypt = function(plain){
    plain = plain || "";
    return encrypt(plain, serverPublic, serverRealBit, padding);
};

//解密,使用服务端私钥解密
exports.serverDecrypt = function(cipher){
    cipher = cipher || "";
    return decrypt(cipher, serverPrivate, serverMaxBit, padding);
};

//用于获取内容的字节数
function bytes(text, coding) {
    if (typeof text === 'undefined') {
        throw new Error("must have a arg.");
    }

    coding = coding || 'utf8';
    return Buffer.byteLength(text.toString(), coding);
}

function encrypt(plain, publicKey, realBit, padding){
    var start1 = 0;
    var end1   = realBit;
    var result1 = '';
    var originBuff = new Buffer(plain);
    var originByte = bytes(plain, 'utf8');
    while(start1 < originByte){
        var originTmp  = originBuff.slice(start1, end1);
        result1 += publicKey.encrypt(originTmp, 'binary', 'binary', padding);
        start1 += realBit;
        end1 += realBit;
    }

    var encrypted =  encoding.convert(result1, 'binary', 'base64');

    return encrypted.toString();
}

function decrypt(cipher, privateKey, maxBit, padding){
    var start2 = 0;
    var end2   = maxBit;
    var result2 = '';
    var cipherBuff = encoding.convert(cipher, 'base64', 'binary');   //这个地方很关键,直接使用new Buffer(cipher, 'base64') 报编码错误
    var cipherByte = bytes(cipher, 'base64');
    while(start2 < cipherByte){
        var cipherTmp  = cipherBuff.slice(start2, end2);    //请注意slice函数的用法
        result2 += privateKey.decrypt(cipherTmp, 'binary', 'binary', padding); //先保存成二进制,待完成解密后再转换成字符串
        start2 += maxBit;
        end2 += maxBit;
    }

    var decrypted =  encoding.convert(result2, 'binary', 'utf8');
    return decrypted.toString();
}

说明: 这个部分是比较重要的,也是主要的加密,解密方法,ursa模块没有对明文或者密文过长的情况做处理,这个需要自己做分段处理(另外一个模块node-rsa中无需自己处理),在加密,解密过程中需求还需要注意数据的编码方式,以及相互之间的转换,否则在加密或者解密的时候会报错。下面是加密,解密过程中遇到的问题。

明文过长

        return encodeBuffer(rsa.publicEncrypt(buf, padding), outEncoding);
                                ^
Error: error:0406D06E:rsa routines:RSA_padding_add_PKCS1_type_2:data too large for key size
    at Object.encrypt (/home/zhaomaoxin/project/node/xxt-crypto/node_modules/ursa/lib/ursa.js:235:33)
    at Object.<anonymous> (/home/zhaomaoxin/project/node/xxt-crypto/rsa.js:58:24)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)
    at node.js:906:3

密文过长

        return encodeBuffer(rsa.privateDecrypt(buf, padding), outEncoding);
                                ^
Error: error:0406506C:rsa routines:RSA_EAY_PRIVATE_DECRYPT:data greater than mod len
    at Object.decrypt (/home/zhaomaoxin/project/node/xxt-crypto/node_modules/ursa/lib/ursa.js:291:33)
    at Object.<anonymous> (/home/zhaomaoxin/project/node/xxt-crypto/rsa.js:120:24)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)
    at node.js:906:3

编码错误

        return encodeBuffer(rsa.privateDecrypt(buf, padding), outEncoding);
                                ^
Error: error:0407109F:rsa routines:RSA_padding_check_PKCS1_type_2:pkcs decoding error
    at Object.decrypt (/home/closure/project/node/xxt-crypto/node_modules/ursa/lib/ursa.js:291:33)
    at Object.<anonymous> (/home/closure/project/node/xxt-crypto/rsa.js:113:30)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)
    at node.js:906:3

说明: 编码错误的问题,引入了encoding模块进行换转,这个问题绕了挺久的,后面直接将每次切割的片段console.log()出来,发现还是base64编码的密文,猜测错误可能在编码上,做了转换之后没有报错,而且解密成功了。

总结: 初次使用RSA操作,遇到了很多问题,一一解决了,至少达到加密解密的目的了,以上就是我的分享,毕竟新手,如果有不对或者不妥之处还望指点,相互学习。

13 回复

先顶再看

目前node v0.12 版本,增加了RSA public/private key 的加密解密 API。你可以去看看

你好,使用RSA 先要安装ursa,始终安装不了? gyp ERR! stack Error: Can’t find Python executable “python”, you can set the PYT HON env variable. gyp ERR! stack at failNoPython (B:\nodejs\node_modules\npm\node_modules\node -gyp\lib\configure.js:120:14) gyp ERR! stack at B:\nodejs\node_modules\npm\node_modules\node-gyp\lib\confi gure.js:83:11 gyp ERR! stack at Object.oncomplete (fs.js:107:15) gyp ERR! System Windows_NT 6.2.9200 gyp ERR! command “node” “B:\nodejs\node_modules\npm\node_modules\node-gyp\ bin\node-gyp.js” "rebuild" gyp ERR! cwd B:\nodejs\node_modules\ursa

@xuyufei 有例子可以看一下吗,发一下地址,谢谢

@thinkive 你的错误很明显,就是python的问题,你用的是windows,需要将python加入系统环境变量中

nodejs难道不支持DecryptPublic吗??即用RSA的公钥解密,比如快用的SDK就要求这样

@xuyufei 你好,我看了下api 不过不知道怎么用,不知道有没例子,我调用了下,用的是no_padding的但是加密后每次数据还是变化的,有点不对,可以请教下么?qq:843134354

@zhaomaoxin 这个ursa安装都不成功也不知道啥原因,有时说64位,我是最高版本的node v4.2.1

privateKey.decrypt privateKey 有这个方法吗?

rsa 秘钥加密 消息内容, 公钥 解密消息内容 ; rsa 秘钥签名 消息内容 , 公钥 验证消息内容是否被篡改

为啥,这里 秘钥用于加密 ? 是签名吗 ?注释我看不懂????

回到顶部