Node.js 基于 ursa 模块的 RSA 加密解密(已与IOS,Android实现加密通信)

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

一. 相关知识
这里推介几篇博客,在做加密解密之前先了解一下,我以前没做过,所以上网找了一些,提供大家参考。
算法原理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操作,遇到了很多问题,一一解决了,至少达到加密解密的目的了,以上就是我的分享,毕竟新手,如果有不对或者不妥之处还望指点,相互学习。
————————————————
版权声明:本文为CSDN博主「赵茂鑫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jameslong108159/java/article/details/43566843

posted on 2020-05-04 17:43  zhangzongshan  阅读(1105)  评论(0编辑  收藏  举报

导航