mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-22 19:29:24 +02:00
parent
1d891aa7c6
commit
1d59539cc9
@ -37,6 +37,7 @@ begin
|
||||
T:=P.Targets.AddUnit('src/hashutils.pp');
|
||||
T:=P.Targets.AddUnit('src/sha256.pp');
|
||||
T.Dependencies.AddUnit('hashutils');
|
||||
T:=P.Targets.AddUnit('src/onetimepass.pp');
|
||||
T:=P.Targets.AddUnit('src/crc.pas');
|
||||
T:=P.Targets.AddUnit('src/ntlm.pas');
|
||||
T:=P.Targets.AddUnit('src/uuid.pas');
|
||||
|
129
packages/hash/src/onetimepass.pp
Normal file
129
packages/hash/src/onetimepass.pp
Normal file
@ -0,0 +1,129 @@
|
||||
{
|
||||
This file is part of the Free Component Library.
|
||||
Copyright (c) 2021 by the Free Pascal team.
|
||||
|
||||
HOTP and TOTP One-time password algorithms. Compatible with the Google Authenticator.
|
||||
|
||||
See the file COPYING.FPC, included in this distribution,
|
||||
for details about the copyright.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
}
|
||||
|
||||
unit onetimepass;
|
||||
|
||||
{$mode ObjFPC}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
SysUtils , basenenc, types, DateUtils;
|
||||
|
||||
const
|
||||
TOTP_Mod = 1000000;
|
||||
TOTP_KeyRegeneration = 30; // Time step for TOTP generation. Google Authenticator uses 30 seconds.
|
||||
|
||||
Type
|
||||
TRandomBytes = Procedure (aBytes : TByteDynArray);
|
||||
|
||||
function HOTPCalculateToken(const aSecret: AnsiString; const Counter: LongInt): LongInt;
|
||||
function TOTPCalculateToken(const aSecret: AnsiString): LongInt;
|
||||
function TOTPGenerateToken(const aSecret: AnsiString): LongInt;
|
||||
function TOTPValidate(const aSecret: AnsiString; const Token: LongInt; const WindowSize: LongInt; var Counter: LongInt): Boolean;
|
||||
Function TOTPSharedSecret(aRandom : TRandomBytes = Nil) : String;
|
||||
|
||||
implementation
|
||||
|
||||
uses sha1, hmac;
|
||||
|
||||
// @Result[8]
|
||||
Function Int64ToRawString(const Value: Int64) : AnsiString;
|
||||
|
||||
var
|
||||
B: array[0..7] of Byte;
|
||||
I: Int32;
|
||||
begin
|
||||
PInt64(@B)^ := Value;
|
||||
Result:='';
|
||||
for I := 7 downto 0 do
|
||||
Result:=Result+AnsiChar(B[I]);
|
||||
end;
|
||||
|
||||
function TOTPCalculateToken(const aSecret: String): Longint;
|
||||
|
||||
begin
|
||||
Result:=HOTPCalculateToken(aSecret,-1);
|
||||
end;
|
||||
|
||||
function HOTPCalculateToken(const aSecret: String; const Counter: Longint): Longint;
|
||||
|
||||
var
|
||||
Digest: TSHA1Digest;
|
||||
Key: UInt32;
|
||||
Offset: Longint;
|
||||
Part1, Part2, Part3, Part4: UInt32;
|
||||
SecretBinBuf: TBytes;
|
||||
STime: String;
|
||||
Time: Longint;
|
||||
|
||||
begin
|
||||
Time := Counter;
|
||||
if Time=-1 then
|
||||
Time := DateTimeToUnix(Now,False) div TOTP_KeyRegeneration;
|
||||
SecretBinBuf:=Base32.Decode(aSecret);
|
||||
STime:=Int64ToRawString(Time);
|
||||
Digest:=HMACSHA1Digest(TEncoding.UTF8.GetAnsiString(SecretBinBuf), STime);
|
||||
Offset := Digest[19] and $0F;
|
||||
Part1 := (Digest[Offset + 0] and $7F);
|
||||
Part2 := (Digest[Offset + 1] and $FF);
|
||||
Part3 := (Digest[Offset + 2] and $FF);
|
||||
Part4 := (Digest[Offset + 3] and $FF);
|
||||
Key := (Part1 shl 24) or (Part2 shl 16) or (Part3 shl 8) or Part4;
|
||||
Result := Key mod TOTP_Mod; // mod 1000000 in case of otpLength of 6 digits
|
||||
end;
|
||||
|
||||
function TOTPGenerateToken(const aSecret: AnsiString): LongInt;
|
||||
begin
|
||||
Result := HOTPCalculateToken(aSecret, -1);
|
||||
end;
|
||||
|
||||
Function TOTPSharedSecret(aRandom : TRandomBytes = Nil) : String;
|
||||
|
||||
var
|
||||
RandomKey: TByteDynArray;
|
||||
I : Integer;
|
||||
|
||||
begin
|
||||
RandomKey:=[];
|
||||
SetLength(RandomKey,10);
|
||||
if aRandom <> Nil then
|
||||
aRandom(RandomKey)
|
||||
else
|
||||
For I:=0 to 9 do
|
||||
RandomKey[I]:=Random(256);
|
||||
Result:=Base32.Encode(RandomKey);
|
||||
end;
|
||||
|
||||
// @Secret Base32 encoded, @WindowSize=1
|
||||
function TOTPValidate(const aSecret: String; const Token: LongInt; const WindowSize: LongInt; var Counter: LongInt): Boolean;
|
||||
var
|
||||
TimeStamp: Longint;
|
||||
UnixTime: Longint;
|
||||
begin
|
||||
Result := False;
|
||||
UnixTime := DateTimeToUnix(Now,False);
|
||||
TimeStamp := UnixTime div TOTP_KeyRegeneration;
|
||||
Counter := Timestamp-WindowSize;
|
||||
while Counter <= TimeStamp+WindowSize do
|
||||
begin
|
||||
Result := HOTPCalculateToken(aSecret, Counter) = Token;
|
||||
if Result then
|
||||
Exit;
|
||||
Inc(Counter);
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
77
packages/hash/tests/testonetimepass.pp
Normal file
77
packages/hash/tests/testonetimepass.pp
Normal file
@ -0,0 +1,77 @@
|
||||
unit testonetimepass;
|
||||
|
||||
{$mode ObjFPC}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
FPCUnit, TestRegistry, Classes, SysUtils, onetimepass ;
|
||||
|
||||
type
|
||||
|
||||
{ TTestOnetimePass }
|
||||
|
||||
TTestOnetimePass = class(TTestCase)
|
||||
Published
|
||||
Procedure Test1Interval;
|
||||
Procedure Test2Intervals;
|
||||
Procedure TestValid1;
|
||||
Procedure TestInValid1;
|
||||
Procedure TestGen;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
Const
|
||||
Secret = 'MFRGGZDFMZTWQ2LK';
|
||||
|
||||
Procedure TTestOnetimePass.Test1Interval;
|
||||
|
||||
begin
|
||||
AssertEquals('1 interval', 765705, HOTPCalculateToken(Secret, 1));
|
||||
end;
|
||||
|
||||
procedure TTestOnetimePass.Test2Intervals;
|
||||
begin
|
||||
AssertEquals('2 interval', 816065, HOTPCalculateToken(Secret, 2));
|
||||
end;
|
||||
|
||||
procedure TTestOnetimePass.TestValid1;
|
||||
|
||||
Var
|
||||
C,Tok : LongInt;
|
||||
|
||||
begin
|
||||
C:=1;
|
||||
Tok:=TOTPCalculateToken(Secret);
|
||||
AssertTrue('Valid',TOTPValidate(Secret,Tok,1,C));
|
||||
end;
|
||||
|
||||
procedure TTestOnetimePass.TestInValid1;
|
||||
Var
|
||||
C,Tok : LongInt;
|
||||
|
||||
begin
|
||||
C:=1;
|
||||
Tok:=TOTPCalculateToken(Secret);
|
||||
AssertFalse('Invalid',TOTPValidate(Secret,Tok+1,1,C));
|
||||
end;
|
||||
|
||||
procedure TTestOnetimePass.TestGen;
|
||||
|
||||
var
|
||||
lSecret : String;
|
||||
C,Tok : LongInt;
|
||||
|
||||
begin
|
||||
c:=1;
|
||||
lSecret:=TOTPSharedSecret();
|
||||
AssertEquals('Length',16,Length(lSecret));
|
||||
Tok:=TOTPCalculateToken(lSecret);
|
||||
AssertTrue('Valid',TOTPValidate(lSecret,Tok,1,C));
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TTestOnetimePass);
|
||||
end.
|
||||
|
116
packages/hash/tests/testsha256.pp
Normal file
116
packages/hash/tests/testsha256.pp
Normal file
@ -0,0 +1,116 @@
|
||||
unit testsha256;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, fpcunit, testutils, testregistry, sha256, hashutils;
|
||||
|
||||
type
|
||||
|
||||
{ TTestSHA256 }
|
||||
|
||||
TTestSHA256 = class(TTestCase)
|
||||
Public
|
||||
Procedure TestHexString(Const aString,aDigest : String);
|
||||
Procedure TestBase64String(Const aString,aDigest : String);
|
||||
Procedure TestHMACString(Const aString,aKey,aDigest : String);
|
||||
published
|
||||
procedure TestEmpty;
|
||||
procedure TestSmallString;
|
||||
procedure TestEmptyBase64;
|
||||
procedure TestSmallBase64;
|
||||
procedure TestSmallHMAC;
|
||||
procedure TestHMACStream;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
Procedure TTestSHA256.TestHexString(Const aString,aDigest : String);
|
||||
|
||||
var
|
||||
Digest : AnsiString;
|
||||
S : TBytes;
|
||||
|
||||
begin
|
||||
S:=[];
|
||||
Digest:='';
|
||||
S:=TEncoding.UTF8.GetAnsiBytes(aString);
|
||||
SHA256Hexa(S, Digest);
|
||||
AssertEquals('Correct hex digest',aDigest, Digest);
|
||||
end;
|
||||
|
||||
procedure TTestSHA256.TestBase64String(const aString, aDigest: String);
|
||||
var
|
||||
Digest : AnsiString;
|
||||
S : TBytes;
|
||||
|
||||
begin
|
||||
S:=TEncoding.UTF8.GetAnsiBytes(aString);
|
||||
Digest:='';
|
||||
SHA256Base64(S,False,Digest);
|
||||
AssertEquals('Correct base64 digest',aDigest, Digest);
|
||||
end;
|
||||
|
||||
procedure TTestSHA256.TestHMACString(const aString, aKey, aDigest: String);
|
||||
var
|
||||
Digest : AnsiString;
|
||||
S,K : TBytes;
|
||||
|
||||
begin
|
||||
S:=TEncoding.UTF8.GetAnsiBytes(aString);
|
||||
K:=TEncoding.UTF8.GetAnsiBytes(aKey);
|
||||
HMACSHA256Hexa(K,S,Digest);
|
||||
AssertEquals('Correct base64 digest',aDigest, Digest);
|
||||
end;
|
||||
|
||||
procedure TTestSHA256.TestEmpty;
|
||||
|
||||
|
||||
begin
|
||||
TestHexString('','E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855');
|
||||
end;
|
||||
|
||||
procedure TTestSHA256.TestSmallString;
|
||||
|
||||
begin
|
||||
TestHexString('abc','BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD');
|
||||
end;
|
||||
|
||||
procedure TTestSHA256.TestEmptyBase64;
|
||||
begin
|
||||
TestBase64String('','47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU');
|
||||
end;
|
||||
|
||||
procedure TTestSHA256.TestSmallBase64;
|
||||
|
||||
begin
|
||||
TestBase64String('abc','ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0');
|
||||
end;
|
||||
|
||||
procedure TTestSHA256.TestSmallHMAC;
|
||||
begin
|
||||
TestHMACString('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||
'Secret key' ,
|
||||
'6AE3261635F57BF68B6E3DF9C06ED14D3FA793F1B7BE55BC3429895B09F52F77');
|
||||
end;
|
||||
|
||||
procedure TTestSHA256.TestHMACStream;
|
||||
|
||||
Var
|
||||
S : TStringStream;
|
||||
|
||||
begin
|
||||
S:=TStringStream.Create('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
|
||||
try
|
||||
AssertEquals('Correct hash','3964294B664613798D1A477EB8AD02118B48D0C5738C427613202F2ED123B5F1',StreamSHA256Hexa(S));
|
||||
finally
|
||||
S.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TTestSHA256);
|
||||
end.
|
||||
|
Loading…
Reference in New Issue
Block a user