【关于前端加密】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

相关文章

Spring Boot 整合 SSE的高级实践(Server-Sent Events)

《SpringBoot整合SSE的高级实践(Server-SentEvents)》SSE(Server-SentEvents)是一种基于HTTP协议的单向通信机制,允许服务器向浏览器持续发送实... 目录1、简述2、Spring Boot 中的SSE实现2.1 添加依赖2.2 实现后端接口2.3 配置超时时

Python使用getopt处理命令行参数示例解析(最佳实践)

《Python使用getopt处理命令行参数示例解析(最佳实践)》getopt模块是Python标准库中一个简单但强大的命令行参数处理工具,它特别适合那些需要快速实现基本命令行参数解析的场景,或者需要... 目录为什么需要处理命令行参数?getopt模块基础实际应用示例与其他参数处理方式的比较常见问http

HTML5中的Microdata与历史记录管理详解

《HTML5中的Microdata与历史记录管理详解》Microdata作为HTML5新增的一个特性,它允许开发者在HTML文档中添加更多的语义信息,以便于搜索引擎和浏览器更好地理解页面内容,本文将探... 目录html5中的Mijscrodata与历史记录管理背景简介html5中的Microdata使用M

html5的响应式布局的方法示例详解

《html5的响应式布局的方法示例详解》:本文主要介绍了HTML5中使用媒体查询和Flexbox进行响应式布局的方法,简要介绍了CSSGrid布局的基础知识和如何实现自动换行的网格布局,详细内容请阅读本文,希望能对你有所帮助... 一 使用媒体查询响应式布局        使用的参数@media这是常用的

HTML5表格语法格式详解

《HTML5表格语法格式详解》在HTML语法中,表格主要通过table、tr和td3个标签构成,本文通过实例代码讲解HTML5表格语法格式,感兴趣的朋友一起看看吧... 目录一、表格1.表格语法格式2.表格属性 3.例子二、不规则表格1.跨行2.跨列3.例子一、表格在html语法中,表格主要通过< tab

Java Optional的使用技巧与最佳实践

《JavaOptional的使用技巧与最佳实践》在Java中,Optional是用于优雅处理null的容器类,其核心目标是显式提醒开发者处理空值场景,避免NullPointerExce... 目录一、Optional 的核心用途二、使用技巧与最佳实践三、常见误区与反模式四、替代方案与扩展五、总结在 Java

Vue3组件中getCurrentInstance()获取App实例,但是返回null的解决方案

《Vue3组件中getCurrentInstance()获取App实例,但是返回null的解决方案》:本文主要介绍Vue3组件中getCurrentInstance()获取App实例,但是返回nu... 目录vue3组件中getCurrentInstajavascriptnce()获取App实例,但是返回n

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

JS+HTML实现在线图片水印添加工具

《JS+HTML实现在线图片水印添加工具》在社交媒体和内容创作日益频繁的今天,如何保护原创内容、展示品牌身份成了一个不得不面对的问题,本文将实现一个完全基于HTML+CSS构建的现代化图片水印在线工具... 目录概述功能亮点使用方法技术解析延伸思考运行效果项目源码下载总结概述在社交媒体和内容创作日益频繁的

前端CSS Grid 布局示例详解

《前端CSSGrid布局示例详解》CSSGrid是一种二维布局系统,可以同时控制行和列,相比Flex(一维布局),更适合用在整体页面布局或复杂模块结构中,:本文主要介绍前端CSSGri... 目录css Grid 布局详解(通俗易懂版)一、概述二、基础概念三、创建 Grid 容器四、定义网格行和列五、设置行