Documentation
¶
Overview ¶
Package otpmigration decodes the Google Authenticator "Export accounts" payload — the otpauth-migration://offline?data=… URI (and the bare base64 behind it) — into the list of 2FA accounts it carries: issuer, account name, secret, algorithm, digit count, OTP type, and HOTP counter. A single export QR packs ALL of a user's seeds, so decoding one is a high-value post-exploitation / device-forensics step: it bulk-recovers every TOTP/HOTP secret, each ready to feed into totp_generate for the live codes. Pure offline transform; no network or device.
Wrap-vs-native judgement ¶
Native. The payload is a small protobuf (MigrationPayload) whose schema is public and stable. We parse the wire format directly with google.golang.org/protobuf/encoding/protowire — already an indirect dependency of the module, used here only as a low-level varint/bytes reader, with NO generated .pb.go and NO new go.mod entry. The base64 + base32 + URI assembly is stdlib. Nothing is wrapped or shelled out.
Schema (reverse-engineered, stable since the 2020 export feature) ¶
message MigrationPayload {
message OtpParameters {
bytes secret = 1; // raw secret bytes (base32-encode for display)
string name = 2; // account / label
string issuer = 3;
Algorithm algorithm = 4; // 0 unspec,1 SHA1,2 SHA256,3 SHA512,4 MD5
DigitCount digits = 5; // 0 unspec,1 SIX,2 EIGHT
OtpType type = 6; // 0 unspec,1 HOTP,2 TOTP
int64 counter = 7; // HOTP only
}
repeated OtpParameters otp_parameters = 1;
int32 version = 2;
int32 batch_size = 3;
int32 batch_index = 4;
int32 batch_id = 5;
}
Verifiable / no confidently-wrong output ¶
Anchored to the canonical migration example used across the ecosystem — data CjEKCkhlbGxvId6tvu8SGFRlc3Q6YWxpY2VAZ29vZ2xlLmNvbRoHRXhhbXBsZSABKAEwAg== → issuer "Example", secret JBSWY3DPEHPK3PXP (base32 of the bytes 48656C6C6F21DEADBEEF, "Hello!"+0xDEADBEEF), SHA1 / 6 digits / TOTP. An unknown enum value is surfaced raw ("UNSPECIFIED(n)") rather than guessed; a truncated or non-protobuf payload is rejected. The secret is the correctness-critical field, and it is cross-checked by that independent base32 computation.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Account ¶
type Account struct {
Issuer string `json:"issuer,omitempty"`
Name string `json:"name"`
Secret string `json:"secret"` // base32, no padding (the otpauth form)
Algorithm string `json:"algorithm"`
Digits int `json:"digits"`
Type string `json:"type"` // totp / hotp
Counter int64 `json:"counter,omitempty"` // HOTP only
OtpauthURI string `json:"otpauth_uri"`
}
Account is one decoded OTP entry, with a reconstructed otpauth:// URI ready to paste into totp_generate.
type Result ¶
type Result struct {
Version int `json:"version,omitempty"`
BatchSize int `json:"batch_size,omitempty"`
BatchIndex int `json:"batch_index,omitempty"`
BatchID int `json:"batch_id,omitempty"`
Count int `json:"count"`
Accounts []Account `json:"accounts"`
}
Result is the decoded migration payload.