批量迁移 Microsoft Authenticator TOTP 至 Canokey

引言

书接上文,新购入 Canokey Canary 后,有一大工作就是将之前的 2FA - TOTP(Time-based One-time Password)验证码迁移保存至新的硬件密钥。然而,对于账户数量较多的情况,使用手动方式迁移效率低,工作量大。

本文介绍了一个笔者编写的脚本,用于批量、快速迁移 TOTP 到 Canokey 中(或 Yubikey)。

脚本内容

这是个 python 脚本:

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
import argparse
import os
import sqlite3
import sys
import time

DB_PATH = os.getenv("DB_PATH", "PhoneFactor")
CANOKEY_PIN = os.getenv("CANOKEY_PIN", "123456")


def read_from_db() -> list:
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute("SELECT name, username, oath_secret_key FROM accounts")
res = c.fetchall()
conn.close()
return res


def read_from_errorfile() -> list:
res = []
with open("./error_list.txt", "r", encoding="utf-8") as file:
for line in file:
elements = line.strip().split()
if len(elements) == 3:
res.append(tuple(elements))
return res


def write_to_canokey(_name: str, _username: str, _oath_secret_key: str) -> bool:
_name = _name.replace(" ", "")
_username = _username.replace(" ", "")
res = os.system(f"ckman oath accounts add -p {CANOKEY_PIN} -i {_name} {_username} {_oath_secret_key}")
if res != 0:
err_list.append((_name, _username, _oath_secret_key))
if input("Continue to import next? (y/n) ").lower() != "y":
print("Exiting...")
return False
return True


if __name__ == '__main__':
# Change working directory to script directory
os.chdir(sys.path[0])
# Parse arguments
parser = argparse.ArgumentParser()
parser.add_argument("-r", '--retry-error', action='store_true', help='retry from error list file.')
args = parser.parse_args()
# Read data
data = read_from_errorfile() if args.retry_error else read_from_db()
# Write data to canokey
err_list = []
for row in data:
print(f"Writing {row[0]} {row[1]}...")
if write_to_canokey(row[0], row[1], row[2]):
time.sleep(3)
else:
break
# Write error list to file
if err_list:
with open("error_list.txt", "w", encoding="utf-8") as f:
f.write("\n".join([f"{x[0]} {x[1]} {x[2]}" for x in err_list]))
print("\nError(maybe) list written to error_list.txt")
# Done
print("Done!")

该项目的 Github 地址如下,后续更新可能于此进行:

https://github.com/hui-shao/MicrosoftAuthenticator_TOTP_to_Canokey

数据库准备

  1. 在一台有 ROOT 的 Android 设备上登录 Microsoft Authenticator 软件,并完成数据同步。
  2. 使用文件管理工具,访问 /data/data/com.azure.authenticator/databases/PhoneFactor
  3. PhoneFactorPhoneFactor-shmPhoneFactor-wal(如果存在后两个则复制)复制到 PC 上,放到脚本所在的目录下。
  4. (可能需要)在电脑上,使用 SQLiteStudio 或其他工具打开一次 PhoneFactor 数据库,以解决一些缓存和同步的问题。

环境配置

  1. 安装 Python

  2. 安装 ckman

    1
    pip install canokey-manager
  3. 配置下面的环境变量

    DB_PATH:PhoneFactor 数据库的位置。

    CANOKEY_PIN:TOTP 应用的 Pin 码。

使用

正常启动:

1
python main.py

运行过程中,如果 ckman 返回值不是 1 的条目(意味着可能出现错误),程序会暂停,并向用户确认是否继续导入剩余条目。

运行完成后,会将上述”可能出错“条目,加入到一个 error_list 中,并存为 error_list.txt 文件,请注意该文件包含密钥。

错误重试:

在运行时添加命令行参数:-r--retry-error

1
python main.py -r

与正常启动相比,唯一区别在于:程序将会读取 error_list.txt 作为数据来源,而不是从数据库中读取。

随后的行为与正常启动完全一致,包括再次更新错误列表。

其他

对于 Yubikey,请安装 yubikey-manager 的 CLI 版本:

1
pip install --user yubikey-manager

然后自行修改代码,将其中的 ckman 更换为 ykman