@@ -59,12 +59,16 @@ TArgon2 = class(TObject)
59
59
FSalt: TBytes;
60
60
FKnownSecret: TBytes;
61
61
FAssociatedData: TBytes;
62
+ class function StringToUtf8 (Source: UnicodeString): TBytes;
63
+ class function PasswordStringPrep (Source: UnicodeString): UnicodeString;
62
64
protected
63
65
FHashType: Cardinal; // 0=Argon2d, 1=Argon2i, 2=Argon2id
64
66
function GenerateSeedBlock (const Passphrase; PassphraseLength, DesiredNumberOfBytes: Integer): TBytes;
65
67
function GenerateInitialBlock (const H0: TBytes; ColumnIndex, LaneIndex: Integer): TBytes;
66
68
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;
68
72
69
73
class function Base64Encode (const data: array of Byte): string;
70
74
class function Base64Decode (const s: string): TBytes;
@@ -431,6 +435,16 @@ class procedure TArgon2.BurnBytes(var data: TBytes);
431
435
SetLength(data, 0 );
432
436
end ;
433
437
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
+
434
448
const
435
449
// The Blake2 IV comes from the SHA2-512 IV.
436
450
// 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
462
476
var
463
477
ar: TArgon2;
464
478
algorithm: string;
479
+ passwordUtf8: TBytes;
465
480
version, iterations, memorySizeKB, parallelism: Integer;
466
481
salt, expected, actual: TBytes;
467
482
t1, t2, freq: Int64;
@@ -475,8 +490,9 @@ class function TArgon2.CheckPassword(const Password: UnicodeString; const Expect
475
490
if not ar.TryParseHashString(ExpectedHashString, { out} algorithm, { out} version, { out} iterations, { out} memorySizeKB, { out} parallelism, { out} salt, { out} expected) then
476
491
raise EArgon2Exception.Create(' Could not parse password hash string' );
477
492
try
493
+ passwordUtf8 := TArgon2.PasswordToBytes(Password);
478
494
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 );
480
496
QueryPerformanceCounter(t2);
481
497
482
498
if Length(actual) <> Length(expected) then
@@ -499,6 +515,7 @@ class function TArgon2.CheckPassword(const Password: UnicodeString; const Expect
499
515
finally
500
516
ar.BurnBytes(actual);
501
517
ar.BurnBytes(expected);
518
+ ar.BurnBytes({ var} passwordUtf8);
502
519
end ;
503
520
finally
504
521
ar.Free;
@@ -846,7 +863,7 @@ class function TArgon2.HashPassword(const Password: UnicodeString; const Iterati
846
863
try
847
864
salt := ar.GenerateSalt;
848
865
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
850
867
try
851
868
derivedBytes := TArgon2.DeriveBytes(utf8Password, Length(Password)*SizeOf(WideChar), salt, Iterations, MemorySizeKB, Parallelism, 32 );
852
869
finally
@@ -869,23 +886,20 @@ procedure BurnString(var s: UnicodeString);
869
886
end ;
870
887
end ;
871
888
872
- class function TArgon2.PasswordStringPrep (Source: UnicodeString): TBytes ;
889
+ class function TArgon2.PasswordStringPrep (Source: UnicodeString): UnicodeString ;
873
890
var
874
- normalizedLength: Integer;
875
- normalized: UnicodeString;
876
891
strLen: Integer;
892
+ normalized: UnicodeString;
877
893
dw: DWORD;
878
894
i: Integer;
879
895
const
880
896
CodePage = CP_UTF8;
881
897
SLenError = ' [PasswordStringPrep] Could not get length of destination string. Error %d (%s)' ;
882
898
SConvertStringError = ' [PasswordStringPrep] Could not convert utf16 to utf8 string. Error %d (%s)' ;
883
899
begin
900
+ Result := ' ' ;
884
901
if Length(Source) = 0 then
885
- begin
886
- SetLength(Result, 0 );
887
902
Exit;
888
- end ;
889
903
890
904
{
891
905
NIST Special Publication 800-63-3B (Digital Authentication Guideline - Authentication and Lifecycle Management)
@@ -896,7 +910,7 @@ class function TArgon2.PasswordStringPrep(Source: UnicodeString): TBytes;
896
910
- C (Composition): adds character+diacritic into single code point (if possible)
897
911
- D (Decomposition): separates an accented character into the letter and the diacritic
898
912
899
- SASLprep (rfc4013) says to use NFKC:
913
+ SASLprep (RFC4013) agrees, saying to use NFKC:
900
914
901
915
2.2. Normalization
902
916
@@ -928,7 +942,7 @@ class function TArgon2.PasswordStringPrep(Source: UnicodeString): TBytes;
928
942
929
943
RFC7613 - Preparation, Enforcement, and Comparison of Internationalized Strings Representing Usernames and Passwords
930
944
931
- reverses earlier RFC, and specifies NFC:
945
+ and reverses earlier RFC, and specifies NFC:
932
946
933
947
4.2.2. Enforcement
934
948
@@ -979,7 +993,7 @@ class function TArgon2.PasswordStringPrep(Source: UnicodeString): TBytes;
979
993
980
994
This loss of data when using KC is evident in RFC7613's requirement:
981
995
982
- ... halfwidth characters MUST NOT be mapped to their decomposition mappings...
996
+ ...halfwidth characters MUST NOT be mapped to their decomposition mappings...
983
997
984
998
Using Form NFKC causes the half-width character
985
999
@@ -1037,59 +1051,76 @@ class function TArgon2.PasswordStringPrep(Source: UnicodeString): TBytes;
1037
1051
1038
1052
// We use concrete variable for length, because i've seen it return asking for 64 bytes for a 6 byte string
1039
1053
// 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
1042
1056
begin
1043
1057
dw := GetLastError;
1044
1058
raise EConvertError.CreateFmt(SLenError, [dw, SysErrorMessage(dw)]);
1045
1059
end ;
1046
1060
1047
1061
// Allocate memory for destination string
1048
- SetLength(normalized, normalizedLength );
1062
+ SetLength(Result, strLen );
1049
1063
1050
1064
// 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);
1051
1079
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 ;
1059
1085
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 );
1073
1097
1074
- // Allocate memory for destination string
1075
- SetLength(Result, strLen+ 1 ); // +1 for the null terminator
1098
+ if Length(Source) = 0 then
1099
+ Exit;
1076
1100
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 ;
1087
1111
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)]);
1093
1124
end ;
1094
1125
end ;
1095
1126
0 commit comments