本文代码:本文代码
0.HTTPS简介
0.1. SSL和TLS
0.2. HTTPS
.1. 加密
1.1 对称加密
1.1.1 描述
1.1.2 简单实现
1.1.3 AES
1.2 非对称加密
1.2.1 单向函数
1.2.2 RSA加密算法
1.2.3 RSA加密
1.3 哈希
1.3.1 哈希函数
1.3.2 哈希碰撞
1.3.3 哈希分类
1.3.4 hash使用
1.3.4.1 简单哈希
1.3.4.2 md5
1.3.4.3 sha256
1.4 数字签名
1.5 数字证书
1.5.1 数字证书原理
1.6 Diffie-Hellman算法
1.6.1 Diffie-Hellman实现
1.6.2 Diffie-Hellman算法
1.7 ECC
1.7.1 ECC原理
1.7.2 ECC使用
2. UDP服务器
2.1 createCA.js
2.2 ca.js
2.3 utils.js
2.4 protocol.js
2.5 udp_server.js
2.6 udp_client.js
3. 数字证书实战
3.1 自建CA
3.2 生成服务器CA证书
3.3 服务端
3.4 客户端
0. HTTPS简介
0.1. SSL和TLS
- 传输层安全性协议(Transport Layer Security,缩写TLS),及其前身安全套接层(Secure Sockets Layer,缩写SSL)是一种安全协议,目的是为互联网通信,提供安全及数据完整性保障
0.2. HTTPS #
- HTTPS 协议的主要功能基本都依赖于 TLS/SSL 协议,TLS/SSL 的功能实现主要依赖于三类基本算法
①散列函数 散列函数验证信息的完整性
②对称加密 对称加密算法采用协商的密钥对数据加密
③非对称加密 非对称加密实现身份认证和密钥协商
- 加密就是研究如何安全通信的
- 保证数据在传输过程中不会被窃听
- crypto
1.1 对称加密
- 对称加密是最快速、最简单的一种加密方式,加密(encryption)与解密(decryption)用的是同样的密钥(secret key)
- 主流的有AES和DES
1.1.1 描述
1.1.2 简单实现
- 消息 abc
- 密钥 3
- 密文 def
let secretKey = 3;
function encrypt(str) {
let buffer = Buffer.from(str);
for (let i = 0; i < buffer.length; i++) {
buffer[i] = buffer[i] + secretKey;
}
return buffer.toString();
}
function decrypt(str) {
let buffer = Buffer.from(str);
for (let i = 0; i < buffer.length; i++) {
buffer[i] = buffer[i] - secretKey;
}
return buffer.toString();
}
let message = 'abc';
let secret = encrypt(message);
console.log(secret);
let value = decrypt(secret);
console.log(value);
1.1.3 AES
- crypto.html
algorithm用于指定加密算法,如aes-128-ecb、aes-128-cbc等
key是用于加密的密钥
iv参数用于指定加密时所用的向量 - 如果加密算法是128,则对应的密钥是16位,加密算法是256,则对应的密钥是32位
const crypto = require("crypto");
function encrypt(data, key, iv) {
let decipher = crypto.createCipheriv("aes-128-cbc", key, iv);
decipher.update(data);
return decipher.final("hex");
}
function decrypt(data, key, iv) {
let decipher = crypto.createDecipheriv("aes-128-cbc", key, iv);
decipher.update(data, "hex");
return decipher.final("utf8");
}
let key = "1234567890123456";
let iv = "1234567890123456";
let data = "hello";
let encrypted = encrypt(data, key, iv);
console.log("数据加密后:", encrypted); // 39b071b5bf699723ef33a644741bfa31
let decrypted = decrypt(encrypted, key, iv);
console.log("数据解密后:", decrypted); // hello
1.2 非对称加密
- 提出的原因:互联网上没有办法安全的交换密钥
1.2.1 单向函数
- 单向函数顺向计算起来非常的容易,但求逆却非常的困难。也就是说,已知x,我们很容易计算出f(x)。但已知f(x),却很难计算出x
- 整数分解又称素因数分解,是将一个正整数写成几个约数的乘积
- 给出45这个数,它可以分解成3×3×5,根据算术基本定理,这样的分解结果应该是独一无二的。
- 然而,随着数的增大,找到其所有素因数的难度也显著增加。目前,对于非常大的数(如几百位或更多位的数),没有已知的快速算法可以在合理的时间内完成素因数分解。正是基于大数素因数分解的困难性,许多现代加密技术(如RSA加密算法)才得以实现。这些算法依赖于大数的难以分解性来确保数据的安全性。
1.2.2 RSA加密算法
let p = 3, q = 11;//计算完立刻销毁
let N = p * q;
let fN = (p - 1) * (q - 1);//欧拉函数
let e = 7;
for (var d = 1; e * d % fN !== 1; d++) {//拓展欧几里得算法
d++;
}
//d=3
let publicKey = { e, N };
let privateKey = { d, N };
function encrypt(data) {
return Math.pow(data, publicKey.e) % publicKey.N;
}
function decrypt(data) {
return Math.pow(data, privateKey.d) % privateKey.N;
}
let data = 5;
let secret = encrypt(data);
console.log(secret);//14
let _data = decrypt(secret);
console.log(_data);//5
// 1024位二进制数分解
/**
公开 N e c
私密 d
e * d % fN == 1
(p - 1) * (q - 1)
N = p * q
*/
1.2.3 RSA加密
// 导入 crypto 模块,用于生成密钥对和加密/解密操作
let { generateKeyPairSync, privateEncrypt, publicDecrypt } = require("crypto");
// 生成 RSA 密钥对
let rsa = generateKeyPairSync("rsa", {
// 设置密钥长度为 1024 位
modulusLength: 1024,
// 公钥编码方式
publicKeyEncoding: {
type: "spki",
format: "pem",
},
// 私钥编码方式,并设置口令短语
privateKeyEncoding: {
type: "pkcs8",
format: "pem",
cipher: "aes-256-cbc",
passphrase: "server_passphrase",
},
});
// 定义待加密的消息
let message = "hello";
// 使用私钥对消息进行加密
let enc_by_prv = privateEncrypt(
{
key: rsa.privateKey, // 使用生成的私钥
passphrase: "server_passphrase", // 私钥的口令短语
},
Buffer.from(message, "utf8") // 将消息转换为 Buffer 对象
);
// 打印加密后的消息(以十六进制字符串形式)
console.log("加密后的信息: " + enc_by_prv.toString("hex"));
// 使用公钥对加密后的消息进行解密
let dec_by_pub = publicDecrypt(rsa.publicKey, enc_by_prv);
// 打印解密后的消息
console.log("解密后的信息: " + dec_by_pub.toString("utf8"));
1.3 哈希
hash 切碎的食物
1.3.1 哈希函数
- 哈希函数的作用是给一个任意长度的数据生成出一个固定长度的数据
- 安全性 可以从给定的数据X计算出哈希值Y,但不能从哈希值Y计算出数据X
- 独一无二 不同的数据一定会产出不同的哈希值
- 长度固定 不管输入多大的数据,输出长度都是固定的
1.3.2 哈希碰撞
- 所谓哈希(hash),就是将不同的输入映射成独一无二的、固定长度的值(又称"哈希值")。它是最常见的软件运算之一
- 如果不同的输入得到了同一个哈希值,就发生了哈希碰撞(collision)
- 防止哈希碰撞的最有效方法,就是扩大哈希值的取值空间
- 16个二进制位的哈希值,产生碰撞的可能性是 65536 分之一。也就是说,如果有65537个用户,就一定会产生碰撞。哈希值的长度扩大到32个二进制位,碰撞的可能性就会下降到 4,294,967,296 分之一
console.log(Math.pow(2, 16));//65536
console.log(Math.pow(2, 32));//42亿
1.3.3 哈希分类
- 哈希还可以叫摘要(digest)、校验值(chunkSum)和指纹(fingerPrint)
- 如果两段数据完全一样,就可以证明数据是一样的
- 哈希有二种
①普通哈希用来做完整性校验,流行的是MD5
② 加密哈希用来做加密,目前最流行的加密算法是 SHA256( Secure Hash Algorithm) 系列
1.3.4 hash使用 #
1.3.4.1 简单哈希 #
function hash(input) {
return input % 1024;
}
let r1 = hash(100);
let r2 = hash(1124);
console.log(r1, r2);
1.3.4.2 md5
var crypto = require('crypto');
var content = '123456';
var result = crypto.createHash('md5').update(content).digest("hex")
console.log(result);//32位十六进制 = 128位二进制
1.3.4.3 sha256
var crypto = require("crypto");
const salt = "123456";
const content = "hello";
const sha256 = (str) =>
crypto.createHmac("sha256", salt).update(str, "utf8").digest("hex");
let ret = sha256(content);
console.log(ret); // ac28d602c767424d0c809edebf73828bed5ce99ce1556f4df8e223faeec60edd (64位十六进制 = 256位二进制)
1.4 数字签名
- 数字签名的基本原理是用私钥去签名,而用公钥去验证签名
// 导入crypto模块,用于生成密钥对和签名验证
let { generateKeyPairSync, createSign, createVerify } = require("crypto");
// 设置一个密码短语,用于加密私钥
let passphrase = "zhufeng";
// 使用同步方法生成RSA密钥对,设置公钥和私钥的编码方式
let rsa = generateKeyPairSync("rsa", {
modulusLength: 1024,
publicKeyEncoding: {
type: "spki",
format: "pem",
},
privateKeyEncoding: {
type: "pkcs8",
format: "pem",
cipher: "aes-256-cbc",
passphrase,
},
});
// 定义待签名的内容
let content = "hello";
// 调用getSign函数获取签名
const sign = getSign(content, rsa.privateKey, passphrase);
// 验证签名并记录结果
let serverCertIsValid = verifySign(content, sign, rsa.publicKey);
// 输出验证结果
console.log("服务器证书是否合法", serverCertIsValid); // true
// 定义生成签名的函数
function getSign(content, privateKey, passphrase) {
// 创建一个签名对象
var sign = createSign("RSA-SHA256");
// 更新签名对象的内容
sign.update(content);
// 返回签名后的结果,格式为十六进制字符串
return sign.sign({ key: privateKey, format: "pem", passphrase }, "hex");
}
// 定义验证签名的函数
function verifySign(content, sign, publicKey) {
// 创建一个验证签名的对象
var verify = createVerify("RSA-SHA256");
// 更新验证对象的内容
verify.update(content);
// 验证签名,并返回验证结果
return verify.verify(publicKey, sign, "hex");
}
1.5 数字证书
- 数字证书是一个由可信的第三方发出的,用来证明所有人身份以及所有人拥有某个公钥的电子文件
// 导入crypto模块,用于生成密钥对、签名验证及哈希计算
let {
generateKeyPairSync,
createSign,
createVerify,
createHash,
} = require("crypto");
// 设置一个密码短语,用于加密私钥
let passphrase = "zhufeng";
// 使用同步方法生成RSA密钥对,设置公钥和私钥的编码方式
let rsa = generateKeyPairSync("rsa", {
modulusLength: 1024,
publicKeyEncoding: {
type: "spki",
format: "pem",
},
privateKeyEncoding: {
type: "pkcs8",
format: "pem",
cipher: "aes-256-cbc",
passphrase,
},
});
// 创建包含域名和公钥的信息对象
const info = {
domain: "http://127.0.0.1:8080",
publicKey: rsa.publicKey,
};
// 创建SHA-256哈希对象,更新内容为信息对象的JSON字符串表示形式,并获取十六进制格式的摘要
const hash = createHash("sha256").update(JSON.stringify(info)).digest("hex");
// 调用getSign函数获取签名
const sign = getSign(hash, rsa.privateKey, passphrase);
// 创建包含信息对象和签名的对象
const cert = { info, sign };
// 验证签名并记录结果
let certIsValid = verifySign(hash, cert.sign, rsa.publicKey);
// 输出验证结果
console.log("certIsValid", certIsValid);
// 定义生成签名的函数
function getSign(content, privateKey, passphrase) {
// 创建一个签名对象
var sign = createSign("RSA-SHA256");
// 更新签名对象的内容
sign.update(content);
// 返回签名后的结果,格式为十六进制字符串
return sign.sign({ key: privateKey, format: "pem", passphrase }, "hex");
}
// 定义验证签名的函数
function verifySign(content, sign, publicKey) {
// 创建一个验证签名的对象
var verify = createVerify("RSA-SHA256");
// 更新验证对象的内容
verify.update(content);
// 验证签名,并返回验证结果
return verify.verify(publicKey, sign, "hex");
}
1.6 Diffie-Hellman算法 #
- Diffie-Hellman算法是一种密钥交换协议,它可以让双方在不泄漏密钥的情况下协商出一个密钥来
-1.6.1 Diffie-Hellman实现
let N = 23;
let p = 5;
let secret1 = 6; //这是密钥
let A = Math.pow(p, secret1) % N; //8
console.log("p=", p, "N=", N, "A=", A); // p= 5 N= 23 A= 8
let secret2 = 15;
let B = Math.pow(p, secret2) % N; //19
console.log("p=", p, "N=", N, "B=", B); // p= 5 N= 23 B= 19
console.log(Math.pow(B, secret1) % N); // 2
console.log(Math.pow(A, secret2) % N); // 2 协商最终秘钥是2
1.6.2 Diffie-Hellman算法
// 导入crypto模块,用于创建Diffie-Hellman密钥交换对象
const { createDiffieHellman } = require("crypto");
// 创建客户端的Diffie-Hellman对象,指定密钥长度为512位
var client = createDiffieHellman(512);
// 生成客户端的公钥
var client_keys = client.generateKeys();
// 获取Diffie-Hellman算法中使用的素数
var prime = client.getPrime();
// 获取Diffie-Hellman算法中使用的生成元
var generator = client.getGenerator();
// 使用相同的素数和生成元创建服务器端的Diffie-Hellman对象
var server = createDiffieHellman(prime, generator);
// 生成服务器端的公钥
var server_keys = server.generateKeys();
// 客户端计算共享密钥
var client_secret = client.computeSecret(server_keys);
// 服务器端计算共享密钥
var server_secret = server.computeSecret(client_keys);
// 打印客户端计算出的共享密钥(十六进制格式)
console.log("client_secret: " + client_secret.toString("hex")); // caccbb77799bde7628d87d1f1619e34a68161c93d5a842fbcf82ece99ce28a7f0c5b9b52c4ef888cd5547c2a17dcd7c71c2474f2855d801f74cca2e6d2b618ad
// 打印服务器端计算出的共享密钥(十六进制格式)
console.log("server_secret: " + server_secret.toString("hex")); // caccbb77799bde7628d87d1f1619e34a68161c93d5a842fbcf82ece99ce28a7f0c5b9b52c4ef888cd5547c2a17dcd7c71c2474f2855d801f74cca2e6d2b618ad
1.7 ECC
- 圆曲线加密算法(ECC) 是基于椭圆曲线数学的一种公钥加密的算法
1.7.1 ECC原理
let G = 3;
let a = 5;
let A = G * a;
let b = 7;
let B = G * b;
console.log(a * B); // 两个值相等
console.log(b * A); // 两个值相等
1.7.2 ECC使用
// 导入crypto模块,用于创建ECDH(椭圆曲线Diffie-Hellman)密钥交换对象
let { createECDH } = require("crypto");
// 创建客户端的ECDH对象,指定使用secp521r1曲线
const clientDH = createECDH("secp521r1");
// 生成客户端的公钥参数
const clientDHParams = clientDH.generateKeys();
// 创建服务器端的ECDH对象,指定使用secp521r1曲线
const serverDH = createECDH("secp521r1");
// 生成服务器端的公钥参数
const serverDHParams = serverDH.generateKeys();
// 客户端计算共享密钥
const clientKey = clientDH.computeSecret(serverDHParams);
// 服务器端计算共享密钥
const serverKey = serverDH.computeSecret(clientDHParams);
// 打印客户端计算出的共享密钥(十六进制格式)
console.log("clientKey", clientKey.toString("hex")); // 007c5dba6ca75aecf60e0863e137cc4866ed19fdbefb8d89fca72fdbbc5cbea9f5b7f479f0913330f3d4e61f7d94479a51311dc063f83cd85128246baa41ba19b75f
// 打印服务器端计算出的共享密钥(十六进制格式)
console.log("serverKey", serverKey.toString("hex")); // 007c5dba6ca75aecf60e0863e137cc4866ed19fdbefb8d89fca72fdbbc5cbea9f5b7f479f0913330f3d4e61f7d94479a51311dc063f83cd85128246baa41ba19b75f
2. UDP服务器
密钥协商过程:
2.1 createCA.js
// 导入crypto模块,用于生成RSA密钥对
let { generateKeyPairSync } = require("crypto");
// 设置CA(证书颁发机构)的密码短语
const ca_passphrase = "ca";
// 生成RSA密钥对,设置公钥和私钥的编码方式
let CA = generateKeyPairSync("rsa", {
modulusLength: 1024,
publicKeyEncoding: {
type: "spki",
format: "pem",
},
privateKeyEncoding: {
type: "pkcs8",
format: "pem",
cipher: "aes-256-cbc",
passphrase: ca_passphrase,
},
});
// 导入fs模块,用于文件系统操作
let fs = require("fs");
// 导入path模块,用于处理路径
let path = require("path");
// 将公钥写入文件 "CA.publicKey"
fs.writeFileSync(path.resolve(__dirname, "CA.publicKey"), CA.publicKey);
// 将私钥写入文件 "CA.privateKey"
fs.writeFileSync(path.resolve(__dirname, "CA.privateKey"), CA.privateKey);
2.3 utils.js
const {
createCipheriv,
createDecipheriv,
createSign,
createVerify,
} = require("crypto");
function encrypt(data, key) {
let decipher = createCipheriv("aes-256-cbc", key, "1234567890123456");
decipher.update(data);
return decipher.final("hex");
}
function decrypt(data, key) {
let decipher = createDecipheriv("aes-256-cbc", key, "1234567890123456");
decipher.update(data, "hex");
return decipher.final("utf8");
}
function getSign(content, privateKey, passphrase) {
var sign = createSign("RSA-SHA256");
sign.update(content);
return sign.sign({ key: privateKey, format: "pem", passphrase }, "hex");
}
function verifySign(content, sign, publicKey) {
var verify = createVerify("RSA-SHA256");
verify.update(content);
return verify.verify(publicKey, sign, "hex");
}
module.exports = {
encrypt,
decrypt,
getSign,
verifySign,
};
2.4 protocol.js
module.exports = {
ClientHello: "ClientHello",
ServerHello: "ServerHello",
Certificate: "Certificate",
ServerKeyExchange: "ServerKeyExchange",
ServerHelloDone: "ServerHelloDone",
ClientKeyExchange: "ClientKeyExchange",
ChangeCipherSpec: "ChangeCipherSpec",
EncryptedHandshakeMessage: "EncryptedHandshakeMessage",
};
2.5 udp_server.js
const dgram = require("dgram");
const udp_server = dgram.createSocket("udp4");
const protocol = require("./protocol");
const {
generateKeyPairSync,
randomBytes,
createHash,
createECDH,
} = require("crypto");
const server_passphrase = "server";
const { getSign, decrypt, encrypt } = require("./utils");
require("./createCA");
const { requestCert } = require("./ca");
let serverRSA = generateKeyPairSync("rsa", {
modulusLength: 1024,
publicKeyEncoding: {
type: "spki",
format: "pem",
},
privateKeyEncoding: {
type: "pkcs8",
format: "pem",
cipher: "aes-256-cbc",
passphrase: server_passphrase,
},
});
let serverRandom = randomBytes(8).toString("hex");
const serverInfo = {
domain: "http://127.0.0.1:20000",
publicKey: serverRSA.publicKey,
};
let serverCert = requestCert(serverInfo);
let clientRandom;
const serverDH = createECDH("secp521r1");
const ecDHServerParams = serverDH.generateKeys().toString("hex");
const ecDHServerParamsSign = getSign(
ecDHServerParams,
serverRSA.privateKey,
server_passphrase
);
let masterKey;
let sessionKey;
udp_server.on("listening", () => {
const address = udp_server.address();
console.log(`server running ${address.address}: ${address.port}`);
});
udp_server.on("message", (data, remote) => {
let message = JSON.parse(data);
switch (message.type) {
case protocol.ClientHello:
//2.在服务器生成随机数,通过ServerHello发送给客户端
clientRandom = message.clientRandom;
udp_server.send(
JSON.stringify({
type: protocol.ServerHello,
serverRandom, //服务器端随机数
cipherSuite: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", //约定的加密套件
}),
remote.port,
remote.address
);
//3.Certificate 服务器把包含自己公钥的证书发送给客户端进行验证
udp_server.send(
JSON.stringify({
type: protocol.Certificate,
serverCert, //服务器公钥证书
}),
remote.port,
remote.address
);
//4.ServerKeyExchange 服务器端生成DH参数,并用服务器私钥进行签名发给客户端
udp_server.send(
JSON.stringify({
type: protocol.ServerKeyExchange,
ecDHServerParams,
ecDHServerParamsSign,
}),
remote.port,
remote.address
);
//5.Server Hello Done 服务器发送完成
udp_server.send(
JSON.stringify({
type: protocol.ServerHelloDone,
}),
remote.port,
remote.address
);
break;
case protocol.ClientKeyExchange:
//6.ClientKeyExchange 服务器收到客户端DH参数后加上服务器DH参数生成pre-master-key
//再由pre-master-key生成masterKey和sessionKey
let { ecDHClientParams } = message;
preMasterKey = serverDH
.computeSecret(Buffer.from(ecDHClientParams, "hex"))
.toString("hex");
masterKey = createHash("md5")
.update(preMasterKey + clientRandom + serverRandom)
.digest("hex");
sessionKey = createHash("md5")
.update(masterKey + clientRandom + serverRandom)
.digest("hex");
break;
case protocol.ChangeCipherSpec:
//9.服务器通知客户端服务器也已经准备好切换加密套件了
udp_server.send(
JSON.stringify({
type: protocol.ChangeCipherSpec,
}),
remote.port,
remote.address
);
break;
case protocol.EncryptedHandshakeMessage:
console.log("服务器收到解密后的数据:", decrypt(message.data, sessionKey));
//10.服务器收到客户端的加密数据后向客户端回复加密数据
udp_server.send(
JSON.stringify({
type: protocol.EncryptedHandshakeMessage,
data: encrypt("i am server", sessionKey),
}),
remote.port,
remote.address
);
break;
default:
break;
}
});
udp_server.on("error", (error) => {
console.log(error);
});
udp_server.bind(20000, "127.0.0.1");
2.6 udp_client.js
const dgram = require("dgram");
const udp_client = dgram.createSocket("udp4");
const { randomBytes, createHash, createECDH } = require("crypto");
const { verifySign, encrypt, decrypt } = require("./utils");
const url = require("url");
const protocol = require("./protocol");
const fs = require("fs");
const path = require("path");
const cAPublicKey = fs.readFileSync(
path.resolve(__dirname, "CA.publicKey"),
"utf8"
);
const clientRandom = randomBytes(8).toString("hex");
let serverRandom;
let serverPublicKey;
let ecDHServerParams;
let clientDH = createECDH("secp521r1");
let ecDHClientParams = clientDH.generateKeys();
let masterKey;
let sessionKey;
udp_client.on("listening", () => {
const address = udp_client.address();
console.log(`client running ${address.address}: ${address.port}`);
});
udp_client.on("message", (data, remote) => {
let message = JSON.parse(data.toString("utf8"));
switch (message.type) {
case protocol.ServerHello:
serverRandom = message.serverRandom;
break;
case protocol.Certificate:
//3.Certificate 客户收到服务器证书后会用CA的公钥进行验证证书是否合法
let { serverCert } = message;
let { info, sign } = serverCert;
serverPublicKey = info.publicKey;
const serverInfoHash = createHash("sha256")
.update(JSON.stringify(info))
.digest("hex");
let serverCertIsValid = verifySign(serverInfoHash, sign, cAPublicKey);
console.log("验证服务器端证书是否正确?", serverCertIsValid);
let urlObj = url.parse(info.domain);
let serverDomainIsValid =
urlObj.hostname === remote.address && urlObj.port == remote.port;
console.log("验证服务器端域名正确?", serverDomainIsValid);
break;
case protocol.ServerKeyExchange:
//4.ServerKeyExchange 客户端收到服务器的DH参数和参数签名后会用服务器的公钥进行签名,验证服务器拥有私钥
ecDHServerParams = message.ecDHServerParams;
ecDHServerParamsSign = message.ecDHServerParamsSign;
let serverDHParamIsValid = verifySign(
ecDHServerParams,
ecDHServerParamsSign,
serverPublicKey
);
console.log("验证服务器端证书DH参数是否正确?", serverDHParamIsValid);
break;
case protocol.ServerHelloDone:
//6.ClientKeyExchange 客户端生成DH参数并且发给服务器
udp_client.send(
JSON.stringify({
type: protocol.ClientKeyExchange,
ecDHClientParams,
}),
remote.port,
remote.address
);
//6.ClientKeyExchange 服务器收到客户端DH参数后加上服务器DH参数生成pre-master-key
//再由pre-master-key生成masterKey和sessionKey
preMasterKey = clientDH
.computeSecret(Buffer.from(ecDHServerParams, "hex"))
.toString("hex");
masterKey = createHash("md5")
.update(preMasterKey + clientRandom + serverRandom)
.digest("hex");
sessionKey = createHash("md5")
.update(masterKey + clientRandom + serverRandom)
.digest("hex");
//7.Change Cipher Spec 通知服务器客户端已经准备好切换成加密通信了
udp_client.send(
JSON.stringify({
type: protocol.ChangeCipherSpec,
}),
remote.port,
remote.address
);
//8.加密握手信息并传送给服务器端
udp_client.send(
JSON.stringify({
type: protocol.EncryptedHandshakeMessage,
data: encrypt("i am client", sessionKey),
}),
remote.port,
remote.address
);
break;
case protocol.EncryptedHandshakeMessage:
//10.客户端你好到服务器的加密握手数据
//这个报文的目的就是告诉对端自己在整个握手过程中收到了什么数据,发送了什么数据。来保证中间没人篡改报文
console.log("客户端收到解密后的数据:", decrypt(message.data, sessionKey));
break;
default:
break;
}
});
udp_client.on("error", (error) => {
console.log(error);
});
//1.ClientHello 客户端向服务器发送客户端随机数,服务器需要保存在服务器端
udp_client.send(
JSON.stringify({
type: protocol.ClientHello,
clientRandom,
}),
20000,
"127.0.0.1"
);
2.7 运行结果
- OpenSSL下载 (linux环境下)
- WinOpenSSL下载 (windows环境下)
3.1 自建CA
运行在winOpenSSl命令行中:
// 1.生成CA私匙
openssl genrsa -des3 -out ca.private.pem 1024
// 2.生成CA证书请求
openssl req -new -key ca.private.pem -out ca.csr
// 3.生成CA根证书
openssl x509 -req -in ca.csr -extensions v3_ca -signkey ca.private.pem -out ca.crt
3.2 生成服务器CA证书
openssl.cnf文件:
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
[req_distinguished_name]
countryName = CN
countryName_default = CN
stateOrProvinceName = Beijing
stateOrProvinceName_default = Beijing
localityName = Beijing
localityName_default = Beijing
organizationalUnitName = HD
organizationalUnitName_default = HD
commonName = localhost
commonName_max = 64
[ v3_req ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
#注意这个IP.1的设置,IP地址需要和你的服务器的监听地址一样 DNS为server网址,可设置多个ip和dns
IP.1 = 127.0.0.1
DNS.1 = localhost
运行在winOpenSSl命令行中:
// 1.生成server私匙
openssl genrsa -out server.private.pem 1024
// 2.生成server证书请求
openssl req -new -key server.private.pem -out server.csr
// 3.生成server证书
openssl x509 -days 365 -req -in server.csr -extensions v3_req -CAkey ca.private.pem -CA ca.crt -CAcreateserial -out server.crt -extfile openssl.cnf
3.3 服务端
**server.js**
const https = require("https");
const fs = require("fs");
const path = require("path");
const options = {
key: fs.readFileSync(path.resolve(__dirname, "ssl/server.private.pem")),
cert: fs.readFileSync(path.resolve(__dirname, "ssl/server.crt")),
};
https
.createServer(options, (req, res) => {
res.end("hello world\n");
})
.listen(9000);
console.log("server https is running 9000");
3.4 客户端
**client.js**
const https = require("https");
const options = {
hostname: "127.0.0.1",
port: 9000,
path: "/",
method: "GET",
requestCert: true, //请求客户端证书
rejectUnauthorized: false, //不拒绝不受信任的证书
};
const req = https.request(options, (res) => {
let buffers = [];
res.on("data", (chunk) => {
buffers.push(chunk);
});
res.on("end", () => {
console.log(buffers.toString());
});
});
req.end();
3.5 运行结果: