A recent update of the Google Authenticator app on Android brought an export/import feature. This enables users to copy their 2FA codes to a new device.
The format of the export seems to not be publicly documented (yet?). I believe that this export function is quite interesting and would like to see innovative solutions for back-up or interopability between devices. It would also be in the general public's interest if there would be a single import/export format for 2FA codes and this one looks promising to me.
This repo contains my interpretation of the export data. I hope this enables other developers to come up with new cool solutions that use the exported secrets.
** NOTE THAT THE 2FA CODES ARE SECRETS THAT YOU SHOULD TREAT AS SUCH! **
An interesting blog about the update was published at Ctrl blog.
The layers of data in the export are:
- QR code with encapsulated data:
- URL with
otpauth-migration://offline?data=
, where data contains: - URL encoding
- Base64
- Protocol Buffers serialized data
A reconstructed definition of the protobuf file is included in the repo.
With some regular bash tools and protoc you can extract the data like this:
$ function urldecode() { : "${*//+/ }"; echo -e "${_//%/\\x}"; }
$ urldecode '<DATA VALUE>' | base64 -d | protoc --decode_raw
1 {
1: "\r\037)\335\260l\377\216=\0352O+\352\221.o\177\014e\030\217\352NM~V\322\322 \320\351@\314C"
2: "Demo Issuer:Demo Account"
4: 1
5: 1
6: 2
}
2: 1
3: 1
4: 0
5: 1045125660
Note however that this is just raw decoding the protoc buffer and will show field IDs without assigning names. You'll have to lookup the meaning of each field in the Format description.
Alternatively use the provided OtpMigration.proto and instruct protoc
to decode a MigrationPayload
:
$ urldecode '<DATA VALUE>' | base64 -d | protoc --decode=MigrationPayload OtpMigration.proto
otp_parameters {
secret: "\r\037)\335\260l\377\216=\0352O+\352\221.o\177\014e\030\217\352NM~V\322\322 \320\351@\314C"
name: "Demo Issuer:Demo Account"
algorithm: SHA1
digits: SIX
type: TOTP
}
version: 1
batch_size: 1
batch_index: 0
batch_id: 1045125660
parse.py contains a sample python script that parses an otpauth-migration URL. The OtpMigration_pb2.py is generated with protoc --python_out=. OtpMigration.proto
.
$ pip install -r requirements.txt
$ ./parse.py '<OTP URL>'
version: 1
batch_size: 1
batch_index: 0
batch_id: 1045125660
otp_parameters:
secret: b'BUPSTXNQNT7Y4PI5GJHSX2URFZXX6DDFDCH6UTSNPZLNFURA2DUUBTCD'
name: Demo Issuer:Demo Account
issuer:
algorithm: SHA1
digits: SIX
type: TOTP
counter: 0
Another sample I've included is export2yubikey.py. This script generates ykman commands to import the secrets into a yubikey.
$ ./export2yubikey.py '<OTP URL>'
ykman oath add -o TOTP -d 6 -s SHA1 -p 30 "example1" INWGK5TFOIQFS33VEFBHK5BAJZXSAU3FMNZGK5A=
ykman oath add -o TOTP -d 6 -s SHA1 -p 30 -i "some issuer" "example2" KN2G64BANF2CCTTPORUGS3THEBUGK4TF
...
See OtpMigration.proto.
message
ID | Name | Type |
---|---|---|
1 | otp_parameters | OtpParameters |
2 | version | int32 |
3 | batch_size | int32 |
4 | batch_index | int32 |
5 | batch_id | int32 |
message
ID | Name | Type |
---|---|---|
1 | secret | bytes |
2 | name | string |
3 | issuer | string |
4 | algorithm | Algorithm |
5 | digits | DigitCount |
6 | type | OtpType |
7 | counter | int64 |
enum
Value | Name |
---|---|
0 | ALGORITHM_TYPE_UNSPECIFIED |
1 | SHA1 |
2 | SHA256 |
3 | SHA512 |
4 | MD5 |
enum
Value | Name |
---|---|
0 | DIGIT_COUNT_UNSPECIFIED |
1 | SIX |
2 | EIGHT |
enum
Value | Name |
---|---|
0 | OTP_TYPE_UNSPECIFIED |
1 | HOTP |
2 | TOTP |