Python - 获取微信聊天记录

简介

最近接到公司的一个任务,要获取微信聊天记录,心想,这应该不太可能吧,毕竟微信这么多人用,要是能够轻易导出聊天记录,那不是不太科学。加上Android手机现在基本上很难root。于是去网上搜索了一下相关资料。发现还真可以导出聊天记录。通过 db browser for sqlite可以查看。但是如果全部手动完成,是有点麻烦。所以就考虑用Python实现解密和解析。

导出微信聊天db文件

  • 手机端导出
    发现现在的Android手机都很难root,而db文件肯定在微信的应用目录下。拿不出来的。
  • 电脑端导出(mac)
    从~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application\ Support/com.tencent.xinWeChat下找到对应账号的数据存储目录。


    屏幕快照 2019-10-17 下午5.46.38.png
  • Message
    存储聊天消息
  • Contact
    存储联系人信息

获取db密钥

  1. 打开微信
  2. 命令行运行 lldb -p $(pgrep WeChat)
  3. 在 lldb 中输入 br set -n sqlite3_key, 回车
  4. 还是在 lldb 中,输入 c, 回车

  1. 扫码登录微信
  2. 这时候回到 lldb 界面, 输入 memory read --size 1 --format x --count 32 $rsi, 回车

命令行回显结果

0x000000000000: 0xab 0xcd 0xef 0xab 0xcd 0xef 0xab 0xcd
0x000000000008: 0xab 0xcd 0xef 0xab 0xcd 0xef 0xab 0xcd
0x000000000010: 0xab 0xcd 0xef 0xab 0xcd 0xef 0xab 0xcd
0x000000000018: 0xab 0xcd 0xef 0xab 0xcd 0xef 0xab 0xcd

忽略左边的地址( 0x000000000000: ,0x000000000008:),从左到右,从上到下,把形如 0xab 0xcd 的数据拼起来,然后去掉所有的 "0x"和空格、换行, 得到 64 个字符的字符串,这就是 微信数据库的 key

可以遇到的问题(来自网上,我在使用过程中没有这个问题))

*.db 文件要在另一台 PC 或 Mac 上 登录微信, 才能被关闭,否则里面的聊天记录不是最新的;当然也可以强制调用 wal_checkpoint, 不过作为土办法,在另一台电脑上登一下微信也无妨。
那么wal_checkpoint是什么?是一个sqlite命令,可以参考:https://www.sqlite.org/pragma.html#pragma_wal_checkpoint,如果不想看英文,那就百度一下吧!

db browser for sqlite

db数据库可视化工具,打开需要查看的数据库文件:


屏幕快照 2019-10-17 下午6.43.43.png

先输入0x,然后把上面得到的密钥复制进来。由于编码的问题,可能复制进来会出现打不开的情况,你可以先把密钥复制到软件执行sql语句的地方,然后在复制。一会python读取db数据库会参考上面的设置参数。

python解密db数据库

这里需要使用sqlite来操作数据库,而sqlite本身是不支持加密和解密的,那么这部分就需要我们自己来完成,但是网上已经有开源库了。

  • 使用到的python库
  1. 加密解密db库——pysqlcipher3
  2. 加密算法库——sqlcipher
  3. 微信db加密库——wcdb
  • 参考文档
    https://docs.python.org/2/library/sqlite3.html#cursor-objects
    https://docs.python.org/2/library/sqlite3.html#cursor-objects

  • db解密
    路径配置

    # 加密DB文件绝对路径,微信DB加密方案参考:    https://github.com/Tencent/wcdb
    DB_PATH = '/Users/pengdaosong/pds/python/PythonStudy/db/encryption/'
    # 解密后生成不加密DB文件绝对路径
    DB_OUT_PATH = '/Users/pengdaosong/pds/python/PythonStudy/db/decrypt/'
    DB_OUT_JSON_PATH = '/Users/pengdaosong/pds/python/PythonStudy/db/json/'
    DB_ROOT_PATH = '/Users/pengdaosong/pds/python/PythonStudy/db/'
    # 解密数据库key
    DB_KEY = "x''1133d3dd61764a9e80699b42b61e443515aeb00cf4f5497d9729adcb6c8e78e2''"
    # 新的DB加密数据KEY,如果为空,者不加密
    DB_NEW_KEY = ''
    

    这里说一下DB_KEY的写法,官网写法是这样的:"x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'";但是这样写始终是不能打开数据库的,始终报“不是一个数据库文件”。换成上面的写法后可以打开。所以,这里如果不能打开,把这两种写都试一试。像在密钥前加'0x'等都可以试一试。

解密

import pysqlcipher3.dbapi2 as sqlite
import config as cf
def decrypt(path, fileName):
    # ———————————————————————————————————数据库操作———————————————————————————————————
    # 参考:https://www.sqlite.org/pragma.html#pragma_wal_checkpoint

    # 连接数据库如果文件不存在,会自动在当前目录创建:
    db = sqlite.connect(path + fileName)
    # 创建一个Cursor:
    db_cursor = db.cursor()

    # ————————————————————————————————————解密数据DB———————————————————————————————————
    # sqlcipher加密解密参考:https://github.com/sqlcipher/android-database-sqlcipher/issues/94
    # sqlcipher开源库地址:https://github.com/sqlcipher/sqlcipher/issues 
    # sqlcipherApi:https://www.zetetic.net/sqlcipher/sqlcipher-api/
    db_cursor.execute("PRAGMA key='" + cf.DB_KEY + "';")
    db_cursor.execute("PRAGMA cipher_compatibility=3;")
    db_cursor.execute("PRAGMA cipher_page_size=1024;")
    db_cursor.execute("PRAGMA kdf_iter=64000;")
    db_cursor.execute("PRAGMA cipher_hmac_algorithm=HMAC_SHA1;")
    db_cursor.execute("PRAGMA cipher_kdf_algorithm=PBKDF2_HMAC_SHA1;")
    # 将解密文件导入新的DB文件
    decrypt = cf.DB_OUT_PATH + 'decrypt_' + fileName
    db_cursor.execute("ATTACH DATABASE '" + decrypt + "' AS db_de KEY '" + cf.DB_NEW_KEY + "';  -- empty key will disable encryption")
    db_cursor.execute("SELECT sqlcipher_export('db_de');")
    db_cursor.execute("DETACH DATABASE db_de;")
    db_cursor.close()

更多加密参数设置参考:sqlcipher使用文档

将数据库导出为json

import pysqlcipher3.dbapi2 as sqlite
import config as cf_wx
import dbToJson
def parse(path, fileName):
    db = sqlite.connect(path + fileName)
    db_cursor = db.cursor()

    all_table = db_cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table';").fetchall()
    print(all_table)

    for x in all_table:
        table_name = x[0]
        print("Searching", table_name)
        try:
            t = db_cursor.execute('SELECT * FROM ' + table_name + ';')
            dbToJson.tableToFile(db_cursor,table_name)   
            print('\n')

        except BaseException as e:
            print(e)
            continue

    db_cursor.close()
    db.close()
import json
import numpy as np
import config as cf_wx

class MyEncoder(json.JSONEncoder):
  def default(self, obj):
      if isinstance(obj, np.ndarray):
          return obj.tolist()
      elif isinstance(obj, bytes):
          # utf-8会报错:'utf-8' codec can't decode byte 0xfc in position 14: invalid start byte
          return str(obj, encoding='ISO-8859-15')
      return json.JSONEncoder.default(self, obj)

def tableToJson(cursor, table):
  query = 'SELECT * FROM ' + table
  rows = cursor.execute(query)

  items = []
  for row in rows:
      item = {}
      for key in cursor.description:
          item.update({key[0]: value for value in row})
      items.append(item)

  # 注意不要写道for里面了,不然数据结果不对    
  js = json.dumps(items, ensure_ascii=False, cls=MyEncoder, indent=4)
  print(js)
  print('--------------------------------')

def tableToFile(cursor, table):
  query = 'SELECT * FROM ' + table
  rows = cursor.execute(query)
  items = []
  for row in rows:
      item = {}
      # 参考:https://stackoverflow.com/questions/3300464/how-can-i-get-dict-from-sqlite-query
      for idx, col in enumerate(cursor.description):
          value = row[idx]
          item.update({col[0]: value})
      items.append(item)

  # 注意不要写道for里面了,不然数据结果不对
  json_name = ""
  if(table.endswith('.db')):
      json_name = table[:(table.__len__ - 3)]
  else:
      json_name = table
  file = open(cf_wx.DB_OUT_JSON_PATH + json_name + ".json", 'w+')
  # ensure_ascii默认为True,汉字会被编码成'\u4e00\u6839\u806a'
  js = json.dumps(items, ensure_ascii=False, cls=MyEncoder, indent=4)
  file.write(js)
  file.close()

关联

查看聊天消息数据发现,不能把聊天消息数据表和对应的聊天人对应起来,因为消息表并没有存是和谁在聊天。如果不能和对应的人联系起来,岂不是感觉很不好。那这里就要用到联系人数据库了,密钥和上面的一样。打开联系人的数据查看:


屏幕快照 2019-10-17 下午7.30.41.png

这是其中一个联系人,将"m_nsUsrName"进行32 MD5后得到32为字符串。在者字符串前面加上"Chat_"就是这个联系人在消息数据库里面对应的表名。这样就可以通过这个字符串在消息数据库里找到对应的聊天消息了。

其它库

https://datasette.readthedocs.io/en/stable/ecosystem.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容