本文介绍如何用java读取并解析 com.tencent.mm 下的 EnMicroMsg.db 中的聊天数据,.db文件是sqlite数据库文件,但是EnMicroMsg.db是需要密钥的,称之为sqlcipher,所以,用一般的sqlite连接驱动是无法连接的。
1. Maven依赖
<!--https://github.com/Willena/sqlite-jdbc-crypt-->
<dependency>
<groupId>io.github.willena</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.48.0.0</version>
</dependency>
2. 生成数据库访问密钥
密钥是通过 IMEI + uin 的MD5取前7位生成的。
IMEI:手机卡标识,新版微信使用的是固定值1234567890ABCDEF(老版微信的IMEI可能需要从配置文件中查找,一般就在com.tencent.mm/shared_prefs下的某个文件中)
uin:微信用户标识,com.tencent.mm/shared_prefs 文件夹下,以下文件都可能会存在uin,需要逐一解析 (可以试用dom4j解析xml数据)
auth_info_key_prefs.xml
app_brand_global_sp.xml
com.tencent.mm_preferences.xml
XWEB_USER_INFO.xml
密钥具体生成如下(DigestUtil引用的是hutool工具包):
String imeiStatic = "1234567890ABCDEF";
String uin = "14*****50";
String md5 = DigestUtil.md5Hex(imeiStatic + uin);
String key = md5.substring(0, 7);
3. java访问EnMicroMsg.db
获取数据库连接的代码如下,其中的key就是上一步生成的密钥:
package com.cdsxyc.common.sqlite;
import lombok.extern.slf4j.Slf4j;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
@Slf4j
public class SqliteConnector {
public static final String DRIVER_CLASS = "org.sqlite.JDBC";
public static final String URL_PREFIX = "jdbc:sqlite:";
static {
try {
// 加载驱动
Class.forName(DRIVER_CLASS);
} catch (ClassNotFoundException e) {
log.error("加载Sqlite驱动出错", e);
throw new RuntimeException(e);
}
}
/**
* 获取连接
*/
public static Connection getConnection(String url, String key) {
return getConnection(url, key, true);
}
/**
* 获取连接
*
* @param url .db文件目录地址
* @param key 访问密钥
* @param printErrorLog 是否打印错误日志
*/
public static Connection getConnection(String url, String key, boolean printErrorLog) {
if (!url.contains(URL_PREFIX)) {
url = URL_PREFIX + url;
}
url = url + "?cipher=sqlcipher&legacy=1&kdf_iter=4000&key=" + key;
try {
return DriverManager.getConnection(url);
} catch (SQLException e) {
if (printErrorLog) {
log.warn("获取Sqlite连接出错,url={}", url, e);
}
throw new RuntimeException(e);
}
}
public static void closeConnection(Connection connection) {
if (connection != null) {
try {
connection.close();
//log.info("关闭数据库连接");
} catch (SQLException e) {
log.error("关闭Sqlite连接出错", e);
}
}
}
}
获取到 Connection后,后续的使用方式就和JDBC类似了,sql语法也都基本差不多,不懂的可以去查询JDBC的使用方法。
4. 将db数据库和密钥进行匹配
如果该手机登录过多个微信号,则会出现多个EnMicroMsg.db文件和多个uin文件,此时需要将db文件和通过uin计算出来的key逐一进行匹配,这里一个简单的示例:
/**
* 匹配密钥和数据库
*
* @param enMicroMsgFileList 数据库文件列表
* @param keys 密钥列表
*/
public static void matchKeyDb(List<String> enMicroMsgFileList, List<String> keys) {
// 已匹配成功的key(减少匹配次数)
Set<String> successKey = new HashSet<>();
// 尝试用每个uin去解锁数据库
for (String enMicroMsg : enMicroMsgFileList) {
for (String key : keys) {
if (successKey.contains(key)) {
// 已匹配成功的key,不再重复匹配
continue;
}
Connection connection = null;
try {
// 匹配数据库时,不打印错误日志
connection = SqliteConnector.getConnection(enMicroMsg, key, false);
String sql = "select * from message limit 1";
ResultSet resultSet = JdbcExecutor.executeQuery(connection, sql);
if (resultSet.next()) {
// 访问成功
log.info("数据库:{}与密钥:{}匹配成功!", enMicroMsg, key);
successKey.add(key);
// 获取用户目录
File f = new File(enMicroMsg);
if (f.getParentFile() != null && f.getParentFile().exists()) {
String parentFileName = f.getParentFile().getName();
// 用户目录名称是一个32位的MD5值
if (parentFileName.length() == 32) {
log.info("用户目录名称为:{}", parentFileName);
}
}
}
resultSet.close();
break;
} catch (Exception ignore) {
} finally {
SqliteConnector.closeConnection(connection);
}
}
}
}
5. sqlcipher连接工具下载
连接工具有点难找,我这里重新上传了一份,sqlcipher连接工具.zip(点击下载)
==================================================
博主说明:文中若有错误,敬请指出,如有侵权,请及时联系删除。