Google Authenticator code does not match server generated code

后端 未结 1 1293
野性不改
野性不改 2021-02-09 21:10

Background


I\'m currently working on a two-factor authentication system where user are able to authenticate using their smartphone. Before the user can make use o
1条回答
  •  终归单人心
    2021-02-09 21:42

    Alright, after digging through the code of Google Authenticator I finally found what I was doing wrong.

    Key Encoding

    Just so it's clear: Google Authenticator does expect a base32 encoded string as a secret. So whether you enter it manually or via a QR code, you have to make sure your secret is a base32 encoded string when you give it to Google Authenticator.

    From EnterKeyActivity:

    /*
     * Verify that the input field contains a valid base32 string,
     * and meets minimum key requirements.
     */
    private boolean validateKeyAndUpdateStatus(boolean submitting) {
        //...
    }
    

    Storing

    Google Authenticator is storing the key you give it in the database as is. So this means it stores the base32 string of your secret directly in the database.

    From EnterKeyActivity:

    private String getEnteredKey() {
        String enteredKey = mKeyEntryField.getText().toString();
        return enteredKey.replace('1', 'I').replace('0', 'O');
    }
    
    protected void onRightButtonPressed() {
        //...
        if (validateKeyAndUpdateStatus(true)) {
            AuthenticatorActivity.saveSecret(this, mAccountName.getText().toString(), getEnteredKey(), null, mode, AccountDb.DEFAULT_HOTP_COUNTER);
            exitWizard();
        }
        //...
    }
    

    From AuthenticatorActivity:

    static boolean saveSecret(Context context, String user, String secret, String originalUser, OtpType type, Integer counter) {
        //...
        if (secret != null) {
              AccountDb accountDb = DependencyInjector.getAccountDb();
              accountDb.update(user, secret, originalUser, type, counter);
    
              //...
        }
    }
    

    Retrieval

    When Google Authenticator retrieves the secret from the database, it decodes the base32 string so it can use the genuine secret.

    From OtpProvider:

    private String computePin(String secret, long otp_state, byte[] challenge) throws OtpSourceException {
        //...
    
        try {
            Signer signer = AccountDb.getSigningOracle(secret);
            //...
        }
    }
    

    From AccountDb:

    static Signer getSigningOracle(String secret) {
        try {
            byte[] keyBytes = decodeKey(secret);
            //...
        }
    }
    
    private static byte[] decodeKey(String secret) throws DecodingException {
      return Base32String.decode(secret);
    }
    

    Mistake

    My mistake was that, on the server-side, I was using the base32 encoded key for generating TOTP codes, since I thought Google Authenticator used that as well. In hindsight it's of course very logical, but I couldn't find too much info about this. Hopefully this will help out some more people in the future.

    TL;DR

    Make sure the secret/key you pass to Google Authenticator is a base32 encoded string. Make sure that on the server side you're not using the base32 encoded string, but the decoded string. In Python you can encode and decode you secret/key as follows:

    import base64
    
    base64.b32encode(self.key)
    base64.b32decode(self.key)
    

    0 讨论(0)
提交回复
热议问题