6

バックグラウンド


現在、ユーザーがスマートフォンを使用して認証できる二要素認証システムに取り組んでいます。ユーザーが自分のデバイスを使用する前に、まずデバイスを確認する必要があります。そのためには、私が提供した QR コードをスキャンし、続いて表示されるコードを入力する必要があります。

問題


QR コードのスキャンは正常に機能し、Google 認証アプリによって正しく読み取られます。ただし、生成されたコードは、サーバー上で生成しているコードと一致しません。

私は何を試しましたか


私の問題を見つけることを期待して、いくつかのことを試しました。

  1. 'thiswasmysecretkeyused'デフォルトの secret:とbase64.b32encode()エンコードされたバージョンの secret: の両方を Google Authenticator アプリに直接挿入しようとし 'ORUGS43XMFZW26LTMVRXEZLUNNSXS5LTMVSA===='ましたが、これらは両方ともサーバーとは異なるコードを生成しました。

  2. ====キーの末尾が原因で機能しない可能性があると読んだので、それらも含めずに追加してみました。それでも良い結果は得られません (同じコードが生成されます)

  3. TOTP コードを生成するために別のアルゴリズムを使用してみました。これは、使用しているアルゴリズム ( django-otp ) が正しくない場合に備えてです。私が使用した別のアルゴリズムは、この回答から取られました。どちらのアルゴリズムも、同じキーを使用すると同じコードを生成しました。

  4. システムの時刻を確認しました。オペレーティング システムが15:03スマートフォンと同じように表示されていることがわかりました。time.time()両方でPythonで時間をダンプした後datetime.datetime.now()、返された時間がオペレーティングシステムの時間より1時間遅れていることがわかりました。を示してい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 はそれを (有効な) キーとして認識しないと言います。

4

1 に答える 1

10

さて、 Google Authenticator のコードを掘り下げた後、私は最終的に自分が間違っていたことを発見しました。

キーエンコーディング

明確にするために、Google Authenticatorbase32エンコードされた文字列をシークレットとして想定しています。そのため、手動で入力する場合でも 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);
}

間違い

私の間違いは、サーバー側で、base32TOTP コードを生成するためにエンコードされたキーを使用していたことです。Google Authenticator もそれを使用していると思っていたからです。後から考えると、もちろん非常に論理的ですが、これに関する情報はあまり見つかりませんでした。うまくいけば、これは将来さらに多くの人々を助けるでしょう。

TL;DR

Google Authenticator に渡すシークレット/キーがbase32エンコードされた文字列であることを確認してください。サーバー側で、base32エンコードされた文字列ではなく、デコードされた文字列を使用していることを確認してください。Python では、次のようにシークレット/キーをエンコードおよびデコードできます。

import base64

base64.b32encode(self.key)
base64.b32decode(self.key)
于 2015-12-01T09:11:14.833 に答える