AES + RSA による JavaScript + GO データの暗号化と復号化

ブラウザ側で小児用の暗号化を行うことは、暗い夜空にいくつかの星が点在し、ハッカーに「ここに貴重な情報がある、来てカードを裏返しなさい」と告げるようなものです。

02-01.pngブラウザ側の暗号化は比較的安全です。

その具体的な安全性は、その中に含まれる情報の価値、つまりクラッカーの攻撃に値するかどうかによって決まります。

典型的なジョークのように:

顧客は自分で一連のソフトウェアを開発し、ダニエルにクラックの難易度をテストさせるためにクラック防止プラグインを購入しました。

3日後、ダニエルは「なかなか大変で3日かかった」と落ち込んで言いました。

よく見てみると、クラック防止プラグインが壊れていることが判明しました...

1. 情報の調整

実際、JavaScript + Go の暗号化と復号化、実際のコード呼び出しは非常に簡単です。

サーバーパートナーと私はオンライン情報に直面していましたが、Kuku は 1 回の操作でそれを完了しました。

02-02.png

そのため、コード リンクに入る前に、この知識ポイントを理解したい他の友人が内部のロジックについて混乱しないように、まずいくつかの基本情報を調整して同期します。

1.1 暗号化と復号化とは何ですか

いわゆる「暗号化」とは、「何らかの処理」によって情報を読み取れなくし、不正アクセスから守ることです。

そして、加工された情報を一般の人が理解できる情報に変換するのが「復号化」です。

たとえば、あなたが商品を手に入れようとしているのに、相手が「天の王が虎を覆っている」と言ったとしましょう。

身元を示すために電話に出るつもりですか?

02-03.png

「ペアリングコード」とは、実は情報処理の一つであり、相手の質問に答えて初めて次に進むことができます。

1.2 データ暗号化が必要な理由

安全のために"。

絶対的なセキュリティなど存在しないのに、なぜセキュリティを囲い込む必要があるのでしょうか。

ブラウザ側でのデータの暗号化と復号化では、次のことのみが保証されます。

  1. 送信されるデータは暗号化または暗号化されており、たとえ誰かがパケットキャプチャソフトを通じてデータを取得したとしても、理解または解読することは困難です。
  2. データが改ざんされていないことを確認し、送信者の身元を確認する

知らせ!上記の 2 つの点は、パケット キャプチャを防ぐだけであり、インジェクションを防ぐことはできません。

これは、相手が JavaScript スクリプトを介して挿入して、最初に作成したコードを置き換えることを意味します。

02-04.png

こうすることで、暗号化する前に元の情報を取得できます。

1.3 対称暗号化

いわゆる「対称暗号化」とは、送信データを暗号化および復号化するために全員が同じキーを取得することを意味します。

たとえば、対称暗号化は、友人と金庫を購入し、2 つの同一のキーを割り当てるようなものです。

taとチャットするたびに、手紙を書いて金庫に投げ込み、金庫を相手に投げます。

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

再举个栗子,高中同学 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 首页,欢迎一起折腾~

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

jsliang のドキュメント ライブラリは、Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Licenseに基づいてJunrong Liangによってライセンスされています。github.com/LiangJunronの作業に基づいています… このライセンスの範囲外での使用許可は、creativecommons.org/licenses/ by… で入手できます。

おすすめ

転載: juejin.im/post/7264044879209906213
おすすめ