Steam 模拟手机令牌二步验证

Time-based One-time Password

第三方 Steam 工具

第三方工具安全性各有不同,风险自负。

以上工具都支持第三方的 Steam 二步验证。

虽然 Steam 的字母数字混合五位验证码看起来和别的 OTP Authenticator 高贵,其实都是一种东西。
即基于 Time-based One-time Password 算法的一次性密码生成器,又称:

  • 两步验证
  • 二步验证
  • 双重验证
  • 双因素验证
  • 多因素验证
  • 虚拟 MFA(我只看到过阿里这么叫)
  • 动态令牌
  • 2FA
  • Two-Factor Authentication
  • Multi-Factor Authentication
  • OTP(One Time Password)

花名很多嘛。

Steam++

它家只要有 shared_secretidentity_secret 就允许导入。
也就是说,最少:

1
2
3
4
{
  "shared_secret": "[key_secret_1]",
  "identity_secret": "[key_secret_2]"
}

就可以正常运作了。
但这样的话,比较关键的 恢复代码 以及 设备 ID 等额外信息都没有。

所以还是推荐该有的都加上,新建文本文档,命名为 min.example.maFile

1
2
3
4
5
6
7
8
9
{
  "account_name": "[your_steam_username]",
  "revocation_code": "[revocation_code]",
  "device_id": "android:[phone_uuid]",
  "uri": "otpauth://totp/Steam:[your_steam_username]?secret=[totp_secret]&issuer=Steam",
  "shared_secret": "[key_secret_1]",
  "identity_secret": "[key_secret_2]",
  "steamid": "[your_steam_64bit_id]"
}

除了最关键的两个密钥之外比较关键的是:revocation_code - 恢复代码。
其次是 uri 里面有 [totp_secret],别的都是锦上添花的东西。

WinAuth

导出的格式类似 WinAuth.example.maFile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "account_name": "[your_steam_username]",
  "revocation_code": "[revocation_code]",
  "device_id": "android:[phone_uuid]",
  "uri": "otpauth://totp/Steam:[your_steam_username]?secret=[totp_secret]&issuer=Steam",
  "shared_secret": "[key_secret_1]",
  "identity_secret": "[key_secret_2]",
  "serial_number": "[serial_number]",
  "server_time": "[server_time]",
  "token_gid": "[token_gid]",
  "secret_1": "[secret_1]",
  "status": 1,
  "steamguard_scheme": "2",
  "steamid": "[your_steam_64bit_id]"
}

Steam Desktop Authenticator

导出的格式类似 SDA.example.maFile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "account_name": "[your_steam_username]",
  "revocation_code": "[revocation_code]",
  "device_id": "android:[phone_uuid]",
  "uri": "otpauth://totp/Steam:[your_steam_username]?secret=[totp_secret]&issuer=Steam",
  "shared_secret": "[key_secret_1]",
  "identity_secret": "[key_secret_2]",
  "serial_number": "[serial_number]",
  "server_time": "[server_time]",
  "token_gid": "[token_gid]",
  "secret_1": "[secret_1]",
  "status": 1,
  "fully_enrolled": true,
  "Session": {
    "SessionID": "[string]",
    "SteamLogin": "[string]",
    "SteamLoginSecure": "[string]",
    "WebCookie": "[string]",
    "OAuthToken": "[string]",
    "SteamID": "[string]"
  }
}

KeePass

我是 KeePass 用户,TOTP 插件还蛮多的,我用的 KeeTrayTOTP
用上面的软件绑定手机令牌后导出,用文本编辑器打开 .maFile 文件,会有 uri 项:

"uri": "otpauth://totp/Steam:[your_steam_username]?secret=[totp_secret]&issuer=Steam",

记录下 [totp_secret],这个就是 Steam 令牌作为 TOTP 工具时的序列号。

编码复现

之前用 Python 和 Go 复现过 Steam 令牌算法,其实就是标准的 TOTP 算法,不过最后生成验证码的时候用了一个取巧的小 trick。序列还是那个序列,但把原本的 6 位或 8 位纯数字验证码 hash 成了 5 位字母数字混合验证码而已。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from time import time
import pyotp
from hmac import new as hmac_sha1
from struct import pack, unpack


def generate_steam_two_factor_code(secret: str):
    totp = pyotp.parse_uri(f"otpauth://totp/Steam:whatever?secret={secret}&issuer=Steam")
    # pyotp.OTP.byte_secret(totp) == base64.b64decode(shared_secret)
    hasher = hmac_sha1(pyotp.OTP.byte_secret(totp), pack('>Q', int(time()) // 30), totp.digest)
    hmac_hash = bytearray(hasher.digest())
    start = ord(hmac_hash[19:20]) & 0xF
    code_num = unpack('>I', hmac_hash[start:start + 4])[0] & 0x7fffffff
    charset = '23456789BCDFGHJKMNPQRTVWXY'
    steam_code = ''
    for _ in range(5):
        code_num, i = divmod(code_num, len(charset))
        steam_code += charset[i]
    return steam_code
updatedupdated2022-05-012022-05-01