feat: add Microsoft Authenticator export tool

This commit is contained in:
wuzf
2025-05-25 01:06:35 +08:00
parent 83e9042759
commit e5ffc3a82c
2 changed files with 471 additions and 0 deletions

View File

@@ -0,0 +1,181 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Microsoft Authenticator PhoneFactor 数据库导出为 otpauth:// 格式
使用方法:
python3 tools/MSAuthExport/MSAuthExportScript.py
输出文件:
msauth-export-<timestamp>.txt在脚本目录生成
详细说明请参考: tools/MSAuthExport/README.md
"""
import sqlite3
import os
import sys
import base64
from datetime import datetime
from urllib.parse import quote
def base64_to_base32(base64_str):
"""
将 Base64 编码的字符串转换为 Base32 编码
用于处理 Microsoft 个人账户account_type=1的密钥
"""
try:
# 解码 Base64
decoded_bytes = base64.b64decode(base64_str)
# Base32 字母表
base32_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
# 转换为 Base32
result = []
bits = 0
bit_count = 0
for byte in decoded_bytes:
bits = (bits << 8) | byte
bit_count += 8
while bit_count >= 5:
bit_count -= 5
index = (bits >> bit_count) & 0x1F
result.append(base32_alphabet[index])
# 处理剩余的位
if bit_count > 0:
index = (bits << (5 - bit_count)) & 0x1F
result.append(base32_alphabet[index])
return ''.join(result)
except Exception as e:
print(f'Base64 转换错误: {e}')
return None
# 数据库路径(固定位置)
script_dir = os.path.dirname(os.path.abspath(__file__))
DB_PATH = os.path.join(script_dir, 'databases', 'PhoneFactor')
if not os.path.exists(DB_PATH):
print('错误: 未找到 PhoneFactor 数据库文件')
print('\n请将数据库文件放在: tools/MSAuthExport/databases/PhoneFactor')
print('提示: 如果有 PhoneFactor-shm 和 PhoneFactor-wal 文件,请一起复制')
print(' (脚本运行时会自动合并 WAL 文件到主数据库)')
print('\n详细说明请参考: tools/MSAuthExport/README.md')
sys.exit(1)
DB_PATH = os.path.normpath(DB_PATH)
# 生成输出文件名(带时间戳),确保在脚本目录下创建
timestamp = datetime.now().strftime('%Y-%m-%d-%H%M%S')
output_filename = f'msauth-export-{timestamp}.txt'
OUTPUT_FILE = os.path.join(script_dir, output_filename)
print('=' * 60)
print('Microsoft Authenticator 导出工具')
print('=' * 60)
print(f'输出文件: {OUTPUT_FILE}')
print('=' * 60)
# 连接到 SQLite 数据库
try:
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# 查询所有有效账户(包括所有 account_type
cursor.execute("""
SELECT name, username, oath_secret_key, account_type
FROM accounts
WHERE oath_secret_key IS NOT NULL
AND oath_secret_key != ''
ORDER BY account_type, name, username
""")
rows = cursor.fetchall()
if not rows:
print('\n未找到任何有效账户')
conn.close()
sys.exit(0)
print(f'\n找到 {len(rows)} 个有效账户\n')
# 生成 otpauth:// URLs
otpauth_urls = []
for index, row in enumerate(rows, 1):
name, username, secret_key, account_type = row
# 根据 account_type 处理密钥
if account_type == 1:
# Microsoft 个人账户,需要 Base64 → Base32 转换
secret_cleaned = secret_key.replace(' ', '').replace('\t', '').replace('\n', '').replace('\r', '')
secret = base64_to_base32(secret_cleaned)
if secret is None:
print(f' [{index:2d}] 转换失败,跳过: {name or "Unknown"} {username or ""}')
continue
account_type_label = '(account_type=1, Microsoft)'
elif account_type == 2:
# 企业账户,使用 SHA256
secret = secret_key.replace(' ', '').replace('\t', '').replace('\n', '').replace('\r', '').upper()
account_type_label = '(account_type=2, SHA256)'
else:
# 标准 TOTP 账户 (account_type=0)
secret = secret_key.replace(' ', '').replace('\t', '').replace('\n', '').replace('\r', '').upper()
account_type_label = '(account_type=0)'
# URL 编码
issuer = name or 'Unknown'
account = username or ''
issuer_encoded = quote(issuer)
account_encoded = quote(account) if account else ''
# 构建 label格式issuer:account
if account:
label = f'{issuer_encoded}:{account_encoded}'
else:
label = issuer_encoded
# 根据 account_type 选择算法
algorithm = 'SHA256' if account_type == 2 else 'SHA1'
# 构建 otpauth:// URL
otpauth_url = f'otpauth://totp/{label}?secret={secret}&digits=6&period=30&algorithm={algorithm}&issuer={issuer_encoded}'
otpauth_urls.append(otpauth_url)
# 显示进度
try:
display_issuer = issuer[:24] if len(issuer) <= 26 else issuer[:24] + '..'
display_account = account[:24] if len(account) <= 26 else account[:24] + '..'
print(f' [{index:2d}] {display_issuer.ljust(26)} {display_account.ljust(26)} {account_type_label}')
except (UnicodeEncodeError, UnicodeDecodeError):
print(f' [{index:2d}] [Account {index}] {account_type_label}')
# 写入文件
with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
for url in otpauth_urls:
f.write(url + '\n')
conn.close()
print('\n' + '=' * 60)
print(f'成功导出 {len(otpauth_urls)} 个账户到文件: {OUTPUT_FILE}')
print('=' * 60)
print('\n下一步操作:')
print(' 1. 在 2FA Manager 中点击 "导入" 按钮')
print(f' 2. 选择或拖拽文件: {OUTPUT_FILE}')
print(f' 3. 应该成功导入所有 {len(otpauth_urls)} 个账户\n')
except sqlite3.Error as e:
print(f'\n数据库错误: {e}')
sys.exit(1)
except Exception as e:
print(f'\n错误: {e}')
sys.exit(1)

View File

@@ -0,0 +1,290 @@
# Microsoft Authenticator 导出工具
将 Microsoft Authenticator 的 PhoneFactor SQLite 数据库转换为通用的 otpauth:// URL 格式,以便导入到 2FA Manager 或其他兼容的 2FA 应用。
## 📋 功能特性
- ✅ 从 PhoneFactor SQLite 数据库提取所有 2FA 账户
- ✅ 支持所有账户类型account_type=0/1/2
- ✅ 自动处理 Microsoft 个人账户account_type=1的 Base64 → Base32 密钥转换
- ✅ 支持企业账户account_type=2的 SHA256 算法
- ✅ 自动清理密钥中的空格、换行符等无效字符
- ✅ 导出标准 otpauth:// URL 格式,兼容所有主流 2FA 应用
- ✅ 自动合并 WAL 文件到主数据库
- ✅ Python 3.x 支持,使用现代 Python 语法
## 📦 前置要求
- Python 3.x推荐 3.6 或更高版本)
- SQLite3Python 内置)
- 已 Root 的 Android 设备或模拟器(用于提取数据库文件)
## 📱 步骤 1从 Microsoft Authenticator 导出数据库
### 方法:使用文件管理器
**手机需要 Root 权限,如果没有可以使用 Android 模拟器登录账号**
1. 使用支持 Root 的文件管理器(如 Root Explorer、Solid Explorer
2. 导航到 `/data/data/com.azure.authenticator/databases/`
3. 复制以下文件到电脑:
- `PhoneFactor`(主数据库文件,必需)
- `PhoneFactor-wal`WAL 日志,如果存在请一起复制)
- `PhoneFactor-shm`(共享内存索引,如果存在请一起复制)
**注意**
- WAL 文件包含最新的账户数据,务必一起复制
- 脚本运行时会自动将 WAL 文件合并到主数据库,合并后 WAL 和 SHM 文件会被自动删除(这是正常行为)
## 🚀 步骤 2运行导出脚本
### 文件放置
将数据库文件放在以下固定位置:
```
tools/MSAuthExport/databases/PhoneFactor
```
如果有 WAL 和 SHM 文件,也放在同一目录:
```
tools/MSAuthExport/databases/
├── PhoneFactor (主数据库,必需)
├── PhoneFactor-wal (WAL 日志,如果存在)
└── PhoneFactor-shm (共享内存,如果存在)
```
### 运行脚本
```bash
# 或在脚本目录运行
cd tools/MSAuthExport
python MSAuthExportScript.py
```
### 运行结果示例
```
============================================================
Microsoft Authenticator 导出工具
============================================================
输出文件: msauth-export-2025-12-14-164508.txt
============================================================
找到 50 个有效账户
[ 1] AWS amazon@qq.com (account_type=0)
[ 2] GitHub test@gmail.com (account_type=0)
[ 3] Microsoft test@outlook.com (account_type=1, Microsoft)
[ 4] AzureAD user@company.com (account_type=2, SHA256)
...
============================================================
成功导出 50 个账户到文件: msauth-export-2025-12-14-164508.txt
============================================================
下一步操作:
1. 在 2FA Manager 中点击 "导入" 按钮
2. 选择或拖拽文件: msauth-export-2025-12-14-164508.txt
3. 应该成功导入所有 50 个账户
```
## 📄 输出格式说明
脚本会生成一个带时间戳的文本文件 `msauth-export-<timestamp>.txt`,每行是一个标准的 otpauth:// URL
```
otpauth://totp/GitHub:username?secret=JBSWY3DPEHPK3PXP&digits=6&period=30&algorithm=SHA1&issuer=GitHub
otpauth://totp/Microsoft:user%40outlook.com?secret=KQA5S3QN5PCKOJM4&digits=6&period=30&algorithm=SHA1&issuer=Microsoft
otpauth://totp/AzureAD:admin%40company.com?secret=ABCD1234EFGH5678&digits=6&period=30&algorithm=SHA256&issuer=AzureAD
```
### 字段说明
- **secret**: 密钥(已清理空格,转为大写)
- **digits**: 验证码位数(固定 6 位)
- **period**: 刷新周期(固定 30 秒)
- **algorithm**: 哈希算法SHA1 或 SHA256根据账户类型自动选择
- **issuer**: 服务提供商名称
## 📥 步骤 3导入到 2FA Manager
1. 访问你的 2FA Manager 实例
2. 点击 **"导入"** 按钮
3. 选择或拖拽生成的 `msauth-export-*.txt` 文件
4. 系统会自动识别 otpauth:// URL 格式并预览
5. 确认无误后点击 **"执行导入"**
导入后即可在 2FA Manager 中使用所有账户。
## 🔧 技术细节
### 账户类型处理
Microsoft Authenticator 使用 `account_type` 字段区分账户类型,脚本会自动识别并处理:
| account_type | 说明 | 处理方式 | 算法 |
| ------------ | -------------------------------------- | -------------------- | ------ |
| 0 | 标准 TOTP 账户(如 Google、GitHub 等) | 直接使用 Base32 密钥 | SHA1 |
| 1 | Microsoft 个人账户 | Base64 → Base32 转换 | SHA1 |
| 2 | 企业账户(如 Azure AD | 直接使用 Base32 密钥 | SHA256 |
### Base64 → Base32 转换
Microsoft 个人账户account_type=1的密钥存储为 Base64 编码,脚本会自动转换为标准的 Base32 格式:
- **输入Base64**: `VAHZbg3rxKclnA==`
- **输出Base32**: `KQA5S3QN5PCKOJM4`
转换过程完全自动化,无需手动操作。
### 密钥清理
脚本会自动清理所有密钥中的:
- 空格 (` `)
- 制表符 (`\t`)
- 换行符 (`\n`, `\r`)
并统一转换为大写字母,确保符合标准格式。
### WAL 文件自动合并
SQLite 的 WALWrite-Ahead Logging模式会将最新的数据写入单独的 `-wal` 文件。当脚本打开数据库时:
1. SQLite 自动检测 WAL 文件
2. 将 WAL 中的更改合并到主数据库checkpoint 操作)
3. 合并完成后自动删除 WAL 和 SHM 文件
**这是 SQLite 的正常行为**,不用担心:
- ✅ 所有数据已安全合并到主数据库
- ✅ 数据库处于一致状态
- ✅ 不会有数据丢失
## ⚠️ 注意事项
### 安全警告
- ⚠️ **数据库文件包含所有 2FA 密钥的明文,务必妥善保管**
- ⚠️ **导出的文本文件包含可直接使用的密钥,使用后请立即删除**
- ⚠️ **不要将数据库文件或导出文件上传到公共平台或云存储**
- ⚠️ **建议在操作完成后,从电脑中彻底删除数据库文件和导出文件**
### Root 权限要求
- 提取数据库文件**必须**需要 Root 权限
- Android 系统保护应用数据目录 `/data/data/`,普通用户无法访问
- 如果设备未 Root可以使用 Android 模拟器(如 BlueStacks、Memu登录 Microsoft Authenticator
### 兼容性
**脚本兼容性**
- Python 3.6 或更高版本
- Windows、Linux、macOS
**导出格式兼容性**
- Google Authenticator
- Authy
- Aegis Authenticator
- 2FAS Auth
- 1Password
- Bitwarden
- 等所有支持标准 TOTP 的应用
## 🛠️ 故障排除
### 问题 1未找到数据库文件
**错误信息**
```
错误: 未找到 PhoneFactor 数据库文件
```
**解决方案**
1. 确认文件路径正确:`tools/MSAuthExport/databases/PhoneFactor`
2. 检查文件是否从手机正确复制
3. 确认文件名拼写正确(区分大小写)
### 问题 2未找到任何有效账户
**可能原因**
- 数据库为空Microsoft Authenticator 中没有账户)
- 数据库文件损坏
- 复制文件时出错
**解决方案**
1. 在手机上打开 Microsoft Authenticator确认账户存在
2. 重新从手机复制数据库文件(包括 WAL 文件)
3. 如果使用模拟器,确保账户已同步
### 问题 3WAL 和 SHM 文件被删除了
这是**正常现象**,不是错误:
- SQLite 在打开数据库时会自动合并 WAL 文件
- 合并后的数据已完整保存在主数据库文件中
- 删除 WAL/SHM 是 SQLite 的标准行为
**不需要任何操作**,数据已安全保存。
### 问题 4Microsoft 账户转换失败
**错误信息**
```
[XX] 转换失败,跳过: Microsoft user@outlook.com
```
**解决方案**
1. 这可能是因为密钥格式不符合 Base64 标准
2. 检查导出文件,确认其他账户是否正常导出
3. 如果问题持续,请提 Issue 并附上错误信息
### 问题 5导入后验证码不正确
**可能原因**
- 设备时间不同步
- 密钥格式错误
**解决方案**
1. 确保所有设备时间与标准时间同步(误差应小于 30 秒)
2. 检查导出文件中的密钥格式
3. 尝试重新导出和导入
## 📝 更新日志
**版本 2.0.0** (2025-12-14)
- ✅ 添加对所有账户类型的支持account_type=0/1/2
- ✅ 实现 Base64 → Base32 自动转换
- ✅ 支持 SHA256 算法(企业账户)
- ✅ 简化数据库路径为单一固定位置
- ✅ 添加 WAL 文件自动合并说明
- ✅ 改进错误提示和文档
**版本 1.0.0** (2025-12-13)
- 初始版本
- 基本的 otpauth:// URL 导出功能
## 📄 许可证
本工具是 2FA Manager 项目的一部分,遵循相同的开源许可证。
---
**最后更新**: 2025-12-14
**脚本版本**: 2.0.0
**作者**: 2FA Manager Team