微信聊天记录解析(1):Java访问 EnMicroMsg.db

本文介绍如何用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 + uinMD5取前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(点击下载

==================================================

博主说明:文中若有错误,敬请指出,如有侵权,请及时联系删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值