文章目录
- AES算法
- 基本介绍
- 加密模式
- 模式与IV
- 接口响应AES解密
AES算法
基本介绍
高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法。
对称加密算法中加解密密钥都是一样的。
AES 的主要特性:
-
块加密:AES 是一种分组加密算法,它将数据分成固定长度的块进行加密。每个块的长度为 128 位(16 字节)。
-
密钥长度:AES 允许使用三种不同长度的密钥,分别为:
• 128 位(16 字节)
• 192 位(24 字节)
• 256 位(32 字节)
密钥越长,算法的安全性越高。
加密模式
常见的 AES 加密模式
- ECB(Electronic Codebook Mode,电子密码本模式)
• 每一个数据块独立加密。
• 简单但不安全,因为相同的明文块会生成相同的密文块,容易被攻击者通过统计方法分析。
• 不适合加密长数据,只适合短且无重复的数据。
• 不需要 IV(初始化向量)。
- CBC(Cipher Block Chaining Mode,密码块链模式)
• 每一个块的加密都会依赖前一个块的密文。
• 加密第一块时需要一个随机的IV(初始化向量),之后每一块的加密结果都会用于加密下一个块,形成链式结构。
• 安全性高,防止了相同明文块生成相同密文块的情况,常用来加密敏感数据。
• 需要一个 IV。
- CFB(Cipher Feedback Mode,密码反馈模式)
• 类似于 CBC,但它是一种自同步的流模式加密,可以逐字节或逐位加密数据块。
• 前面的密文会影响后面的块,加密时依然需要 IV。
- OFB(Output Feedback Mode,输出反馈模式)
• 和 CFB 类似,但它不会在加密过程中使用密文,而是通过生成一个加密流来加密明文。加密流是基于密钥和 IV 生成的。
• 适用于流数据。
- CTR(Counter Mode,计数器模式)
• 将数据块和计数器的值进行加密(而非直接加密数据块)。
• 它是并行化的,适合多核处理器,可以加快加密/解密速度,适合大数据量。
• 需要 IV,且生成加密流。
模式与IV
IV 就是初始化向量
IV 是用于初始化加密的随机数,它在某些模式(例如 CBC、CFB、OFB、CTR)下用于保证加密的随机性,即使使用相同的密钥和相同的明文,IV 的不同也能保证密文的不同。IV 不需要保密,但需要唯一和随机。常见的做法是将 IV 作为密文的一部分存储或传输。
在 CBC 模式 中,第一块数据是通过明文块和 IV 一起加密的,后续的块则通过前一块的密文和当前的明文块加密,形成一种“链接”结构。每次加密都会使用不同的 IV,保证了加密内容的唯一性,即使明文相同,IV 的不同也会使密文不同。
下面代码展示了一个简单的ECB模式下不需要IV的解密信息
import base64
from Crypto.Cipher import AES # pip install pycryptodome
# 加密后的参数
str1 = "Gs/nl960sHJcPrPr83z9MRMC7LBr/GY/K8gmbW68iFdjLCYTehkRYafNPbLvsQ8v"
# 加密后的结果
str2 = "+LZgDXrUvn52pmaRCuHYFlNTvfhGg9p7waDQjjW54Kf334rhYVaS7eN/5USlS6d9NKDpcBGl0GzPqlLY6qb70TB90VLhfFhW6zT3fWTp/dZTQbO/ZNK3ui57bLi7K4uyMmE3xzM1bF+gO7de4rIzH93dGG3/DsIXgaEwK5qn4r6gTW4oyz6LXKKUSOubFXeCnqSVRGnQorAs/9HLm3R6o9Uasfjv5LA08vJ5fRuvsY3F3vXIxXpPyY7e10hnYCMGAo1fvm1I86q0U2BBxRkQDcgRjlEcBXmG9xbocqX8m3HCPZCVPs82sAE2yUCUDpOZ4KVCUtzkpSQr0I/Bt2tJwyG/cgf5aVKOBUJdCX3jMS/t4TRvE9SZJTUMxdjwZKTd0EfyQjdQAGCo6K+Jxil8wjYhhzsDsWu3Aga8LC1+j2WpkzjBDrJ4jMsgCEdHuNXr8KpOmuZvQD/JZKZmqceZd4HpEXigmlog86hfNG9unUSvdCnkittVLYZz9OaWV1Tq1Loa0O4T/jrVQhcmpJM/dA8qkPRS8m1RjWXgpeGDQ1aDT48082kJ3QIm3RHqGfV4e0e0wpdWDCK2TmbcOM2PT2WXOvWOPXoeI2KRpEVwJaOJE2T0dZe7Y7BYHsootmampwZxnObjiqWJiqU7rA6ejLDFkFCtKrodymMKm7gf7Ee2+NqoQTEtBa8y348NzPhK9sgz6rGS6Q5d8BaX8rcWDH0LTMvi28tMgEr7OpyURucomYTeLjIqDmGsLiLwezbWxaB9QvAvHvg6Wavd2Z4IBL0WVl3YJzrHjxmy4VLCJHOzheKeLebOYTpqf5plfIx2ZstWlVi3OPFnNgnmGkTw3trYQeOIIUzGU+TkYKD8tzWQjGAmnM77bNieLHZus+TiGs5+MvGKWJnerL0SpK5mye7jX1wqJPIu0aPklZZ0B+LZ7dazpQAlbYTIH1EczHsELHwGY4m65XwMcztn27y7x9ZryvsLcmIC4bVENmeREWalgedTFZa+zEQ/yoCPCUrVyFCdlaeAz7JidLFSZulycdZp8o+O568We+83a4B+lMrDe4jLnuK0xXdPyyNgdlmvNYJH1Gbds8Iq8HKRiSZ7XA=="
# 解密函数
def AES_Decrypt(data, key):
cipher = AES.new(key.encode('UTF-8'), AES.MODE_ECB)
text = base64.b64decode(data.encode('utf8'))
result = cipher.decrypt(text)
detext = result.decode('UTF-8')
return detext[0:len(detext) - ord(detext[-1])]
print('解密后的参数:' + AES_Decrypt(str1, 'YWJjZGVmZ2hpamts'))
print('解密后的结果:' + AES_Decrypt(str2, 'YWJjZGVmZ2hpamts'))
# 使用 'YWJjZGVmZ2hpamts' 作为加密密钥,它是一个 Base64 编码的字符串,对应解码后的密钥为 'abcdefghijklmno'。
解释:
- AES.new(key.encode(‘UTF-8’), AES.MODE_ECB):
• 使用 AES 库中的 new 方法创建一个 AES 加密器。
• key.encode(‘UTF-8’):将密钥 key 转换为字节数组(因为 AES 算法需要密钥为字节类型)。
• AES.MODE_ECB:选择使用 ECB 模式,即电子密码本模式,这种模式不需要 IV(初始化向量),每个数据块独立加密。
- base64.b64decode(data.encode(‘utf8’)):
• data.encode(‘utf8’):将传入的 Base64 编码数据转换为字节格式。
• base64.b64decode():将 Base64 编码的密文解码成原始的加密数据(字节数组)。AES 解密的输入必须是加密的字节数组,所以这里需要先将 Base64 字符串解码。
- cipher.decrypt(text):
• cipher.decrypt():使用之前创建的 AES 对象来解密 Base64 解码后的字节数据 text。解密后的结果是一个字节数组。
- detext = result.decode(‘UTF-8’):
• 将解密后的字节数组转换为 UTF-8 格式的字符串 detext。此时,解密后的结果仍然包含了填充字符(PKCS7 填充)。
- detext[0:len(detext) - ord(detext[-1])]:
• PKCS7 填充移除:AES 加密时,块大小必须是 16 字节的倍数,因此使用了 PKCS7 填充。在解密后,需要将填充的字符移除。
• detext[-1]:取出字符串的最后一个字符(这是 PKCS7 填充的填充符的数量)。
• ord(detext[-1]):获取这个填充字符的 ASCII 值,它代表了填充字符的数量。
• detext[0:len(detext) - ord(detext[-1])]:通过计算字符串长度减去填充字符的数量,去除这些填充字符,从而恢复原始的明文数据。
接口响应AES解密
这里以一个网站为例:(仅供参考学习,不做任何其他用途)
以如下接口进行测试:
curl -X POST https://XXX.com
该接口返回的响应如下:
{
"data": "",
"secret": "a4515dfc4befd2ec"
}
现在就是在寻找这个突破点了,首先可以利用chrome 进行全局搜索这个关键词: secret
发现了这个js 代码
function Pe(e) {
return e && e.secret ? (0,
s.parseJson)(Ie(e.data, e.secret)) : e
}
1. e && e.secret:
• 这是一个逻辑检查,首先检查 e 是否存在(即 e 不为 null 或 undefined),然后检查 e 对象中是否包含 secret 属性。
• 如果 e 和 e.secret 同时存在,则执行后续的解密逻辑。如果 e 不存在或 e.secret 不存在,则直接返回 e。
2. (0, s.parseJson)(Ie(e.data, e.secret)):
• 这是函数的核心部分,首先调用了 Ie(e.data, e.secret) 函数,并将返回的结果作为参数传递给 s.parseJson 函数。
• Ie(e.data, e.secret):
• 假设 Ie 是一个解密函数,它使用 e.data(加密数据)和 e.secret(密钥或相关参数)来进行解密,返回解密后的数据。
• s.parseJson:
• 假设这是一个用于解析 JSON 数据的函数,作用是将解密后的字符串数据转换为 JavaScript 对象。parseJson 通常是用来将 JSON 字符串解析为 JavaScript 对象的函数。
3. return e:
• 如果 e 或 e.secret 不存在,则不会执行解密和 JSON 解析逻辑,而是直接返回 e 对象。
然后进行加密的探究,查看Ie函数:
//解密加密的数据 e,使用指定的密钥 t。它采用的是 AES-CBC(Cipher Block Chaining)模式,并使用了 PKCS7 填充方式。
function Ie(e, t) {
var n, o, r, a = p().enc.Utf8.parse(t), i = null !== (n = null === (o = window.TurboApply) || void 0 === o || null === (r = o.data) || void 0 === r ? void 0 : r.aesIv) && void 0 !== n ? n : "", l = p().enc.Utf8.parse(i), s = p().AES.decrypt(e, a, {
iv: l,
mode: p().mode.CBC,
padding: p().pad.Pkcs7
});
return p().enc.Utf8.stringify(s).toString()
}
这就是加密的信息了,仔细分析这个函数
- a = p().enc.Utf8.parse(t):
• t 是传入的密钥,这一行代码将它转换为 UTF-8 格式的字节数组。
• 这个转换是必要的,因为 AES 解密算法需要使用字节数组格式的密钥。
- 获取 IV(初始化向量):
• i 代表 初始化向量(IV),它通过多个条件获取。
• n = null === (o = window.TurboApply) || void 0 === o || null === (r = o.data) || void 0 === r ? void 0 : r.aesIv:
• 尝试从 window.TurboApply.data.aesIv 中获取初始化向量。
• 这里使用了 可选链(Optional Chaining),依次判断 window.TurboApply、window.TurboApply.data 和 window.TurboApply.data.aesIv 是否存在。
• 如果这些对象链中的任意一个不存在,则会返回 undefined,否则返回 r.aesIv。
• i = n !== null && n !== undefined ? n : “”:
• 如果 n 不为空且不为 undefined,则将 n 的值赋给 i,否则 i 为一个空字符串。
这里获取iv的方法:
console.log(window.TurboApply)
此时就可以拿到对应的iv值了。
解密函数:
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
def decrypt_aes(encrypted_data, key, iv):
# 将 key 和 iv 转换为字节
key_bytes = key.encode('utf-8')
iv_bytes = iv.encode('utf-8')
# base64 解码加密数据
encrypted_data_bytes = base64.b64decode(encrypted_data)
# 创建 AES CBC 模式的解密器
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
# 解密数据并移除填充
decrypted_data = unpad(cipher.decrypt(encrypted_data_bytes), AES.block_size)
# 将解密后的数据转换回 UTF-8 字符串
return decrypted_data.decode('utf-8')
# 示例使用
encrypted_data = ""
key = "XXXXXX" # secret 的值
iv = "xxx"
# 调用解密函数
decrypted_message = decrypt_aes(encrypted_data, key, iv)
print("解密后的信息:", decrypted_message)
这里的iv 该网站是一直不变的,所以比较好获得。
初始化向量(IV)在 加密时是一个非常关键的安全要素,尤其是在使用诸如 AES-CBC 这样的分组加密模式时。
IV 的作用
• IV 的主要作用是在加密相同的明文时生成不同的密文,防止攻击者通过分析密文模式推断出明文内容。
• 如果不使用 IV 或者使用相同的 IV,对于相同的明文每次加密都会产生相同的密文,这就会带来安全隐患,特别是在反复加密相似数据时。
IV 的要求
-
随机性:每次加密操作应该使用一个唯一的随机 IV。即使是同样的密钥和明文,随机生成的 IV 会确保密文不同。
-
唯一性:同一个密钥加密多个明文时,每次加密都应该使用不同的 IV,以避免相同明文产生相同的密文。
-
公开性:IV 并不需要保密,它通常与密文一起传输。加密方可以将 IV 作为密文的一部分传给解密方。
如果 IV 不变会带来的问题
如果一个网站始终使用不变的 IV,将会带来安全问题:
• 重放攻击:如果攻击者知道某些相同的明文总是生成相同的密文,他们可以通过对比不同密文的相似性进行分析,甚至猜测出部分明文内容。
• 模式识别:攻击者可以通过捕捉多个加密数据包,分析不同加密操作之间的模式,进而推断出部分明文。
因此,不建议使用固定的 IV,否则会大大降低加密的安全性。
网站实现中的 IV 设计
-
动态生成 IV:最好的做法是每次加密操作都动态生成一个随机的 IV。可以使用加密库中的随机函数来生成 16 字节的随机数(对于 AES),然后在加密时使用这个 IV。
-
IV 与密文一同传输:通常,IV 会被和加密后的密文一起传输。因为 IV 是公开的,因此可以附加在密文的前面或后面。解密时,只需要从密文中提取出 IV。