HOTP と TOTP は、スマートフォンの認証アプリが表示するコードです。どちらの関数もsgcCrypto_OTPに含まれており、HMAC(ハッシュ化 & メッセージ認証を参照)を基盤としています。
HOTP(カウンターベース)
コードは、共有された秘密と、使用のたびに1つずつ進むカウンターに依存します。クライアントとサーバーは同期を保つ必要があります。クライアントがサーバー側に届かないコードを生成してしまうと(たとえばネットワーク接続がない状態でボタンが押された場合など)、カウンターがずれてしまい、サーバーが再同期するまでクライアントがその後に表示するコードはすべて失敗します。このずれこそが、ほとんどの一般消費者向け認証アプリで TOTP が HOTP に取って代わった主な理由です。
function sgcHOTP(const aSecret: TBytes; aCounter: Int64; aDigits: Integer = 6): string;
TOTP(時間ベース)
HOTP のカウンターは、現在の Unix 時刻を固定の間隔(デフォルトでは 30 秒)で区切ったものに置き換えられているため、クライアントとサーバーは同期したカウンターではなく、おおよそ同期した時計だけを必要とします。これは、あらゆる認証アプリが実装している方式です。
function sgcTOTP(const aSecret: TBytes; aUnixTime: Int64; aDigits: Integer = 6;
aStep: Integer = 30; aHash: TsgcKDFHash = khSHA1): string;
{ Takes the base32 secret exactly as an authenticator app displays or scans it. }
function sgcTOTP_FromBase32(const aSecretBase32: string; aUnixTime: Int64;
aDigits: Integer = 6; aStep: Integer = 30; aHash: TsgcKDFHash = khSHA1): string;
コードの検証
aWindowは、現在のステップの前後それぞれ指定した回数分のステップから生成されるコードを受け入れます。これにより、多少の時計のずれや、入力に時間がかかったユーザーを吸収します。比較は内部的に定数時間で行われるため、推測のうち何桁が正解しているかが漏れることはありません。
function sgcTOTP_Verify(const aSecret: TBytes; const aCode: string; aUnixTime: Int64;
aWindow: Integer = 1; aDigits: Integer = 6; aStep: Integer = 30; aHash: TsgcKDFHash = khSHA1): Boolean;
var
oSecret: TBytes;
begin
oSecret := sgcRandomBytes(20);
if sgcTOTP_Verify(oSecret, edtCode.Text, DateTimeToUnix(Now, False)) then
{ accepted };
end;
秘密のプロビジョニング
認証アプリは、otpauth://URI をエンコードした QR コードをスキャンすることでセットアップされます。この中の秘密は Base32 でエンコードされています(暗号技術の概要ページのsgcBase32Encodeを参照)。
var
oSecret: TBytes;
vURI: string;
begin
oSecret := sgcRandomBytes(20);
vURI := 'otpauth://totp/MyApp:user@example.com?secret=' + sgcBase32Encode(oSecret) +
'&issuer=MyApp&digits=6&period=30';
{ render vURI as a QR code for the user to scan }
end;