最近在学习Js逆向时发现AES涉及的知识点比较多,编写对应的Py脚本时涉及的细节也比较多,就导致了会出现各种各样的小bug,所以心血来潮写一篇文章进行一个系统的总结(学艺不精,如有错误,欢迎各位师傅指正)
1、AES简介
AES是最为常见的对称加密算法(对称加密就是加密与解密使用的秘钥是一个),大致的加密流程如下
含义介绍
明文P:等待加密的数据
秘钥K:用来加密明文的密码,在对称加密算法中,加密与解密的密钥是相同的。密钥为接收方与发送方协商产生,但不可以直接在网络上传输,否则会导致密钥泄漏,例如我们在进行Js逆向时,得到了该秘钥就可以尝试解密网站中的加密数据
AES加密函数:设 AES 加密函数为 E,则 C = E(K, P),其中 P 为明文,K 为密钥,C 为密文。也就是说,把明文 P 和密钥 K 作为加密函数的参数输入,则加密函数 E 会输出密文 C
密文 C:经过 AES 加密后的数据
AES 解密函数:设 AES 解密函数为 D,则 P = D(K, C),其中 C 为密文,K 为密钥,P 为明文。也就是说,把密文 C 和密钥 K 作为解密函数的参数输入,则解密函数会输出明文 P
实现 AES 有几种模式,主要有 ECB、CBC、CFB 和 OFB 这几种。本章主要介绍最常用的 ECB 和 CBC 模式。
由于我们主要是研究Js逆向中的AES加密,只需要知晓其基本概念和用法即可,详细的加密逻辑暂时不用深究
2、加密模式_ECB
ECB是AES几种加密模式中最简单的加密模式,简单来说就是将明文切分为若干个组分别加密,实际上有很明显的弱点,就是相同的明文会得到同样的密文。因为每个分组加密方式和密钥都相同,若分组明文相同,加密后密文也相同。
这里给出我自己写的利用Python3实现AES_ECB模式的加解密完整demo,每一步都会在注释中进行详细的解释,首先是AES_ECB加密代码,如下
def encrypt(key, text):
# 确保密钥长度是16
key = key[:16] # 截断16位
# 因为AES加密算法处理的是字节数据,所以需要将密钥转化为bytes类型
text_bytes = text.encode('utf-8')
# 由于AES加密要求输入数据的长度必须是块大小的整数倍(AES的块大小是16字节)
# 使用pad函数来填充数据,以确保其长度是16的倍数
padded_text = pad(text_bytes, AES.block_size)
# 初始化AES加密器(使用ECB模式)
aes = AES.new(key, AES.MODE_ECB)
# 加密
encrypt_aes = aes.encrypt(padded_text)
# 注意:加密后的数据是字节串,可能包含无法直接以文本形式存储或传输的字符
# 形如b'x036x19xe1x12x15xa7x9bHx16!'
# 加密后的字节串编码为Base64格式的字节串
encrypted_text = base64.b64encode(encrypt_aes)
# 然后.decode('utf-8')将字节串转换为字符串
# 形如b'AzYZxk2RHr7uq6cN8SVA/hOmMGx+Nfc54RIVp5tIFiE='
encrypted_text = encrypted_text.decode('utf-8')
# 形如AzYZxk2RHr7uq6cN8SVA/hOmMGx+Nfc54RIVp5tIFiE=
return encrypted_text
AES_ECB模式只需要秘钥key即可对明文进行加密,所以这里传入key(必须是16位)和要加密的明文text,并且aes的加解密函数,形如
encrypt_aes = aes.encrypt(padded_text)
处理的都是字节串,也就是padded_text必须是个字节串,这里是加密函数,解密函数也是一样,所以在调用该数据进行加解密前,可以通过
# 将text字符串转为字节串
text_bytes = text.encode('utf-8')
# 相反的,下面这样就是将字节串转为字符串
text = text_bytes.decode('utf-8')
字节串和字符串看下面两个就已经可以区分了
# 字符串
text = 'AzYZxk2RHr7uq6cN8SVA/hOmMGx+Nfc54RIVp5tIFiE='
# 字节串
# aes加解密函数,处理的就是字节串
text = b'AzYZxk2RHr7uq6cN8SVA/hOmMGx+Nfc54RIVp5tIFiE='
注意调用加密函数得到的结果是个字节串,不利用阅读传输和存储,所通过是将接加密后得到的字节串进行base64编码后再转为字符串,从而得到最终的密文
def decrypt(key, text):
# 初始化加密器
aes = AES.new(key[:16], AES.MODE_ECB)
# 注意:无论什么格式的数据,base64解码后得到的一定是字节串!!!
# 如果是加密,则编码后的数据类型和编码前相同
# 所以这里对字符串进行base64解码,得到字节串,中间不需要主动进行字符串转字节串的操作
# 解密之后形如b'x036x19xe1x12x15xa7x9bHx16!'的字节串
base64_decrypted = base64.b64decode(text)
# 调用AES解密函数解密字节串
# 解密之后形如b'Hello, AES Encryption!nnnnnnnnnn'的字节串
decrypted_padded = aes.decrypt(base64_decrypted)
# 去除填充部分,然后将字节串改为字符串
decrypted_text = unpad(decrypted_padded, AES.block_size).decode('utf-8')
# 最后得到完整明文
return decrypted_text
这个代码也很容易理解,但有两个需要注意的地方,一个是传入的密文text是字符串,但是此处不需要先转化为字节串再base64解码。而是直接进行base64解码,原因是任何类型的数据,在进行base64解码之后,得到的结果都是字节串,可以看下面一个小demo
import base64
# 假设这是一个Base64编码的字符串
encoded_str = "SGVsbG8gV29ybGQ="
# 解码Base64字符串
decoded_bytes = base64.b64decode(encoded_str)
# 打印解码后的字节串,可以看到得到的结果从字符串变为了字节串
print(decoded_bytes) # 输出: b'Hello World'
# 只有将字节串结果进行utf-8编码,才能得到字符串
decoded_str = decoded_bytes.decode('utf-8')
print(decoded_str) # 输出: Hello World
所以此处在调用aes解码函数前,不需要手动进行字符串转字节串,然后再去对字节串进行base64解码(因为base64解码的结果会强制转换五位字节串)
第二个需要注意的地方就是去除填充部分,这个部分需要跟加密函数中的填充代码进行比对,从而理解填充这个操作
最后给出一个完整的AES_ECB模式的加解密以及示例demo
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
# 加密函数
def encrypt(key, text):
# 确保密钥长度是16
key = key[:16] # 截断16位
# 因为AES加密算法处理的是字节数据,所以需要将密钥转化为bytes类型
text_bytes = text.encode('utf-8')
# 由于AES加密要求输入数据的长度必须是块大小的整数倍(AES的块大小是16字节)
# 使用pad函数来填充数据,以确保其长度是16的倍数
padded_text = pad(text_bytes, AES.block_size)
# 初始化AES加密器(使用ECB模式)
aes = AES.new(key, AES.MODE_ECB)
# 加密
encrypt_aes = aes.encrypt(padded_text)
# 注意:加密后的数据是字节串,可能包含无法直接以文本形式存储或传输的字符
# 形如b'x036x19xe1x12x15xa7x9bHx16!'
# 加密后的字节串编码为Base64格式的字节串
encrypted_text = base64.b64encode(encrypt_aes)
# 然后.decode('utf-8')将字节串转换为字符串
# 形如b'AzYZxk2RHr7uq6cN8SVA/hOmMGx+Nfc54RIVp5tIFiE='
encrypted_text = encrypted_text.decode('utf-8')
# 形如AzYZxk2RHr7uq6cN8SVA/hOmMGx+Nfc54RIVp5tIFiE=
return encrypted_text
def decrypt(key, text):
# 初始化加密器
aes = AES.new(key[:16], AES.MODE_ECB)
# 注意:无论什么格式的数据,base64解码后得到的一定是字节串!!!
# 如果是加密,则编码后的数据类型和编码前相同
# 所以这里对字符串进行base64解码,得到字节串,中间不需要主动进行字符串转字节串的操作
# 解密之后形如b'x036x19xe1x12x15xa7x9bHx16!'的字节串
base64_decrypted = base64.b64decode(text)
# 调用AES解密函数解密字节串
# 解密之后形如b'Hello, AES Encryption!nnnnnnnnnn'的字节串
decrypted_padded = aes.decrypt(base64_decrypted)
# 去除填充部分,然后将字节串改为字符串
decrypted_text = unpad(decrypted_padded, AES.block_size).decode('utf-8')
# 最后得到完整明文
return decrypted_text
# 示例使用
key = b'thisisakey123456'
text = 'Hello, AES Encryption!'
encrypted = encrypt(key, text)
print("Encrypted:", encrypted)
decrypted = decrypt(key, encrypted)
print("Decrypted:", decrypted)
运行之后结果如下
成功实现了对明文的加密和解密,感兴趣的师傅可以复制代码自己下去试试,结合代码的注释,很快就能理解了
3、加密模式_CBC
AES_CBC加密模式算是AES几个模式中使用的最多的,因为它的安全性比ECB模式高了很多,在这种方法中,每个密文块都依赖于它前面的所有密文块。同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量iv
AES_CBC模式的加解密,在代码中相比于AES_ECB模式多了一个iv(初始化向量)需要处理,所以这里直接给出AES_CBC的完整加解密Demo,如下
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
# 加密函数
def encrypt(key, iv, text):
key = key[:16]
# 转换明文字符串为为字节串
text_bytes = text.encode('utf-8')
# 填充
padded_text = pad(text_bytes, AES.block_size)
# 初始化AES加密器(使用CBC模式,需要传入iv)
aes = AES.new(key, AES.MODE_CBC, iv)
# 加密
encrypt_aes = aes.encrypt(padded_text)
# 加密后的字节串编码为Base64格式的字节串
encrypted_text = base64.b64encode(encrypt_aes)
# 转换为字符串
encrypted_text = encrypted_text.decode('utf-8')
return encrypted_text
# 解密函数
def decrypt(key, iv, text):
# 解码Base64
base64_decrypted = base64.b64decode(text)
# 初始化AES解密器(使用CBC模式)
aes = AES.new(key[:16], AES.MODE_CBC, iv)
# 解密
decrypted_padded = aes.decrypt(base64_decrypted)
# 去除填充部分
decrypted_text = unpad(decrypted_padded, AES.block_size).decode('utf-8')
return decrypted_text
# 秘钥
key = b'thisisakey123456'
# 初始化向量长度必须与块大小相同(AES为16字节)
# 初始化向量
iv = b'1234567812345678'
text = 'Hello, AES Encryption with CBC Mode!'
# 加密
encrypted = encrypt(key, iv, text)
print("Encrypted:", encrypted)
# 解密时需要使用相同的IV
decrypted = decrypt(key, iv, encrypted)
print("Decrypted:", decrypted)
大致流程与AES_ECB相同,只不过在初始化加密器和解密器时,带上了一个初始化向量iv,这个iv也必须与AES块大小相同,即16字节,运行效果如下(如果key和iv固定,那相同明文得到的密文也固定)
在实际逆向中,通常寻找目标的key与iv来对站点的加密数据进行解密,如下
4、总结
除了这两种常见的模式之外,AES还有CFB,OFB等模式,但这两种是Js逆向中最常见的,所以这两种是必须要理解的,其他模式等遇到的时候再去了解就行,今天的文章就到这里啦,有任何的意见和建议也欢迎各位师傅交流!!
5、杂谈
另外我本人的课程在9月底也会加入Js逆向的内容哦,通过基础代码讲解,实战Js逆向案例分析,提供Js逆向练习站点,让你快速上手Js逆向!
强力亲推:专属于大学生的SRC漏洞挖掘课程来啦!!
等9月底Js逆向课程开始之后可能会小幅度涨价,但老学员不受影响,所以欢迎有兴趣的师傅们咨询课程哦
原文始发于微信公众号(Daylight庆尘):JS逆向入门:AES加解密及其Python脚本实现与解析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论