一、前言
SecureCRT的加密方式有两种:旧版使用双Blowfish加密,固定密钥和随机填充;新版使用AES-CBC,密钥基于配置口令的哈希,且包含校验信息。该篇文章主要介绍新版加解密的思路,并提供解密的脚本和实操的解密效果。
二、加密逻辑
新版加密 (SecureCRTCryptoV2)
算法:AES-256 (CBC 模式),密钥基于配置口令的 SHA256 哈希。该加密支持动态口令,包含完整性校验
2.1 编码转换
将明文密码转为 UTF-8 字节。
plain_bytes = Plaintext.encode('utf-8')
2.2 添加元信息
- 前 4 字节:明文长度(小端序)
- 末尾 32 字节:明文的 SHA256 哈希值(用于完整性校验)
plain_with_meta = len(plain_bytes).to_bytes(4, 'little') + plain_bytes + SHA256(plain_bytes).digest()
2.3 填充
使用随机字节填充至 AES 块大小(16 字节对齐)。
padded_plain = plain_with_meta + os.urandom(AES.block_size - len(plain_with_meta) % AES.block_size)
2.4 加密
使用基于配置口令生成的 AES-256 密钥进行加密(IV 为全零)。
key = SHA256(ConfigPassphrase.encode()).digest()
cipher = AES.new(key, CBC, iv=全零IV)
ciphertext = cipher.encrypt(padded_plain)
注:
AES.new()生成一个 AES 对象,后续可用 .encrypt() 和 .decrypt() 方法进行加密/解密。iv(初始化向量):在CBC模式需要,长度需为 16 字节(ECB 模式 不需要 iv,但安全性较低)。需随机生成且每次加密不同,可通过os.urandom(16)生成。
2.5 输出结果
最终密文以十六进制字符串形式返回。
整个过程如下图所示:

三、解密逻辑
3.1 AES-256解密
3.1.1 密钥生成
因为AES-256解密需要对应的密钥,因此解密之前,需要还原加密时使用的 AES-256 密钥
输入参数:用户提供的 ConfigPassphrase(若未设置则为空字符串)
生成方式:
key = SHA256(config_passphrase.encode()).digest() # 输出32字节密钥
注:若配置口令错误,生成的密钥错误 → 导致后续解密数据无效 → 哈希校验失败。
因此为了提高代码鲁棒性,可以考虑加上错误检查。
3.1.2 AES-256-CBC 解密
参数:
- IV:固定为 16字节全零(
b'\x00'*16) - 密钥:上一步生成的 key
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
decrypted_data = cipher.decrypt(ciphertext_bytes)
注:
CBC 模式需要块对齐(16字节),所以如果密文无法完成块对齐,那么无法成功解密(可在解密前进行检查)。
又因为加密时已填充随机字节,所以解密后需精确截取有效数据。
解密后的数据格式如下:

即前面的4字节(红色框)指明了明文的长度,我们可以根据明文长度(3.2节),截取出对应的明文(3.3节)。
3.2 读取明文长度
plain_length = int.from_bytes(decrypted_data[0:4], 'little') # 小端序解析
为什么是小端序呢。使用小端序还是大端序,得和加密部分对应。
即如果加密使用小端序,那么解密也对应使用小端序。大端序同理。
根据实测,SecureCRT加密时使用了小端序,因此我们便对应使用小端序进行解密。
3.3 提取明文数据
plain_bytes = decrypted_data[4 : 4 + plain_length]
从第4字节开始,取 plain_length 字节,提取哈希校验值
3.4 完整性校验
这一步不是必须的,但是是解密完整步骤中的重要一环。
3.4.1 提取SHA256校验值
expected_hash = decrypted_data[4 + plain_length : 4 + plain_length + 32]
SHA校验值位于明文数据后的32字节。
3.4.2 哈希完整性校验
验证解密数据的完整性和口令正确性。如果校验不通过,那么直接说明我们拿到的不是实际的明文密码。有以下的可能情况:
- 因密钥错误(3.1.1节生成的密钥有误),导致解密出的
plain_bytes和expected_hash有误。 - 密文被篡改,导致解密后的结构化数据损坏
actual_hash = SHA256.new(plain_bytes).digest()
if actual_hash != expected_hash:
raise ValueError("Hash mismatch")
3.5 明文解码
将UTF-8字节数据转为字符串
return plain_bytes.decode('utf-8')
整个解密代码如下所示:
from Crypto.Hash import SHA256
from Crypto.Cipher import AES
def decrypt_securecrt_v2(ciphertext_hex, config_passphrase=''):
# ciphertext_hex (str): 十六进制格式的密文字符串
# config_passphrase (str): 配置口令(若未设置则留空)
# 将密文转换为字节
ciphertext = bytes.fromhex(ciphertext_hex)
# 生成解密密钥3.1.1
key = SHA256.new(config_passphrase.encode('utf-8')).digest()
# 初始化AES解密器(IV为全零)
iv = b'\x00' * AES.block_size
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
# 执行AES解密3.1.2
decrypted_data = cipher.decrypt(ciphertext)
# 解析数据结构(格式:4字节长度 + 明文 + 32字节SHA256)
if len(decrypted_data) < 4 + 32:
raise ValueError("密文长度过短")
# 提取明文长度(小端序)3.2
plain_length = int.from_bytes(decrypted_data[0:4], 'little')
# 提取明文部分3.3
plain_start = 4
plain_end = plain_start + plain_length
plain_bytes = decrypted_data[plain_start:plain_end]
# 提取校验哈希3.4.1
hash_start = plain_end
expected_hash = decrypted_data[hash_start:hash_start + 32]
# 验证哈希完整性3.4.2
actual_hash = SHA256.new(plain_bytes).digest()
if actual_hash != expected_hash:
raise ValueError("哈希校验失败,可能配置口令错误或数据被篡改")
# 返回UTF-8解码结果3.5
return plain_bytes.decode('utf-8')
if __name__ == "__main__":
sample_cipher = "67ce8baadd44bc43bbaee41041234f0c1373125cf65bbfa708f0e17f42e510266ab9eecdd26b11c4f26bc42fc7430dd7cf954710580db4cceac2e538c0d87043"
try:
# 情况1:没有配置口令
plain = decrypt_securecrt_v2(sample_cipher)
# 情况2:有配置口令
# plain = decrypt_securecrt_v2(sample_cipher, "mypass")
print(f"解密结果: {plain}")
except Exception as e:
print(f"解密失败: {str(e)}")
四、实操
4.1 从ini文件中获取密文密码

密文即图中的绿色选中部分。
4.2 替换值并运行解密脚本

原创,转发请注明。

1万+

被折叠的 条评论
为什么被折叠?



