使用Python脚本解密Navicat保存的数据库连接密码
在数据库管理过程中,Navicat是一款广泛使用的工具。然而,有时用户可能会忘记保存在Navicat中的数据库连接密码,或者在迁移配置时发现这些密码已被加密且无法直接读取。本文将介绍Navicat密码的加密原理,并提供一个Python脚本来帮助用户找回这些遗忘的密码。
Navicat密码加密机制
Navicat为了保护用户数据的安全,会对存储的密码进行加密。了解其加密方式是找回密码的关键。
加密算法与固定密钥
Navicat采用AES-128-CBC(Cipher Block Chaining)加密模式。这是一种对称加密算法,意味着加密和解密使用相同的密钥。Navicat的加密过程中,使用的密钥(Key)和初始化向量(Initialization Vector, IV)是固定的:
KEY = b'libcckeylibcckey' # 16字节的AES密钥
IV = b'libcciv libcciv ' # 16字节的初始化向量
由于密钥和IV是公开的,任何了解这些信息的人都可以解密Navicat加密的密码。虽然这并非最安全的做法,但它为密码恢复提供了便利。
密码加密流程
当你在Navicat中保存一个数据库连接时,其密码的处理流程如下:
- 将明文密码转换为UTF-8编码的字节序列。
- 使用PKCS7填充机制处理字节序列,确保其长度能被AES块大小整除。
- 使用AES-128-CBC算法、上述固定密钥和IV对填充后的数据进行加密。
- 将加密后的字节序列转换为大写的十六进制字符串进行存储。
需要注意的是,尽管此方法可以找回密码,但也意味着导出的Navicat连接配置文件(.ncx文件)中包含了可被解密的数据,因此应妥善保管这些文件。
获取加密密码
在进行解密操作前,需要先从Navicat的配置文件中提取加密后的密码字符串。
导出连接配置
- 启动Navicat。
- 选择菜单栏中的"文件" -> "导出连接"。
- 在弹出的窗口中,选择包含需要恢复密码的数据库连接。
- 将连接导出为
.ncx文件格式。
定位加密密码字符串
使用任何文本编辑器打开导出的 .ncx 文件。在文件中搜索 Password 标签,你将找到类似以下的加密字符串:
<Connection ...>
<Password>A1B2C3D4E5F678901234567890ABCDEF</Password>
...
</Connection>
<Password> 标签内的十六进制字符串就是待解密的加密密码。
Python解密脚本实现
下面是一个Python脚本,用于解密从Navicat导出的密码。
安装必要的库
首先,需要安装 cryptography 库。如果尚未安装,请在终端或命令提示符中运行以下命令:
pip install cryptography
编写解密函数
创建一个Python文件(例如 navicat_decrypt.py),并添加以下代码:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
import binascii
import xml.etree.ElementTree as ET
# Navicat固定的AES密钥和初始化向量
KEY = b'libcckeylibcckey'
IV = b'libcciv libcciv '
def decrypt_navicat_password(encrypted_password_hex):
"""
解密Navicat加密的密码字符串。
Args:
encrypted_password_hex: Navicat保存的十六进制大写加密密码字符串。
Returns:
解密后的明文密码字符串,如果解密失败则返回None。
"""
try:
# 将十六进制字符串转换为字节
encrypted_bytes = binascii.unhexlify(encrypted_password_hex)
# 创建AES-128-CBC解密器
cipher = Cipher(algorithms.AES(KEY), modes.CBC(IV), backend=default_backend())
decryptor = cipher.decryptor()
# 执行解密
decrypted_padded_bytes = decryptor.update(encrypted_bytes) + decryptor.finalize()
# PKCS7解填充
# 注意:Navicat的填充可能与标准的PKCS7略有不同,这里尝试标准解填充
# 如果标准解填充不成功,可能需要根据实际填充字节进行调整
# PKCS7填充的标准是,填充的字节值等于填充的字节数
# 例如,如果需要填充3个字节,则每个填充字节的值都是0x03
# 查找最后一个字节的值,该值即为填充的字节数
padding_len = decrypted_padded_bytes[-1]
# 验证填充是否正确(可选,但推荐)
# 检查最后padding_len个字节是否都等于padding_len
is_correct_padding = all(p == padding_len for p in decrypted_padded_bytes[-padding_len:])
if not is_correct_padding:
# 尝试另一种常见的填充处理方式,有时密文末尾会有额外的NULL字节
# 这种情况在Navicat中不常见,但可以作为备选
while decrypted_padded_bytes.endswith(b'\x00'):
decrypted_padded_bytes = decrypted_padded_bytes[:-1]
# 如果依然不成功,返回错误
# raise ValueError("Invalid padding") # 抛出异常会中断执行,这里选择返回None
return None # 或者直接返回None表示解密失败
# 移除填充
decrypted_bytes = decrypted_padded_bytes[:-padding_len]
# 将解密后的字节串解码为UTF-8字符串
return decrypted_bytes.decode('utf-8')
except (binascii.Error, ValueError, TypeError) as e:
print(f"解密失败: {e}")
return None
except Exception as e:
print(f"发生未知错误: {e}")
return None
def find_and_decrypt_passwords_in_ncx(ncx_file_path):
"""
解析.ncx文件,找到所有加密密码并尝试解密。
Args:
ncx_file_path: .ncx配置文件的路径。
Returns:
一个字典,键为连接名称,值为解密后的密码。
"""
try:
tree = ET.parse(ncx_file_path)
root = tree.getroot()
connections_data = {}
# 遍历所有Connection节点
for connection_node in root.findall('.//Connection'):
connection_name_element = connection_node.find('Name')
password_element = connection_node.find('Password')
if connection_name_element is not None and password_element is not None:
connection_name = connection_name_element.text
encrypted_password = password_element.text
if encrypted_password:
decrypted_password = decrypt_navicat_password(encrypted_password)
if decrypted_password:
connections_data[connection_name] = decrypted_password
else:
connections_data[connection_name] = "[解密失败]"
else:
connections_data[connection_name] = "[无密码]"
elif connection_name_element is not None:
connections_data[connection_name_element.text] = "[无密码字段]"
return connections_data
except FileNotFoundError:
print(f"错误: 文件未找到 - {ncx_file_path}")
return None
except ET.ParseError:
print(f"错误: 无法解析XML文件 - {ncx_file_path}")
return None
except Exception as e:
print(f"处理文件时发生错误: {e}")
return None
if __name__ == "__main__":
# 示例用法:
# 1. 替换 'your_connections.ncx' 为你实际导出的.ncx文件路径
# 2. 脚本会打印出找到的连接名称和对应的明文密码
ncx_file = 'your_connections.ncx' # !!! 请将这里替换为你的.ncx文件路径 !!!
print(f"正在从文件 '{ncx_file}' 中查找并解密密码...\n")
decrypted_passwords = find_and_decrypt_passwords_in_ncx(ncx_file)
if decrypted_passwords:
if not decrypted_passwords:
print("在文件中未找到任何数据库连接配置。")
else:
print("="*30)
print(" 解密结果:")
print("="*30)
for name, pwd in decrypted_passwords.items():
print(f"连接名: {name:<25} 密码: {pwd}")
print("="*30)
else:
print("未能成功处理.ncx文件,请检查文件路径和格式。")
# 也可以直接测试单个加密密码
# test_encrypted_pwd = "CFCB9D0434693474F8C70FC796C7F0F3" # 示例加密密码
# decrypted_single = decrypt_navicat_password(test_encrypted_pwd)
# print(f"\n测试单个密码解密:")
# print(f"加密: {test_encrypted_pwd}")
# print(f"解密: {decrypted_single}")
如何使用脚本
- 将上面提供的Python代码保存为一个
.py文件(例如navicat_decrypt.py)。 - 确保你已经按照"准备工作"中的步骤导出了Navicat连接配置为
.ncx文件。 - 修改脚本中的
ncx_file = 'your_connections.ncx'这一行,将'your_connections.ncx'替换为你实际的.ncx文件路径。 - 在终端或命令提示符中,导航到保存脚本的目录,然后运行脚本:
python navicat_decrypt.py。 - 脚本将输出找到的所有数据库连接名称以及它们对应的明文密码。如果某个密码未能成功解密,将显示
[解密失败]。
通过这个脚本,你可以轻松找回遗忘的Navicat连接密码,从而更高效地管理你的数据库。
