转载请保留这部分内容,注明出处。
关注公众号“头号前端”,每周新鲜前端好文推送。另外,头条号前端团队非常 期待你的加入
本文将通过crypto的api深入了解加密算法
安全加密
当发送方A向接收方B发送数据时,需要考虑的问题有:
-
数据的安全性。
-
数据的完整性,即数据不被篡改。
-
数据的真实性,即数据确实来自于发送方,传输过程中没有被替换。
-
数据的不可否认性,即验证发送方确实发送了数据。
保证安全性
对称密钥加密 Symmetric Encryption
对称密钥加密又叫专用密钥加密或共享密钥加密,即发送和接收数据的双方必使用相同的密钥对明文进行加密和解密运算。
流程
-
A 使用密钥加密数据
-
A 将密文发送给 B
-
B 收到密文后,使用相同的密钥对其进行解密,取得原始数据
优点:速度快
缺点:密钥被盗就被破解、密钥管理不方便(每个用户都要对应一个密钥)
实现算法有:凯撒密码,AES(Advanced Encryption Standard)、DES(Data Encryption Standard)、动态口令等。
推荐:AES
AES加解密特点
分组长度是128bit,也就是16字节。
-
加密第一步,针对每个分组逐个字节的进行SubBytes的操作。笼统的说,就是每个字节根据256个值的替换表,将当前字节替换成另外一个字节。
-
加密第二步,以单个字节为单位进行ShiftRows处理,就是将字节有规律的打乱。
-
加密第三部,再以4字节为单位进行MixColumns处理,就是进行比特运算变成另外的4个字节。
-
加密第四步,还是4字节为单位与轮密钥进行XOR运算。至此一轮运算就结束了。
解密的过程就是加密的逆向过程。
分组密码的模式
对称密钥算法DES、AES都属于分组密码,分组密码的特点是分组的长度是固定的。但是由于明文的长度不固定且基本超过分组长度,所以就需要进行多轮的迭代加密。 模式就是指的多轮迭代的方式。
-
ECB模式:Electronic CodeBook mode(电子密码本模式)
-
CBC模式:Cipher Block Chaining mode(密码分组链接模式)推荐使用
-
CFB模式:Cipher FeedBack mode(密文反馈模式)
-
OFB模式:Output FeedBack mode(输出反馈模式)
-
CTR模式:CounTeR mode(计数器模式)推荐使用
NodeJS示例:Cipher、Decipher
// 加密
const crypto = require("crypto");
const algorithm = "aes-192-cbc";
const password = "Password used to generate key";
// Use the async `crypto.scrypt()` instead.
const key = crypto.scryptSync(password, "salt", 24);
// Use `crypto.randomBytes` to generate a random iv instead of the static iv
// shown here.
const iv = Buffer.alloc(16, 0); // Initialization vector.
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update("some clear text data", "utf8", "hex");
encrypted += cipher.final("hex");
console.log(encrypted);
// Prints: e5f79c5915c02171eec6b212d5520d44480993d7d622a7c4c2da32f6efda0ffa
// 解密
const crypto = require("crypto");
const algorithm = "aes-192-cbc";
const password = "Password used to generate key";
// Use the async `crypto.scrypt()` instead.
const key = crypto.scryptSync(password, "salt", 24);
// The IV is usually passed along with the ciphertext.
const iv = Buffer.alloc(16, 0); // Initialization vector.
const decipher = crypto.createDecipheriv(algorithm, key, iv);
// Encrypted using same algorithm, key and iv.
const encrypted =
"e5f79c5915c02171eec6b212d5520d44480993d7d622a7c4c2da32f6efda0ffa";
let decrypted = decipher.update(encrypted, "hex", "utf8");
decrypted += decipher.final("utf8");
console.log(decrypted);
// Prints: some clear text data
openssl list -cipher-algorithms 可以列出支持的加密算法
Cipher 类的创建
创建 Cipher 类可以使用crypto模块的 crypto.createCipher() 和 crypto.createCipheriv() 方法。OpenSSL推荐使用 pbkdf2 来替换 EVP_BytesToKey ,因此在创建 Cipher 类时,建议使用 crypto.pbkdf2 来派生 key 和 iv ,并使用 createCipheriv() 来创建加密流。
-
crypto.createCipher(algorithm, password):用给定的算法和密钥,创建并返回一个Cipher加密算法的对象。参数:algorithm算法是依赖OpenSSL库支持的算法, 例如:'aes192'算法等,password是用来派生key和iv的,它必须是一个'binary'二进制格式的字符串或者是一个Buffer。 -
crypto.createCipheriv(algorithm, key, iv):用给定的算法、密钥和向量,创建并返回一个Cipher加密算法的对象。参数:algorithm与createCipher方法相同,key密钥是一个被算法使用的原始密钥,iv是一个 初始化向量 。key密钥和iv向量必须是'binary'二进制格式的字符串或者是一个Buffer。
使用 Cipher 类加密数据
Cipher 加密对象是一个可读写的 Stream 流。可以使用 Cipher 类中的 update 方法写入纯文本的数据,数据输入完成后通过 final 方法返回加密后的数据。
-
cipher.update(data, [input_encoding], [output_encoding]):更新Cipher类的加密数据。data:要更新的Cipher加密对象的数据,编码input_encoding可以是:'utf8'、'ascii'、'binary'。如果没有编码参数,那么data必须是一个Buffer。 -
参数
output_encoding指定加密数据的输出编码,可以是:'binary'、'base64'或'hex',如果未设置这个参数,将会返回一个Buffer。 -
cipher.final([output_encoding]):返回加密后的内容,output_encoding为:'binary'、'base64'或'hex'。 如果没有提供编码格式,如果未设置这个参数,将会返回一个Buffer。 -
注意:调用
final()后不能再用Cipher对象 -
cipher.setAutoPadding(auto_padding=true):设置输入数据自动填充到块大小功能,这个函数必须在cipher.final之前调用。如果auto_padding是false,那么整个输入数据的长度必须是加密器的块大小的整倍数,否则final会失败。这对非标准的填充很有用,例如:使用0x0而不是PKCS的填充。 -
cipher.setAuthTag(buffer):加密认证模式(目前支持:GCM),这个方法返回经过计算的认证标志Buffer。必须在使用final方法完成加密后调用。 -
cipher.setAAD(buffer):对于加密认证模式(目前支持:GCM),用这个方法设置附加认证数据( AAD )。
相关API资料:
Node.js的加密模块crypto之使用Cipher类加密数据: itbilu.com/nodejs/core…
Node.js的加密模块crypto之使用Decipher类解密数据: itbilu.com/nodejs/core…
非对称密钥加密 Asymmetric Encryption
非对称加密算法需要两个密钥: 公开密钥 (publickey:简称公钥)和私有密钥(privatekey:简称私钥)。公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
公开密钥加密
流程
-
首先由接收方 B 生成公钥和私钥
-
B 把公钥发送给 A
-
A 使用 B 发来的公钥加密数据,然后发送给 B
-
B 使用私钥对密文进行解密,得到原始数据
优点:安全性高、密钥管理方便
缺点:加密速度慢、无法防止中间人攻击,A 不知道收到的公钥是否是来自 B

实现算法有:RSA 算法、椭圆曲线加密算法等
推荐:RSA
NodeJS示例:privateEncrypt、privateDecrypt、publicEncrypt、publicDecrypt
// 公钥加密
let encryptString = crypto.publicEncrypt(
{
key: publicKey,
padding: crypto.constants.RSA_NO_PADDING
},
Buffer.from("需要加密的内容")
);
encryptString = encryptString.toString("base64");
// 私钥加密
let encryptString = crypto.privateEncrypt(
{
key: privateKey,
padding: crypto.constants.RSA_NO_PADDING
},
Buffer.from("需要加密的内容")
);
encryptString = encryptString.toString("base64");
// 私钥解密
crypto.privateDecrypt(privateKey, buffer);
// 公钥解密
crypto.publicDecrypt(key, buffer);
注意
1024位的证书,加密时最大支持117个字节,解密时为128;
2048位的证书,加密时最大支持245个字节,解密时为256。
所以在加密和解密较大内容的过程中需要分块进行。推荐使用node-rsa库。
非对称密钥概览
密钥配送
密钥配送问题:如何安全地把密钥给到接受者?
配送问题解决办法
-
事先共享密钥,缺点是需要很多密钥,且管理麻烦。
-
通过密钥中心分配,缺点是全部人员所有通信密钥都产生于一个地方,这个地方的压力就相当大,而且一旦此服务器坏掉,则所有人员无法通信。
-
公钥密码解决办法,缺点如果得到服务器私钥就可以解密所有数据。
-
Diffie-Hellman密钥交换解决办法:SSL通信使用的就是这种技术。
密钥交换协议
流程
-
A 生成密钥 P
-
A 把密钥 P 发送给 B
-
A 和 B 各自准备自己的私钥 SA 和 SB
-
A 利用密钥 P 和私钥 SA 合成新的密钥 P-SA
-
B 也利用密钥 P 和私有密钥 SB 合成新的密钥 P-SB
-
A 将密钥 P-SA 发送给 B,B 也将密钥 P-SB 发送给 A
-
A 将私有密钥 SA 和收到的密钥 P-SB 合成新的密钥 SA-P-SB(合成结果和合成顺序无关,合成密钥无法被分解) h)同样地,B 也将私钥 SB 和收到的密钥 P-SA 合成新的密钥 P-SA-SB。于是 A 和 B 都得到了密钥 P-SA-SB。这个密钥将作为“加密密钥”和“解密密钥”使用。
优点:DH 利用“离散对数问题”解决中间人攻击
实现算法有:DH、ECDH
推荐:ECDH > DH
NodeJS示例:DiffieHellman、DiffieHellmanGroup、ECDH
ECDH 是基于ECC(Elliptic Curve Cryptosystems,椭圆曲线密码体制,参看ECC)的Diffie-Hellma密钥交换算法。交换双方可以在不共享任何秘密的情况下协商出一个密钥。与 Diffie-Hellman 相比ECDH具有ECC的高强度、短密钥长度、计算速度快等优点。Node.js的crypto模块,封装了 ECDH 类,可以用这个类来生成EC Diffie-Hellman交换密钥。
const crypto = require('crypto');
const assert = require('assert');
// Generate Alice's keys...
const alice = crypto.createECDH('secp521r1');
const aliceKey = alice.generateKeys();
// Generate Bob's keys...
const bob = crypto.createECDH('secp521r1');
const bobKey = bob.generateKeys();
// Exchange and generate the secret...
const aliceSecret = alice.computeSecret(bobKey);
const bobSecret = bob.computeSecret(aliceKey);
assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex'));
// OK
相关API资料:
Node.js的加密模块crypto之使用DiffieHellman类生成交换密钥: itbilu.com/nodejs/core…
Node.js的加密模块crypto之使用ECDH类生成EC Diffie-Hellman交换密钥: itbilu.com/nodejs/core…
常用密钥交换算法: chziyue.com/post/57.htm…
DH需要和数字签名一起使用才安全,相关: www.slideshare.net/dganesan11/…
混合加密
流程
-
接收方 B 事先生成公钥和私钥
-
B 将公钥发送给 A
-
A 使用收到的公钥对共享密钥(对称密钥)进行加密,并发送给 B
-
B 使用私钥解密,得到共享密钥
-
接下来 A 只要使用对称密钥加密好数据发送给 B 即可
优点:使用处理速度快的对称密钥加密数据同时保证对称密钥的安全性。 HTTPS 的 TLS 加密就是用的混合加密。
保证完整性
哈希函数(散列函数、哈希算法)
什么是哈希?在记录的关键字与记录的存储地址之间建立的一种对应关系叫哈希函数。 哈希函数就是一种映射,是从关键字到存储地址的映射。 通常,包含哈希函数的算法的算法复杂度都假设为O(1),这就是为什么在哈希表中搜索数据的时间复杂度会被认为是"平均为O(1)的复杂度".
哈希冲突/碰撞
冲突(碰撞) 对于不同的关键字ki、kj,若ki != kj,但H(ki) = H(kj)的现象叫冲突(collision) ,即不同的输入却有相同的输出。我们应该尽量避免冲突,因为冲突不仅会使我们在查找的时候效率变慢,还甚至会被攻击者利用从而大量消耗系统资源。 至于冲突的解决方案有很多种,具体可以参考这篇 哈希表针对冲突的两种方式优缺点是什么? 。
哈希算法的分类
安全加密哈希算法
在安全方面应用主要体现在以下三个方面:
-
文件校验
-
数字签名
-
鉴权协议
在NodeJS中我们可以使用原生crypto模块对数据进行加密,crypto.getHashes()查看支持的哈希算法,其参数 algorithm 取决与平台上所安装的 OpenSSL 版本所支持的算法。
const crypto = require('crypto');
console.log(crypto.getHashes());
/*
[ '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' ]
*/
除了我们常用的md5,sha-1,sha-2族外,还有像DSA-SHA1,RSA-SHA1,sha1WithRSAEncryption,其中sha1WithRSAEncryption和RSA-SHA1等价,DSA和RSA都是加密算法,DSA和RSA的区别在于,DSA用于签名,而RSA可用于签名和加密。
查找哈希算法
一般性能好的哈希算法都会根据不同系统做优化。 这里有一篇文章详细介绍了各种非加密哈希的性能比较( aras-p.info/blog/2016/0… )。
文章详细列出了各个平台(甚至包括手机和XBOXOne以及asm.js)之间的哈希算法性能比较,同时也对不同输入数据量做了对比。 似乎在跨平台使用方面CityHash64在64位系统性能最佳,而xxHash32在32位系统性能最好。 在数据量大方面,不同平台也有不同的选择 Intel CPU:总体来说xxhash64更好,随之FarmHash64(如果使用了SSE4.2),xxHash32更适合32位系统。 苹果手机CPU(A9):CityHash64在64位系统性能最佳,而xxHash32在32位系统性能最好。 SpookyV2更适合在XBOXOne中使用。 而短字符串输入使用FNV-1a性能最优(在pc,手机和XBOX中少于8字节,在asm.js中少于20字节),而且它的实现很简单。
更多详细内容可查看“常用哈希函数介绍”: zhuanlan.zhihu.com/p/101390996
保证真实性及不可否认性
消息认证码
是一种确认完整性并进行认证的技术,英文是message authentication code,简称MAC。 消息认证码的输入是任意长度的消息和一个发送者与接收者之间共享的密钥,输出是固定长度的数据,这个数据称为MAC值。 从上面的输入和输出关系,我们很容易想到,消息认证码和单向哈希函数有相似之处。所以可以理解为,消息认证码是一种与密钥相关联的单向哈希函数。 单向哈希函数保证了完整性,无法被篡改;共享密钥只有发送者和接收者知道,保证了可以检查发送者身份的正确性。
消息认证码的作用
消息认证码同时解决消息的完整性和发送者正确性。
-
消息完整性解决的是消息是否有被篡改的问题,也就是保证了消息的完整性。
-
消息认证解决的是消息的发送者正确性的问题,确保消息不是其他人伪装发送的。
流程
-
A 向 B 发送密文前,A 生成了一个用于制作消息认证码的密钥,然后使用安全的方法将密钥发送给了 B
-
A 使用密文和密钥生成一个值,例如生成 1abc。这个由密钥和密文生成的值就是消息认证码,简称 MAC(Message Authentication Code)
-
A 将 MAC(1abc)和密文发送给 B
-
和 A 一样,B 也需要使用密文和密钥生成 MAC。经过对比,B 可以确认自己计算出的值和 A 发的值是否一致。
-
接下来 B 只需要使用密钥对密文进行解密即可
实现算法有:HMAC(Hash-based MAC)、OMAC(One-Key MAC)、CMAC(Cipher-based MAC)
推荐:HMAC
优点:可以实现认证和检测篡改功能
缺点:MAC 不能确定密钥由哪方生成(A 或 B)
HMAC的具体实现步骤

-
密钥填充
-
如果共享密钥小于哈希值的长度,则用0补充。
-
如果共享密钥大于哈希值的长度,则使用单向哈希函数计算出共享密钥的哈希值。
-
-
填充后的密钥与ipad的XOR
-
ipad是一种比特序列,是使用00110110这一比特序列不断循环直到分组长度的比特序列。i是inner的意思。
-
填充后的密钥和ipad XOR计算后的值称为 ipadkey。
-
-
与消息组合
- 将ipadkey附在消息的开头。
-
计算哈希值
- 使用单向哈希函数计算消息和ipadkey组合的哈希值。
-
填充后的密钥与opad XOR
-
opad是一种比特序列,是使用01011100这一比特序列不断循环直到分组长度的比特序列。o是outer的意思。
-
填充后的密钥和opad XOR计算后的值称为 opadkey。
-
-
与哈希值合并
- 将第4步计算的哈希值拼接在opadkey后面。
-
计算哈希值
- 将第6步组合后的值进行hash,计算出的哈希值就是MAC值。
NodeJS示例:createHmac
const crypto = require("crypto");
const hmac = crypto.createHmac("sha256", "a secret");
hmac.update("some data to hash");
console.log(hmac.digest("hex"));// Prints:// 7fd04df92f636fd450bc841c9418e5825c17f33ad9c87c518115a45971f7f77e
crypto.createHmac 会创建并返回一个code>Hmac对象,也就是通过给定的加密算法和密钥生成的加密图谱。与 Hash 类一样,它也是一个可读写的 Stream 流。可以使用 hmac.update 方法写入用计算 hmac 的数据。当写入流结束后,使用 hash.digest() 方法来获取计算后的值。
-
hmac.update(data):更新要计算hmac的data,由于其本身也是个流对象,所以这个方法可以被多次调用。 -
hmac.digest([encoding]):计算传入的数据的hmac值,encoding参数可以是'hex'、'binary'、'base64',如果没有指定encoding,将返回 buffer。 -
注意:调用
digest()后不能再用hmac对象
相关API资料:
Node.js的加密模块crypto之使用Hmac类计算哈希密钥: itbilu.com/nodejs/core…
数字签名
数组签名的作用
消息认证码只能解决防篡改和确认发送者,却无法解决**防否认
-
简单的说使用了类似公钥的技术来解决防止否认的问题。
-
公钥是发送方使用接收方的公钥加密,而接收方使用自己的私钥解密。
-
数字签名正好相反,发送方使用自己的私钥加密,而其他任何人都使用公钥进行解密。
- 由于是使用公钥进行解密,也就是说任何人都可以进行解密,毕竟签名的作用是用来验证的。既然是任何人,意味着第三方也可以做验证了。
由于是用自己的私钥进行加密,所以无论如何是没法否认消息不是自己发的了,因为私钥只有自己知道。
流程
-
消息发送者 A 准备好需要发送的消息、私钥和公钥
-
A 将公钥发送给 B
-
A 使用私钥加密消息,加密后的消息就是数字签名
-
A 将消息和签名都发送给 B
-
B 使用公钥对密文(签名)解密
-
B 对解密后的消息进行确认,看它是否和收到的消息一致
优点:可以确认谁是消息发送者 缺点:无法确定公钥的制造者是谁
NodeJS示例:Sign、Verify
let sign = crypto
.createSign("RSA-SHA256")
.update("签名内容")
.sign(privateKey, "base64");
let verify = crypto.createVerify("RSA-SHA256");
let result = verify.verify(publicKey, "签名内容");
Sign 类简介
Sign 签名对象是一个可读写的 Stream 流。可以使用 Sign 类中的 update 方法写入需要签名的数据,数据输入完成后通过 sign 方法返回数据的数字签名。
-
sign.update(data):更新Sign类的签名数据,data为要更新的数据,由于Sign是一个可读写的流,此方法可以被多次调用。 -
sign.sign(private_key, [output_format]):根据传送给Sign的数据来计算数字签名,private_key可以是一个对象或是字符串。如果为字符串时,则是一个包含了签名私钥的字符串,该私钥用的是PEM编码的。如是为对象时,对象如下:-
key:包含 PEM 编码的私钥 -
passphrase:私钥的密码
-
output_format :返回值的编码格式, 格式可以是 'binary' 、 'hex' 、 'base64' 。如果没有设置 ,将返回 Buffer 。
- 注:调用
sign()后不能再使用sign对象。
相关API资料: Node.js的加密模块crypto之使用Sign类生成数字签名并使用Verify类验证数字签名: itbilu.com/nodejs/core…
使用场景
-
安全信息公告
- 信息安全公告是发布官方漏洞的,但是这个消息是需要看的人判断真伪、判断是否被篡改的。所以在消息后加上数字签名即可,消息本身无需加密,因为本来就是给别人看的。
-
软件下载
- 为了防止软件被篡改,导致使用者用了盗版软件,所以软件发布者需要加上数字签名,以帮助软件使用者做校验。
-
公钥证书
- 为了保证自己使用的公钥是合法的公钥,所以需要对公钥进行数字签名加以验证。这个就是公钥证书。
-
SSL/TLS
- SSL/TLS在认证服务器身份是否合法时会使用服务器证书,它就是加上了数字签名的服务器公钥。对客户端认证也会使用客户端证书。
数字签名无法解决的问题
数字签名可以 防篡改、检验发送者真伪、防否认,这些都是针对消息本身的。
但是在解决消息本身的这些问题时,带入了一个数字签名机制本身的问题,就是公钥持有者的真伪问题(中间人攻击)。 判断公钥是否合法,这就需要使用到证书,而且需要引入第三方来认证。
数字证书
证书就是由认证机构对某个公钥施加数字签名,并附上此公钥所属人的姓名、组织、邮箱形成的文件。又称为公钥证书。
证书的生成
数字证书是数字证书在一个身份和该身份的持有者所拥有的公/私钥对之间建立了一种联系,由认证中心(CA)或者认证中心的下级认证中心颁发的。根证书是认证中心与用户建立信任关系的基础。在用户使用数字证书之前必须首先下载和安装。

认证中心把用户证书的基本信息做哈希算法,然后用自己的私钥对哈希值进行加密。

数字证书的分发
-
第一种途径是带外分发(Out-of-band Distribution),即离线方式。例如,密钥对是由软件运营商代替客户生成,证书也是由运营商代替客户从CA下载,然后把私钥和下载的证书一起储存在软盘里,再交给用户的。
-
第二种途径是带内分发(In-band distribution),即用户从网上下载数字证书到自己的电脑中。下载时,用户要向CA出示“参考号”和“授权码”,以向CA证明自己的身份。这样做成本较低,但对使用计算机不太熟悉的用户来说,可能在下载时会碰到一些麻烦。
-
CA还把证书集中放置在公共的数据库中公布,用户可以随用随查询随调用。
流程
-
A 持有公钥 PA 和私钥 SA
-
A 首先需要向认证中心申请发行证书,证明公钥 PA 确实由自己生成
-
认证中心里保管着他们自己准备的公钥 PC 和私钥 SC
-
A 将公钥 PA 和包含邮箱(域名)的个人资料发送给认证中心
-
认证中心对收到的资料进行确认,判断其是否为 A 本人的资料。确认完毕后,认证中心使用自己的私钥 SC,根据 A 的资料生成数字签名
-
认证中心将生成的数字签名和资料放进同一个文件中,然后把这个文件发送给 A,这个文件就是 A 的数字证书
-
A 将作为公钥的数字证书发送给 B
-
B 收到数字证书后,确认证书里的邮件地址(域名)确实是 A 的。接着,B 获取了认证中心的公钥
-
B 对证书内的签名进行验证,判断它是否为认证中心给出的签名。证书中的签名只能用认证中心的公钥 PC 进行验证。如果验证结果没有异常,就能说明这份证书的确是由认证中心发行的。校验证书的合法性包括以下步骤:
-
校验证书证书有效期
-
校验签发者签名(证书链)
-
检查证书作废列表(CRL,OCSP)
-
-
确认了证书是由认证中心发行的有效的证书后,且邮件地址(域名)就是 A 的之后,B 从证书中取出 A 的公钥 PA
优点:解决数字签名的缺点
NodeJS示例:Certificate
const { Certificate } = require("crypto");
const spkac = getSpkacSomehow();
const challenge = Certificate.exportChallenge(spkac);
console.log(challenge.toString("utf8"));// Prints: the challenge as a UTF8 string
const publicKey = Certificate.exportPublicKey(spkac);
console.log(publicKey);// Prints: the public key as <Buffer ...>
console.log(Certificate.verifySpkac(Buffer.from(spkac)));// Prints: true or false
综合应用:SSL/TLS
SSL/TLS实际上就是上面多种技术以某种方式组合在一起的组合套餐,以帮助实现通讯安全。
篇幅较长,相关资料: www.jianshu.com/p/18dc9a213…
资料
-
TLS中的加密算法: www.thesslstore.com/blog/cipher…
-
《图解密码技术》
-
官方文档: nodejs.org/dist/latest…