之前说搞一个完整的前后端项目,前端用nuxt3,后端用nestjs,先搞个简单的用户体系,里面包含了连接数据库,增删改查操作,联表查询,用户token校验,数据加解密传输
就在搞前后端数据加解密传输的过程中卡住了,卡了一天,今天终于搞定了,我现在还原一下问题完整过程:
先上前后端加解密代码:
最开始我是想着都是js,就想前后端公用前端这套代码了
前端加解密:
encrypt(word, keyStr, ivStr) {
if (!word) {
return ''
}
if (typeof word === 'object') {
word = JSON.stringify(word)
}
keyStr = keyStr ? keyStr : config.aesKey;
ivStr = ivStr ? ivStr : config.aesIv;
console.log(word, ' : ', keyStr, ' : ', ivStr)
let key = CryptoJS.enc.Utf8.parse(keyStr);
let iv = CryptoJS.enc.Utf8.parse(ivStr);
let srcs = CryptoJS.enc.Utf8.parse(word);
let encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
return encrypted.toString();
},
// 解密
decrypt(word, keyStr, ivStr) {
keyStr = keyStr ? keyStr : config.aesKey;
ivStr = ivStr ? ivStr : config.aesIv;
var key = CryptoJS.enc.Utf8.parse(keyStr);
let iv = CryptoJS.enc.Utf8.parse(ivStr);
var decrypt = CryptoJS.AES.decrypt(word, key, {
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
});
// decrypt = CryptoJS.enc.Utf8.stringify(decrypt);
return decrypt.toString(CryptoJS.enc.Utf8);
}
结果后端解密时报不能读取enc of undefined,完整的不记得了,反正意思就是没有CryptJS这个东西是undefined,所以说后端是node环境不能直接跟前端用一套,所以需要node环境的一套
后端加解密:
encrypt(text, keyStr = '') {
keyStr = keyStr || config.aesKey;
let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(keyStr), Buffer.from(config.aesIv));
let encrypted = cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
return encrypted;
},
decrypt(word, keyStr = '') {
if (!word) {
return ''
}
let decrypted = ''
word = word.replace(/\r\n/g, '');
keyStr = keyStr || config.aesKey;
let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(keyStr), Buffer.from(config.aesIv));
decipher.setAutoPadding(true); // 这将自动移除PKCS#7填充
decrypted = decipher.update(word, 'hex', 'utf8') + decipher.final('utf8');
return decrypted;
}
OK,前端请求一下,我们对前端加密完的数据包了一层对象,看起来前端加密是没问题的,
看下后端报错
很明显,这个是key长度问题,看下我们后端使用的解密模式,aes-256-cbc,要求秘钥32位,但是我们这里是16位的,那改成32位(前后端一起改)
前端加密没问题
看下后端报错:
这个大概意思是最终解密块长度有问题,当时没看懂,就想其他办法
1、换成ecb加解密模式,不用iv,试了好多次,也不行,而且这种模式crypto也不推荐再使用,这个过程也花了不少时间,过程忽略了
好,暂时没看懂问题所在,那我们就换种方式查问题:
1、前端加密,前端解密,看打印没问题:
2、后端加密,后端解密,
第一个报错,加密数据格式不对,我们给的对象格式,他需要string格式,加个转换
if (typeof text === 'object') {
text = JSON.stringify(text)
}
再试一下,后端加解密也成功了
前后端各自加解密都能成功,交叉为啥就不能成功了,其实到这里看到加密之后的数据格式应该就能看出问题来了,但是当时是没想到这一步的,但是试了很多次,很多种方法,改里面的各种参数尝试,搞了一天没搞定,我现在是成功之后按照之前大概的试错逻辑去走的所以很快就把逻辑理清楚了,
观察前后端加密后的数据,这里我们看到差距了,看后端加密这里这句话
let encrypted = cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
这里把加密串转成16进制格式数据了,后端解密方法能解密出来也是因为他是直接解密的16进制数据,那我们前端这里是什么数据格式呢,没关系,不会的话可以去问AI:
所以到这里基本思路清晰了,base64需要先转16进制,怎么转呢,同样问AI:
在对加密数据解密前做个处理,至于这个是否16进制格式数据判断函数搜一下就有了
if (!isHex(word)) {
const buffer = Buffer.from(word, 'base64');// Base64 解码为 Buffer
word = buffer.toString('hex');//buffer解析为16进制
}
再请求一次
搞定,之前还做了很多猜想,因为看大家写的加解密的例子秘钥都是16位的,于是猜测是不是前端默认是16位的,所以把后端的解密模式也改成了aes-128-cbc,后面再试的时候发现32位aes-256-cbc也没问题,所以意味着前端其实没有固定秘钥位数,后端解密的时候用的模式才固定了秘钥位数,前端跟着后端走就行
继续,后端到前端也要加密传输,前端解析
可以看到,后端传回来的是16进制格式的,所以也要转换格式
const buffer = Buffer.from(encrypted, 'hex');
// 将 Buffer 转换为 Base64 编码的字符串
const base64String = buffer.toString('base64');
return base64String;
没问题,前端解密,成功,切记,这里还是字符串,记得转换一下
转换成json
莫名其妙解析不了,看了半天,发现是字符串最后有多余空白
尝试去除空白,各种方法都试了,发现不行,后面把字符串复制一下出来,粘贴到控制台发现是这么个玩意
查了下大概率是填充问题,再看下解密代码,跟之前加密用的不一样:
改成
没问题了
至此,前后端完整加解密流程完成,奉上最终代码:
前端
import CryptoJS from "crypto-js";
import config from "~/config";
/*
* AES加密解密
*
* */
export default {
// 加密
encrypt(word, keyStr, ivStr) {
if (!word) {
return ''
}
if (typeof word === 'object') {
word = JSON.stringify(word)
}
keyStr = keyStr ? keyStr : config.aesKey;
ivStr = ivStr ? ivStr : config.aesIv;
console.log(word, ' : ', keyStr, ' : ', ivStr)
let key = CryptoJS.enc.Utf8.parse(keyStr);
let iv = CryptoJS.enc.Utf8.parse(ivStr);
let srcs = CryptoJS.enc.Utf8.parse(word);
let encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
return encrypted.toString();
},
// 解密
decrypt(word, keyStr, ivStr) {
keyStr = keyStr ? keyStr : config.aesKey;
ivStr = ivStr ? ivStr : config.aesIv;
var key = CryptoJS.enc.Utf8.parse(keyStr);
let iv = CryptoJS.enc.Utf8.parse(ivStr);
var decrypt = CryptoJS.AES.decrypt(word, key, {
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
// decrypt = CryptoJS.enc.Utf8.stringify(decrypt);
return decrypt.toString(CryptoJS.enc.Utf8);
}
};
后端
import * as crypto from 'crypto';
import config from "../config";
/*
* AES加密解密
*
* */
function isHex(str) {
return /[0-9a-fA-F]+$/.test(str);
}
export default {
// 加密
encrypt(text, keyStr = '') {
if (typeof text === 'object') {
text = JSON.stringify(text)
}
keyStr = keyStr || config.aesKey;
let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(keyStr), Buffer.from(config.aesIv));
let encrypted = cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
// 十六进制字符串转换为 Buffer
const buffer = Buffer.from(encrypted, 'hex');
// 将 Buffer 转换为 Base64 编码的字符串
const base64String = buffer.toString('base64');
return base64String;
},
decrypt(word, keyStr = '') {
if (!word) {
return ''
}
let decrypted = ''
word = word.replace(/\r\n/g, '');
if (!isHex(word)) {
// word = Buffer.from(word, 'hex');
const buffer = Buffer.from(word, 'base64');// Base64 解码为 Buffer
word = buffer.toString('hex');//buffer解析为16进制
}
console.log('decrypt222 ', word);
keyStr = keyStr || config.aesKey;
let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(keyStr), Buffer.from(config.aesIv));
decipher.setAutoPadding(true); // 这将自动移除PKCS#7填充
decrypted = decipher.update(word, 'hex', 'utf8') + decipher.final('utf8');
return decrypted;
}
};
我把遇到的问题用文字打出来,以便同样遇到此问题的同学能够找到此篇文章
Invalid key length
wrong final block length
Unexpected non-whitespace character
好了,搞完继续搞我的用户体系的功能去了
版权归原作者 ssheepsean 所有, 如有侵权,请联系我们删除。