Код Google Authenticator не совпадает с кодом, созданным сервером

Фон


В настоящее время я работаю над двухфакторной системой аутентификации, где пользователи могут проходить аутентификацию с помощью своего смартфона. Прежде чем пользователь сможет использовать свое устройство, он должен сначала проверить его. Для этого им нужно отсканировать предоставленный им QR-код и ввести код, который впоследствии отобразится.

проблема


Сканирование QR-кода работает нормально и корректно читается приложением Google Authenticator. Однако сгенерированные коды не совпадают с теми, которые я генерирую на сервере.

Что я пробовал


Я попробовал пару вещей в надежде найти свою проблему.

  1. Я попытался напрямую вставить оба секрета по умолчанию: 'thiswasmysecretkeyused' и base64.b32encode() закодированная версия секрета: 'ORUGS43XMFZW26LTMVRXEZLUNNSXS5LTMVSA====' в приложении Google Authenticator, но оба сгенерированных кода отличаются от сервера.

  2. Я читал, что в конце ==== из-за ключа может не работать, поэтому я попытался добавить один без них. Все еще нет хороших результатов (они генерируют одинаковые коды)

  3. Я попытался использовать другой алгоритм для генерации кодов TOTP, поскольку в маловероятном случае неправильный алгоритм, который я использую ( django-otp). Другой алгоритм, который я использовал, был взят из этого ответа. Оба алгоритма генерировали одинаковые коды при использовании одного и того же ключа.

  4. Я проверил, сколько времени в моей системе. Я видел, что операционная система показывает 15:03 так же, как мой смартфон был. После сброса времени в Python с обоими time.time() а также datetime.datetime.now() Я видел, что возвращаемое время было на один час меньше времени операционной системы; показ 14:03, Я пытался добавить 3600 секунд до метки времени, используемой для генерации кода, но безрезультатно.

  5. Я пробовал несколько других вещей, но не могу вспомнить, кем они были.

  6. Я посмотрел код, который принимает ключи в Google Authenticator, и убедился, что он ожидает строку base32. Насколько я знаю, моя кодировка ключа правильная. Из кода ( EnterKeyActivity.java, строка 78):

    Убедитесь, что поле ввода содержит допустимую строку base32

Код


Генерация секретного ключа;

def generate_shared_key(self):
    # create hash etc.
    return base64.b32encode(hasher.hexdigest())

Генерация QR-кода;

key = authenticator.generate_shared_key()
qrcode = pyqrcode.create('otpauth://totp/someurl.nl?secret=' + key)

Генерация кода TOTP;

def generate_code(self, drift_steps=0, creation_interval=30, digits=6, t0=0):
    code = str(totp(self.generate_shared_key(), creation_interval, timestamp, digits, drift_steps))
    return code.zfill(digits)

Если вам нужен еще какой-нибудь код, такой как django-otp, фактически генерирующий код Totp, дайте мне знать.

ошибки


Нет ошибок

Догадки


Я догадываюсь, что я где-то ошибаюсь при генерации ключа или при передаче ключа в Google Authenticator. Так как даже ручное введение ключа в Google Authenticator не может генерировать правильные коды. Делает ли Google Authenticator что-то еще с ключом после его сохранения, например, добавляет пользователя?

В другом алгоритме, который я использовал, я также заметил, что секрет там сначала расшифровывается;

key = base64.b32decode(secret, True) 

Мой оригинальный ключ (хэш SHA512) неверен? Должен ли я или не должен кодировать это с base64.b32encode()? Если я попытаюсь отсканировать созданный QR-код без кодирования хеша, Google Authenticator скажет, что не распознает его как (действительный) ключ.

1 ответ

Решение

Хорошо, покопавшись в коде Google Authenticator, я наконец-то обнаружил, что я делаю не так.

Кодировка Ключа

Просто так понятно: Google Authenticator действительно ожидает base32 закодированная строка как секрет. Поэтому, вводите ли вы его вручную или с помощью QR-кода, вы должны убедиться, что ваш секрет base32 закодированная строка, когда вы передаете ее в Google Authenticator.

Из EnterKeyActivity:

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

хранения

Google Authenticator хранит ключ, который вы даете, в базе данных как есть. Так что это означает, что он хранит base32 Строка вашего секрета прямо в базе данных.

Из 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();
    }
    //...
}

От 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);

          //...
    }
}

поиск

Когда Google Authenticator получает секрет из базы данных, он декодирует base32 Строка, чтобы он мог использовать подлинный секрет.

От OtpProvider:

private String computePin(String secret, long otp_state, byte[] challenge) throws OtpSourceException {
    //...

    try {
        Signer signer = AccountDb.getSigningOracle(secret);
        //...
    }
}

Из AccountDb:

static Signer getSigningOracle(String secret) {
    try {
        byte[] keyBytes = decodeKey(secret);
        //...
    }
}

private static byte[] decodeKey(String secret) throws DecodingException {
  return Base32String.decode(secret);
}

ошибка

Моя ошибка заключалась в том, что на стороне сервера я использовал base32 закодированный ключ для генерации кодов TOTP, так как я думал, что Google Authenticator также использовал это. Оглядываясь назад, это, конечно, очень логично, но я не мог найти слишком много информации об этом. Надеюсь, это поможет еще большему количеству людей в будущем.

TL; DR

Убедитесь, что секретный ключ / ключ, который вы передаете в Google Authenticator, является base32 закодированная строка. Убедитесь, что на стороне сервера вы не используете base32 закодированная строка, но декодированная строка. В Python вы можете кодировать и декодировать свой секрет / ключ следующим образом:

import base64

base64.b32encode(self.key)
base64.b32decode(self.key)
Другие вопросы по тегам