AES算法与接口解密

目录

@[TOC]

AES算法

基本介绍

高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法。

在这里插入图片描述

对称加密算法中加解密密钥都是一样的。

AES 的主要特性:

  1. 块加密:AES 是一种分组加密算法,它将数据分成固定长度的块进行加密。每个块的长度为 128 位(16 字节)。

  2. 密钥长度:AES 允许使用三种不同长度的密钥,分别为:

• 128 位(16 字节)

• 192 位(24 字节)

• 256 位(32 字节)

密钥越长,算法的安全性越高。

加密模式

常见的 AES 加密模式

  1. ECB(Electronic Codebook Mode,电子密码本模式)

• 每一个数据块独立加密。

• 简单但不安全,因为相同的明文块会生成相同的密文块,容易被攻击者通过统计方法分析。

• 不适合加密长数据,只适合短且无重复的数据。

• 不需要 IV(初始化向量)。

  1. CBC(Cipher Block Chaining Mode,密码块链模式)

• 每一个块的加密都会依赖前一个块的密文。

• 加密第一块时需要一个随机的IV(初始化向量),之后每一块的加密结果都会用于加密下一个块,形成链式结构。

安全性高,防止了相同明文块生成相同密文块的情况,常用来加密敏感数据。

• 需要一个 IV。

  1. CFB(Cipher Feedback Mode,密码反馈模式)

• 类似于 CBC,但它是一种自同步的流模式加密,可以逐字节或逐位加密数据块。

• 前面的密文会影响后面的块,加密时依然需要 IV。

  1. OFB(Output Feedback Mode,输出反馈模式)

• 和 CFB 类似,但它不会在加密过程中使用密文,而是通过生成一个加密流来加密明文。加密流是基于密钥和 IV 生成的。

• 适用于流数据。

  1. 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'。

解释:

  1. AES.new(key.encode(‘UTF-8’), AES.MODE_ECB)

• 使用 AES 库中的 new 方法创建一个 AES 加密器。

• key.encode(‘UTF-8’):将密钥 key 转换为字节数组(因为 AES 算法需要密钥为字节类型)。

AES.MODE_ECB:选择使用 ECB 模式,即电子密码本模式,这种模式不需要 IV(初始化向量),每个数据块独立加密。

  1. base64.b64decode(data.encode(‘utf8’))

• data.encode(‘utf8’):将传入的 Base64 编码数据转换为字节格式。

• base64.b64decode():将 Base64 编码的密文解码成原始的加密数据(字节数组)。AES 解密的输入必须是加密的字节数组,所以这里需要先将 Base64 字符串解码。

  1. cipher.decrypt(text)

• cipher.decrypt():使用之前创建的 AES 对象来解密 Base64 解码后的字节数据 text。解密后的结果是一个字节数组。

  1. detext = result.decode(‘UTF-8’)

• 将解密后的字节数组转换为 UTF-8 格式的字符串 detext。此时,解密后的结果仍然包含了填充字符(PKCS7 填充)。

  1. 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()
        }

这就是加密的信息了,仔细分析这个函数

  1. a = p().enc.Utf8.parse(t)

• t 是传入的密钥,这一行代码将它转换为 UTF-8 格式的字节数组。

• 这个转换是必要的,因为 AES 解密算法需要使用字节数组格式的密钥。

  1. 获取 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 的要求

  1. 随机性:每次加密操作应该使用一个唯一的随机 IV。即使是同样的密钥和明文,随机生成的 IV 会确保密文不同。

  2. 唯一性:同一个密钥加密多个明文时,每次加密都应该使用不同的 IV,以避免相同明文产生相同的密文。

  3. 公开性:IV 并不需要保密,它通常与密文一起传输。加密方可以将 IV 作为密文的一部分传给解密方。

如果 IV 不变会带来的问题

如果一个网站始终使用不变的 IV,将会带来安全问题:

重放攻击:如果攻击者知道某些相同的明文总是生成相同的密文,他们可以通过对比不同密文的相似性进行分析,甚至猜测出部分明文内容。

模式识别:攻击者可以通过捕捉多个加密数据包,分析不同加密操作之间的模式,进而推断出部分明文。

因此,不建议使用固定的 IV,否则会大大降低加密的安全性。

网站实现中的 IV 设计

  1. 动态生成 IV:最好的做法是每次加密操作都动态生成一个随机的 IV。可以使用加密库中的随机函数来生成 16 字节的随机数(对于 AES),然后在加密时使用这个 IV。

  2. IV 与密文一同传输:通常,IV 会被和加密后的密文一起传输。因为 IV 是公开的,因此可以附加在密文的前面或后面。解密时,只需要从密文中提取出 IV。

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦