Код Google Authenticator не совпадает с кодом, созданным сервером
Фон
В настоящее время я работаю над двухфакторной системой аутентификации, где пользователи могут проходить аутентификацию с помощью своего смартфона. Прежде чем пользователь сможет использовать свое устройство, он должен сначала проверить его. Для этого им нужно отсканировать предоставленный им QR-код и ввести код, который впоследствии отобразится.
проблема
Сканирование QR-кода работает нормально и корректно читается приложением Google Authenticator. Однако сгенерированные коды не совпадают с теми, которые я генерирую на сервере.
Что я пробовал
Я попробовал пару вещей в надежде найти свою проблему.
Я попытался напрямую вставить оба секрета по умолчанию:
'thiswasmysecretkeyused'
иbase64.b32encode()
закодированная версия секрета:'ORUGS43XMFZW26LTMVRXEZLUNNSXS5LTMVSA===='
в приложении Google Authenticator, но оба сгенерированных кода отличаются от сервера.Я читал, что в конце
====
из-за ключа может не работать, поэтому я попытался добавить один без них. Все еще нет хороших результатов (они генерируют одинаковые коды)Я попытался использовать другой алгоритм для генерации кодов TOTP, поскольку в маловероятном случае неправильный алгоритм, который я использую ( django-otp). Другой алгоритм, который я использовал, был взят из этого ответа. Оба алгоритма генерировали одинаковые коды при использовании одного и того же ключа.
Я проверил, сколько времени в моей системе. Я видел, что операционная система показывает
15:03
так же, как мой смартфон был. После сброса времени в Python с обоимиtime.time()
а такжеdatetime.datetime.now()
Я видел, что возвращаемое время было на один час меньше времени операционной системы; показ14:03
, Я пытался добавить3600
секунд до метки времени, используемой для генерации кода, но безрезультатно.Я пробовал несколько других вещей, но не могу вспомнить, кем они были.
Я посмотрел код, который принимает ключи в 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();
}
//...
}
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)