Skip to content

Commit 0eda2df

Browse files
committed
Update hash password function to prep the password
1 parent 47bfcea commit 0eda2df

File tree

2 files changed

+119
-79
lines changed

2 files changed

+119
-79
lines changed

Argon2.pas

Lines changed: 83 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,16 @@ TArgon2 = class(TObject)
5959
FSalt: TBytes;
6060
FKnownSecret: TBytes;
6161
FAssociatedData: TBytes;
62+
class function StringToUtf8(Source: UnicodeString): TBytes;
63+
class function PasswordStringPrep(Source: UnicodeString): UnicodeString;
6264
protected
6365
FHashType: Cardinal; //0=Argon2d, 1=Argon2i, 2=Argon2id
6466
function GenerateSeedBlock(const Passphrase; PassphraseLength, DesiredNumberOfBytes: Integer): TBytes;
6567
function GenerateInitialBlock(const H0: TBytes; ColumnIndex, LaneIndex: Integer): TBytes;
6668
class procedure BurnBytes(var data: TBytes);
67-
class function PasswordStringPrep(Source: UnicodeString): TBytes;
69+
class procedure BurnString(var s: UnicodeString);
70+
71+
class function PasswordToBytes(Source: UnicodeString): TBytes;
6872

6973
class function Base64Encode(const data: array of Byte): string;
7074
class function Base64Decode(const s: string): TBytes;
@@ -431,6 +435,16 @@ class procedure TArgon2.BurnBytes(var data: TBytes);
431435
SetLength(data, 0);
432436
end;
433437

438+
class procedure TArgon2.BurnString(var s: UnicodeString);
439+
begin
440+
if Length(s) > 0 then
441+
begin
442+
UniqueString({var}s); //We can't FillChar the string if it's shared, or its in the constant data page
443+
FillChar(s[1], Length(s), 0);
444+
s := '';
445+
end;
446+
end;
447+
434448
const
435449
//The Blake2 IV comes from the SHA2-512 IV.
436450
//The values are the the fractional part of the square root of the first 8 primes (2, 3, 5, 7, 11, 13, 17, 19)
@@ -462,6 +476,7 @@ class function TArgon2.CheckPassword(const Password: UnicodeString; const Expect
462476
var
463477
ar: TArgon2;
464478
algorithm: string;
479+
passwordUtf8: TBytes;
465480
version, iterations, memorySizeKB, parallelism: Integer;
466481
salt, expected, actual: TBytes;
467482
t1, t2, freq: Int64;
@@ -475,8 +490,9 @@ class function TArgon2.CheckPassword(const Password: UnicodeString; const Expect
475490
if not ar.TryParseHashString(ExpectedHashString, {out}algorithm, {out}version, {out}iterations, {out}memorySizeKB, {out}parallelism, {out}salt, {out}expected) then
476491
raise EArgon2Exception.Create('Could not parse password hash string');
477492
try
493+
passwordUtf8 := TArgon2.PasswordToBytes(Password);
478494
QueryPerformanceCounter(t1);
479-
actual := TArgon2.DeriveBytes(Password, Length(Password)*SizeOf(WideChar), salt, iterations, memorySizeKB, parallelism, 32);
495+
actual := TArgon2.DeriveBytes(passwordUtf8, Length(Password)*SizeOf(WideChar), salt, iterations, memorySizeKB, parallelism, 32);
480496
QueryPerformanceCounter(t2);
481497

482498
if Length(actual) <> Length(expected) then
@@ -499,6 +515,7 @@ class function TArgon2.CheckPassword(const Password: UnicodeString; const Expect
499515
finally
500516
ar.BurnBytes(actual);
501517
ar.BurnBytes(expected);
518+
ar.BurnBytes({var}passwordUtf8);
502519
end;
503520
finally
504521
ar.Free;
@@ -846,7 +863,7 @@ class function TArgon2.HashPassword(const Password: UnicodeString; const Iterati
846863
try
847864
salt := ar.GenerateSalt;
848865

849-
utf8Password := TArgon2.PasswordStringPrep(Password); //use proper normalization of spaces,
866+
utf8Password := TArgon2.PasswordToBytes(Password); //use proper normalization Form C, convert all spaces, utf8 encoding
850867
try
851868
derivedBytes := TArgon2.DeriveBytes(utf8Password, Length(Password)*SizeOf(WideChar), salt, Iterations, MemorySizeKB, Parallelism, 32);
852869
finally
@@ -869,23 +886,20 @@ procedure BurnString(var s: UnicodeString);
869886
end;
870887
end;
871888

872-
class function TArgon2.PasswordStringPrep(Source: UnicodeString): TBytes;
889+
class function TArgon2.PasswordStringPrep(Source: UnicodeString): UnicodeString;
873890
var
874-
normalizedLength: Integer;
875-
normalized: UnicodeString;
876891
strLen: Integer;
892+
normalized: UnicodeString;
877893
dw: DWORD;
878894
i: Integer;
879895
const
880896
CodePage = CP_UTF8;
881897
SLenError = '[PasswordStringPrep] Could not get length of destination string. Error %d (%s)';
882898
SConvertStringError = '[PasswordStringPrep] Could not convert utf16 to utf8 string. Error %d (%s)';
883899
begin
900+
Result := '';
884901
if Length(Source) = 0 then
885-
begin
886-
SetLength(Result, 0);
887902
Exit;
888-
end;
889903

890904
{
891905
NIST Special Publication 800-63-3B (Digital Authentication Guideline - Authentication and Lifecycle Management)
@@ -896,7 +910,7 @@ class function TArgon2.PasswordStringPrep(Source: UnicodeString): TBytes;
896910
- C (Composition): adds character+diacritic into single code point (if possible)
897911
- D (Decomposition): separates an accented character into the letter and the diacritic
898912
899-
SASLprep (rfc4013) says to use NFKC:
913+
SASLprep (RFC4013) agrees, saying to use NFKC:
900914
901915
2.2. Normalization
902916
@@ -928,7 +942,7 @@ class function TArgon2.PasswordStringPrep(Source: UnicodeString): TBytes;
928942
929943
RFC7613 - Preparation, Enforcement, and Comparison of Internationalized Strings Representing Usernames and Passwords
930944
931-
reverses earlier RFC, and specifies NFC:
945+
and reverses earlier RFC, and specifies NFC:
932946
933947
4.2.2. Enforcement
934948
@@ -979,7 +993,7 @@ class function TArgon2.PasswordStringPrep(Source: UnicodeString): TBytes;
979993
980994
This loss of data when using KC is evident in RFC7613's requirement:
981995
982-
... halfwidth characters MUST NOT be mapped to their decomposition mappings...
996+
...halfwidth characters MUST NOT be mapped to their decomposition mappings...
983997
984998
Using Form NFKC causes the half-width character
985999
@@ -1037,59 +1051,76 @@ class function TArgon2.PasswordStringPrep(Source: UnicodeString): TBytes;
10371051

10381052
//We use concrete variable for length, because i've seen it return asking for 64 bytes for a 6 byte string
10391053
// normalizedLength := NormalizeString(5, PWideChar(Source), Length(Source), nil, 0);
1040-
normalizedLength := FoldString(MAP_PRECOMPOSED, PWideChar(Source), Length(Source), nil, 0);
1041-
if normalizedLength = 0 then
1054+
strLen := FoldString(MAP_PRECOMPOSED, PWideChar(Source), Length(Source), nil, 0);
1055+
if strLen = 0 then
10421056
begin
10431057
dw := GetLastError;
10441058
raise EConvertError.CreateFmt(SLenError, [dw, SysErrorMessage(dw)]);
10451059
end;
10461060

10471061
// Allocate memory for destination string
1048-
SetLength(normalized, normalizedLength);
1062+
SetLength(Result, strLen);
10491063

10501064
// Now do it for real
1065+
// normalizedLength := NormalizeString(5, PWideChar(Source), Length(Source), PWideChar(normalized), Length(normalized));
1066+
strLen := FoldString(MAP_PRECOMPOSED, PWideChar(Source), Length(Source), PWideChar(Result), Length(Result));
1067+
if strLen = 0 then
1068+
begin
1069+
dw := GetLastError;
1070+
raise EConvertError.CreateFmt(SLenError, [dw, SysErrorMessage(dw)]);
1071+
end;
1072+
end;
1073+
1074+
class function TArgon2.PasswordToBytes(Source: UnicodeString): TBytes;
1075+
var
1076+
prepared: UnicodeString;
1077+
begin
1078+
prepared := TArgon2.PasswordStringPrep(Source);
10511079
try
1052-
// normalizedLength := NormalizeString(5, PWideChar(Source), Length(Source), PWideChar(normalized), Length(normalized));
1053-
normalizedLength := FoldString(MAP_PRECOMPOSED, PWideChar(Source), Length(Source), PWideChar(normalized), Length(normalized));
1054-
if normalizedLength = 0 then
1055-
begin
1056-
dw := GetLastError;
1057-
raise EConvertError.CreateFmt(SLenError, [dw, SysErrorMessage(dw)]);
1058-
end;
1080+
Result := TArgon2.StringToUtf8(prepared);
1081+
finally
1082+
TArgon2.BurnString({var}prepared);
1083+
end;
1084+
end;
10591085

1060-
{
1061-
Now perform the conversion of UTF-16 to UTF-8
1062-
}
1063-
// Determine real size of destination string, in bytes
1064-
strLen := WideCharToMultiByte(CodePage, 0,
1065-
PWideChar(normalized), normalizedLength, //Source
1066-
nil, 0, //Destination
1067-
nil, nil);
1068-
if strLen = 0 then
1069-
begin
1070-
dw := GetLastError;
1071-
raise EConvertError.CreateFmt(SLenError, [dw, SysErrorMessage(dw)]);
1072-
end;
1086+
class function TArgon2.StringToUtf8(Source: UnicodeString): TBytes;
1087+
var
1088+
strLen: Integer;
1089+
dw: DWORD;
1090+
i: Integer;
1091+
const
1092+
CodePage = CP_UTF8;
1093+
SLenError = '[StringToUtf8] Could not get length of destination string. Error %d (%s)';
1094+
SConvertStringError = '[StringToUtf8] Could not convert utf16 to utf8 string. Error %d (%s)';
1095+
begin
1096+
SetLength(Result, 0);
10731097

1074-
// Allocate memory for destination string
1075-
SetLength(Result, strLen+1); //+1 for the null terminator
1098+
if Length(Source) = 0 then
1099+
Exit;
10761100

1077-
// Convert source UTF-16 string (WideString) to the destination using the code-page
1078-
strLen := WideCharToMultiByte(CodePage, 0,
1079-
PWideChar(normalized), normalizedLength, //Source
1080-
PAnsiChar(Result), strLen, //Destination
1081-
nil, nil);
1082-
if strLen = 0 then
1083-
begin
1084-
dw := GetLastError;
1085-
raise EConvertError.CreateFmt(SConvertStringError, [dw, SysErrorMessage(dw)]);
1086-
end;
1101+
// Determine real size of destination string, in bytes
1102+
strLen := WideCharToMultiByte(CP_UTF8, 0,
1103+
PWideChar(Source), Length(Source), //Source
1104+
nil, 0, //Destination
1105+
nil, nil);
1106+
if strLen = 0 then
1107+
begin
1108+
dw := GetLastError;
1109+
raise EConvertError.CreateFmt(SLenError, [dw, SysErrorMessage(dw)]);
1110+
end;
10871111

1088-
//Set the null terminator
1089-
Result[strLen] := 0;
1090-
finally
1091-
//Burn the intermediate normalized form
1092-
BurnString(normalized);
1112+
// Allocate memory for destination string
1113+
SetLength(Result, strLen);
1114+
1115+
// Convert source UTF-16 string (WideString) to the destination using the code-page
1116+
strLen := WideCharToMultiByte(CodePage, 0,
1117+
PWideChar(Source), Length(Source), //Source
1118+
PAnsiChar(Result), strLen, //Destination
1119+
nil, nil);
1120+
if strLen = 0 then
1121+
begin
1122+
dw := GetLastError;
1123+
raise EConvertError.CreateFmt(SConvertStringError, [dw, SysErrorMessage(dw)]);
10931124
end;
10941125
end;
10951126

Argon2Tests.pas

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ procedure TArgon2Tests.NormalizedPasswordsMatch;
119119
i: U+0069
120120
n: U+006E
121121
}
122-
password1 := 'A' + #$0308 + #$FB01 + 'n';
123-
password2 := #$00C4 + 'f' + 'i' + 'n';
122+
password1 := 'A' + #$0308 + #$FB01 + 'n'; //uncomposed form
123+
password2 := #$00C4 + 'f' + 'i' + 'n'; //composed form
124124

125125
hash := TArgon2.HashPassword(password1, 1, 16, 1);
126126
bRes := TArgon2.CheckPassword(password2, hash, {out}passwordRehashNeeded);
@@ -161,7 +161,7 @@ procedure TArgon2Tests.PasswordPrep_HalfWidthFullWidth;
161161
U+FF54 FULLWIDTH LATIN SMALL LETTER t UTF8 0xEF 0xBD 0x94
162162
}
163163
pass := #$ff34 + #$ff45 + #$ff53 + #$ff54;
164-
TestStringPrep(pass, [$ef, $bc, $b4, $ef, $bd, $85, $ef, $bd, $93, $ef, $bd, $94, 0]);
164+
TestStringPrep(pass, [$ef, $bc, $b4, $ef, $bd, $85, $ef, $bd, $93, $ef, $bd, $94]);
165165

166166
{
167167
Halfwidth
@@ -174,12 +174,10 @@ procedure TArgon2Tests.PasswordPrep_HalfWidthFullWidth;
174174
U+1162 HANGUL JUNGSEONG AE UTF8 0xE1 0x85 0xA2
175175
}
176176
pass := #$ffc3;
177-
TestStringPrep(pass, [$ef, $bf, $83, 0]);
177+
TestStringPrep(pass, [$ef, $bf, $83]);
178178
end;
179179

180180
procedure TArgon2Tests.PasswordPrep_Spaces;
181-
var
182-
pass: UnicodeString;
183181
begin
184182
{
185183
SASLprep rules for passwords
@@ -200,23 +198,23 @@ procedure TArgon2Tests.PasswordPrep_Spaces;
200198
201199
4. Normalization Rule: Unicode Normalization Form C (NFC) MUST be applied to all characters.
202200
}
203-
TestStringPrep(#$0020, [$20, $00]); //U+0020 SPACE
204-
TestStringPrep(#$00A0, [$20, $00]); //U+00A0 NO-BREAK SPACE
205-
TestStringPrep(#$1680, [$20, $00]); //U+1680 OGHAM SPACE MARK
206-
TestStringPrep(#$2000, [$20, $00]); //U+2000 EN QUAD
207-
TestStringPrep(#$2001, [$20, $00]); //U+2001 EM QUAD
208-
TestStringPrep(#$2002, [$20, $00]); //U+2002 EN SPACE
209-
TestStringPrep(#$2003, [$20, $00]); //U+2003 EM SPACE
210-
TestStringPrep(#$2004, [$20, $00]); //U+2004 THREE-PER-EM SPACE
211-
TestStringPrep(#$2005, [$20, $00]); //U+2005 FOUR-PER-EM SPACE
212-
TestStringPrep(#$2006, [$20, $00]); //U+2006 SIX-PER-EM SPACE
213-
TestStringPrep(#$2007, [$20, $00]); //U+2007 FIGURE SPACE
214-
TestStringPrep(#$2008, [$20, $00]); //U+2008 PUNCTUATION SPACE
215-
TestStringPrep(#$2009, [$20, $00]); //U+2009 THIN SPACE
216-
TestStringPrep(#$200A, [$20, $00]); //U+200A HAIR SPACE
217-
TestStringPrep(#$202F, [$20, $00]); //U+202F NARROW NO-BREAK SPACE
218-
TestStringPrep(#$205F, [$20, $00]); //U+205F MEDIUM MATHEMATICAL SPACE
219-
TestStringPrep(#$3000, [$20, $00]); //U+3000 IDEOGRAPHIC SPACE
201+
TestStringPrep(#$0020, [$20]); //U+0020 SPACE
202+
TestStringPrep(#$00A0, [$20]); //U+00A0 NO-BREAK SPACE
203+
TestStringPrep(#$1680, [$20]); //U+1680 OGHAM SPACE MARK
204+
TestStringPrep(#$2000, [$20]); //U+2000 EN QUAD
205+
TestStringPrep(#$2001, [$20]); //U+2001 EM QUAD
206+
TestStringPrep(#$2002, [$20]); //U+2002 EN SPACE
207+
TestStringPrep(#$2003, [$20]); //U+2003 EM SPACE
208+
TestStringPrep(#$2004, [$20]); //U+2004 THREE-PER-EM SPACE
209+
TestStringPrep(#$2005, [$20]); //U+2005 FOUR-PER-EM SPACE
210+
TestStringPrep(#$2006, [$20]); //U+2006 SIX-PER-EM SPACE
211+
TestStringPrep(#$2007, [$20]); //U+2007 FIGURE SPACE
212+
TestStringPrep(#$2008, [$20]); //U+2008 PUNCTUATION SPACE
213+
TestStringPrep(#$2009, [$20]); //U+2009 THIN SPACE
214+
TestStringPrep(#$200A, [$20]); //U+200A HAIR SPACE
215+
TestStringPrep(#$202F, [$20]); //U+202F NARROW NO-BREAK SPACE
216+
TestStringPrep(#$205F, [$20]); //U+205F MEDIUM MATHEMATICAL SPACE
217+
TestStringPrep(#$3000, [$20]); //U+3000 IDEOGRAPHIC SPACE
220218
end;
221219

222220
function TArgon2Tests.Blake2b(const Data; DataLen, DesiredBytes: Integer; const Key; KeyLen: Integer): TBytes;
@@ -393,6 +391,10 @@ procedure TArgon2Tests.Test_Argon2i;
393391
ar: TArgon2;
394392
expected, actual: TBytes;
395393
begin
394+
{
395+
Argon2d test vector
396+
https://tools.ietf.org/id/draft-irtf-cfrg-argon2-03.html#rfc.section.6.1
397+
}
396398
password := TBytes.Create(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); //32 bytes of 1
397399

398400
//Argon2i
@@ -404,6 +406,7 @@ procedure TArgon2Tests.Test_Argon2i;
404406
ar.Salt := TBytes.Create(2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2); //16 bytes of 2
405407
ar.KnownSecret := TBytes.Create(3, 3, 3, 3, 3, 3, 3, 3); //8 bytes of 3
406408
ar.AssociatedData := TBytes.Create(4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4); //12 bytes of 4
409+
407410
actual := ar.GetBytes(password[0], Length(password), 32);
408411
finally
409412
ar.Free;
@@ -785,7 +788,7 @@ procedure TArgon2Tests.UnicodeNormalizationComposition;
785788
}
786789
password := 'A' + #$0308 + #$FB01 + 'n';
787790

788-
TestStringPrep(password, [$C3, $84, $EF, $AC, $81, $6E, 0]);
791+
TestStringPrep(password, [$C3, $84, $EF, $AC, $81, $6E]);
789792
end;
790793

791794
function TArgon2Tests.GetBlake2bUnkeyedTestVector(Index: Integer): string;
@@ -1234,11 +1237,11 @@ procedure TArgon2Tests.CheckEqualsBytes(const ExpectedBytes: array of Byte; cons
12341237

12351238
procedure TArgon2Tests.TestStringPrep(const s: UnicodeString; Expected: array of Byte);
12361239
var
1237-
data: TBytes;
1240+
actual: TBytes;
12381241
begin
1239-
data := TArgon2Friend.PasswordStringPrep(s);
1242+
actual := TArgon2Friend.PasswordToBytes(s);
12401243

1241-
CheckEqualsBytes(Expected, data, s);
1244+
CheckEqualsBytes(Expected, actual, s);
12421245
end;
12431246

12441247
function TArgon2Tests.GetBlake2bKeyedTestVector(Index: Integer): string;
@@ -1575,6 +1578,12 @@ procedure TArgon2Tests.HashAlgorithm_Blake2b_Splits;
15751578
hasher: IHashAlgorithm;
15761579
split1, split2: Integer;
15771580
begin
1581+
if not FindCmdLineSwitch('IncludeSlowTests', ['-','/'], True) then
1582+
begin
1583+
Status('Skipping slow test. Use -IncludeSlowTests');
1584+
Exit;
1585+
end;
1586+
15781587
{
15791588
https://github.com/BLAKE2/BLAKE2/blob/master/csharp/Blake2Sharp.Tests/SequentialTests.cs
15801589

0 commit comments

Comments
 (0)