【关于前端加密】CryptoJS与blake2b实践

2023-10-14 11:50

本文主要是介绍【关于前端加密】CryptoJS与blake2b实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

环境:前端vue3,服务端go

需求1:const hashed = (mac+symbol+timestamp)的blake2b128 hash值

需求2:使用aescbc-128算法加密token、body

需求3:解密接口返回的response data

blake2b

  1. 使用 npm 或者 yarn 安装依赖

npm install blake2b

或者

bashCopy codeyarn add blake2b

  1. 在 Vue 3 项目中引入库:

import blake2b from 'blake2b';

  1. 在需要使用 Blake2b-128 哈希的地方,调用库进行计算:
js复制代码// 计算 Blake2b-128 哈希 
const hash = blake2b(16).update('Hello, World!').digest('hex'); 
console.log(hash)

💡
在 blake2b(16) 中的 16 表示生成的 Blake2b 哈希的输出长度(以字节为单位)。在这种情况下,16 表示输出的哈希值长度为 16 字节,即 128 位。

Blake2b 是一种多功能哈希函数,它可以生成不同长度的哈希值。通过指定不同的输出长度,你可以控制生成的哈希值的位数。例如,如果你想要一个 256 位的哈希值,你可以使用 blake2b(32),其中 32 表示输出长度为 32 字节(256 位)。
在使用 Blake2b 哈希库时,根据你的具体需求,你可以选择适当的输出长度来满足你的要求。请注意,输出长度越长,哈希值的唯一性和强度可能会更高,但也会增加存储和传输的开销。

AES-CBC-128加密算法

使用CryptoJS 库来实现

  1. 使用 npm 或 yarn 安装 CryptoJS:

npm install crypto-js

或者

yarn add crypto-js

  1. 在需要使用 AES-CBC-128 加密的地方,引入库:

import CryptoJS from 'crypto-js';

  1. 使用 CryptoJS 进行 AES-CBC-128 加密:
js复制代码// 定义密钥和初始向量(示例) 
const key = CryptoJS.enc.Hex.parse('000102030405060708090a0b0c0d0e0f'); // 128-bit key 
const iv = CryptoJS.enc.Hex.parse('101112131415161718191a1b1c1d1e1f'); // 128-bit IV // 加密 
const plaintext = 'Hello, World!'; 
const encrypted = CryptoJS.AES.encrypt(plaintext, key, { iv: iv }).toString(); console.log(encrypted);

在上述示例中,我们使用 CryptoJS 库进行 AES-CBC-128 加密。我们提供了一个 128 位的密钥和一个 128 位的初始向量(IV),并使用 CryptoJS.AES.encrypt 方法进行加密操作。加密结果以字符串形式输出。

实操

以上是两个工具的基本用法,接下来进入实践部分

前提:const ac = 与服务端约定一个常量

目标:在请求头中添加session

aescbc-128加密(计算session)

const ac = "aaabbbccc"

token: "aaabbbccc"

  1. ac+timestamp(时间戳),计算blake2b128得到hash值
  2. 使用aescbc-128算法加密token,加密key为hashed前16个字符转byte数组(Uint8Array),iv为后16个字符转byte数组
  3. 将加密后的结果做base64,即为session
js复制代码
// 转换为byte数组
const strToByteArray = (str: string) => {return new Uint8Array([...str].map((c) => c.charCodeAt(0)))
}// 计算 Blake2b-128 哈希
export const calcBlake2b = (val: string) => {return blake2b(16).update(strToByteArray(val)).digest('hex')
}// 模拟go的HexDecode(js版本) 
// 每两位转为一个十进制数字   比如 :  d4 --- >  212
const HexDecode = (hexString: string) => {const decimalArray = []for (let i = 0; i < hexString.length; i += 2) {const hexPair = hexString.substr(i, 2)const decimalNumber = parseInt(hexPair, 16)decimalArray.push(decimalNumber)}return new Uint8Array(decimalArray)
}// 计算session
export const geneOpenApiSession = (ac: string,token: string,timestamp: string
) => {const hashed = calcBlake2b(ac + timestamp)// hashed前16个字符转换为byte数组 128-bit keyconst keyBytes = strToByteArray(hashed.substring(0, 16))// hashed后16个字符转换为byte数组 128-bit IVconst ivBytes = strToByteArray(hashed.substring(hashed.length - 16))// aescbc-128算法const plaintext = CryptoJS.enc.Hex.parse(token) // 同:CryptoJS.lib.WordArray.create(HexDecode(token))const encrypted = CryptoJS.AES.encrypt(plaintext, CryptoJS.lib.WordArray.create(keyBytes), {iv: CryptoJS.lib.WordArray.create(ivBytes)}).toString()return encrypted
}

CryptoJS.enc.Hex.parse

💡
CryptoJS.enc.Hex.parse是 CryptoJS 库中的一个方法,用于将十六进制字符串转换为 CryptoJS 所使用的 WordArray 对象。

对于 AES 加密算法,输入数据通常是 WordArray 类型。WordArray 是 CryptoJS 中用于表示二进制数据的数据结构。

示例

js复制代码const CryptoJS = require('crypto-js'); 
const hexString = '48656c6c6f2c20576f726c6421'; 
// 将十六进制字符串转换为 WordArray 对象 
const wordArray = CryptoJS.enc.Hex.parse(hexString); 
console.log(wordArray);

CryptoJS.lib.WordArray.create

💡
CryptoJS.lib.WordArray.create 可以将字节数组(Uint8Array)转换为 CryptoJS 的 WordArray 对象。

直接使用 new Uint8Array 创建的字节数组作为初始向量(iv)或密钥(key),在 CryptoJS 中可能会导致报错。这是因为 CryptoJS 的加密方法期望的是一个 CryptoJS 的WordArray 对象作为初始向量,而不是原始的字节数组。

CryptoJS.enc.Utf8.parse

💡
CryptoJS.enc.Utf8.parse 是 CryptoJS 中的一个方法,用于将 UTF-8 编码的字符串转换为 WordArray 对象。

示例

js复制代码const CryptoJS = require('crypto-js'); 
const str = 'Hello, World!'; // 将 UTF-8 编码的字符串转换为 WordArray 对象 
const wordArray = CryptoJS.enc.Utf8.parse(str); 
console.log(wordArray);

💡CryptoJS.AES.encrypt参数说明:

message:要加密的消息,可以是字符串或 WordArray 对象。CryptoJS.AES.encrypt 方法是用于加密字符串或字节数组的,如果您想要加密一个对象,您需要先将对象转换为字符串或字节数组,然后再进行加密。 key:加密密钥。可以是字符串、WordArray 对象或 WordArray 可转换为字符串的格式(如十六进制字符串)。

密钥的长度必须为 128 位(16 字节),因为 AES-CBC-128 使用 128 位的密钥。如果密钥长度不符合要求,将无法正确执行加密和解密操作。

options:加密选项。一个包含 iv(初始向量)等属性的对象。 iv:初始向量的长度也必须为 128 位(16 字节),与密钥长度相同。初始向量用于增加加密的随机性和安全性。 padding(填充):CryptoJS.pad.ZeroPadding 表示使用 Zero Padding(零填充)模式进行数据填充。当要加密的数据长度不是分组长度的整数倍时,需要进行填充操作以满足分组加密的要求。Zero Padding 是一种常见的填充方式,它在数据的末尾添加零字节,使数据长度变为分组长度的整数倍。 mode(加密模式):CryptoJS.mode.CBC 表示使用 CBC(Cipher Block Chaining,密码分组链接)模式进行加密。CBC 是一种常用的分组密码模式,它通过将前一个加密分组的密文与当前分组的明文进行异或运算,增加了密码的随机性和安全性。在 CBC 模式中,还需要提供一个初始化向量(IV)作为额外的参数 返回值(加密结果):一个 CipherParams 对象。

加密对象示例

js复制代码const CryptoJS = require('crypto-js');const key = 'your-key'; // 密钥
const iv = 'your-iv'; // 初始化向量// 要加密的对象
const objectToEncrypt = { name: 'John', age: 30 };// 将对象转换为字符串
const jsonString = JSON.stringify(objectToEncrypt);// 将字符串转换为 WordArray 对象
const data = CryptoJS.enc.Utf8.parse(jsonString);// 使用 CryptoJS 进行 AES 加密
const encrypted = CryptoJS.AES.encrypt(data, key, { iv }).toString();console.log(encrypted);

WordArray

💡
WordArray 对象是 CryptoJS 中用于表示字节序列的抽象。它提供了一系列方法和功能,用于在加密、解密和其他操作中处理字节序列

js复制代码// 将 WordArray 对象还原为字节数组 
const byteArray = Array.from(wordArray.sigBytes === 0 ? \[] : new Uint8Array(wordArray.words.buffer));

其他加密插件:

在 JavaScript 中,有多个库可以用于实现 AES-CBC-128 算法加密。以下是一些常用的库:

  1. CryptoJS:CryptoJS 是一个流行的加密库,它提供了丰富的加密算法支持,包括 AES-CBC。您可以使用 CryptoJS.AES.encrypt 函数来进行 AES-CBC-128 加密。它是一个纯 JavaScript 实现的库,易于使用。
  • 可以通过 npm 安装:

npm install crypto-js

  • 官方文档:cryptojs.gitbook.io/docs/
  1. Forge:Forge 是一个强大的加密和密码学库,提供了多种加密算法的实现,包括 AES-CBC。您可以使用 forge.cipher.createCipher 函数进行 AES-CBC-128 加密。
  • 可以通过 npm 安装:

npm install node-forge

  • 官方文档:github.com/digitalbaza…
  1. SJCL:Stanford Javascript Crypto Library(SJCL)是一个全功能的加密库,支持多种加密算法,包括 AES-CBC。它旨在提供安全可靠的加密功能。
  • 可以通过 npm 安装:

npm install sjcl

  • 官方文档:bitwiseshiftleft.github.io/sjcl/
  1. WebCrypto API:现代浏览器提供了 WebCrypto API,它是一个原生的加密 API,支持多种加密算法,包括 AES-CBC。使用 WebCrypto API 可以在浏览器中进行高性能的加密操作。
  • 可以直接在现代浏览器中使用,无需安装额外的库。 MDN 文档:developer.mozilla.org/en-US/docs/…

在 JavaScript 社区中,CryptoJS 是最常用和广泛使用的加密库之一。它有许多用户和开发者使用它来进行数据加密和解密操作。

CryptoJS 提供了简单易用的 API,并支持多种常见的加密算法,包括 AES、DES、3DES、RC4、SHA 等。它的使用方式灵活,适合用于各种项目和应用场景。是一个成熟且稳定的库,拥有广泛的文档和社区支持。相对而言,CryptoJS 在 JavaScript 社区中的知名度和使用率更高。

aescbc-128解密(解析res.data)

const ac = "aaabbbccc"

token: "aaabbbccc"

  1. const aeskeystr := Blake128Calc([]byte(ac + timestamp)) // timestamp
  2. 解密body,解密key为 HexDecode(aeskeystr),iv为HexDecode(token)

接口返回二进制流:

截图 (1).png

 

设定responseType:‘arraybuffer’

  • 将 ArrayBuffer 转换为 Uint8Array
js复制代码
if (res.request.responseType === 'arraybuffer') { const uint8Array = new Uint8Array(res.data) // 将 ArrayBuffer 转换为 Uint8Array return decryptApiBody(ac, token, String(timestamp), uint8Array) 
}
js复制代码export const decryptApiBody = (ac: string,token: string,timestamp: string,data: any
) => {const aeskeystr = calcBlake2b(ac + timestamp)const key = CryptoJS.enc.Hex.parse(aeskeystr)const iv = CryptoJS.enc.Hex.parse(token)// data: Uint8Array转为字符串const ciphertext = CryptoJS.enc.Base64.stringify(CryptoJS.lib.WordArray.create(data))// 解密const decrypted = CryptoJS.AES.decrypt(ciphertext, key, { iv })// 解密结果直接转为Uint8Array会出现多余字符,导致JSON.parse失败,所以先转为Base64// 将解密结果转换为 Base64 字符串const decryptedBase64 = decrypted.toString(CryptoJS.enc.Base64)// 将 Base64 字符串转换为 Uint8Arrayconst decodedString1 = window.atob(decryptedBase64) // base64解码const decryptedUint8Array = new Uint8Array(decodedString1.length)for (let i = 0; i < decodedString1.length; i++) {decryptedUint8Array[i] = decodedString1.charCodeAt(i)}// 将 Uint8Array 转换为字符串const textDecoder = new TextDecoder('utf-8')const decodedString = textDecoder.decode(decryptedUint8Array)return decodedString ? JSON.parse(decodedString) : {}
}

CryptoJS.enc.Base64.stringify

💡
CryptoJS.enc.Base64.stringify 方法将 Uint8Array 密文数据转换为 Base64 编码的字符串。

Uint8Array不能直接作为解密参数。转为WordArray也不行

CryptoJS.AES.encrypt(....).toString()

💡
toString默认行为是将加密后的数据转换为base64编码的字符串。

toString方法还接受一个可选的参数,用于指定输出格式。

例如,如果你想要将加密结果转换为Hex字符串:

decrypted.toString(CryptoJS.enc.hex)

但在有些情况下,toString()结果与encrypted.toString(CryptoJS.enc.Base64)不同,这可能与传入的message格式有关 ?(存疑)

例如这个例子中,在加密时encrypted.toString()、encrypted.toString(CryptoJS.enc.Base64)、encrypted.toString(CryptoJS.enc.hex)三种形式输出结果相同; 在解密时encrypted.toString()与encrypted.toString(CryptoJS.enc.hex)结果相同,与encrypted.toString(CryptoJS.enc.Base64)不同;

💡坑点

将解密后的数据decrypted转换为 Uint8Array 的话,decodedString会出现多余字符串,导致JSON.parse失败:

猜测是由于:

如果将 Uint8Array 直接转换为字符串,可能会产生额外的字符,这是因为字符串的构造方式和编码方式的差异。

在 JavaScript 中,使用默认的字符串构造函数将 Uint8Array 转换为字符串时,它会尝试将字节序列解释为 UTF-16 编码的字符。如果 Uint8Array 中的字节序列无法正确解码为有效的 UTF-16 字符,则会出现乱码或额外的字符。

所以先把decrypted转为base64,再转Uint8Array

 

这篇关于【关于前端加密】CryptoJS与blake2b实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/210308

相关文章

SQLite3命令行工具最佳实践指南

《SQLite3命令行工具最佳实践指南》SQLite3是轻量级嵌入式数据库,无需服务器支持,具备ACID事务与跨平台特性,适用于小型项目和学习,sqlite3.exe作为命令行工具,支持SQL执行、数... 目录1. SQLite3简介和特点2. sqlite3.exe使用概述2.1 sqlite3.exe

全面解析HTML5中Checkbox标签

《全面解析HTML5中Checkbox标签》Checkbox是HTML5中非常重要的表单元素之一,通过合理使用其属性和样式自定义方法,可以为用户提供丰富多样的交互体验,这篇文章给大家介绍HTML5中C... 在html5中,Checkbox(复选框)是一种常用的表单元素,允许用户在一组选项中选择多个项目。本

HTML5 搜索框Search Box详解

《HTML5搜索框SearchBox详解》HTML5的搜索框是一个强大的工具,能够有效提升用户体验,通过结合自动补全功能和适当的样式,可以创建出既美观又实用的搜索界面,这篇文章给大家介绍HTML5... html5 搜索框(Search Box)详解搜索框是一个用于输入查询内容的控件,通常用于网站或应用程

CSS3中的字体及相关属性详解

《CSS3中的字体及相关属性详解》:本文主要介绍了CSS3中的字体及相关属性,详细内容请阅读本文,希望能对你有所帮助... 字体网页字体的三个来源:用户机器上安装的字体,放心使用。保存在第三方网站上的字体,例如Typekit和Google,可以link标签链接到你的页面上。保存在你自己Web服务器上的字

SQL中JOIN操作的条件使用总结与实践

《SQL中JOIN操作的条件使用总结与实践》在SQL查询中,JOIN操作是多表关联的核心工具,本文将从原理,场景和最佳实践三个方面总结JOIN条件的使用规则,希望可以帮助开发者精准控制查询逻辑... 目录一、ON与WHERE的本质区别二、场景化条件使用规则三、最佳实践建议1.优先使用ON条件2.WHERE用

Springboot整合Redis主从实践

《Springboot整合Redis主从实践》:本文主要介绍Springboot整合Redis主从的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言原配置现配置测试LettuceConnectionFactory.setShareNativeConnect

html 滚动条滚动过快会留下边框线的解决方案

《html滚动条滚动过快会留下边框线的解决方案》:本文主要介绍了html滚动条滚动过快会留下边框线的解决方案,解决方法很简单,详细内容请阅读本文,希望能对你有所帮助... 滚动条滚动过快时,会留下边框线但其实大部分时候是这样的,没有多出边框线的滚动条滚动过快时留下边框线的问题通常与滚动条样式和滚动行

java中Optional的核心用法和最佳实践

《java中Optional的核心用法和最佳实践》Java8中Optional用于处理可能为null的值,减少空指针异常,:本文主要介绍java中Optional核心用法和最佳实践的相关资料,文中... 目录前言1. 创建 Optional 对象1.1 常规创建方式2. 访问 Optional 中的值2.1

Nginx Location映射规则总结归纳与最佳实践

《NginxLocation映射规则总结归纳与最佳实践》Nginx的location指令是配置请求路由的核心机制,其匹配规则直接影响请求的处理流程,下面给大家介绍NginxLocation映射规则... 目录一、Location匹配规则与优先级1. 匹配模式2. 优先级顺序3. 匹配示例二、Proxy_pa

MyBatis编写嵌套子查询的动态SQL实践详解

《MyBatis编写嵌套子查询的动态SQL实践详解》在Java生态中,MyBatis作为一款优秀的ORM框架,广泛应用于数据库操作,本文将深入探讨如何在MyBatis中编写嵌套子查询的动态SQL,并结... 目录一、Myhttp://www.chinasem.cnBATis动态SQL的核心优势1. 灵活性与可