密码学基础
现代密码学主要包含以下几个方面:
- 对称加密(Symmetric Cryptography),以 DES,AES,RC4 为代表。
- 非对称加密(Asymmetric Cryptography),以 RSA,ElGamal,椭圆曲线加密ECC 为代表。
- 哈希函数(Hash Function),以 MD5,SHA-1,SHA-512 等为代表。
- 数字签名(Digital Signature),以 RSA 签名,ElGamal 签名,DSA签名,基于椭圆曲线密码的数字签名算法ECDSA 为代表。
对称加密
对信息进行明/密文变换时,加解和解密使用相同密钥的密码体制。

- 用途:信息量大的加密
- 优点:算法公开、速度快、保密强度高、占用空间小
- 缺点:1. 密钥的分发和管理非常复杂, 建立安全的信道之前,如何实现通信双方的加密密钥的交换; 2. 若接收方伪造一个消息并诬陷是发送方发送的,发送方无法辩解,也就是无法解决消息的确认问题
非对称加密
对信息进行明/密文变换时,加密和解密密钥不相同的密码体制。每个用户都具有一对密钥,一个用于加密,一个用于解密,其中加密密钥可以公开,称之为公钥, 解密密钥属于秘密,称之为私钥,只有用户一人知道。

用途:加密关键性的、核心的机密数据 优点:算法复杂,安全性高。通信双方不需要通过建立一个安全信道来进行密钥的交换,密钥空间小,降低了密钥管理的难度 缺点:1. 加解密速度慢,不适合通信负荷较重的情况 2. 如果一个人用自己的公钥加密数据发送给我,我无法断定是谁发送的; 3. 我用私钥加密的数据,任何知道我公钥的人都能解密我的数据。
混合加密
因为对称加密速度快,但很难保证密钥传输的安全性,非对称加密加解最大的优点是事先不需要传输密钥,但速度慢,因此实际应用中,经常采取混合密码体制。 即同时使用对称密码和非对称密码的体制。假如A要向B传输数据,工作过程如下:
- A选取一个随机数,做为对称加密的密钥,即会话密钥;
- A用上面的会话密钥加密通信内容,再用B的公钥加密会话密钥后,一并发送给B;
- B收到数据后,先用自己的私钥解密出会话密钥,然后用会话密钥解密出通信内容。
哈希函数
通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。 摘要(digest):将长度不固定的消息作为输入,通过运行hash函数,生成固定长度的输出,这段输出就叫做摘要。
digest = Hash(message)
常见的摘要算法 与 对应的输出位数如下: MD5:128位 SHA-1:160位 SHA256 :256位 SHA512:512位
用途:检测消息或者密钥等信息对象中的任何微小的变化, 应用到数字签名中。还有一致性验证,安全访问认证等。 优点:单向加密算法,算法不可逆 缺点:可以通过碰撞法破解
数字签名
数字签名是指发送方以电子形式签名一个消息或文件,签名后的消息或文件能在网络中传输,并表示签名人对该消息或文件的内容负有责任。 数字签名综合使用了消息摘要和非对称加密技术,可以保证接受者能够核实发送者对报文的签名,发送者事后不抵赖报文的签名,接受者不能篡改报文内容和伪造对报文的签名。

- 发送方要发送消息运用散列函数(MD5、SHA-1等)形成消息摘要;
- 发送方用自己的私钥对消息摘要进行加密,形成数字签名;
- 发送方将数字签名附加在消息后发送给接收方;
- 接受方用发送方的公钥对签名信息进行解密,得到消息摘要;
- 接收方以相同的散列函数对接收到的消息进行散列,也得到一份消息摘要;
- 接收方比较两个消息摘要,如果完全一致,说明数据没有被篡改,签名真实有效;否则拒绝该签名。
Node.js中crypto模块
nodejs通过crypto模块实现加密解密。crypto 模块提供了加密功能,包括哈希、HMAC、加密、解密、签名、以及验证功能的以整套封装,利用了OpenSSL来实现其加密技术。
Hash算法
示例:
const crypto = require('crypto')
const hash = crypto.createHash('md5')
// 指定要摘要的原始内容,可以在摘要被输出之前使用多次update方法来添加摘要内容
hash.update('Hello, world!')
hash.update('Hello, nodejs!')
console.log(hash.digest('hex'))
// 摘要输出,在使用digest方法之后不能再向hash对象追加摘要内容。
hash.update('Hello, error!') // Error [ERR_CRYPTO_HASH_FINALIZED]: Digest already called
Hmac算法
哈希运算消息认证码(Hash-based Message Authentication Code),HMAC 运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。
示例:
const crypto = require('crypto')
const hmac = crypto.createHmac('sha256', 'secret-key')
hmac.update('Hello, world!')
hmac.update('Hello, nodejs!')
console.log(hmac.digest('hex')) // 80f7e22570...
对称加密
AES是一种常用的对称加密算法,加解密都用同一个密钥。crypto模块提供了AES支持,但是需要自己封装好函数,便于使用。
示例:
var crypto = require('crypto');
//加密
function encrypt(str,secret){
var cipher = crypto.createCipher('aes192',secret); //
var enc = cipher.update(str,'utf8','hex');
enc += cipher.final('hex');
return enc;
}
//解密
function decrypt(str,secret){
var decipher = crypto.createDecipher('aes192',secret);
var dec = decipher.update(str,'hex','utf8');
dec += decipher.final('utf8');
return dec;
}
var data = 'Hello, this is a secret message!'
var key = 'Password!'
var encrypted = encrypt(data, key)
var decrypted = decrypt(encrypted, key)
console.log('Plain text: ' + data)
console.log('Encrypted text: ' + encrypted)
console.log('Decrypted text: ' + decrypted)
输了结果如下:
> Plain text: Hello, this is a secret message!
> Encrypted text: 8a944d97bdabc157a5b7a40cb180e713f901d2eb454220d6aaa1984831e17231f87799ef334e3825123658c80e0e5d0c
> Decrypted text: Hello, this is a secret message!
非对称加密
RSA算法是一种非对称加密算法,即由一个私钥和一个公钥构成的密钥对,通过私钥加密,公钥解密,或者通过公钥加密,私钥解密。其中,公钥可以公开,私钥必须保密。我们借助openssl准备好私钥和公钥。
- 命令行执行以下命令以生成一个RSA密钥对
$ openssl genrsa -aes256 -out rsa-key.pem 2048
根据提示输入密码,这个密码是用来加密RSA密钥的,加密方式指定为AES256,生成的RSA的密钥长度是2048位。执行成功后,我们获得了加密的rsa-key.pem文件。
- 通过上面的rsa-key.pem加密文件,我们可以导出原始的私钥
$ openssl rsa -in rsa-key.pem -outform PEM -out rsa-prv.pem
输入第一步的密码,我们获得了解密后的私钥。类似的,我们用下面的命令导出原始的公钥:
$ openssl rsa -in rsa-key.pem -outform PEM -pubout -out rsa-pub.pem
这样,我们就准备好了原始私钥文件rsa-prv.pem和原始公钥文件rsa-pub.pem,编码格式均为PEM。
示例:
const fs = require('fs'),
crypto = require('crypto');
// 从文件加载key:
function loadKey(file) {
// key实际上就是PEM编码的字符串:
return fs.readFileSync(file, 'utf8');
}
let
prvKey = loadKey('./rsa-prv.pem'),
pubKey = loadKey('./rsa-pub.pem'),
message = 'Hello, world!';
// 使用私钥加密:
let enc_by_prv = crypto.privateEncrypt(prvKey, Buffer.from(message, 'utf8'));
console.log('encrypted by private key: ' + enc_by_prv.toString('hex'));
// 使用公钥解密
let dec_by_pub = crypto.publicDecrypt(pubKey, enc_by_prv);
console.log('decrypted by public key: ' + dec_by_pub.toString('utf8'));
// 使用公钥加密:
let enc_by_pub = crypto.publicEncrypt(pubKey, Buffer.from(message, 'utf8'));
console.log('encrypted by public key: ' + enc_by_pub.toString('hex'));
// 使用私钥解密:
let dec_by_prv = crypto.privateDecrypt(prvKey, enc_by_pub);
console.log('decrypted by private key: ' + dec_by_prv.toString('utf8'));
通过输出结果,我们可以看出,无论是私钥加密,公钥解密,还是公钥加密,私钥解密,解密后的消息都与原始消息相同。
数字签名
const fs = require('fs'),
crypto = require('crypto');
// 从文件加载key:
function loadKey(file) {
// key实际上就是PEM编码的字符串:
return fs.readFileSync(file, 'utf8');
}
let
prvKey = loadKey('./rsa-prv.pem'),
pubKey = loadKey('./rsa-pub.pem'),
message = 'Hello, world!';
let sign = crypto.createSign('RSA-SHA256');
sign.update(message);
let signature = sign.sign(prvKey, 'hex'); //生成签名(私钥加密)
let verify = crypto.createVerify('RSA-SHA256');
verify.update(message);
let verifyResult = verify.verify(pubKey,signature,'hex'); //验证签名(公钥解密)
console.log(verifyResult) //true
椭圆曲线加密
椭圆曲线加密算法,即:Elliptic Curve Cryptography,简称ECC,是基于椭圆曲线数学理论实现的一种非对称加密算法。 相比RSA,ECC优势是可以使用更短的密钥,来实现与RSA相当或更高的安全。
原理
椭圆曲线加解密原理:
公开密钥算法总是要基于一个数学上的难题。比如RSA依据的是:给定两个素数p、q 很容易相乘得到n,而对n进行因式分解却相对困难。
那椭圆曲线上有什么难题呢?考虑如下等式:
K=kG [其中K,G为Ep(a,b)上的点,k为小于n(n是点G的阶)的整数]
给定k和G,根据加法法则,计算K很容易;但给定K和G,求k就相对困难了。这就是椭圆曲线加密算法采用的难题。
我们把点G称为基点(base point),k(k<n,n为基点G的阶)称为私有密钥(privte key),K称为公开密钥(public key)。
k = 2,K为G的2倍点; k = 3,K为G的3倍点; k = 4,K为G的4倍点; ...
如果给定椭圆曲线上K为G的一个倍点,如何计算K为G的多少倍?
直观上理解,正向计算一个倍点是容易的,反向计算一个点K是G的几倍点则困难的多。
因此在椭圆曲线算法中,将倍数k做为私钥,将K做为公钥。
椭圆曲线数字签名原理:椭圆曲线数字签名算法(ECDSA)是使用椭圆曲线密码(ECC)对数字签名算法(DSA)的模拟。
secp256k1曲线
比特币使用椭圆曲线算法生成公钥和私钥,选择的是secp256k1曲线。
具体使用过程是,先随机生成一个私钥,然后通过椭圆曲线加密算法算法(ECC)得到一个公钥,然后再使用椭圆曲线签名算法(ECDSA)和私钥结合进行签名
示例:
const { randomBytes } = require('crypto');
const secp256k1 = require('secp256k1');
const ecdsa = require('ecdsa');
// 随机生成一个数, 作为通信内容
const data = randomBytes(32);
console.log(data);
// 随机产生一个私钥
let privKey
do {
privKey = randomBytes(32);
console.log(privKey);
} while (!secp256k1.privateKeyVerify(privKey));
// 根据私钥导出公钥
const pubKey = secp256k1.publicKeyCreate(privKey);
console.log(pubKey);
// 签名
const signature = ecdsa.sign(data, privKey);
console.log(sig);
// 验签
console.log(ecdsa.verify(data, signature, pubKey));//核查签名是否正确
// => true
以上示例代码是椭圆曲线加密算法最简单的使用了,关于复杂的应用案例,这里推荐国产公链ultrain的u3.js,有兴趣的同学可以参考一下它的实现。