One Time Passwords

HOTP and TOTP are the codes a phone authenticator app shows. Both functions live in sgcCrypto_OTP and are built on HMAC (see Hashing & Message Authentication).

HOTP (counter based)

The code depends on a shared secret and a counter that advances by one on every use. The client and the server must stay in step: if the client generates codes the server never sees (a button pressed with no network connection, for example), the counters drift apart and every code the client shows afterward fails until the server resynchronizes. That drift is the main reason TOTP replaced HOTP for most consumer authenticator use.


function sgcHOTP(const aSecret: TBytes; aCounter: Int64; aDigits: Integer = 6): string;

TOTP (time based)

The counter in HOTP is replaced with the current Unix time divided into fixed steps, 30 seconds by default, so the client and the server only need roughly synchronized clocks rather than a synchronized counter. This is what every authenticator app implements.


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;

Verifying a code

aWindow accepts codes from that many steps before and after the current one, which absorbs a small clock skew or a user who was slow to type the code. The comparison is constant time internally, so it does not leak how many digits of a guess were correct.


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;

Provisioning a secret

An authenticator app is set up by scanning a QR code that encodes an otpauth:// URI. The secret inside it is base32 encoded (see sgcBase32Encode on the Cryptography overview page):


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;