Код 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. # P9 #
    # P10 #

Код


Создание секретного ключа;

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)

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

Ошибки


Ошибок нет.

Предчувствия


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

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

key = base64.b32decode(secret, True) 

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


comment
Строка base64.base32encode() верна, поэтому, возможно, опубликуйте и ту, которая также не совпадает.   -  person l'L'l    schedule 30.11.2015
comment
Я предполагаю, что вы пробовали реализовать, как показано в этих примерах?   -  person Bob Dylan    schedule 30.11.2015
comment
@ l'L'l Не могли бы вы объяснить, что имеете в виду? Я знаю, что ключи верны (насколько они одинаковы), но должен ли я отправлять строку в кодировке base64.b32encode() в Google Authenticator? Что вы имеете в виду под несоответствующим?   -  person Bono    schedule 01.12.2015
comment
@BobDylan Привет, Боб, нет, я не пробовал, но это не имеет значения. Второй метод, который я пробовал, - это просто генерация OTP, и он должен генерировать одни и те же пароли на стороне сервера и на стороне клиента, но этого не происходит.   -  person Bono    schedule 01.12.2015


Ответы (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)
person Bono    schedule 01.12.2015