Skip to content

Commit

Permalink
Comment and minor source cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
darianmiller committed Oct 25, 2021
1 parent e1f4276 commit 959946d
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 12 deletions.
25 changes: 14 additions & 11 deletions Source/radRTL.HOTP.pas
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ EOTPException = class(Exception);
THOTP = class
private const
ModTable: array [0 .. 2] of integer = (1000000, 10000000, 100000000); // 6,7,8 zeros matching OTP Length
FormatTable: array [0 .. 2] of string = ('%.6d', '%.7d', '%.8d'); // 6,7,8 string length (padded left with zeros)
FormatTable: array [0 .. 2] of string = ('%.6d', '%.7d', '%.8d'); // 6,7,8 string length (padded left with zeros)
RFCMinimumKeyLengthBytes = 16; // length of shared secret MUST be 128 bits (16 bytes)
public
/// <summary> HOTP: HMAC-Based One-Time Password Algorithm</summary>
Expand All @@ -45,31 +45,34 @@ class function THOTP.GeneratePassword(const pBase32EncodedSecretKey:string; cons

var
vEncodedKey:TBytes;
vDecdedKey:TBytes;
vDecodedKey:TBytes;
vData:TBytes;
vHMAC:TBytes;
vOffset:integer;
vBinCode:integer;
vPinNumber:Integer;
vPinNumber:integer;
begin
vEncodedKey := TEncoding.UTF8.GetBytes(pBase32EncodedSecretKey); // assume secret was stored as UTF8 (prover and verifier must match)
if Length(vEncodedKey) < RFCMinimumKeyLengthBytes then
begin
// RFC minimum length required (Note: did not see this limitation in other implementations)
raise EOTPException.CreateRes(@sOTPKeyLengthTooShort);
end;
vDecdedKey := TBase32.Decode(vEncodedKey);
vData := ReverseByteArray(ConvertToByteArray(pCounterValue)); // Convert to big-endian
vHMAC := THashSHA1.GetHMACAsBytes(vData, vDecdedKey);
vDecodedKey := TBase32.Decode(vEncodedKey);
vData := ReverseByteArray(ConvertToByteArray(pCounterValue)); // RFC reference implmentation reversed order of CounterValue (movingFactor) bytes
vHMAC := THashSHA1.GetHMACAsBytes(vData, vDecodedKey); // SHA1 = 20 byte digest

// rfc notes: extract a 4-byte dynamic binary integer code from a 160-bit (20-byte) HMAC-SHA-1 binary digest.
vOffset := vHMAC[19] and $0F;
vBinCode := ((vHMAC[vOffset] and $7F) shl 24) or ((vHMAC[vOffset + 1] and $FF) shl 16) or ((vHMAC[vOffset + 2] and $FF) shl 8) or (vHMAC[vOffset + 3] and $FF);
// rfc notes: extract a 4-byte dynamic binary integer code from the HMAC result
vOffset := vHMAC[19] and $0F; // extract a random number 0 to 15 (from the value of the very last byte of the hash digest AND 0000-1111)

// trim hash result to 6, 7, or 8 digits in length
// 4 bytes extracted starting at this random offset (first bit intentionally zero'ed to avoid compatibility problems with signed vs unsigned MOD operations)
vBinCode := ((vHMAC[vOffset] and $7F) shl 24) // byte at offset AND 0111-1111 moved to first 8 bits of result
or (vHMAC[vOffset + 1] shl 16) or (vHMAC[vOffset + 2] shl 8) or vHMAC[vOffset + 3];

// trim 31-bit unsigned value to 6 to 8 digits in length
vPinNumber := vBinCode mod THOTP.ModTable[Ord(pOutputLength)];

// Format result, padded left with zeros as needed
// Format the 6 to 8 digit OTP result by padding left with zeros as needed
Result := Format(FormatTable[Ord(pOutputLength)], [vPinNumber]);
end;

Expand Down
2 changes: 1 addition & 1 deletion Source/radRTL.TOTP.pas
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class function TTOTP.GetCurrentUnixTimestamp():Int64;
// https://datatracker.ietf.org/doc/html/rfc6238
class function TTOTP.GeneratePassword(const pBase32EncodedSecretKey:string; const pOutputLength:TOTPLength = TOTPLength.SixDigits):string;
begin
Result := TTOTP.GeneratePassword(pBase32EncodedSecretKey, GetCurrentUnixTimestamp, pOutputLength);
Result := THOTP.GeneratePassword(pBase32EncodedSecretKey, GetCurrentUnixTimestamp, pOutputLength);
end;


Expand Down

0 comments on commit 959946d

Please sign in to comment.