前言 : nodejs 为我们提供了原生的 Crypto,加密模块,其主要利用 openssl 库来提供通用的加密和 hash 算法,nodejs 使用 C++ 实现这些算法后,通过该模块提供给我们调用,相比于 js 算法,执行效率大为提升。
Crypto 之 hash 算法
我们先来看看 hash 算法,我们可以通过 crypto.createHash 来创建一个 hash 实例。
我们可以创建的 hash 类型有 md5, sha1, sha256 等。
md5 加密
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const crypto = require('crypto');
const md5 = crypto.createHash('md5');
md5.update('hello world');
console.log(md5.digest('hex'));
md5.update('hello world');
|
这里需要注意的是,MD5 存在碰撞性,可以被暴力破解。除此之外,当进行 digest 方法后,之前创建的 hash 实例会被清空,故而无法再次 update。
update 具有记忆功能
1 2 3 4 5 6 7 8 9 10 11
| const crypto = require('crypto');
let one = crypto.createHash('md5'); one.update('hello'); one.update('world');
let two = crypto.createHash('md5'); two.update('helloworld');
// 分步 update 效果和把字符串按顺序拼接打包 update 产生的结果相同 console.log(one.digest('hex') === two.digest('hex'));
|
多次 update 等于是把字符串相加,然后在hash.digest()将字符串加密返回
hmac 加密
hmac 是密钥相关的哈希运算消息认证码。即使用一个密钥和一段待加密文本,通过 hash 算法,生成密文的加密方式。该加密方式使用 crypto.createHmac(algorithm, key)
函数,使用方式和上文 md5 一致,下面我们看一个 hmac 加密的例子
首先我们使用 openssl genrsa -out server.pem 1024
命令创建一个 key1.pem 文件作为密钥,然后执行 hmac 加密操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const crypto = require('crypto'); const fs = require('fs');
let pem = fs.readFileSync('key1.pem'); let key = pem.toString('ascii');
let hmac = crypto.createHmac('sha1', key);
hmac.update('hello world');
console.log(hmac.digest('hex'));
|
除了需要一个密钥证书之外,其余操作和 createHash 一致
Public Key Cryptography 公开密钥解密解密
Crypto 模块提供的公开密钥加密包含 Cipher, Decipher, Sign, Verify 四个类。我们着重说明一下 Cipher 和 Decipher 这两个常用的加密解密方法。
用到的函数
- crypto.createCipher(algorithm, password)
- crypto.createCipheriv(algorithm, key, iv)
- cipher.update(data, [input_encoding], [output_encoding])
- algorithm 表示加密算法 ( 可以使用
openssl list-cipher-algorithms
查看系统支持的加密算法 )
- password 表示用来加密的密钥,也可以用于派生 key 和 iv
- update 方法中,input_encoding 表示传入的数据格式 ( ‘utf8’, ‘ascii’, ‘binary’ ),默认是 binary
- output_encoding 表示返回 block 的数据格式
第一个 Cipher 例子 ( aes-256-cbc 加密方式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const crypto = require('crypto'); const fs = require('fs');
let pem = fs.readFileSync('key1.pem'); let key = pem.toString();
let plainText = 'Hello World'; console.log(`原始文本: ${plainText}`);
let cipher = crypto.createCipher('aes-256-cbc', key);
let crypted = cipher.update(plainText, 'utf8', 'hex'); crypted += cipher.final('hex');
console.log(`加密后文本: ${crypted}`);
let decipher = crypto.createDecipher('aes-256-cbc', key);
let dec = decipher.update(crypted, 'hex', 'utf8'); dec += decipher.final('utf8');
console.log(`解密后文本: ${dec}`);
|
程序输出信息:
PS:加密后文本随着 key.pem 的不同也会输出不同的值,这里是笔者使用密钥加密后结果
1 2 3
| 原始文本: Hello World 加密后文本: 141d14c35a7f0a4e6a0eb9c7ed757e59 解密后文本: Hello World
|
Cipher ( aes192 ) 方式加密 (非事件监听方式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const crypto = require('crypto'); const fs = require('fs');
let pem = fs.readFileSync('key1.pem'); let key = pem.toString();
let plainText = 'Hello World'; console.log(`原始文本: ${plainText}`);
let cipher = crypto.createCipher('aes192', key); let crypted = cipher.update(plainText, 'utf8', 'hex'); crypted += cipher.final('hex'); console.log(`加密后文本: ${crypted}`);
let decipher = crypto.createDecipher('aes192', key); let dec = decipher.update(crypted, 'hex', 'utf8'); dec += decipher.final('utf8');
console.log(`解密后文本: ${dec}`);
|
输出结果:
1 2 3
| 原始文本: Hello World 加密后文本: 4df8aad7a959d228ef2081e48a768745 解密后文本: Hello World
|
Cipher ( aes192 ) 方式加密 (事件监听方式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| const crypto = require('crypto'); const fs = require('fs');
let pem = fs.readFileSync('key1.pem'); let key = pem.toString();
let plainText = 'Hello World'; console.log(`原始文本: ${plainText}`);
let crypted = '';
let cipher = crypto.createCipher('aes192', key);
cipher.on('readable', () => { const data = cipher.read(); if (data) crypted += data.toString('hex'); }); cipher.on('end', () => { console.log(`加密后的文本: ${crypted}`); });
cipher.write(plainText); cipher.end();
let decipher = crypto.createDecipher('aes192', key); let dec = decipher.update(crypted, 'hex', 'utf8'); dec += decipher.final('utf8');
console.log(`解密后文本: ${dec}`);
|
执行结果:
1 2 3
| 原始文本: Hello World 解密后文本: Hello World 加密后的文本: 4df8aad7a959d228ef2081e48a768745
|
对该公开密钥加密解密过程进行封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| const crypto = require('crypto'); const fs = require('fs');
function encrypt(data, key, method = 'aes192') { const cipher = crypto.createCipher(method, key); let crypted = cipher.update(data, 'utf8', 'hex'); crypted += cipher.final('hex'); return crypted; }
function decrypt(encrypted, key, method = 'aes192') { const decipher = crypto.createDecipher(method, key); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; }
let data = '这是一条加密信息!';
let key = fs.readFileSync('key1.pem');
let encrypted = encrypt(data, key, 'aes192');
let decrypted = decrypt(encrypted, key, 'aes192');
console.log('原始文本: ' + data); console.log('加密文本: ' + encrypted); console.log('解密文本: ' + decrypted);
|
执行结果:
1 2 3
| 原始文本: 这是一条加密信息! 加密文本: a63a8e870834f3ea197103392640bbff3f25086ae0cc84ce54f5bd7a8288b8a2 解密文本: 这是一条加密信息!
|
Diffie-Hellman 密钥交换协议/算法
我们可以使用该算法,确定一个对称密钥,再用该密钥进行加密解密操作。需要注意的是 Diffie-Hellman 算法不能用于加密传输密文,只能用于确立一个公共密钥,双方确定要用的密钥后,要使用其他对称密钥操作加密算法实际加密和解密消息。
需要用到的函数
- diffieHellman.getPrime([encoding]) 获得 dh 算法生成的素数 (encoding 为编码格式,可选 binary (默认), hex , base64)
- diffieHellman.getGenerator([encoding]) 同上
- diffieHellman.generateKeys([encoding]) 按照指定编码格式生成 私有/公有 key,返回值是公有 key,公有key会传递给另外一方,用来生成对称密钥用
- diffieHellman.getPrivateKey([encoding]) 生成私有密钥
- diffieHellman.getPublicKey([encoding]) 生成公有密钥
- diffieHellman.setPublicKey(public_key, [encoding]) 以指定的编码格式设置公有 key
- diffieHellman.setPrivateKey(public_key, [encoding]) 以指定的编码格式设置私有 key
- diffieHellman.computeSecret(other_public_key, [input_encoding], [output_encoding]) 根据 对方的公有 key 算出对称密钥, input_encoding 是对方密钥输入的编码格式,output_encoding 是对称密钥输出的格式
- crypto.getDiffieHellman(group_name) 生成一个预定义的dh交换key对象 (支持 modp1, modp2, modp5 等,其优点是不用交换生成key,只需要在握手前使用一样的group系数即可,节约了大家的处理时间和握手时间)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const crypto = require("crypto");
let person1 = crypto.createDiffieHellman(512, 'base64'); let person1_keys = person1.generateKeys('hex');
let prime = person1.getPrime(); let generator = person1.getGenerator();
console.log('Prime: ' + prime.toString('hex')); console.log('Generator: ' + generator.toString('hex'));
let person2 = crypto.createDiffieHellman(prime, generator); let person2_keys = person2.generateKeys('hex');
let person1_secret = person1.computeSecret(person2_keys, 'hex', 'base64'); let person2_secret = person2.computeSecret(person1_keys, 'hex', 'base64');
console.log(`第一个人的 key: ${person1_secret}`); console.log(`第二个人的 key: ${person2_secret}`);
console.log(`key 是否相同: ${person1_secret === person2_secret}`);
|
执行结果:
1 2 3 4 5
| Prime: ba226abde807d86cf1f632fa1563599d5695ba32cd338acc62680cd29f6905b348d6d9b36bd3b8cc00926a5155a36592398de4fe97259e9fc80f3f651025f56b Generator: 02 第一个人的 key: ORuR1bvIqMCb2usK8gYPgCqvbs7Fyz9xj0z+gQ9SNAuIVPnwiKAAuEhu5HpvUkbmwfwmDpXH3Q/ciAyezGkB0Q== 第二个人的 key: ORuR1bvIqMCb2usK8gYPgCqvbs7Fyz9xj0z+gQ9SNAuIVPnwiKAAuEhu5HpvUkbmwfwmDpXH3Q/ciAyezGkB0Q== key 是否相同: true
|
上述例子,我们使用 DF 算法生成了 2个密钥 key,可以看到,在交换素数以后,person1 和 person 2 生成的密钥是相同的,我们就可以使用这个密钥配合其他加密算法,去传输密文了!