Navicat 11 存储的连接密码提取与解密

题目背景

从 Windows 用户目录中的 NTUSER.DAT 注册表文件提取 Navicat 保存的数据库连接密码。

步骤

1. 定位注册表文件

NTUSER.DAT 位于用户目录 Users\chenchen\ 下,这是 Windows 用户配置的注册表 hive 文件。

2. 解析注册表

使用 python-registry 库解析 NTUSER.DAT:

1
2
3
from Registry import Registry
reg = Registry.Registry("NTUSER.DAT")
premiumsoft = reg.open("Software\\PremiumSoft")

Navicat 保存连接信息的路径:Software\PremiumSoft\Navicat\Servers\<连接名>

3. 提取加密密码

发现 3 个 MySQL 连接,用户名均为 root:

连接名 主机 密文 (Pwd)
ceshi localhost:3306 BE536F6210406B878B
measu 10.xxx.xxx.xxx:3308 99E5C424961F27FC2FD4
widi 57.xxx.xxx.xxx:33032 CA50766B9665F921CC0CD065CA

4. 解密算法

参考 https://github.com/tianhe1986/FatSmallTools

Navicat 11 使用 Blowfish 加密:

  • Key: SHA1(“3DC5CA39”)
  • IV: 0xd9c7c3c8870d64bd
  • 模式: ECB + 自定义 XOR 链
1
2
3
4
5
6
7
8
9
10
11
from Crypto.Cipher import Blowfish
from hashlib import sha1

key = sha1(b'3DC5CA39').digest()
iv = bytes([0xd9, 0xc7, 0xc3, 0xc8, 0x87, 0x0d, 0x64, 0xbd])
cipher = Blowfish.new(key, Blowfish.MODE_ECB)

# 解密流程:每8字节一组
# 1. Blowfish ECB 解密
# 2. 与当前 IV 异或得到明文
# 3. 更新 IV = IV XOR 密文块

5. 解密结果

连接名 主机 用户名 密码
ceshi localhost:3306 root 19ieudgla
measu 10.xxx.xxx.xxx:3308 root lala789we@
widi 57.xxx.xxx.xxx:33032 root s76deq3yuia87

完整解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
from Crypto.Cipher import AES, Blowfish
from hashlib import sha1
import binascii

class NavicatPassword:
def __init__(self, version=11):
self.version = version
# Navicat 12: AES-128-CBC
self.aes_key = b'libcckeylibcckey'
self.aes_iv = b'libcciv libcciv '
# Navicat 11: Blowfish
self.blowfish_key = sha1(b'3DC5CA39').digest()
self.blowfish_iv = bytes([0xd9, 0xc7, 0xc3, 0xc8, 0x87, 0x0d, 0x64, 0xbd])

def xor_bytes(self, a, b):
return bytes([x ^ y for x, y in zip(a, b)])

def decrypt(self, encrypted):
if self.version == 12:
return self.decrypt_twelve(encrypted)
else:
return self.decrypt_eleven(encrypted)

def decrypt_twelve(self, encrypted):
"""Navicat 12 使用 AES-128-CBC"""
encrypted_bytes = binascii.unhexlify(encrypted)
if len(encrypted_bytes) % 16 != 0:
encrypted_bytes += b'\x00' * (16 - len(encrypted_bytes) % 16)
cipher = AES.new(self.aes_key, AES.MODE_CBC, self.aes_iv)
decrypted = cipher.decrypt(encrypted_bytes)
return decrypted.rstrip(b'\x00').decode('utf-8', errors='ignore')

def decrypt_eleven(self, encrypted):
"""Navicat 11 使用 Blowfish + 自定义 XOR 链"""
encrypted_bytes = binascii.unhexlify(encrypted)
result = b''
current_vector = self.blowfish_iv
cipher = Blowfish.new(self.blowfish_key, Blowfish.MODE_ECB)

# 处理完整的 8 字节块
round_count = len(encrypted_bytes) // 8
left_length = len(encrypted_bytes) % 8

for i in range(round_count):
block = encrypted_bytes[i * 8:(i + 1) * 8]
decrypted_block = cipher.decrypt(block)
temp = self.xor_bytes(decrypted_block, current_vector)
current_vector = self.xor_bytes(current_vector, block)
result += temp

# 处理剩余字节
if left_length > 0:
remaining = encrypted_bytes[round_count * 8:]
encrypted_vector = cipher.encrypt(current_vector)
result += self.xor_bytes(remaining, encrypted_vector[:left_length])

return result.rstrip(b'\x00').decode('utf-8', errors='ignore')

def encrypt(self, plaintext):
if self.version == 12:
return self.encrypt_twelve(plaintext)
else:
return self.encrypt_eleven(plaintext)

def encrypt_eleven(self, plaintext):
"""Navicat 11 加密"""
plaintext_bytes = plaintext.encode('utf-8')
result = b''
current_vector = self.blowfish_iv
cipher = Blowfish.new(self.blowfish_key, Blowfish.MODE_ECB)

round_count = len(plaintext_bytes) // 8
left_length = len(plaintext_bytes) % 8

for i in range(round_count):
block = plaintext_bytes[i * 8:(i + 1) * 8]
temp = self.xor_bytes(block, current_vector)
encrypted_block = cipher.encrypt(temp)
current_vector = self.xor_bytes(current_vector, encrypted_block)
result += encrypted_block

if left_length > 0:
remaining = plaintext_bytes[round_count * 8:]
encrypted_vector = cipher.encrypt(current_vector)
result += self.xor_bytes(remaining, encrypted_vector[:left_length])

return result.hex().upper()

def encrypt_twelve(self, plaintext):
"""Navicat 12 加密"""
plaintext_bytes = plaintext.encode('utf-8')
if len(plaintext_bytes) % 16 != 0:
plaintext_bytes += b'\x00' * (16 - len(plaintext_bytes) % 16)
cipher = AES.new(self.aes_key, AES.MODE_CBC, self.aes_iv)
encrypted = cipher.encrypt(plaintext_bytes)
return encrypted.hex().upper()


if __name__ == '__main__':
# 从 NTUSER.DAT 提取的密文
passwords = {
'ceshi (localhost:3306, root)': 'BE536F6210406B878B',
'measu (10.xxx.xxx.xxx:3308, root)': '99E5C424961F27FC2FD4',
'widi (57.xxx.xxx.xxx:33032, root)': 'CA50766B9665F921CC0CD065CA',
}

nav = NavicatPassword(version=11)
for name, enc_pwd in passwords.items():
decrypted = nav.decrypt(enc_pwd)
print(f"\n连接: {name}")
print(f"密文: {enc_pwd}")
print(f"明文: {decrypted}")