package simcu::otp import std.random.* import std.convert.* /** * otpauth:// URI 生成(Google Authenticator / 常见 OTP App 标准)。 * 格式:otpauth://totp/{issuer}:{account}?secret={base32}&issuer={issuer}&algorithm=SHA1&digits=6&period=30 * 同时提供随机 Base32 secret 生成工具。 */ public class OtpUri { /// 生成随机 Base32 secret(默认 20 字节 = 160 bit,与 RFC 4226 密钥长度一致) public static func generateSecret(): String { generateSecret(20) } /// 生成指定字节数的随机 Base32 secret public static func generateSecret(bytes: Int64): String { let rnd = Random() let data = Array(bytes, repeat: 0) for (i in 0..bytes) { data[i] = UInt8(rnd.nextUInt64() & 0xff) } Base32.encode(data) } /// 生成 otpauth:// 链接(默认 6 位 / 30s 步长) public static func build(issuer: String, account: String, secretBase32: String): String { build(issuer, account, secretBase32, 6, 30) } /// 生成 otpauth:// 链接(指定位数) public static func build(issuer: String, account: String, secretBase32: String, digits: Int64): String { build(issuer, account, secretBase32, digits, 30) } /// 生成 otpauth:// 链接(指定位数与步长) public static func build(issuer: String, account: String, secretBase32: String, digits: Int64, period: Int64): String { let label = issuer + ":" + account var sb = StringBuilder() sb.append("otpauth://totp/") sb.append(urlEncode(label)) sb.append("?secret=") sb.append(secretBase32) sb.append("&issuer=") sb.append(urlEncode(issuer)) sb.append("&algorithm=SHA1&digits=") sb.append(digits.toString()) sb.append("&period=") sb.append(period.toString()) sb.toString() } private static func urlEncode(s: String): String { var sb = StringBuilder() for (ch in s) { match (ch) { case ':' => sb.append("%3A") case '/' => sb.append("%2F") case ' ' => sb.append("%20") case _ => sb.append(Rune(ch)) } } sb.toString() } }