Cifrado y descifrado de datos JavaScript + GO a través de AES + RSA

Hacer algo de encriptación pediátrica en el lado del navegador es como salpicar algunas estrellas en el oscuro cielo nocturno, diciéndoles a los piratas informáticos "aquí hay información valiosa, vengan y den la vuelta a las cartas".

02-01.pngEl cifrado en el lado del navegador es relativamente seguro.

Su seguridad específica depende del valor de la información que contiene, si vale la pena o no.

Como ese clásico chiste:

Un cliente desarrolló un conjunto de software por sí mismo y compró un complemento anti-craqueo para que Daniel probara la dificultad del descifrado.

Tres días después, Daniel dijo deprimido que fue bastante difícil y que tomó 3 días.

Después de una mirada más cercana, resulta que el complemento anti-craqueo se ha roto...

1. Alineación de la información

De hecho, el cifrado y descifrado de JavaScript + Go, la llamada de código real es muy simple.

Mi compañero de servidor y yo nos enfrentábamos a la información en línea, y Kuku lo logró con una sola operación.

02-02.png

Entonces, antes de ingresar el enlace del código, primero alineamos y sincronizamos cierta información básica, para evitar que otros amigos que quieran entender este punto de conocimiento se confundan sobre la lógica interna.

1.1 ¿Qué es el cifrado y descifrado?

El llamado "cifrado" consiste en hacer que su información sea ilegible y protegida del acceso ilegal a través de "algún tipo de procesamiento".

Y "descifrar" es convertir la información procesada en información que la gente normal pueda entender.

Por ejemplo , vas a obtener los bienes, y la otra parte dijo "el rey de los cielos cubre al tigre".

¿Vas a contestar la llamada para mostrar tu identidad?

02-03.png

"Emparejar códigos" es en realidad una forma de procesar información. Solo cuando responde las preguntas de la otra parte puede continuar.

1.2 Por qué es necesario el cifrado de datos

por seguridad".

Por qué se debe encerrar la seguridad, porque no hay seguridad absoluta.

El cifrado y descifrado de datos en el lado del navegador solo puede garantizar:

  1. Los datos transmitidos están encriptados o codificados. Incluso si alguien toma los datos a través del software de captura de paquetes, es difícil de entender o descifrar.
  2. Asegúrese de que los datos no hayan sido manipulados y confirme la identidad del remitente

¡Aviso! Los 2 puntos anteriores solo pueden evitar la captura de paquetes, no la inyección.

Significa que la otra parte inyecta a través de scripts de JavaScript para reemplazar el código que escribió originalmente.

02-04.png

De esta forma, puede obtener la información original antes de cifrarla.

1.3 Cifrado simétrico

El llamado "cifrado simétrico" significa que todos obtienen la misma clave para cifrar y descifrar los datos transmitidos.

Por ejemplo , el cifrado simétrico es como comprar una caja fuerte con un amigo y asignarle 2 claves idénticas.

Cada vez que chateas con ta, escribes la carta, la arrojas a la caja fuerte y la arrojas a la otra parte.

然后对方拿一样的钥匙,就可以开保险柜,并把他的信也丢到里面仍回给你。

再举个栗子,高中同学 A 和 B 谈恋爱了,但是他们纸条不能直接写情书呀,要不然被发现就哦豁了。

所以他们想了个法子,写纸条的时候,只写数字,比如 108-1,意思就是找某本书的 108 页第 1 个字。

02-05.png

看,他们的钥匙,就是相同的书,这样信息就等同了,对称了。

常见的对称加密算法:DES、3DES、DESX、Blowfish、IDEA、RC4、RC5、RC6 和 AES。

1.4 非对称加密

所谓「非对称加密」,就是 A 生成一对密钥,将公钥给别人,然后私钥自己藏好,别人通过公钥加密信息给 A,A 拿自己的私钥解密。

举个栗子,就是甲方是养蛊的,ta 有一对子母蛊。

甲方会把子蛊给乙方,母蛊留给自己。

这样哪怕子蛊丢了,别人拿子蛊传递信息,也只有甲方才能通过母蛊获取。

这种情况只有甲方才能破解信息,别人都没法理解不同人拿子蛊传递的信息。

02-06.png

所以非对称加密可以用在松鼠党上:我可不管谁拿了子蛊,只要传递信息给我,我就都存起来。

对称加密算法的运行速度比非对称加密算法快,所以需要加密大量的数据时,建议采用对称加密算法,提升加解密速度。

常见的非对称加密算法:RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)

1.5 哈希(Hash)算法

所谓哈希(Hash)算法,其实就是将信息做一个不可逆的转换,然后将信息存储起来。

举个栗子,小明同学将自己的密码,通过哈希(Hash)算法,存到了数据库里。

这样别人监听了小明登录账号的接口,也没法获取到里面的内容。

又或者别人看到了数据库,也不知道小明同学记录的原始密码是什么。

哈希(Hash)算法中,MD5 因为计算机算力提升,可以快速找到一个 MD5 的值对应的原文,所以大家由 MD5 的使用变成了 SHA-256 的使用,提升了加密的安全性。

02-07.png

当然,如果小伙伴们的密码比较简单,例如 123456 或者 666666 这种,被破解的还是大有可能的,可以在线试试 tool.oschina.net/encrypt?typ…

常见的哈希算法:MD2、MD4、MD5、HAVAL、SHA、SHA-1、HMAC、HMAC-MD5、HMAC-SHA1

1.6 数据加密算法的选择

在上面中,我们知道加密算法,大致分为「对称加密」和「非对称加密」以及「哈希(Hash)算法」这 3 种类型。

其中哈希(Hash)算法在对接中不可用了,因为它一般用作存储,不解出来。

而简单的对称加密和非对称加密,可能又过不了安全审查。

所以再三考虑下,我们采用 AES + RSA(对称加密 + 非对称加密)这种加密方式,提高数据传输的安全性。

二、RSA + AES

RSA + AES 的方案,我们采用这种形式:

  1. 服务端生成一堆 RSA 密钥,其中私钥自己保存,公钥下发给浏览器端
  2. 浏览器端通过随机函数,生成 AES 加密需要的 key(下面简称 AES key)
  3. 浏览器端通过 AES 和步骤 2 生成的 key,对要传输的数据进行加密,通过接口的 body 传递
  4. 浏览器端通过 RSA,对自己生成的 AES key 进行加密,通过接口的 header 传递
  5. 服务端拿到数据后,先通过 RSA 解密 header,获取到 AES key,再通过 AES,解密出 body 的数据

02-08.png

2.1 RSA 加解密

这一块的困难点在于 Go,因为 JavaScript 更多用的是一个库,即 jsencrypt

前端的 RSA 加密,是:

  1. 通过 jsencrypt,设置公钥
  2. 加密信息,生成 base64 数据并调用接口,传递给服务端

代码如下:

import JSEncrypt from 'jsencrypt';

const encryptor = new JSEncrypt();

/**
 * @name RSA-设置公钥
 * @param val 公钥
 */
export const setPublicKey = (val: string) => {
  encryptor.setPublicKey(val);
}

/**
 * @name RSA-加密
 * @param data 待加密数据
 * @returns {PromiseLike<ArrayBuffer>} 返回加密字符串
 */
export const rsaEncrypt = (data: string) => {
  return encryptor.encrypt(data) || '';
}

服务端的 RSA 解密,是:

  1. 先解密 base64
  2. 再通过 RSA 解密

Go 的代码可参考:

因为 Go 这边的库可能比较原始,所以如果碰到问题的话,大概率可能是公私钥的生成有问题,它需要按照最原始的标准格式来生成。

2.2 AES 加解密

对这一块确实没啥经验,网上说的文章,要么单独讲 JavaScript 的,要么单独讲 Go 的,整个人看完都懵圈。

好在网上大佬也确实给力,我跟服务端大佬合计了下,将一些点给撇掉了,直接采用:

实际开发中使用 AES 加密解密需要注意的地方:

  • 服务端和我们客户端必须使用一样的密钥和初始向量 IV
  • 服务端和我们客户端必须使用一样的加密模式
  • 服务端和我们客户端必须使用一样的 Padding 模式

首先,上面说这些可能有所信息误导,所以咱们直接看前端通过 JavaScript 生成 AES key 及加解密方法:

// 为什么不直接用 window.crypto,因为这个库做了兼容,可以看 https://github.com/brix/crypto-js/blob/4dcaa7afd08f48cd285463b8f9499cdb242605fa/src/core.js#L13
import CryptoJS from 'crypto-js';

/**
 * @name AESKey
 * @description 生成 AES Key
 * @return 随机生成 16 位的 AES Key
 */
export const createAesKey = () => {
  const expect = 16;
  let str = Math.random().toString(36).substr(2);
  while (str.length < expect) {
    str += Math.random().toString(36).substr(2);
  }
  str = str.substr(0, 16);
  return str;
}

/**
 * @name AES-加密
 * @param raw 待加密字段
 * @param AESKey AES Key
 * @return {string} 返回加密字段
 */
export const aesEncrypt = (raw: any, AESKey: string) => {
  const cypherKey = CryptoJS.enc.Utf8.parse(AESKey);
  CryptoJS.pad.ZeroPadding.pad(cypherKey, 4);

  const iv = CryptoJS.SHA256(AESKey).toString();
  const cfg = { iv: CryptoJS.enc.Utf8.parse(iv) };
  return CryptoJS.AES.encrypt(raw, cypherKey, cfg).toString();
}

/**
 * @name AES-解密
 * @param raw 待解密数据
 * @param AESKey 解密 key
 * @returns {string} 返回解密字符串
 */
export const aesDecrypt = (raw: string, AESKey: string) => {
  const cypherKey = CryptoJS.enc.Utf8.parse(AESKey);
  CryptoJS.pad.ZeroPadding.pad(cypherKey, 4);

  const iv = CryptoJS.SHA256(AESKey).toString();
  const cfg = { iv: CryptoJS.enc.Utf8.parse(iv) };

  const decrypt = CryptoJS.AES.decrypt(raw, cypherKey, cfg);
  return CryptoJS.enc.Utf8.stringify(decrypt).toString();
}

然后,在上面这段代码,有几个关键信息指标需要统一:

  • mode:AES 有各种加密模式,例如 CBC、CFB、CTR 等模式,在前端的 crypto-js 说明文档里面可以看到它含有 6 种

02-09.png

  • iv:AES 在一些加密模式上,需要指定 IV,即初始向量

  • padding:AES 需要加密的数据,不是 16 的倍数的时候,需要对原本的数据做 padding 操作(即补全长度到固定的位数),它有 Pkcs7、AnsiX923 等种类

02-10.png

最后,服务端 Go 的代码可以参考:

package main

import (
  "fmt"

  "github.com/LinkinStars/go-scaffold/contrib/cryptor"
)

func main() {
  key := "1234"

  e := cryptor.AesSimpleEncrypt("Hello World!", key)
  fmt.Println("加密后:", e)
  
  d := cryptor.AesSimpleDecrypt(e, key)
  fmt.Println("解密后:", d)

  iv := cryptor.GenIVFromKey(key)
  fmt.Println("使用的 IV:", iv)
}

// 输出
// 加密后: NHlpzbcTvOj686VaF7fU7g==
// 解密后: Hello World!
// 使用的 IV: 03ac674216f3e15c

参考文献

搜索关键词:浏览器端 rsa + aes


不折腾的前端,和咸鱼有什么区别!

觉得文章不错的小伙伴欢迎点赞/点 Star。

如果小伙伴需要联系 jsliang

个人联系方式存放在 Github 首页,欢迎一起折腾~

争取打造自己成为一个充满探索欲,喜欢折腾,乐于扩展自己知识面的终身学习斜杠程序员。

La biblioteca de documentación de jsliang está autorizada por Junrong Liang bajo una licencia internacional Creative Commons Attribution-NonCommercial-ShareAlike 4.0 .
Basado en el trabajo en github.com/LiangJunron ...
Los permisos para usar fuera de esta licencia están disponibles en creativecommons.org/licenses/by… .

Supongo que te gusta

Origin juejin.im/post/7264044879209906213
Recomendado
Clasificación