关于js解密中遇到base64时的坑
直接贴图,某道翻译的aes解密,但是他先将文本进行base64解密后再进行的aes解密,采用crypto库解密是没有问题的,但是,采用crypto-js库进行解密会报错 Malformed UTF-8 data编码问题,一般这种情况都是密文或者key,iv的问题,所以核验密文,
发现密文中有_以及-这种字符,所以判断这个加密字符串进行了URL 安全 Base64 编码,因为在 URL 中,+
和 /
具有特殊含义。+
通常用于表示空格,而 /
用于路径分隔。因此,在 URL 中使用标准 Base64 编码会导致解析问题(chatgpt解释),所以我们在对加密字符串进行base64解码时需要先将_和-替换掉,才能进行最终的解密。可是这里又有个疑问,既然_和-是错误的字符,那为什么crypto能够解密成功呢,以下是chatgpt原话:
-
内部处理机制:
- Node.js 的
Buffer
模块在解析 Base64 字符串时,会自动处理 URL 安全 Base64 编码中的字符_
和-
。 - 这意味着即使字符串中包含这些字符,Node.js 也会将其视为有效的 Base64 字符进行解码。
- Node.js 的
-
容错性:
Buffer.from
方法在处理 Base64 字符串时,不会严格要求字符必须符合标准 Base64 字符集。它会尝试将字符串解析为二进制数据,即使字符串中包含_
和-
也不会导致解码失败。
为什么 crypto-js
需要手动替换?
虽然 Buffer.from
可以直接解码 URL 安全 Base64 编码的字符串,但在使用 crypto-js
时,需要手动替换 _
和 -
,这是因为 crypto-js
的 Base64 解码实现遵循更严格的 Base64 字符集规范,不自动处理 URL 安全 Base64 编码中的字符。
看到这里就差不多能理解了,因为crypto采用的是buffer的解密,而buffer有一定的容错性,所以也能解密成功,而crypto-js在解析base64时是更为严格的,所以解码失败,要手动的进行替换_和-。
然后再来看python中
我们可以发现,即使对-和_进行了替换,base64解码时也还是会报错Incorrect padding,这是因为Python的base64编码要求输入字符串的长度必须是4的倍数,如果不是,则需要在末尾添加填充字符=
。那我们回到刚才,那反过来,既然这个加密字符串的长度不是4的倍数,那为什么crypto和crypto-js能够解码成功呢,再问chatgpt:
测试一下:
可以发现crypto以及crypto-js对base64的解码并不会严格要求符合长度,即使少了=号也能够解码成功,而python里就不行。所以python要进行手动补全=号达到4的倍数。
下面附带三种解密的方式:
crypto:
function decodeData(t) {
// 将输入文本从 Base64 解码为二进制数据
const base64Decoded = Buffer.from(t, 'base64');
// 计算 MD5 哈希并截取前 16 字节作为密钥和 IV
const key = crypto.createHash('md5').update(o).digest();
const iv = crypto.createHash('md5').update(n).digest();
// 创建 decipher 实例
const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
// 更新 decipher 并最终解密
let decrypted = decipher.update(base64Decoded, 'binary', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
crypto-js:
function decodeData(t) {
//需要手动的替换-以及_
t = t.replace(/-/g, '+').replace(/_/g, '/');
const key = CryptoJS.MD5(o).toString(CryptoJS.enc.Hex);
const iv = CryptoJS.MD5(n).toString(CryptoJS.enc.Hex);
const keyWordArray = CryptoJS.enc.Hex.parse(key);
const ivWordArray = CryptoJS.enc.Hex.parse(iv);
// 解密 AES
const decrypted = CryptoJS.AES.decrypt(
{ ciphertext: CryptoJS.enc.Base64.parse(t) },
keyWordArray,
{ iv: ivWordArray, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
);
return decrypted.toString(CryptoJS.enc.Utf8);
}
Python:
def decodeData(t):
# 一样的替换_和-
t = t.replace("_", "/").replace("-", "+")
# Python中需要验证base64的长度是不是4的倍数,如果不是需要手动的补全结尾的=号,不然也会解码失败
missing_padding = 4 - len(t) % 4
if missing_padding:
t += "=" * missing_padding
decodeiv = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4"
# 秘钥
decodekey = "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl"
# 先把密匙和偏移量进行md5加密 digest()是返回二进制的值
key = hashlib.md5(decodekey.encode(encoding="utf-8")).digest()
iv = hashlib.md5(decodeiv.encode(encoding="utf-8")).digest()
# AES解密 CBC模式解密
aes_en = AES.new(key, AES.MODE_CBC, iv)
# 将已经加密的数据放进该方法
data_new = base64.urlsafe_b64decode(t)
# 参数准备完毕后,进行解密
a = aes_en.decrypt(data_new).decode("utf-8").strip()
return a
本篇文章是给自己做个记录,当然也欢迎大家学习交流。