HOTP와 TOTP는 휴대폰 인증 앱이 보여주는 코드입니다. 두 함수 모두 sgcCrypto_OTP에 들어 있으며 HMAC(해싱 및 메시지 인증 참조)을 기반으로 합니다.
HOTP (카운터 기반)
코드는 공유 비밀과 사용할 때마다 1씩 증가하는 카운터에 따라 결정됩니다. 클라이언트와 서버는 서로 보조를 맞춰야 합니다: 클라이언트가 서버가 결코 보지 못하는 코드를 생성하면(예를 들어 네트워크 연결 없이 버튼을 눌렀을 때), 카운터가 서로 어긋나기 시작하고, 서버가 다시 동기화할 때까지 이후 클라이언트가 보여주는 모든 코드가 실패합니다. 이러한 어긋남이 대부분의 소비자용 인증 앱에서 TOTP가 HOTP를 대체한 주된 이유입니다.
function sgcHOTP(const aSecret: TBytes; aCounter: Int64; aDigits: Integer = 6): string;
TOTP (시간 기반)
HOTP의 카운터는 고정된 간격(기본 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;