前端面试必会HTTPS实战篇

·  阅读 818
前端面试必会HTTPS实战篇

HTTPS算法实战篇

面试造火箭上班拧螺, 大家好我是铁蛋儿,坚持每天进步一点点。

看了很多篇https文章感觉都是在科普原理大多数很详细,但是讲实现的很少,所有就有了这篇文章。

如果还不是很了解http基础小伙伴,请移步B站:HTTP基础, 这位大佬的文章讲的非常详细适合新手。

加油.jpeg

HTTP不安全

​ http协议属于明文传输协议,交互过程以及数据传输都没有进行加密,通信双方也没有进行任何认证,通信过程非常容易遭遇劫持、监听、篡改,严重情况下,会造成恶意的流量劫持等问题,甚至造成个人隐私泄露(比如银行卡卡号和密码泄露)等严重的安全问题。

​ 可以把http通信比喻成寄送信件一样,A给B寄信,信件在寄送过程中,会经过很多的邮递员之手,他们可以拆开信读取里面的内容(因为http是明文传输的)。A的信件里面的任何内容(包括各类账号和密码)都会被轻易窃取。除此之外,邮递员们还可以伪造或者修改信件的内容,导致B接收到的信件内容是假的。

​ 比如常见的,在http通信过程中,“中间人”将广告链接嵌入到服务器发给用户的http报文里,导致用户界面出现很多不良链接; 或者是修改用户的请求头URL,导致用户的请求被劫持到另外一个网站,用户的请求永远到不了真正的服务器。这些都会导致用户得不到正确的服务,甚至是损失惨重。

HTTP利用对称加密实现的数据传输,以下两种方式模拟实现:

1. js模拟模拟实现

// 加密的密钥和解密的密钥是同一个(secret)

let secret = 3

//加密
function encrypt(message) {
  let buffer = Buffer.from(message)
  for (let i = 0; i < buffer.length; i++) {
    buffer[i] = buffer[i] + secret
  }
  return buffer.toString()
}
// 解密
function decrypt(message) {
  let buffer = Buffer.from(message)
  for (let i = 0; i < buffer.length; i++) {
    buffer[i] = buffer[i] - secret
  }
  return buffer.toString()
}

let message = 'abc'
let encryptMessage = encrypt(message)
console.log('encryptMessagen', encryptMessage)

let decryptMessage = decrypt(encryptMessage)
console.log('decryptMessagen', decryptMessage)

复制代码

2. crypto模块实现模拟实现

const crypto = require('crypto');

// 加密
const encrypt = (password, string) => {
  // 使用的对称加密算法是:aes-192-cbc
  const algorithm = 'aes-192-cbc';
  // 生成对称加密秘钥,salt 用于生成秘钥,24 指定秘钥长度是 24 位
  const key = crypto.scryptSync(password, 'salt', 24);
  console.log('key:', key); // => key: <Buffer .....>
  console.log(`秘钥长度: ${key.length}`); // => 秘钥长度: 24

  // 初始化向量
  const iv = Buffer.alloc(16, 0);
  // 获得 Cipher 加密类
  const cipher = crypto.createCipheriv(algorithm, key, iv);

  // utf-8 指定被加密的数据字符编码,hex 指定输出的字符编码
  let encryptedString = cipher.update(string, 'utf8', 'hex');

  encryptedString += cipher.final('hex');

  return encryptedString;
};

const PASSWORD = 'lyreal666';
const encryptedString = encrypt(PASSWORD, '坚持每天进步一点点');
console.log(`加密后的数据是:${encryptedString}`); // => 加密后的数据是:xxxx

// 解密
const decrypt = (password, encryptedString) => {
  const algorithm = 'aes-192-cbc';
  // 采用相同的算法生成相同的秘钥
  const key = crypto.scryptSync(password, 'salt', 24);
  const iv = Buffer.alloc(16, 0);
  // 生成 Decipher 解密类
  const decipher = crypto.createDecipheriv(algorithm, key, iv);

  let decryptedString = decipher.update(encryptedString, 'hex', 'utf8');
  decryptedString += decipher.final('utf8');

  return decryptedString;
};

const decryptedString = decrypt(PASSWORD, encryptedString);
console.log(`解密后的数据时:${decryptedString}`); // => 解密后的数据时:坚持每天进步一点点
复制代码

总结:优缺点

优点:

  1. 计算量小、加密速度快、加密效率高。

缺点:

  1. 交易双方都使用同样密钥,安全性得不到保证;

  2. 每次使用对称加密算

HTTPS怎么实现的安全

HTTPS是通过一下三点实现的数据加密安全:

  1. 非对称加密算法(公钥和私钥)交换对称密钥
  2. 数字证书验证身份(验证公钥是否是伪造的
  3. 利用对称密钥加解密后续传输的数据

非对称加密实现

非对称加密用的是一对秘钥,分别叫做公钥(public key)和私钥(private key),也叫非对称秘钥。

​ 加密有一个密码就行了,为啥要整个非对称加密要两个密码呢?

​ 其实对称加密只要保证加密的密码长度足够长的话,被加密的数据在拿不到密码本身的情况下一般是安全的。但是有个问题就是在实际应用中比如加密网络数据,因为加密和解密使用的是同一个秘钥,所以,服务器和客户端必然是要交换秘钥的,而正是因为对称秘钥由于有一个交换秘钥这一过程可能会被中间人窃取秘钥,一旦对称加密秘钥被窃取,而且被分析出加密算法的话,那么传输的数据对于中间人来说就是透明的。所以对称加密的致命性缺点就是无法保证秘钥的安全性

​ 那么非对称加密就能保证秘钥的安全性了吗?是的,秘钥可以大胆的公开,被公开的秘钥就叫公钥。非对称加密的秘钥由加密算法计算得出,是成对的,可以被公开的那个秘钥称之为公钥不能公开的那个私有的秘钥叫私钥

​ 非对称加密即加解密双方使用不同的密钥,一把作为公钥,可以公开的,一把作为私钥,不能公开,公钥加密的密文只有私钥可以解密,私钥加密的内容,也只有公钥可以解密。

1. js模式实现非对称加密算法

// 欧拉函数
let p = 3, q = 11
let N = p * q; //33
let fN = (p - 1) * (q - 1)
let e = 7 //随意挑一个指数e
// {e,N} {7,33} 成就我们的公钥,公钥可以发给任何人,是公开的
// 公钥和密钥是一对, 公钥加密的数据要用密钥解密,密钥加密的数据要用公钥来解密
// 我们可以从公钥去推算私钥,但是前提是你得知道 fN
// e*d % fN !==1 说明就是我们要找的密钥
for (var d = 1; e * d % fN !== 1; d++) {
  d++;
}

console.log(d) // d=3

let data = 5
let c = Math.pow(data, e) % N;
console.log('c', c)

let original = Math.pow(c, d) % N;
console.log(original)
复制代码

2. crypto模块模拟实现非对称加密

let { generateKeyPairSync, privateEncrypt, publicDecrypt } = require('crypto')

// 生成一对密钥对,一个是公钥,一个私钥
let rsa = generateKeyPairSync('rsa', {
  modulusLength: 1024,
  publicKeyEncoding: {
    type: 'spki',
    format: 'pem' // base64格式的私钥
  },
  privateKeyEncoding: {
    type: 'pkcs8',
    format: 'pem',
    cipher: 'aes-256-cbc',
    passphrase: 'passphrase'
  }
})

let message = '前端铁蛋儿'
// 加密
let encryptMessage = privateEncrypt({
  key: rsa.privateKey,
  passphrase: 'passphrase',
}, Buffer.from(message, 'utf8'))
console.log('encryptMessage', encryptMessage)

// 解密
let decryptMessage = publicDecrypt(rsa.publicKey, encryptMessage)
console.log('decryptMessage', decryptMessage.toString())
复制代码

总结:优缺点

非对称加密相比对称加密更加安全,但也存在两个致命的缺点:

  1. CPU计算资源消耗非常大。一次完全TLS握手,密钥交换时的非对称解密计算量占整个握手过程的90%以上。而对称加密的计算量只相当于非对称加密的0.1%。如果后续的应用层数据传输过程也使用非对称加解密,那么CPU性能开销太庞大,服务器是根本无法承受的。赛门特克给出的实验数据显示,加解密同等数量的文件,非对称算法消耗的CPU资源是对称算法的1000倍以上。

  2. 非对称加密算法对加密内容的长度有限制,不能超过公钥长度。比如现在常用的公钥长度是2048位,意味着待加密内容不能超过256个字节。

所以非对称加解密(极端消耗CPU资源)目前只能用来作对称密钥交换或者CA签名,不适合用来做应用层内容传输的加解密。

数字证书签名

如果使用非对称加密算法,客户端需要一开始就持有公钥,要不没法开展加密行为啊。

如何让A、B客户端安全地得到公钥?

  1. 服务器端将公钥发送给每一个客户端

  2. 服务器端将公钥放到一个远程服务器,客户端可以请求得到

选择方案1,因为方案2又多了一次请求,还要另外处理公钥的放置问题。

公钥被调包了怎么办?又是一个鸡生蛋蛋生鸡问题?

但是方案1有个问题:如果服务器端发送公钥给客户端时,被中间人调包了,怎么办?

答案: 使用第三方机构的公钥解决鸡生蛋蛋生鸡问题

公钥被调包的问题出现,是因为客户端无法分辨返回公钥的人到底是中间人,还是真的服务器。这其实就是密码学中提的身份验证问题。

问题的难点是如果选择直接将公钥传递给客户端的方案,始终无法解决公钥传递被中间人调包的问题。

所以不能直接将服务器的公钥传递给客户端,而是第三方机构使用它的私钥对我们的公钥进行加密后,再传给客户端。客户端再使用第三方机构的公钥进行解密。

1. 数字签名算法模拟实现

let crypto = require('crypto')
let content = '12334561111111111'
 // 32 位
let md5Hash = crypto.createHash('md5').digest('hex')
console.log('md5Hash', md5Hash, md5Hash.length)
//  64位
let salt = '1';
let sha1Hash = crypto.createHmac('sha256', salt).digest('hex')
console.log('sha1Hash', sha1Hash, sha1Hash.length);

复制代码

2. 完整证书算法模拟实现

// 实现证书的原理
let passphrase = 'passphrase'
let { generateKeyPairSync, createHash, createSign, createVerify, } = require('crypto')
// 生成一对密钥对,一个是公钥,一个私钥
let serverRsa = generateKeyPairSync('rsa', {
  modulusLength: 1024,
  publicKeyEncoding: {
    type: 'spki',
    format: 'pem' // base64格式的私钥
  },
  privateKeyEncoding: {
    type: 'pkcs8',
    format: 'pem',
    cipher: 'aes-256-cbc',
    passphrase// 私钥的密码
  }
})

// 生成一对密钥对,一个是公钥,一个私钥
let caRsa = generateKeyPairSync('rsa', {
  modulusLength: 1024,
  publicKeyEncoding: {
    type: 'spki',
    format: 'pem' // base64格式的私钥
  },
  privateKeyEncoding: {
    type: 'pkcs8',
    format: 'pem',
    cipher: 'aes-256-cbc',
    passphrase// 私钥的密码
  }
})
const info = {
  domain: 'http://127.0.0.1:8080',
  publicKey: serverRsa.publicKey
}
// 把这个申请信息发给CA机构请求颁发证书
// 实现的签名的并不是info 而是它的hash 因为签名算法很差,一般不能计算大量的数据
let hash = createHash('sha256').update(JSON.stringify(info)).digest('hex')

let sign = getSign(hash, caRsa.privateKey, passphrase)

let valid = verifySign(hash, sign, caRsa.publicKey)
console.log('浏览器验证CA的签名着', valid)

function getSign(content, privateKey, passphrase) {
  var siginObj = createSign('RSA-SHA256');
  siginObj.update(content);
  return siginObj.sign({
    key: privateKey,
    format: 'pem',
    passphrase
  }, 'hex')
}

function verifySign(content, sign, publicKey) {
  var verifyObj = createVerify('RSA-SHA256');
  verifyObj.update(content)
  return verifyObj.verify(publicKey, sign, 'hex')
}
复制代码

欢迎小伙伴留言指正,一起努力一起进步,坚持每天进步一点点。

欢迎下方关注公众号获取资料 或者 B站关注 前端铁蛋儿直接看视频

公众号.jpg

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改