如何快速找回Navicat本地数据库密码?

前言

在日常工作中,数据库密码的管理至关重要。然而,难免会遇到忘记密码的情况,比如我之前就遇到了MySQL密码遗忘的困扰,而Navicat是我日常工作中最常用的数据库连接工具,因此我自然而然地想到通过Navicat来恢复之前保存的连接密码。于是,我撰写了这篇文章,分享如何通过Navicat恢复已保存的数据库密码。

近期,不少读者反馈之前的方法已经失效,因此我特意寻找并测试了新的解决方案。

本文基于 Navicat Premium 16 版本进行测试,测试日期为 2024年12月16日

尽管本文提供了通过Navicat找回密码的方法,但我并不建议长期依赖此方式。原因在于,随着软件版本的升级或加密策略的调整,这种方法可能会因不可控因素而再次失效。因此,更值得我们关注的是:当数据库密码遗忘时,如何从根本上解决问题?

以MySQL为例,网上有大量详细的文章介绍如何重置MySQL密码。通过学习这些方法,我们不仅能有效解决实际工作中的问题,还能掌握一种“永久有效”的解决方案。这种方法不会因软件更新或加密策略变化而过时,是更为可靠的选择。

总之,本文旨在提供一种临时的解决方案,但更推荐大家深入学习数据库密码重置的方法,以应对未来可能出现的类似问题。

最新方案

第一步,获取连接密文

文件 -> 导出连接

image-20241216193803351

选择要导出的连接,勾选导出密码

image-20241216193900840

使用文本编辑器(如Notepad–)打开文件,找到 Password 获取密文

image-20241216194129033

第二步,使用在线工具解密

打开 Java在线工具,粘贴以下代码

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;

/**
* 用于处理 Navicat 数据库连接密码加密和解密的工具类
* @author <a href="https://github.com/shiguang-coding">時光</a>
* @github <a href="https://github.com/Shiguang-coding/NavicatPasswordUtil">NavicatPasswordUtil</a>
* @blog <a href="https://blog.shiguangdev.cn/2022/01/22/26622ef15f70">如何快速找回Navicat本地数据库密码?</a>
*/
public class NavicatPasswordUtil {

public static void main(String[] args) throws Exception {
// 创建 NavicatPasswordUtil 实例
NavicatPasswordUtil passwordUtil = new NavicatPasswordUtil();

// 待解密的密码字符串
String encryptedPassword = "6EE58FD042645AF6E22B8E376B8EA727";

// 解密 Navicat 12 及以后的版本
String decryptedPassword = passwordUtil.decryptPassword(encryptedPassword, NavicatVersion.VERSION_12);

// 正则替换控制符(如响铃、退格等)
decryptedPassword = decryptedPassword.replaceAll("\\p{Cntrl}", "");

// 输出解密后的明文 结果为 shiguang
System.out.println("解密后的密码: " + decryptedPassword);
}

// AES 加密密钥
private static final String AES_KEY = "libcckeylibcckey";
// AES 加密向量
private static final String AES_IV = "libcciv libcciv ";
// Blowfish 加密密钥
private static final String BLOWFISH_KEY = "3DC5CA39";
// Blowfish 加密向量
private static final String BLOWFISH_IV = "d9c7c3c8870d64bd";

/**
* 加密密码
*
* @param plaintextPassword 明文密码
* @param navicatVersion 加密版本(NavicatVersion.VERSION_11 或 NavicatVersion.VERSION_12)
* @return 加密后的密文密码
* @throws Exception 加密过程中可能抛出的异常
*/
public String encryptPassword(String plaintextPassword, NavicatVersion navicatVersion) throws Exception {
switch (navicatVersion) {
case VERSION_11:
return encryptBlowfish(plaintextPassword);
case VERSION_12:
return encryptAES(plaintextPassword);
default:
throw new IllegalArgumentException("不支持的 Navicat 版本");
}
}

/**
* 解密密码
*
* @param encryptedPassword 密文密码
* @param navicatVersion 解密版本(NavicatVersion.VERSION_11 或 NavicatVersion.VERSION_12)
* @return 解密后的明文密码
* @throws Exception 解密过程中可能抛出的异常
*/
public String decryptPassword(String encryptedPassword, NavicatVersion navicatVersion) throws Exception {
switch (navicatVersion) {
case VERSION_11:
return decryptBlowfish(encryptedPassword);
case VERSION_12:
return decryptAES(encryptedPassword);
default:
throw new IllegalArgumentException("不支持的 Navicat 版本");
}
}

/**
* 使用 Blowfish 加密密码(适用于 Navicat 11 及以前的版本)
*
* @param plaintextPassword 明文密码
* @return 加密后的密文密码
* @throws Exception 加密过程中可能抛出的异常
*/
private String encryptBlowfish(String plaintextPassword) throws Exception {
byte[] iv = hexStringToByteArray(BLOWFISH_IV);
byte[] key = hashToBytes(BLOWFISH_KEY);

int round = plaintextPassword.length() / 8;
int leftLength = plaintextPassword.length() % 8;
StringBuilder encryptedResult = new StringBuilder();
byte[] currentVector = iv.clone();

Cipher cipher = Cipher.getInstance("Blowfish/ECB/NoPadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "Blowfish");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);

for (int i = 0; i < round; i++) {
byte[] block = xorBytes(plaintextPassword.substring(i * 8, (i + 1) * 8).getBytes(), currentVector);
byte[] encryptedBlock = cipher.doFinal(block);
currentVector = xorBytes(currentVector, encryptedBlock);
encryptedResult.append(bytesToHex(encryptedBlock));
}

if (leftLength > 0) {
currentVector = cipher.doFinal(currentVector);
byte[] block = xorBytes(plaintextPassword.substring(round * 8).getBytes(), currentVector);
encryptedResult.append(bytesToHex(block));
}

return encryptedResult.toString().toUpperCase();
}

/**
* 使用 AES 加密密码(适用于 Navicat 12 及以后的版本)
*
* @param plaintextPassword 明文密码
* @return 加密后的密文密码
* @throws Exception 加密过程中可能抛出的异常
*/
private String encryptAES(String plaintextPassword) throws Exception {
byte[] iv = AES_IV.getBytes();
byte[] key = AES_KEY.getBytes();

Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);

byte[] encryptedResult = cipher.doFinal(plaintextPassword.getBytes());
return bytesToHex(encryptedResult).toUpperCase();
}

/**
* 使用 Blowfish 解密密码(适用于 Navicat 11 及以前的版本)
*
* @param encryptedPassword 密文密码
* @return 解密后的明文密码
* @throws Exception 解密过程中可能抛出的异常
*/
private String decryptBlowfish(String encryptedPassword) throws Exception {
byte[] iv = hexStringToByteArray(BLOWFISH_IV);
byte[] key = hashToBytes(BLOWFISH_KEY);
byte[] encryptedBytes = hexStringToByteArray(encryptedPassword.toLowerCase());

int round = encryptedBytes.length / 8;
int leftLength = encryptedBytes.length % 8;
StringBuilder decryptedResult = new StringBuilder();
byte[] currentVector = iv.clone();

Cipher cipher = Cipher.getInstance("Blowfish/ECB/NoPadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "Blowfish");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);

for (int i = 0; i < round; i++) {
byte[] encryptedBlock = Arrays.copyOfRange(encryptedBytes, i * 8, (i + 1) * 8);
byte[] decryptedBlock = xorBytes(cipher.doFinal(encryptedBlock), currentVector);
currentVector = xorBytes(currentVector, encryptedBlock);
decryptedResult.append(new String(decryptedBlock));
}

if (leftLength > 0) {
currentVector = cipher.doFinal(currentVector);
byte[] block = Arrays.copyOfRange(encryptedBytes, round * 8, round * 8 + leftLength);
decryptedResult.append(new String(xorBytes(block, currentVector), StandardCharsets.UTF_8));
}

return decryptedResult.toString();
}

/**
* 使用 AES 解密密码(适用于 Navicat 12 及以后的版本)
*
* @param encryptedPassword 密文密码
* @return 解密后的明文密码
* @throws Exception 解密过程中可能抛出的异常
*/
private String decryptAES(String encryptedPassword) throws Exception {
byte[] iv = AES_IV.getBytes();
byte[] key = AES_KEY.getBytes();
byte[] encryptedBytes = hexStringToByteArray(encryptedPassword.toLowerCase());

Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);

byte[] decryptedResult = cipher.doFinal(encryptedBytes);
return new String(decryptedResult);
}

/**
* 对两个字节数组进行异或操作
*
* @param bytes1 第一个字节数组
* @param bytes2 第二个字节数组
* @return 异或结果字节数组
*/
private static byte[] xorBytes(byte[] bytes1, byte[] bytes2) {
byte[] result = new byte[bytes1.length];
for (int i = 0; i < bytes1.length; i++) {
result[i] = (byte) (bytes1[i] ^ bytes2[i]);
}
return result;
}

/**
* 将十六进制字符串转换为字节数组
*
* @param hexString 十六进制字符串
* @return 字节数组
*/
private static byte[] hexStringToByteArray(String hexString) {
int len = hexString.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
}
return data;
}

/**
* 将字符串哈希为字节数组
*
* @param inputString 输入字符串
* @return 哈希后的字节数组
* @throws Exception 哈希过程中可能抛出的异常
*/
private static byte[] hashToBytes(String inputString) throws Exception {
return MessageDigest.getInstance("SHA-1").digest(inputString.getBytes());
}

/**
* 将字节数组转换为十六进制字符串
*
* @param byteArray 字节数组
* @return 十六进制字符串
*/
private static String bytesToHex(byte[] byteArray) {
StringBuilder result = new StringBuilder();
for (byte b : byteArray) {
result.append(String.format("%02X", b));
}
return result.toString();
}
}

/**
* Navicat 版本枚举
*/
enum NavicatVersion {
VERSION_11,
VERSION_12
}

点击运行即可查看密码

image-20241216202756154

注意:粘贴代码时注意删除自动附加的版权信息内容,否则会导致运行错误

image-20241216205408192

我已将代码发布到GitHub,想要学习的同学可自行克隆下载:NavicatPasswordUtil

了解更多关于Navicat 加密算法的内容:

当然,GitHub上还有很多大佬制作好的现成的工具,例如:navicat_password_decrypt

image-20241216202402673

大家自行探索吧,有什么新的收获或任何疑问可在评论区留言分享或反馈。

历史文章

第一步,找到数据库连接登录密码(密文显示)

方案一,用 navicat 导出连接,查看密码(推荐)

文件 > 导出连接

image-20231116223601739

选择要导出的连接,勾选导出密码

image-20211109142548787

这样就得到了加密显示的连接密码

image-20211109142824853

方案二,通过注册表查看加密显示的密码

按快捷键 Win+R 在弹出的运行窗口中输入 regedit 点击确定 打开注册表编辑器

image-20211109143350339

找到 计算机\HKEY_CURRENT_USER\SOFTWARE\PremiumSoft\NavicatMSSQL\Servers

Servers 目录下就是保存在本地的连接

image-20211109143737524

双击右侧Pwd 即可赋值加密显示的密码字符串

image-20211109144046434

第二步,对密码解密

复制如下代码

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
<?php
namespace FatSmallTools;
class NavicatPassword
{
protected $version = 0;
protected $aesKey = 'libcckeylibcckey';
protected $aesIv = 'libcciv libcciv ';
protected $blowString = '3DC5CA39';
protected $blowKey = null;
protected $blowIv = null;
public function __construct($version = 12)
{
$this->version = $version;
$this->blowKey = sha1('3DC5CA39', true);
$this->blowIv = hex2bin('d9c7c3c8870d64bd');
}
public function encrypt($string)
{
$result = FALSE;
switch ($this->version) {
case 11:
$result = $this->encryptEleven($string);
break;
case 12:
$result = $this->encryptTwelve($string);
break;
default:
break;
}
return $result;
}
protected function encryptEleven($string)
{
$round = intval(floor(strlen($string) / 8));
$leftLength = strlen($string) % 8;
$result = '';
$currentVector = $this->blowIv;
for ($i = 0; $i < $round; $i++) {
$temp = $this->encryptBlock($this->xorBytes(substr($string, 8 * $i, 8), $currentVector));
$currentVector = $this->xorBytes($currentVector, $temp);
$result .= $temp;
}
if ($leftLength) {
$currentVector = $this->encryptBlock($currentVector);
$result .= $this->xorBytes(substr($string, 8 * $i, $leftLength), $currentVector);
}

return strtoupper(bin2hex($result));

}

protected function encryptBlock($block)
{
return openssl_encrypt($block, 'BF-ECB', $this->blowKey, OPENSSL_RAW_DATA|OPENSSL_NO_PADDING);
}

protected function decryptBlock($block)
{
return openssl_decrypt($block, 'BF-ECB', $this->blowKey, OPENSSL_RAW_DATA|OPENSSL_NO_PADDING);
}

protected function xorBytes($str1, $str2)
{
$result = '';
for ($i = 0; $i < strlen($str1); $i++) {
$result .= chr(ord($str1[$i]) ^ ord($str2[$i]));
}
return $result;
}

protected function encryptTwelve($string)
{
$result = openssl_encrypt($string, 'AES-128-CBC', $this->aesKey, OPENSSL_RAW_DATA, $this->aesIv);
return strtoupper(bin2hex($result));
}

public function decrypt($string)
{
$result = FALSE;
switch ($this->version) {
case 11:
$result = $this->decryptEleven($string);
break;
case 12:
$result = $this->decryptTwelve($string);
break;
default:
break;
}
return $result;
}

protected function decryptEleven($upperString)
{
$string = hex2bin(strtolower($upperString));
$round = intval(floor(strlen($string) / 8));
$leftLength = strlen($string) % 8;
$result = '';
$currentVector = $this->blowIv;
for ($i = 0; $i < $round; $i++) {
$encryptedBlock = substr($string, 8 * $i, 8);
$temp = $this->xorBytes($this->decryptBlock($encryptedBlock), $currentVector);
$currentVector = $this->xorBytes($currentVector, $encryptedBlock);
$result .= $temp;
}
if ($leftLength) {
$currentVector = $this->encryptBlock($currentVector);
$result .= $this->xorBytes(substr($string, 8 * $i, $leftLength), $currentVector);
}
return $result;
}



protected function decryptTwelve($upperString)
{
$string = hex2bin(strtolower($upperString));
return openssl_decrypt($string, 'AES-128-CBC', $this->aesKey, OPENSSL_RAW_DATA, $this->aesIv);
}
}



use FatSmallTools\NavicatPassword;

//需要指定版本,11或12

//$navicatPassword = new NavicatPassword(12);

$navicatPassword = new NavicatPassword(11);



//解密
$decode = $navicatPassword->decrypt('15057D7BA390');
echo $decode."\n";

打开网页 https://tool.lu/coderunner/http://www.dooccn.com/php7/ 可在线运行代码

将原来内容替换为复制的代码块,将密码替换,点击执行 即可对密码进行解密

image-20211109144818913

如果执行后的密码乱码

image-20211109145143891

更改一下版本即可

image-20211109145235122