Crypto nodejs 加密

来源:互联网 发布:php mysql.sock 编辑:程序博客网 时间:2024/05/19 05:01

1. Crypto介绍

Crypto库是随Nodejs内核一起打包发布的,主要提供了加密、解密、签名、验证等功能。Crypto利用OpenSSL库来实现它的加密技术,它提供OpenSSL中的一系列哈希方法,包括hmac、cipher、decipher、签名和验证等方法的封装。

Crypto官方文档:http://nodejs.org/api/crypto.html

2. Hash算法

哈希算法,是指将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母,随后的哈希都将产生不同的值。要找到散列为同一个值的两个不同的输入,在计算上是不可能的,所以数据的哈希值可以检验数据的完整性。一般用于快速查找和加密算法。

通常我们对 登陆密码,都是使用Hash算法进行加密,典型的哈希算法包括 ‘md5′,’sha’,’sha1′,’sha256′,’sha512′,’RSA-SHA’。下面我们就做一下算法的测试。

系统环境

  • Win7 64bit

  • Nodejs:v0.10.31

  • Npm:1.4.23

创建项目

~ cd D:\workspace\javascript>~ D:\workspace\javascript>mkdir nodejs-crypto && cd nodejs-crypto

新建文件hash.js,打印出所支持的hash算法。

~ vi hash.jsvar crypto = require('crypto');  # 加载crypto库console.log(crypto.getHashes()); # 打印支持的hash算法

运行程序

~ node hash.js[ 'DSA',  'DSA-SHA',  'DSA-SHA1',  'DSA-SHA1-old',  'RSA-MD4',  'RSA-MD5',  'RSA-MDC2',  'RSA-RIPEMD160',  'RSA-SHA',  'RSA-SHA1',  'RSA-SHA1-2',  'RSA-SHA224',  'RSA-SHA256',  'RSA-SHA384',  'RSA-SHA512',  'dsaEncryption',  'dsaWithSHA',  'dsaWithSHA1',  'dss1',  'ecdsa-with-SHA1',  'md4',  'md4WithRSAEncryption',  'md5',  'md5WithRSAEncryption',  'mdc2',  'mdc2WithRSA',  'ripemd',  'ripemd160',  'ripemd160WithRSA',  'rmd160',  'sha',  'sha1',  'sha1WithRSAEncryption',  'sha224',  'sha224WithRSAEncryption',  'sha256',  'sha256WithRSAEncryption',  'sha384',  'sha384WithRSAEncryption',  'sha512',  'sha512WithRSAEncryption',  'shaWithRSAEncryption',  'ssl2-md5',  'ssl3-md5',  'ssl3-sha1',  'whirlpool' ]

我们看到支持Hash真是不少,究竟怎么选择适合,我也说不清楚。以我对算法的理解,我会以加密计算时间和编码长度,做选择的依据。下面就简单地比较一下几种常见算法。

编辑hash.js文件

~ vi hash.js///////////////////////////// Hash算法///////////////////////////var crypto = require('crypto')    ,fs = require('fs');function hashAlgorithm(algorithm){    var s1 = new Date();    var filename = "package.json";    var txt = fs.ReadStream(filename);    var shasum = crypto.createHash(algorithm);    txt.on('data', function(d) {        shasum.update(d);    });    txt.on('end', function() {        var d = shasum.digest('hex');        var s2 = new Date();        console.log(algorithm+','+(s2-s1) +'ms,'+ d);    });}function doHash(hashs){    hashs.forEach(function(name){        hashAlgorithm(name);    })}//var algs = crypto.getHashes();var algs = [ 'md5','sha','sha1','sha256','sha512','RSA-SHA','RSA-SHA1','RSA-SHA256','RSA-SHA512'];doHash(algs);

运行程序

~ node hash.jsmd5,6ms,85cd416f811574bd4bdb61b241266670sha,18ms,b1fc6647fa4fdb4b1b394f8dc7856ac140e2fbdbsha1,20ms,0777e65066dca985569fa892fa88e21b45dc656dsha256,21ms,5e4aea76f93ee87f422fcbd9458edad0e33ddf256d5d93bcc47977e33cb1654csha512,23ms,94249ec2f83b006354774dd8f8ec81125ea9e1e00f94393d8b20bbd7678e63db53fab6af125e139f9257fd7dbb6c69474e443d059903a9cb2dded03a94c8143RSA-SHA,24ms,b1fc6647fa4fdb4b1b394f8dc7856ac140e2fbdbRSA-SHA1,25ms,0777e65066dca985569fa892fa88e21b45dc656dRSA-SHA256,26ms,5e4aea76f93ee87f422fcbd9458edad0e33ddf256d5d93bcc47977e33cb1654cRSA-SHA512,26ms,94249ec2f83b006354774dd8f8ec81125ea9e1e00f94393d8b20bbd7678e63db53fab6af125e139f9257fd7dbb6c69474e4433d059903a9cb2dded03a94c8143

输出以逗号分隔,分别是算法名、时间、密文。最常见的md5,密文长度最短,计算时间也最少;sha和sha1比较接近;sha512密文最长,计算时间也最长。

由于md5已经有了大量的字典库,对于安全级别一般的网站用sha1吧;如果安全级别要求很高,CPU配置也很牛,可以考虑用sha512。

3. Hmac算法

HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code),HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。HMAC可以有效防止一些类似md5的彩虹表等攻击,比如一些常见的密码直接MD5存入数据库的,可能被反向破解。

定义HMAC需要一个加密用散列函数(表示为H,可以是MD5或者SHA-1)和一个密钥K。我们用B来表示数据块的字节数。(以上所提到的散列函数的分割数据块字长B=64),用L来表示散列函数的输出数据字节数(MD5中L=16,SHA-1中L=20)。鉴别密钥的长度可以是小于等于数据块字长的任何正整数值。应用程序中使用的密钥长度若是比B大,则首先用使用散列函数H作用于它,然后用H输出的L长度字符串作为在HMAC中实际使用的密钥。一般情况下,推荐的最小密钥K长度是L个字节。

由于HMAC就是使用散列函数,所以我们同样选择上面的几种算法进行测试。新建文件hmac.js。

~ vi hmac.js///////////////////////////// Hmac算法///////////////////////////var crypto = require('crypto')    ,fs = require('fs');function hmacAlgorithm(algorithm,key){    var s1 = new Date();    var filename = "package.json";    var txt = fs.ReadStream(filename);    var shasum = crypto.createHmac(algorithm,key);    txt.on('data', function(d) {        shasum.update(d);    });    txt.on('end', function() {        var d = shasum.digest('hex');        var s2 = new Date();        console.log(algorithm+','+(s2-s1) +'ms,'+ d);    });}function doHmac(hashs,key){    console.log("\nKey : %s", key);    console.log("============================");    hashs.forEach(function(name){        hmacAlgorithm(name,key);    })}//var algs = crypto.getHashes();var algs = [ 'md5','sha','sha1','sha256','sha512','RSA-SHA','RSA-SHA1','RSA-SHA256','RSA-SHA512'];// 短KEY的测试setTimeout(function(){    doHmac(algs,"abc");},1)// 长KEY的测试setTimeout(function(){    var key = "jifdkd;adkfaj^&fjdifefdafda,ijjifdkd;adkfaj^&fjdifefdafdaljifdkd;adkfaj^&fjdifefdafda";    doHmac(algs,key);},2*1000)

运行程序

~ node hmac.jsKey : abc============================md5,6ms,bf106a077abcfa0fffe6ec0da039545bsha,6ms,a43a00981346ac64bb7b6fb0641b72a101fb04a5sha1,6ms,aead69a72da77d0615a854dda1086d885807574asha256,7ms,98ac955cb2205ba01a6337951d0ed3fd9b68753544cf81275eced365da57fc5dsha512,8ms,054f37e34b55a19e64a7f88fb60b1122dc0a30e9864ca28d01d61115b13c74de292ab66e85bf007e1a463a52d7c30fdff174618ef954401bc9c2c3318e762c8fRSA-SHA,10ms,a43a00981346ac64bb7b6fb0641b72a101fb04a5RSA-SHA1,11ms,aead69a72da77d0615a854dda1086d885807574aRSA-SHA256,12ms,98ac955cb2205ba01a6337951d0ed3fd9b68753544cf81275eced365da57fc5dRSA-SHA512,13ms,054f37e34b55a19e64a7f88fb60b1122dc0a30e9864ca28d01d61115b13c74de292ab66e85bf007e1a463a52d7c30fdff174618ef954401bc9c2c3318e762c8fKey : jifdkd;adkfaj^&fjdifefdafda,ijjifdkd;adkfaj^&fjdifefdafdaljifdkd;adkfaj^&fjdifefdafda============================md5,5ms,164a8fee6e37bb3e40a9d5dff5c2fd66sha,5ms,ba06f536856553c3756aa36254a63ef35e225d38sha1,7ms,f3a89b0a5ee8a55c2bb6a861748d43e9d44dc489sha256,7ms,f2df911f40e74b2b9bb3d53a7ca4b78d438d511e015d4b50431eaea65339380dsha512,8ms,5b4b57386b1fcc4f1945c47788bf38c013e1cde356fc15e1f946e6bf6738b5dc52ecf17b3ddc80b2ff21f985a1a707df9357fe305e9aa143da073d2cafd794dcRSA-SHA,11ms,ba06f536856553c3756aa36254a63ef35e225d38RSA-SHA1,12ms,f3a89b0a5ee8a55c2bb6a861748d43e9d44dc489RSA-SHA256,14ms,f2df911f40e74b2b9bb3d53a7ca4b78d438d511e015d4b50431eaea65339380dRSA-SHA512,16ms,5b4b57386b1fcc4f1945c47788bf38c013e1cde356fc15e1f946e6bf6738b5dc52ecf17b3ddc80b2ff21f985a1a707df9357fe305e9aa143da073d2cafd794dc

通过比对短key和长key,在编码比较长的算法上面会有一些影响。由于Hmac有了第二参数key,所以会比单独的hash加密登陆密码,有更好的安全性上的保证。

对于网站登陆密码的设计,我们可以做成2个字段存储,用password字段存密文,passkey字段存储key,把算法直接封装到程序里面。

{username: 'xxxx'password: 'aead69a72da77d0615a854dda1086d885807574a',passkey:'abc'}

就算数据库被攻击,黑客也只是拿了密文和key,密码明文并没有被泄露。并且在不知道加密算法的情况下,也很难通过彩虹表进行攻击。

4. 加密和解密算法

对于登陆密码来说,是不需要考虑解密的,通常都会用不可逆的算法,像md5,sha-1等。但是,对于有安全性要求的数据来说,我们是需要加密存储,然后解密使用的,这时需要用到可逆的加密算法。对于这种基于KEY算法,可以分为对称加密和不对称加密。

对称加密算法的原理很容易理解,通信一方用KEK加密明文,另一方收到之后用同样的KEY来解密就可以得到明文。
不对称加密算法,使用两把完全不同但又是完全匹配的一对Key:公钥和私钥。在使用不对称加密算法加密文件时,只有使用匹配的一对公钥和私钥,才能完成对明文的加密和解密过程。
对于这种类型的操作,Crypto包也提供了大量的算法支持。新建文件cipher.js,打印出所支持的算法。

~ vi cipher.jsvar crypto = require('crypto');console.log(crypto.getCiphers());

运行程序
   ~ node cipher.js

[ 'CAST-cbc','aes-128-cbc','aes-128-cbc-hmac-sha1','aes-128-cfb','aes-128-cfb1','aes-128-cfb8','aes-128-ctr','aes-128-ecb','aes-128-gcm','aes-128-ofb','aes-128-xts','aes-192-cbc','aes-192-cfb','aes-192-cfb1','aes-192-cfb8','aes-192-ctr','aes-192-ecb','aes-192-gcm','aes-192-ofb','aes-256-cbc','aes-256-cbc-hmac-sha1','aes-256-cfb','aes-256-cfb1','aes-256-cfb8','aes-256-ctr','aes-256-ecb','aes-256-gcm','aes-256-ofb','aes-256-xts','aes128','aes192','aes256','bf','bf-cbc','bf-cfb','bf-ecb','bf-ofb','blowfish','camellia-128-cbc','camellia-128-cfb','camellia-128-cfb1','camellia-128-cfb8','camellia-128-ecb','camellia-128-ofb','camellia-192-cbc','camellia-192-cfb','camellia-192-cfb1','camellia-192-cfb8','camellia-192-ecb','camellia-192-ofb','camellia-256-cbc','camellia-256-cfb','camellia-256-cfb1','camellia-256-cfb8','camellia-256-ecb','camellia-256-ofb','camellia128','camellia192','camellia256','cast','cast-cbc','cast5-cbc','cast5-cfb','cast5-ecb','cast5-ofb','des','des-cbc','des-cfb','des-cfb1','des-cfb8','des-ecb','des-ede','des-ede-cbc','des-ede-cfb','des-ede-ofb','des-ede3','des-ede3-cbc','des-ede3-cfb','des-ede3-cfb1','des-ede3-cfb8','des-ede3-ofb','des-ofb','des3','desx','desx-cbc','id-aes128-GCM','id-aes192-GCM','id-aes256-GCM','idea','idea-cbc','idea-cfb','idea-ecb','idea-ofb','rc2','rc2-40-cbc','rc2-64-cbc','rc2-cbc','rc2-cfb','rc2-ecb','rc2-ofb','rc4','rc4-40','rc4-hmac-md5','seed','seed-cbc','seed-cfb','seed-ecb','seed-ofb' ]

同样地,在这么一大堆的算法面前,完全不知道如何去选择。我还是以加密和解密的计算时间为参考指标,选出几个常见的算法进行测试。
   

///////////////////////////
   // 加密解密算法
   ///////////////////////////
   var crypto = require(‘crypto’)
       ,fs = require(‘fs’);//加密function cipher(algorithm, key, buf ,cb){    var encrypted = "";    var cip = crypto.createCipher(algorithm, key);    encrypted += cip.update(buf, 'binary', 'hex');    encrypted += cip.final('hex');    cb(encrypted);}//解密function decipher(algorithm, key, encrypted,cb){    var decrypted = "";    var decipher = crypto.createDecipher(algorithm, key);    decrypted += decipher.update(encrypted, 'hex', 'binary');    decrypted += decipher.final('binary');    cb(decrypted);}function cipherDecipherFile(filename,algorithm, key){    fs.readFile(filename, "utf-8",function (err, data) {        if (err) throw err;        var s1 = new Date();        cipher(algorithm, key,data,function(encrypted){            var s2 = new Date();            console.log('cipher:'+algorithm+','+(s2-s1) +'ms');            decipher(algorithm, key,encrypted,function(txt){                var s3 = new Date();                console.log('decipher:'+algorithm+','+(s3-s2) +'ms');//                console.log(txt);            });        });    });}//console.log(crypto.getCiphers());var algs = ['blowfish','aes-256-cbc','cast','des','des3','idea','rc2','rc4','seed'];var key = "abc";var filename = "book.pdf";//"package.json";algs.forEach(function(name){    cipherDecipherFile(filename,name,key);

运行程序
   ~ node cipher.js

cipher:blowfish,46msdecipher:blowfish,95mscipher:des,67msdecipher:des,104mscipher:idea,54msdecipher:idea,88mscipher:rc4,16msdecipher:rc4,44mscipher:des3,158msdecipher:des3,193mscipher:aes-256-cbc,19msdecipher:aes-256-cbc,47mscipher:cast,46msdecipher:cast,82mscipher:seed,64msdecipher:seed,98mscipher:rc2,104msdecipher:rc2,99ms

输出一共3列,第一列,cipher(加密),decipher(解密);第二列,是算法名称;第三列是计算时间。

在选中的这几个算法中,rc4和aes-256-cbc是表现不错的算法,加密和解密时间都比较短,加密时间:解密时间=1:3;其他的算法,总体时间相对较长,有的加密时间:解密时间=1:1。所以,怎么选算法,另外的一个标准就要看业务需求了。如果业务上,解密操作的次数远大于加密操作的次数,而且是在服务器计算,那么我们最好找到,加密时间:解密时间=N:1,N>1的算法;如果加密在服务器端,解密在客户端完成,那么aes-256-cbc算法的计算时间比例就非常适合了。

5. 签名和验证算法

我们除了对数据进行加密和解密,还需要判断数据在传输过程中,是否真实际和完整,是否被篡改了。那么就需要用到签名和验证的算法,利用不对称加密算法,通过私钥进行数字签名,公钥验证数据的真实性。

数字签名的制作和验证过程,如下图所示。


下面我们用程序,来现实图中的操作流程,由于证书是我们自己制作的,不打算对外公开,没有到CA进行认证,所以下过程将不包括公钥伪造,再到CA认证的过程。

首先,我们要用openSSL命令先生成,私钥server.pem和公钥cert.pem。

# 生成私钥~ D:\workspace\javascript\nodejs-crypto>openssl genrsa  -out server.pem 1024Generating RSA private key, 1024 bit long modulus..................++++++..................++++++e is 65537 (0x10001)# 生成公钥~ D:\workspace\javascript\nodejs-crypto>openssl req -key server.pem -new -x509 -out cert.pemYou are about to be asked to enter information that will be incorporatedinto your certificate request.What you are about to enter is what is called a Distinguished Name or a DN.There are quite a few fields but you can leave some blankFor some fields there will be a default value,If you enter '.', the field will be left blank.-----Country Name (2 letter code) [AU]:State or Province Name (full name) [Some-State]:Locality Name (eg, city) []:Organization Name (eg, company) [Internet Widgits Pty Ltd]:Organizational Unit Name (eg, section) []:Common Name (eg, YOUR name) []:Email Address []:

接下来,我们利用生成私钥生成数学签名,然后再用公钥验证数据,是否被篡改。新建文件signer.js。

~ vi signer.js///////////////////////////// 签名验证算法// openssl genrsa  -out server.pem 1024// openssl req -key server.pem -new -x509 -out cert.pem///////////////////////////var crypto = require('crypto')    ,fs = require('fs');function signer(algorithm,key,data){    var sign = crypto.createSign(algorithm);    sign.update(data);    sig = sign.sign(key, 'hex');    return sig;}function verify(algorithm,pub,sig,data){    var verify = crypto.createVerify(algorithm);    verify.update(data);    return verify.verify(pubkey, sig, 'hex')}var algorithm = 'RSA-SHA256';var data = "abcdef";   //传输的数据var privatePem = fs.readFileSync('server.pem');var key = privatePem.toString();var sig = signer(algorithm,key,data); //数字签名var publicPem = fs.readFileSync('cert.pem');var pubkey = publicPem.toString();console.log(verify(algorithm,pubkey,sig,data));         //验证数据,通过公钥、数字签名 =》是原始数据console.log(verify(algorithm,pubkey,sig,data+"2"));    //验证数据,通过公钥、数字签名  =》不是原始数据

运行程序

~ node signer.jstruefalse

两行输出结果,第一行验证的结果是true,表示数据在传输过程中,没有被篡改;第二行验证的结果是false,表示数据在传输过程中被篡改,不是原始的数据。当然,如何保证私钥和公钥的匹配,需要CA第三方来认证,与Crypto库无关本文不再介绍。

6. salt算法

我们知道,如果直接对密码进行散列,那么黑客可以对通过获得这个密码散列值,然后通过查散列值字典(例如MD5密码破解网站),得到某用户的密码。

盐(Salt),在密码学中,是指通过在密码任意固定位置**特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。加盐后的散列值,可以极大的降低由于用户数据被盗而带来的密码泄漏风险,即使通过彩虹表寻找到了散列后的数值所对应的原始内容,但是由于经过了加盐,**的字符串扰乱了真正的密码,使得获得真实密码的概率**降低。

加盐的实现过程通常是在需要散列的字段的特定位置增加特定的字符,打乱原始的字串,使其生成的散列结果产生变化。比如,用户使用了一个密码:

123465

经过MD5散列后,可以得出结果:

3d9188577cc9bfe9291ac66b5cc872b7

但是由于用户密码位数不足,短密码的散列结果很容易被彩虹表破解,因此,在用户的密码末尾添加特定字串:

123465abcdefghijklmnopqrstuvwxyz

因此,加盐后的密码位数更长了,散列的结果也发生了变化:

27e20c64ccb8cce9ad68b8ccff6252cf

新建文件salt.js,实现上面的程序。

~ vi salt.js//////////////////////////////// salt算法//////////////////////////////var crypto = require('crypto');var md5 = crypto.createHash('md5');var txt = "123465";md5.update(txt);console.log(md5.digest('hex'));md5 = crypto.createHash('md5');var salt = "abcdefghijklmnopqrstuvwxyz";md5.update(txt+salt);console.log(md5.digest('hex'));

我们可以不用自己动手加盐,而使用crypto.pbkdf2()函数,默认会调用hmac算法,用sha1的散列函数,并且可以设置迭代次数和密文长度。具体的使用代码如下。

~ vi salt.jsvar crypto = require('crypto');var txt = "123465";var salt = "abcdefghijklmnopqrstuvwxyz";// 生成密文,默认HMAC函数是sha1算法crypto.pbkdf2(txt, salt, 4096, 256, function (err,hash) {    if (err) { throw err; }    console.log(hash.toString('hex'));})

运行程序,生成256位的密文。

~ node salt.js29c7de002d942ebcbf3afe05f3eb0ff620f0515fe6b8f19176736273cd70805afdfcc828a6f227152dfa4d0c4f96da184fbd060d4d4c86a5deb8d704699c3e8653acdb0e5bc3e584e0890a44206bb2926f0289fc8e0abe49fd1876461fcc50f06dc7991c4b93cc4e80076529c73b2f2c56f16b5b319368edf017f3d3583a33aa44fd30f89801f0d8877eb8262925f5fdc40a5c57f1b275e5674784dca635c75bc58b6c22264e0f29e363eb25dedf1a242429084e3e17d344b59cab3b9723db03ee4838b632786d1a9eb968f2523404286e5d0a41a1707577650cc3cc2f1ab65714a4cb31f068e4aefa259c6be68174e0a475d5610168305a4935a14bb221a516

如果加盐每次都是固定的值也会不安全,我们还可以利用随机randomBytes()函数,配合pbkdf2()函数,让每次都是不同的salt,生成安全级别更高的密文。

~ vi salt.js//通过伪随机码生成salt,进行加密crypto.randomBytes(128, function (err, salt) {    if (err) { throw err;}    salt = salt.toString('hex');    console.log(salt); //生成salt    crypto.pbkdf2(txt, salt, 4096, 256, function (err,hash) {        if (err) { throw err; }        hash = hash.toString('hex');        console.log(hash);//生成密文    })})

运行程序

~ node salt.js# 随机生成的salt78e59de99f16697e3eb684dcfa8efa086db0940c7cd47d33f9311e3bfcf9d58bf30915f54b3f72793b5c8568d32f1f15c55cc87affd043d96f1ed1f56c25a8054b3d83a306636f3b9e3bc9e48c3303aff54da006f92e370023165857fce0a1d1ff0b89178ae8c1416747275daba25652ea864d52a80427658ea69dbe500a7261# 通过salt生成的密文48943eb51ea702b436f95bf1dacc7f64bc41cf7cfa4cb40d101d5550c28caafc58ca720934352238430634f21fd5a6a4ef63fe5828c2665362e9902adc0305f93d2523fbd28521ad00947a74ff8229f63ad5796f2e12677cbed6af02b9973ee0187a69ad67e86790471d95f18d6d2c43ef904f7d17a5d8264f8236f227363a016ae2c14559c17236d540e06c5fd443af740721897f76bdbd9711c8499d7a34cae2e917f900fc364f72f9afaf301845c6e0b5c37def949b4af62336a39dbd1e829405d6189536092c7769a5d7e427b8e97419988da4e1bad49c69f25ac4e96f74a0ce3eab9e1433277568105b1dcc0cf9e1f9c91a7ed391c5825eefcd71ef5ca1

我们把salt和密文一起进行存储,保证用户密码的安全性。

7. 程序代码

本文的程序代码,可以直接从Github上面下载本文项目中的源代码,按照片文章中的介绍学**Crypto,下载地址:https://github.com/bsspirit/nodejs-crypto

也可以直接用github命令行来下载:

~ git clone git@github.com:bsspirit/nodejs-crypto.git   # 下载github项目~ cd nodejs-crypto              
0 0
原创粉丝点击