http不安全,在网络上通过明文传输数据

起初设计HTTP协议的目的很单一,只是传输超文本文件,当时也没加密传输数据的需求,所以HTTP一直保持着明文传输数据的特征。
但是这样在传输过程中,数据都有可能被窃取或篡改。客户端和服务器直接可能有个中间人,在通信过程中一切内容都在中间人的掌握中。

使用HTTP传输的内容很容易被中间人窃取/伪造/篡改,这种攻击方式称为中间人攻击。
将HTTP数据交给TCP层之后数据会经过用户终端、wifi路由器、运营商、目标服务器等,在这中间的每个环节中,数据都有可能被窃取或篡改。比如:用户电脑被黑客安装的恶意软件;用户不小心连接上wifi钓鱼路由器等;

HTTP协议栈中引入安全层即HTTPS

伴随这个网上支付,购物等现代场景,HTTP的明文传输必须进行升级改良,增加加密方案。
从HTTP协议栈层面看,可在HTTP和TCP之间插入安全层,所有经过安全层的数据都被加密和解密。
HTTPS并非一个新协议,通常HTTP直接和TCP通信,HTTPS则先和安全层通信,然后安全层再和TCP层通信。
HTTPS所有安全核心都在安全层,安全层SSL或TLS不会影响到上层HTTP,也不会影响下层TCP/IP。安全层两个职责:对发起HTTP请求的数据进行加密,对接收到HTTP的内容进行解密。

第一版本对称加密

对称加密是指加密和解密都使用相同密钥。
使用对称加密实现第一版的HTTPS,在发送HTTPS数据前,浏览器和服务器之间需要协商加密方式和密钥。
image.png
HTTPS首先协商加密方式,这个过程就是HTTPS建立安全连接的过程,为了让加密的密钥更加难破解,我们让服务器和客户端同时决定密钥,具体过程如下:
首先,浏览器发送它所支持的加密方法列表和一个随机数client-random。
第二步,服务器会从加密方法列表选取一个加密方法,然后还会生成一个随机数service-random,并将service-random和加密方法列表返回给浏览器
第三,浏览器和服务器具有相同的client-random和service-random,然后它们再使用相同的方法将client-random和service-random混合起来生成一个秘密master-secret,有了master-secret和加密方法,双方就可以进行数据的加密传输。
但是有漏洞缺陷:在传输client-random和service-random的过程时明文,黑客可以拿到协商加密方法和双方的随机数,由于利用随机数合成密钥的算法是公开的,所以黑客也能合成密钥,这样数据就能被破解。

第二版本非对称加密

使用非对称加密来解决上面对称加密的问题。非对称加密算法有A、B两个密钥,如果用A加密,则只能用B密钥解密。如果用B密钥加密,则只能用A密钥解密。
HTTPS中,服务器会将一个密钥通过明文的形式发送给浏览器,这个密钥称为公钥,服务器留下的密钥是私钥。公钥每个人都能获取到,私钥只有服务器自己知道,不对任何人公开。
image.png
非对称加密的流程:
第一:由于使用非对称加密,服务器需要两个资源(浏览器加密的公钥和服务器解密的HTTP数据私钥)。公钥是给浏览器加密使用,因此服务器会加加密方法和公钥一起发送给浏览器。
第二:浏览器有了服务器的公钥,在浏览器端向服务器发送数据时,就可以使用该公钥加密数据。公钥加密的数据只能是服务器的私钥才能解密。

使用非对称加密看似很安全,但是存在两个严重问题:
1:非对称加密效率太低,严重影响加解密数据的速度。
2:无法保证服务器发送给浏览器的数据是安全的。有可能黑客截取公钥后进行修改,把修改后的公钥发送给浏览器,浏览器加密后还是会被破解。

第三版本,对称和非对称加密配合

在 https 的加密中,加密传输的数据本身使用的是对称加密加密对称秘钥时使用的非对称加密
image.png
混合加密实现HTTPS的流程:
1:浏览器向服务器发送对称加密方法列表和非对称加密方法列表和随机数client-random;
2:服务器保存随机数client-random,选择对称加密和非对称加密方法,然后生成随机数service-random,向浏览器发送选择的加密方法、service-random和公钥;
3:浏览器保存公钥,生成随机数pre-master,然后利用公钥对pre-master加密,并向服务器发送加密后的数据。
4:服务器用私钥解密出pre-master数据,并返回确认消息。

第四版本,数字证书

通过对称和非对称加密混合方式,可以实现数据加密传输。但是依然存在问题,在传输过程中,黑客可以在自己服务器上实现公钥和私钥的生成,而浏览器无法知道收到的公钥是不是黑客改造的。
这时就需要一个权威机构【Certificate Authority】来颁发一个证书,数字证书【Digital Certificate】。
对于浏览器来说,数字证书有两个作用:1:通过数字证书向浏览器证明服务器身份,2,数字证书里包含服务器公钥。
image.png
此时HTTPS请求流程:
服务器没有直接返回公钥给浏览器,而是返回数字证书,公钥包含再数字证书里。
浏览器多一个证书验证操作,验证通过后才进行后续流程。引入数字证书,就能实现服务器身份认证功能。

数字证书的申请和验证

申请数字证书

需要准备一套私钥和公钥,私钥留着自己使用;
用户向 CA 机构提交公钥、公司、站点等信息并等待认证,这个认证过程可能是收费的;
CA 通过线上、线下等多种渠道来验证极客时间所提供信息的真实性,如公司是否存在、企业是否合法、域名是否归属该企业等;
如果信息审核通过,CA 会向极客时间签发认证的数字证书,包含了极客时间的公钥、组织信息、CA 的信息、有效时间、证书序列号等,这些信息都是明文的,同时包含一个 CA生成的签名。
这样完成数字证书的申请过程。前面几步都很好理解,不过最后一步数字签名的过程还需要解释下:首先 CA 使用 Hash 函数来计算用户提交的明文信息,并得出信息摘要;然后 CA 再使用它的私钥对信息摘要进行加密,加密后的密文就是CA 颁给用户的数字签名。

浏览器验证数字证书

有了 CA 签名过的数字证书,当浏览器向服务器发出请求时,服务器会返回数字证书给浏览器。
浏览器接收到数字证书之后,会对数字证书进行验证。首先浏览器读取证书中相关的明文信息,采用 CA 签名时相同的 Hash 函数来计算并得到信息摘要 A;然后再利用对应CA 的公钥解密签名数据,得到信息摘要 B;对比信息摘要 A 和信息摘要 B,如果一致,
则可以确认证书是合法的,即证明了这个服务器是正规的;同时浏览器还会验证证书相关的域名信息、有效时间等信息。
这时候相当于验证了 CA 是谁,但是这个 CA 可能比较小众,浏览器不知道该不该信任它,然后浏览器会继续查找给这个 CA 颁发证书的 CA,再以同样的方式验证它上级 CA的可靠性。通常情况下,操作系统中会内置信任的顶级 CA 的证书信息(包含公钥),如果这个 CA 链中没有找到浏览器内置的顶级的 CA,证书也会被判定非法。
另外,在申请和使用证书的过程中,还需要注意以下三点:
申请数字证书是不需要提供私钥的,要确保私钥永远只能由服务器掌握;
数字证书最核心的是 CA 使用它的私钥生成的数字签名;
内置 CA 对应的证书称为根证书,根证书是最权威的机构,它们自己为自己签名,我们把这称为自签名证书
申请数字证书的机构:https://freessl.cn/

node模拟加密算法

模拟对称加密

nodejs 的 crypto 模块是一个专门用于各种加密的模块,可以用来取摘要(hash),加盐摘要(hmac),对称加密,非对称加密等。使用 crypto 进行对称加密很简单,crypto 模块提供了 Cipher 类用于加密数据,Decipher 用于解密。
常见的对称加密算法有DES3DESAESBlowfish)、IDEARC5RC6,这里演示下使用 AES 算法进行对称加密。

  1. const crypto = require('crypto')
  2. /**
  3. * @description:
  4. * @param {*}
  5. * @return {*}
  6. */
  7. const encrypt = (password, string) => {
  8. // 对称加密算法类型
  9. const algorithm = 'aes-192-cbc';
  10. // 生成对称加密密钥,defineSalt定义一个生成密钥的盐,指定密钥长度24,scrypt是一个基于密码的密钥派生函数
  11. const key = crypto.scryptSync(password, 'defineSalt1', 24)
  12. console.log('key',key)
  13. console.log(`key-length:${key.length}, key-value:${key.toString("hex")}`);
  14. // 初始化向量,Buffer.from和Buffer.alloc都是生成buffer,Buffer.from从字符串或数组创建buffer,Buffer.alloc创建指定大小的buffer
  15. const iv = Buffer.alloc(16, 0)
  16. // 获得Cipher的加密类
  17. const cipher = crypto.createCipheriv(algorithm, key, iv)
  18. let encryptedString = cipher.update(string, 'utf-8', 'hex')
  19. encryptedString += cipher.final('hex')
  20. return encryptedString;
  21. }
  22. const password = "shenshuai"
  23. // 需要被加密的数据
  24. const encryptedString = encrypt(password, "node的crypto实现对称加密")
  25. console.log(`加密后的数据:${encryptedString}`);
  26. /**
  27. * @description: 解密
  28. * @param {*}
  29. * @return {*}
  30. */
  31. const decrypt = (password, encryptedString) => {
  32. const algorithm = 'aes-192-cbc'
  33. // 采用相同的算法和salt值,计算出相同的密钥
  34. const key = crypto.scryptSync(password, 'defineSalt1', 24)
  35. const iv = Buffer.alloc(16, 0)
  36. console.log(`解密是的密钥key${key.toString("hex")}`)
  37. // 解密方法createDecipheriv
  38. const decipher = crypto.createDecipheriv(algorithm, key, iv)
  39. let decryptedString = decipher.update(encryptedString, 'hex', 'utf-8')
  40. decryptedString += decipher.final('utf8')
  41. return decryptedString;
  42. }
  43. const decryptedString = decrypt(password, encryptedString);
  44. console.log(`解密后的数据:${decryptedString}`)

模拟非对称加密

非对称加密有 RSA、ECC(椭圆曲线加密算法)、Diffie-Hellman、El Gamal、DSA(数字签名用),这里演示一下 RSA 加密。

  1. const crypto = require('crypto')
  2. const password = "shenshuai"
  3. /**
  4. * @description: 生成公钥和私钥
  5. * @param {*}
  6. * @return {*}
  7. */
  8. const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
  9. modulusLength: 4096,
  10. publicKeyEncoding:{
  11. type: 'spki',
  12. format: 'pem'
  13. },
  14. privateKeyEncoding:{
  15. type: "pkcs8", //私钥编码格式
  16. format: 'pem',
  17. cipher: 'aes-256-cbc',
  18. passphrase: password
  19. }
  20. });
  21. console.log(publicKey, privateKey);
  22. /**
  23. * @description: 使用公钥加密
  24. * @param {publicKey} 公钥
  25. * @param {string} 要加密的数据
  26. * @return {*} 加密后的数据
  27. */
  28. const encrypt = (publicKey, string)=>{
  29. return crypto.publicEncrypt({key:publicKey, passphrase: password}, Buffer.from(string)).toString('hex')
  30. }
  31. /**
  32. * @description: 使用私钥解密
  33. * @param {privateKey} 私钥
  34. * @param {encryptedString} 被解密的数据
  35. * @return {*} 解密后数据
  36. */
  37. const decrypt = (privateKey, encryptedString) =>{
  38. return crypto.privateDecrypt({key:privateKey, passphrase: password}, Buffer.from(encryptedString, 'hex'))
  39. }
  40. let data = 'node实现非对称加密,使用rsa算法'
  41. const encryptedString = encrypt(publicKey, data)
  42. console.log(`公钥加密后的结果:${encryptedString}`);
  43. const decryptedString = decrypt(privateKey, encryptedString);
  44. console.log(`私钥解密后的结果:${decryptedString}`);