Skip to main content

Google TOTP decode and self-generate

Pre-requirements

  • An ubuntu system
    • with python3 supports
    • with python3-pip
    • with package installer, likes apt

Step1. Export TOTP

Export your TOTP from Google Authenticator by using "Transfer Codes". Take a screenshot of the generated QR-Code.

Step 2. Decode the export

  • Install dependencies

    sudo apt install -y libzbar0 protobuf-compiler
    
    pip install pyzbar pillow protobuf
    
  • Write the protobuf created by Google

    cat > migration.proto <<'EOF'
    syntax = "proto3";
    
    message MigrationPayload {
      repeated OTPParameters otp_parameters = 1;
    }
    
    message OTPParameters {
      bytes secret = 1;
      string name = 2;
      string issuer = 3;
      Algorithm algorithm = 4;
      DigitCount digits = 5;
      OTPType type = 6;
      int64 counter = 7;
    }
    
    enum Algorithm {
      ALGORITHM_UNSPECIFIED = 0;
      SHA1 = 1;
      SHA256 = 2;
      SHA512 = 3;
    }
    
    enum DigitCount {
      DIGIT_COUNT_UNSPECIFIED = 0;
      SIX = 1;
      EIGHT = 2;
    }
    
    enum OTPType {
      OTP_TYPE_UNSPECIFIED = 0;
      HOTP = 1;
      TOTP = 2;
    }
    EOF
    
    protoc --python_out=. migration.proto
    # File migration_pb2.py is generated
    
  • Run the script

    import base64
    import migration_pb2
    import pickle
    from pyzbar.pyzbar import decode
    from PIL import Image
    from urllib.parse import unquote
    
    
    img = Image.open('google_auth.jpg')
    
    decoded_objects = decode(img)
    
    if len(decoded_objects) == 0:
        print('No decoded object from given QRCode')
        exit()
    
    migrations = [datum
                    for obj in decoded_objects
                    if obj.type == 'QRCODE' and 
                        (datum := unquote(obj.data.decode('utf-8'))) and
                        datum.startswith('otpauth-migration://')]
    
    if len(migrations) == 0:
        print('No valid migration extracted from given QRCode')
        exit()
    
    prots = [base64.b64decode(migration.replace('otpauth-migration://offline?data=', '', 1))
                for migration in migrations]
    
    if len(prots) == 0:
        print('No correctly-formed migration extracted from given QRCode')
        exit()
    
    totps = []
    
    payload = migration_pb2.MigrationPayload()
    payload.ParseFromString(prots[0])
    
    for otp in payload.otp_parameters:
        totps.append({
            "issuer": otp.issuer,
            "name": otp.name,
            "secret": otp.secret,
        })
    
    with open('google_auth.bin', 'wb') as fout:
        pickle.dump(totps, fout)
    
  • The script output, the decoded data, should be saved in file google_auth.bin

Step 3. Generate the TOTP codes

  • You only need google_auth.bin and following python script to re-generate TOTP codes
    import hmac
    import hashlib
    import pickle
    import struct
    import time
    
    
    def gen_totp_code(secret, digits=6, period=30, algo=hashlib.sha1):
        counter = int(time.time() // period)
        msg = struct.pack(">Q", counter)
        h = hmac.new(secret, msg, algo).digest()
        offset = h[-1] & 0x0F
        code = struct.unpack(">I", h[offset:offset+4])[0] & 0x7fffffff
        return str(code % (10 ** digits)).zfill(digits)
    
    def load_totp_raws():
        with open('google_auth.bin', 'rb') as fin:
            return pickle.load(fin)
    
    for otp in load_totp_raws():
        print(otp['issuer'])
        print(otp['name'])
        print(gen_totp_code(otp['secret']))
        print()