玩转混合加密

本文转载自微信公众号「全栈修仙之路」,作者阿宝哥 。转载本文请联系全栈修仙之路公众号。

成都创新互联公司长期为上千余家客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为潞州企业提供专业的成都做网站、成都网站制作,潞州网站改版等技术服务。拥有十多年丰富建站经验和众多成功案例,为您定制开发。

数据加密,是一门历史悠久的技术,指通过加密算法和加密密钥将明文转变为密文,而解密则是通过解密算法和解密密钥将密文恢复为明文。它的核心是密码学。

数据加密仍是计算机系统对信息进行保护的一种最可靠的办法。它利用密码技术对信息进行加密,实现信息隐蔽,从而起到保护信息的安全的作用。

本文阿宝哥将介绍如何对数据进行混合加密,即使用对称加密算法与非对称加密算法对数据进行加密,从而进一步保证数据的安全性。阅读完本文,你将了解以下内容:

  • 什么是对称加密、对称加密的过程、对称加密的优缺点及 AES 对称加密算法的使用;
  • 什么是非对称加密、非对称加密的过程、非对称加密的优缺点及 RSA 非对称加密算法的使用;
  • 什么是混合加密、混合加密的过程及如何实现混合加密。

在最后的 阿宝哥有话说 环节,阿宝哥还将简单介绍一下什么是消息摘要算法和什么是 MD5 算法及其用途与缺陷。

好的,现在让我们步入正题。为了让刚接触混合加密的小伙伴更好地了解并掌握混合加密,阿宝哥将乘坐 “时光机” 带大家来到某个发版的夜晚...

[[334543]]

 

那一晚我们团队的小伙伴正在等服务端数据升级,为了让大家 “忘记” 这个漫漫的升级过程,阿宝哥就立马组织了一场关于混合加密的技术分享会。在阿宝哥 “威逼利诱” 之下,团队的小伙伴们很快就到齐了,之后阿宝哥以以下对话拉开了分享会的序幕:

 

几分钟过后,小哥讲完了,基本关键点都有回答上来,但还遗漏了一些内容。为了让小伙伴们更好地理解对称加密,阿宝哥对小哥表述的内容进行了重新梳理,下面让我们来一起认识一下对称加密。

一、对称加密

1.1 什么是对称加密

对称密钥算法(英语:Symmetric-key algorithm)又称为对称加密、私钥加密、共享密钥加密,是密码学中的一类加密算法。这类算法在加密和解密时使用相同的密钥,或是使用两个可以简单地相互推算的密钥。

1.2 对称加密的优点

算法公开、计算量小、加密速度快、加密效率高,适合对大量数据进行加密的场景。 比如 HLS(HTTP Live Streaming)普通加密场景中,一般会使用 AES-128 对称加密算法对 TS 切片进行加密,以保证多媒体资源安全。

1.3 对称加密的过程

发送方使用密钥将明文数据加密成密文,然后发送出去,接收方收到密文后,使用同一个密钥将密文解密成明文读取。

 

1.4 对称加密的使用示例

常见的对称加密算法有 AES、ChaCha20、3DES、Salsa20、DES、Blowfish、IDEA、RC5、RC6、Camellia。这里我们以常见的 AES 算法为例,来介绍一下 AES(Advanced Encryption Standard)对称加密与解密的过程。

下面阿宝哥将使用 crypto-js 这个库来介绍 AES 算法的加密与解密,该库提供了 CryptoJS.AES.encrypt() 方法用于实现 AES 加密,而 AES 解密对应的方法是 CryptoJS.AES.decrypt()。

基于上述两个方法阿宝哥进一步封装了 aesEncrypt() 和 aesDecrypt() 这两个方法,它们分别用于 AES 加密与解密,其具体实现如下所示:

1.4.1 AES 加密方法

 
 
 
 
  1. // AES加密 
  2. function aesEncrypt(content) { 
  3.   let text = CryptoJS.enc.Utf8.parse(JSON.stringify(content)); 
  4.   let encrypted = CryptoJS.AES.encrypt(text, key, { 
  5.     iv: iv, 
  6.     mode: CryptoJS.mode.CBC, 
  7.     padding: CryptoJS.pad.Pkcs7, 
  8.   }); 
  9.   return encrypted.toString(); 

1.4.2 AES 解密方法

 
 
 
 
  1. // AES解密 
  2. function aesDecrypt(content) { 
  3.   let decrypt = CryptoJS.AES.decrypt(content, key, { 
  4.     iv: iv, 
  5.     mode: CryptoJS.mode.CBC, 
  6.     padding: CryptoJS.pad.Pkcs7, 
  7.   }); 
  8.   return decrypt.toString(CryptoJS.enc.Utf8); 

1.4.3 AES 加密与解密示例

 

在以上示例中,我们在页面上创建了 3 个 textarea,分别用于存放明文、加密后的密文和解密后的明文。当用户点击 加密 按钮时,会对用户输入的明文进行 AES 加密,完成加密后,会把密文显示在密文对应的 textarea 中,当用户点击 解密 按钮时,会对密文进行 AES 解密,完成解密后,会把解密后的明文显示在对应的 textarea 中。

以上示例对应的完整代码如下所示:

 
 
 
 
  1.  
  2.  
  3.    
  4.      
  5.      
  6.     AES 对称加密与解密示例 
  7.      
  8.    
  9.    
  10.     

    阿宝哥:AES 对称加密与解密示例(CBC 模式)

     
  11.      
  12.        
  13.         

    ①明文加密 => 加密

     
  14.          
  15.       
 
  •        
  •         

    ②密文解密 => 解密

     
  •          
  •       
  •  
  •        
  •         

    ③解密后的明文

     
  •          
  •       
  •  
  •      
  •      
  •      
  •      
  •      
  •      
  •      
  •      
  •      
  •      
  •    
  •  
  •  

    在上面的示例中,我们通过 AES 对称加密算法,对 “我是阿宝哥” 明文进行加密,从而实现信息隐蔽。

     

    那么使用对称加密算法就可以解决我们前面的问题么?答案是否定,这是因为对称加密存在一些的缺点。

    1.5 对称加密的缺点

    通过使用对称加密算法,我们已经把明文加密成密文。虽然这解决了数据的安全性,但同时也带来了另一个新的问题。因为对称加密算法,加密和解密时使用的是同一个密钥,所以对称加密的安全性就不仅仅取决于加密算法本身的强度,更取决于密钥是否被安全的传输或保管。

    另外对于实际应用场景,为了避免单一的密钥被攻破,从而导致所有的加密数据被破解,对于不同的数据,我们一般会使用不同的密钥进行加密,这样虽然提高了安全性,但也增加了密钥管理的难度。

    由于对称加密存在以上的问题,因此它并不是一种好的解决方案。为了找到更好的方案,阿宝哥开始了另一轮新的对话。

     

     

     

     

    二、非对称加密

    2.1 什么是非对称加密

    非对称加密算法需要两个密钥:公开密钥(publickey:简称公钥)和私有密钥(privatekey:简称私钥)。公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。 因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。

    2.2 非对称加密的优点

    安全性更高,公钥是公开的,私钥是自己保存的,不需要将私钥提供给别人。

    2.3 非对称加密的过程

     

    2.4 非对称加密的使用示例

    常见的非对称加密算法有 RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)。这里我们以常见的 RSA 算法为例,来介绍一下 RSA 非对称加密与解密的过程。

    RSA 是 1977 年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA 就是他们三人姓氏开头字母拼在一起组成的。

    下面阿宝哥将使用 jsencrypt 这个库来介绍 RSA 算法的加密与解密,该库提供了 encrypt() 方法用于实现 RSA 加密,而 RSA 解密对应的方法是 decrypt()。

    2.4.1 创建公私钥

    使用 jsencrypt 这个库之前,我们需要先生成公钥和私钥。接下来阿宝哥以 macOS 系统为例,来介绍一下如何生成公私钥。

    首先我们先来生成私钥,在命令行输入以下命令:

     
     
     
     
    1. $ openssl genrsa -out rsa_1024_priv.pem 1024 

    在该命令成功运行之后,在当前目录下会生成一个 rsa_1024_priv.pem 文件,该文件的内容如下:

     
     
     
     
    1. -----BEGIN RSA PRIVATE KEY----- 
    2. MIICWwIBAAKBgQDocWYwnJ4DYur0BjxFjJkLv4QRJpTJnwjiwxkuJZe1HTIIuLbu 
    3. /yHyHLhc2MAHKL0Ob+8tcKXKsL1oxs467+q0jA+glOUtBXFcUnutWBbnf9qIDkKP 
    4. ... 
    5. bKkRJNJ2PpfWA45Vdq6u+izrn9e2TabKjWIfTfT/ZQ== 
    6. -----END RSA PRIVATE KEY----- 

    然后我们来生成公钥,同样在命令行输入以下命令:

     
     
     
     
    1. $ openssl rsa -pubout -in rsa_1024_priv.pem -out rsa_1024_pub.pem 

    在该命令成功运行之后,在当前目录下会生成一个 rsa_1024_pub.pem 文件,该文件的内容如下:

     
     
     
     
    1. -----BEGIN PUBLIC KEY----- 
    2. MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDocWYwnJ4DYur0BjxFjJkLv4QR 
    3. JpTJnwjiwxkuJZe1HTIIuLbu/yHyHLhc2MAHKL0Ob+8tcKXKsL1oxs467+q0jA+g 
    4. lOUtBXFcUnutWBbnf9qIDkKP2uoDdZ//LUeW7jibVrVJbXU2hxB8bQpBkltZf/xs 
    5. cyhRIeiXxs13vlSHVwIDAQAB 
    6. -----END PUBLIC KEY----- 

    2.4.2 创建 RSA 加密器和解密器

    创建完公私钥之后,我们就可以进一步创建 RSA 加密器和解密器,具体代码如下:

     
     
     
     
    1. const PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- 
    2. MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDocWYwnJ4DYur0BjxFjJkLv4QR 
    3. ... 
    4. cyhRIeiXxs13vlSHVwIDAQAB 
    5. -----END PUBLIC KEY-----`; 
    6.  
    7. const PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY----- 
    8. MIICWwIBAAKBgQDocWYwnJ4DYur0BjxFjJkLv4QRJpTJnwjiwxkuJZe1HTIIuLbu 
    9. ... 
    10. bKkRJNJ2PpfWA45Vdq6u+izrn9e2TabKjWIfTfT/ZQ== 
    11. -----END RSA PRIVATE KEY-----`; 
    12.  
    13. const encryptor = new JSEncrypt(); // RSA加密器 
    14. encryptor.setPublicKey(PUBLIC_KEY); 
    15.  
    16. const decryptor = new JSEncrypt(); // RSA解密器 
    17. decryptor.setPrivateKey(PRIVATE_KEY); 

    2.4.3 RSA 加密与解密示例(下图标题为 RSA非对称加密)

     

    在以上示例中,我们在页面上创建了 3 个 textarea,分别用于存放明文、加密后的密文和解密后的明文。当用户点击 加密 按钮时,会对用户输入的明文进行 RSA 加密,完成加密后,会把密文显示在密文对应的 textarea 中,当用户点击 解密 按钮时,会对密文进行 RSA 解密,完成解密后,会把解密后的明文显示在对应的 textarea 中。

    以上示例对应的完整代码如下所示:

    阿宝哥:RSA 非对称加密与解密示例

       
     
     
     
    1.  
    2.  
    3.    
    4.      
    5.      
    6.     RSA 非对称加密与解密示例 
    7.      
    8.    
    9.    
    10.     

      阿宝哥:RSA 非对称加密与解密示例

       
    11.      
    12.        
    13.         

      ①明文加密 => 加密

       
    14.          
    15.        
    16.        
    17.         

      ②密文解密 => 解密

       
    18.          
    19.        
    20.        
    21.         

      ③解密后的明文

       
    22.          
    23.        
    24.      
    25.      
    26.      
    27.    
    28.  

     

    在上面的示例中,我们通过 RSA 非对称加密算法,对 “我是阿宝哥” 明文进行加密,从而实现信息隐蔽。

     

    那么使用非对称加密算法就可以解决我们前面的问题么?答案是否定,这是因为非对称加密也存在一些的缺点。

    2.5 非对称加密的缺点

    非对称加密算法加密和解密花费时间长、速度慢,只适合对少量数据进行加密。因为我们要提供的是通用的解决方案,即要同时考虑到少量数据和大量数据的情况,所以非对称加密也不是一个好的解决方案。为了解决问题,阿宝哥又重新开启了一轮新的对话。

     

    三、混合加密

    3.1 什么是混合加密

    混合加密是结合 对称加密 和 非对称加密 各自优点的一种加密方式。其具体的实现思路是先使用 对称加密算法 对数据进行加密,然后使用非对称加密算法对 对称加密的密钥进行非对称加密,之后再把加密后的密钥和加密后的数据发送给接收方。

    为了让小伙伴们更加直观理解上述的过程,阿宝哥花了点心思画了一张图,用来进一步说明混合加密的过程,下面我们就一起来看图吧。

    3.2 混合加密的过程

     

    3.3 混合加密的实现

    了解完 “混合加密数据传输流程”,阿宝哥跟小伙伴一起来实现一下上述的混合加密流程。这里我们会基于前面介绍过的对称加密和非对称加密的示例进行开发,即以下示例会直接利用前面非对称加密示例中用到的公私钥。

    3.3.1 创建生成随机 AES 密钥的函数

     
     
     
     
    1. function getRandomAESKey() { 
    2.   return ( 
    3.     Math.random().toString(36).substring(2, 10) + 
    4.     Math.random().toString(36).substring(2, 10) 
    5.   ); 

    3.3.2 创建 AES 加密和解密函数

     
     
     
     
    1. // AES加密 
    2. function aesEncrypt(key, iv, content) { 
    3.   let text = CryptoJS.enc.Utf8.parse(JSON.stringify(content)); 
    4.   let encrypted = CryptoJS.AES.encrypt(text, key, { 
    5.     iv: iv, 
    6.     mode: CryptoJS.mode.CBC, 
    7.     padding: CryptoJS.pad.Pkcs7, 
    8.   }); 
    9.   return encrypted.toString(); 
    10.  
    11. // AES解密 
    12. function aesDecrypt(key, iv, content) { 
    13.   let decrypt = CryptoJS.AES.decrypt(content, key, { 
    14.     iv: iv, 
    15.     mode: CryptoJS.mode.CBC, 
    16.     padding: CryptoJS.pad.Pkcs7, 
    17.   }); 
    18.   return decrypt.toString(CryptoJS.enc.Utf8); 

    3.3.3 创建 RSA 加密器和解密器

     
     
     
     
    1. const PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- 
    2. MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDocWYwnJ4DYur0BjxFjJkLv4QR 
    3. ... 
    4. cyhRIeiXxs13vlSHVwIDAQAB 
    5. -----END PUBLIC KEY-----`; 
    6.  
    7. const PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY----- 
    8. MIICWwIBAAKBgQDocWYwnJ4DYur0BjxFjJkLv4QRJpTJnwjiwxkuJZe1HTIIuLbu 
    9. ... 
    10. bKkRJNJ2PpfWA45Vdq6u+izrn9e2TabKjWIfTfT/ZQ== 
    11. -----END RSA PRIVATE KEY-----`; 
    12.  
    13. const rsaEncryptor = new JSEncrypt(); // RSA加密器 
    14. rsaEncryptor.setPublicKey(PUBLIC_KEY); 
    15.  
    16. const rsaDecryptor = new JSEncrypt(); // RSA解密器 
    17. rsaDecryptor.setPrivateKey(PRIVATE_KEY); 

    3.3.4 创建混合加密加密和解密函数

     
     
     
     
    1. function hybirdEncrypt(data) { 
    2.   const iv = getRandomAESKey(); 
    3.   const key = getRandomAESKey(); 
    4.   const encryptedData = aesEncrypt(key, iv, data); 
    5.   const encryptedIv = rsaEncryptor.encrypt(iv); 
    6.   const encryptedKey = rsaEncryptor.encrypt(key); 
    7.   return { 
    8.     iv: encryptedIv, 
    9.     key: encryptedKey, 
    10.     data: encryptedData, 
    11.    }; 
    12.  
    13. function hybirdDecrypt(encryptedResult) { 
    14.   const iv = rsaDecryptor.decrypt(encryptedResult.iv); 
    15.   const key = rsaDecryptor.decrypt(encryptedResult.key); 
    16.   const data = encryptedResult.data; 
    17.   return aesDecrypt(key, iv, data); 

    3.3.5 混合加密与解密示例

    以上步骤完成之后,我们基本已经完成了混合加密的功能,在看完整代码之前,我们先来看一下实际的运行效果:

     

    备注:密文解密下方对应的 textarea 文本框中,除了加密的数据之外,还会包含使用 RSA 加密过的 AES CBC 模式中的 iv 和 key。

    在以上示例中,我们在页面上创建了 3 个 textarea,分别用于存放明文、加密后的数据和解密后的明文。当用户点击 加密 按钮时,会对用户输入的明文进行混合加密,完成加密后,会把加密的数据显示在密文对应的 textarea 中,当用户点击 解密 按钮时,会对密文进行 混合解密,即先使用 RSA 私钥解密 AES 的 key 和 iv,然后再使用它们对 AES 加密过的密文进行 AES 解密,完成解密后,会把解密后的明文显示在对应的 textarea 中。

    以上示例对应的完整代码如下所示:

    阿宝哥:混合加密与解密示例

       
     
     
     
    1.  
    2.  
    3.    
    4.      
    5.      
    6.     混合加密与解密示例 
    7.      
    8.    
    9.    
    10.     

      阿宝哥:混合加密与解密示例

       
    11.      
    12.        
    13.         

      ①明文加密 => 加密

       
    14.          
    15.        
    16.        
    17.         

      ②密文解密 => 解密

       
    18.          
    19.        
    20.        
    21.         

      ③解密后的明文

       
    22.          
    23.        
    24.      
    25.      
    26.      
    27.      
    28.      
    29.      
    30.      
    31.      
    32.      
    33.      
    34.      
    35.      
    36.      
    37.    
    38.  

     

    3.4 混合加密方案分析

    通过这个示例,相信大家对混合加密已经有了一定的了解。但在实际 Web 项目中,我们一般不会在客户端进行数据解密,而是会把数据提交到服务端,然后由服务端进行数据解密和数据处理。

    HTTP 协议对大多数 Web 开发者来说,都不会陌生。HTTP 协议是基于请求和响应,具体如下图所示:

     

    在对数据安全要求较高的场景或传输敏感数据时,我们就可以考虑利用前面的混合加密方案对提交到服务端的数据进行混合加密,当服务端接收到对应的加密数据时,再使用对应的解密算法对加密的数据进行解密,从而进一步进行数据处理。

    但是如果服务端也要返回敏感数据时,应该怎么办呢?这里阿宝哥给大家介绍一种方案,该方案只需使用一对公私钥。当然该方案仅供大家参考,如果你有好的方案,欢迎给阿宝哥留言或跟阿宝哥交流哟。

    下面我们来看一下该方案的具体操作流程:

    ① 生成一个唯一的 reqId(请求 ID),用于标识当前请求;

    ② 分别生成一个随机的 AES Key 和 AES IV(采用 AES CBC 模式);

    ③ 采用 RSA 非对称加密算法,分别对 AES Key 和 AES IV 进行 RSA 非对称加密;

    ④ 采用随机生成的 AES Key 和 AES IV 对敏感数据进行 AES 对称加密;

    ⑤ 把 reqId 作为 key,AES Key 和 AES IV 组成的对象作为 value 保存到 Map 或 {} 对象中;

    ⑥ 把 reqId、加密后的 AES Key、AES IV 和加密后的数据保存到对象中提交到服务端;

    ⑦ 当服务端接收到数据后,对接收的数据进行解密,然后使用客户端传过来的解密后的 AES Key 和 AES IV 对响应数据进行 AES 对称加密;

    ⑧ 服务端在完成数据加密后,把 reqId 和加密后的数据包装成响应对象,返回给客户端;

    ⑨ 当客户端成功接收服务端的响应后,先获取 reqId,进而从保存 AES Key 和 IV 的 Map 获取该 reqId 对应的 AES 加密信息;

    ⑩ 客户端使用当前 reqId 对应的加密信息,对服务端返回的数据进行解密,当完成解密之后,从 Map 或 {} 对象中删除已有记录。

    现在我们来对上述流程做个简单分析,首先 AES 加密信息都是随机生成的且根据每个请求独立地保存到内存中,把 AES 加密信息中的 Key 和 IV 提交到服务端的时候都会使用 RSA 非对称加密算法进行加密。

    在服务端返回数据的时候,会使用当前请求对应的 AES 加密信息对返回的结果进行加密,同时返回当前请求对应的 reqId(请求 ID)。即服务端不需要再生成新的 AES 加密信息,来对响应数据进行加密,这样就不需要在响应对象中传递 AES 加密信息。

    该方案看似挺完美的,由于我们加密的信息还是存在内存中,如果使用开发者工具对 Web 应用进行调试时,那么还是可以看到每个请求对应的加密信息。那么这个问题该如何解决呢?能不能防止使用开发者工具对我们的 Web 应用进行调试,答案是有的。

    不过这里阿宝哥就不继续展开了,后面可能会单独写一篇文章来介绍如何防止使用开发者工具调试 Web 应用,感兴趣的小伙伴可以给我留言哟。

    四、阿宝哥有话说

    4.1 什么是消息摘要算法

    其实在日常工作中,除了对称加密和非对称加密算法之外。还有一种用得比较广的消息摘要算法。消息摘要算法是密码学算法中非常重要的一个分支,它通过对所有数据提取指纹信息以实现数据签名、数据完整性校验等功能,由于其不可逆性,有时候会被用做敏感信息的加密。消息摘要算法也被称为哈希(Hash)算法或散列算法。

    任何消息经过散列函数处理后,都会获得唯一的散列值,这一过程称为 “消息摘要”,其散列值称为 “数字指纹”,其算法自然就是 “消息摘要算法”了。 换句话说,如果其数字指纹一致,就说明其消息是一致的。

     

    (图片来源 —— https://zh.wikipedia.org/wiki/散列函數)

    消息摘要算法的主要特征是加密过程不需要密钥,并且经过加密的数据无法被解密,目前可以解密逆向的只有 CRC32 算法,只有输入相同的明文数据经过相同的消息摘要算法才能得到相同的密文。 消息摘要算法不存在密钥的管理与分发问题,适合于分布式网络上使用。消息摘要算法主要应用在 “数字签名” 领域,作为对明文的摘要算法。著名的摘要算法有 RSA 公司的 MD5 算法和 SHA-1 算法及其大量的变体。

    消息摘要算法拥有以下特点: