{***************************************************************************** The DEC team (see file NOTICE.txt) licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. A copy of this licence is found in the root directory of this project in the file LICENCE.txt or alternatively at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. *****************************************************************************} /// /// Unit containing all the KDF, MGF, HMAC and PBKDF2 algorithms /// unit DECHashAuthentication; interface uses System.SysUtils, DECHashBase; type /// /// Meta class for all the hashing classes in order to support the /// registration mechanism /// TDECHashAuthenticationClass = class of TDECHashAuthentication; /// /// Type of the KDF variant /// TKDFType = (ktKDF1, ktKDF2, ktKDF3); /// /// Class containing all the KDF, MGF, HMAC and PBKDF2 algorithms /// TDECHashAuthentication = class(TDECHash) strict private /// /// Key deviation algorithm to derrive keys from other keys. /// IEEE P1363 Working Group, ISO 18033-2:2004 /// This is either KDF1 or KDF2 depending on KDFType /// /// /// Source data from which the new key shall be derrived. /// /// /// Size in bytes of the source data passed. /// /// /// Start value for pseudo random number generator /// /// /// Size of the seed in byte. /// /// /// Size of the generated output in byte /// /// /// Type of the algorithm: 1 = KDF1, 2 = KDF2 and 3 = KDF 3 /// /// /// Returns the new derrived key. /// class function KDFInternal(const Data; DataSize: Integer; const Seed; SeedSize, MaskSize: Integer; KDFType: TKDFType): TBytes; inline; public /// /// Detects whether the given hash class is one particularily suited /// for storing hashes of passwords /// /// /// true if it's a hash class specifically designed to store password /// hashes, false for ordinary hash algorithms. /// class function IsPasswordHash: Boolean; override; // mask generation /// /// Mask generation: generates an output based on the data given which is /// similar to a hash function but in contrast does not have a fixed output /// length. Use of a MGF is desirable in cases where a fixed-size hash /// would be inadequate. Examples include generating padding, producing /// one time pads or keystreams in symmetric key encryption, and yielding /// outputs for pseudorandom number generators. /// Indexed Mask generation function, IEEE P1363 working group /// equal to KDF1 except without seed. RFC 2437 PKCS #1 /// /// /// Data from which to generate a mask from /// /// /// Size of the input data in bytes /// /// /// Size of the returned mask in bytes /// /// /// Mask such that one cannot determine the data which had been given to /// generate this mask from. /// class function MGF1(const Data; DataSize, MaskSize: Integer): TBytes; overload; /// /// Mask generation: generates an output based on the data given which is /// similar to a hash function but incontrast does not have a fixed output /// length. Use of a MGF is desirable in cases where a fixed-size hash /// would be inadequate. Examples include generating padding, producing /// one time pads or keystreams in symmetric key encryption, and yielding /// outputs for pseudorandom number generators /// /// /// Data from which to generate a mask from /// /// /// Size of the returned mask in bytes /// /// /// Mask such that one cannot determine the data which had been given to /// generate this mask from. /// class function MGF1(const Data: TBytes; MaskSize: Integer): TBytes; overload; /// /// Key deviation algorithm to derrive keys from other keys. /// IEEE P1363 Working Group, ISO 18033-2:2004 /// /// /// Source data from which the new key shall be derrived. /// /// /// Size in bytes of the source data passed. /// /// /// Salt value /// /// /// Size of the seed/salt in byte. /// /// /// Size of the generated output in byte /// /// /// Returns the new derrived key with the length specified in MaskSize. /// /// /// In earlier versions there was an optional format parameter. This has /// been removed as this is a base class. The method might not have /// returned a result with the MaskSize specified, as the formatting might /// have had to alter this. This would have been illogical. /// class function KDF1(const Data; DataSize: Integer; const Seed; SeedSize, MaskSize: Integer): TBytes; overload; /// /// Key deviation algorithm to derrive keys from other keys. /// IEEE P1363 Working Group, ISO 18033-2:2004 /// /// /// Source data from which the new key shall be derrived. /// /// /// Salt value /// /// /// Size of the generated output in byte /// /// /// Returns the new derrived key with the length specified in MaskSize. /// class function KDF1(const Data, Seed: TBytes; MaskSize: Integer): TBytes; overload; /// /// Key deviation algorithm to derrive keys from other keys. /// IEEE P1363 Working Group, ISO 18033-2:2004 /// /// /// Source data from which the new key shall be derrived. /// /// /// Size in bytes of the source data passed. /// /// /// Salt value /// /// /// Size of the seed/salt in byte. /// /// /// Size of the generated output in byte /// /// /// Returns the new derrived key with the length specified in MaskSize. /// /// /// In earlier versions there was an optional format parameter. This has /// been removed as this is a base class. The method might not have /// returned a result with the MaskSize specified, as the formatting might /// have had to alter this. This would have been illogical. /// class function KDF2(const Data; DataSize: Integer; const Seed; SeedSize, MaskSize: Integer): TBytes; overload; /// /// Key deviation algorithm to derrive keys from other keys. /// IEEE P1363 Working Group, ISO 18033-2:2004 /// /// /// Source data from which the new key shall be derrived. /// /// /// Start value for pseudo random number generator /// /// /// Size of the generated output in byte /// /// /// Returns the new derrived key with the length specified in MaskSize. /// class function KDF2(const Data, Seed: TBytes; MaskSize: Integer): TBytes; overload; /// /// Key deviation algorithm to derrive keys from other keys. /// /// /// Source data from which the new key shall be derrived. /// /// /// Size in bytes of the source data passed. /// /// /// Salt value /// /// /// Size of the seed/salt in byte. /// /// /// Size of the generated output in byte /// /// /// Returns the new derrived key with the length specified in MaskSize. /// /// /// In earlier versions there was an optional format parameter. This has /// been removed as this is a base class. The method might not have /// returned a result with the MaskSize specified, as the formatting might /// have had to alter this. This would have been illogical. /// class function KDF3(const Data; DataSize: Integer; const Seed; SeedSize, MaskSize: Integer): TBytes; overload; /// /// Key deviation algorithm to derrive keys from other keys. /// /// /// Source data from which the new key shall be derrived. /// /// /// Salt value /// /// /// Size of the generated output in byte /// /// /// Returns the new derrived key with the length specified in MaskSize. /// class function KDF3(const Data, Seed: TBytes; MaskSize: Integer): TBytes; overload; // DEC's own KDF + MGF /// /// Key deviation algorithm to derrive keys from other keys. The alrorithm /// implemented by this method does not follow any official standard. /// /// /// Source data from which the new key shall be derrived. /// /// /// Size in bytes of the source data passed. /// /// /// Salt value /// /// /// Size of the seed/salt in byte. /// /// /// Size of the generated output in byte /// /// /// Optional parameter: can be used to specify a different default value /// for the index variable used in the algorithm. /// /// /// Returns the new derrived key with the length specified in MaskSize. /// class function KDFx(const Data; DataSize: Integer; const Seed; SeedSize, MaskSize: Integer; Index: UInt32 = 1): TBytes; overload; /// /// Key deviation algorithm to derrive keys from other keys. /// /// /// This variant of the algorithm does not follow an official standard. /// It has been created by the original author of DEC. /// /// /// Source data from which the new key shall be derrived. /// /// /// Salt value /// /// /// Size of the generated output in byte /// /// /// Optional parameter: can be used to specify a different default value /// for the index variable used in the algorithm. /// /// /// Returns the new derrived key with the length specified in MaskSize. /// class function KDFx(const Data, Seed: TBytes; MaskSize: Integer; Index: UInt32 = 1): TBytes; overload; /// /// Mask generation: generates an output based on the data given which is /// similar to a hash function but incontrast does not have a fixed output /// length. Use of a MGF is desirable in cases where a fixed-size hash /// would be inadequate. Examples include generating padding, producing /// one time pads or keystreams in symmetric key encryption, and yielding /// outputs for pseudorandom number generators. /// /// /// This variant of the algorithm does not follow an official standard. /// It has been created by the original author of DEC. /// /// /// Data from which to generate a mask from /// /// /// Size of the passed data in bytes /// /// /// Size of the returned mask in bytes /// /// /// Looks like this is a salt applied to each byte of output data? { TODO : Clarify this parameter } /// /// /// Mask such that one cannot determine the data which had been given to /// generate this mask from. /// class function MGFx(const Data; DataSize, MaskSize: Integer; Index: UInt32 = 1): TBytes; overload; /// /// Mask generation: generates an output based on the data given which is /// similar to a hash function but incontrast does not have a fixed output /// length. Use of a MGF is desirable in cases where a fixed-size hash /// would be inadequate. Examples include generating padding, producing /// one time pads or keystreams in symmetric key encryption, and yielding /// outputs for pseudorandom number generators. /// /// /// This variant of the algorithm does not follow an official standard. /// It has been created by the original author of DEC. /// /// /// Data from which to generate a mask from /// /// /// Size of the returned mask in bytes /// /// /// Looks like this is a salt applied to each byte of output data? { TODO : Clarify this parameter } /// /// /// Mask such that one cannot determine the data which had been given to /// generate this mask from. /// class function MGFx(const Data: TBytes; MaskSize: Integer; Index: UInt32 = 1): TBytes; overload; /// /// HMAC according to rfc2202: hash message authentication code allow to /// verify both the data integrity and the authenticity of a message. /// /// /// This is the secret key which shall not be transmitted over the line. /// The sender uses this key to create the resulting HMAC, transmits the /// text and the HMAC over the line and the receiver recalculates the HMAC /// based on his copy of the secret key. If his calculated HMAC equals the /// transfered HMAC value the message has not been tampered. /// /// /// Text over which to calculate the HMAC /// /// /// Calculated HMAC /// class function HMAC(const Key, Text: TBytes): TBytes; overload; /// /// HMAC according to rfc2202: hash message authentication code allow to /// verify both the data integrity and the authenticity of a message. /// /// /// This is the secret key which shall not be transmitted over the line. /// The sender uses this key to create the resulting HMAC, transmits the /// text and the HMAC over the line and the receiver recalculates the HMAC /// based on his copy of the secret key. If his calculated HMAC equals the /// transfered HMAC value the message has not been tampered. /// /// /// Text over which to calculate the HMAC /// /// /// Calculated HMAC /// class function HMAC(const Key, Text: RawByteString): TBytes; overload; /// /// Password based key deviation function 2 /// RFC 2898, PKCS #5. /// This can be used to create a login sheme by storing the output, /// number of iterations and the salt. When the user enters a password /// this calculation is done using the same parameters as stored for his /// user account and comparing the output. /// /// /// Password to create the deviation from /// /// /// Salt used to modify the password /// /// /// Number of iterations to perform /// /// /// Length of the resulting key in byte /// class function PBKDF2(const Password, Salt: TBytes; Iterations: Integer; KeyLength: Integer): TBytes; overload; /// /// Password based key deviation function 2 /// RFC 2898, PKCS #5. /// This can be used to create a login sheme by storing the output, /// number of iterations and the salt. When the user enters a password /// this calculation is done using the same parameters as stored for his /// user account and comparing the output. /// /// /// Password to create the deviation from /// /// /// Salt used to modify the password /// /// /// Number of iterations to perform /// /// /// Length of the resulting key in byte /// class function PBKDF2(const Password, Salt: RawByteString; Iterations: Integer; KeyLength: Integer): TBytes; overload; end; /// /// All hash classes with hash algorithms specially developed for password /// hashing should inherit from this class in order to be able to distinguish /// those from normal hash algorithms not really meant to be used for password /// hashing. /// TDECPasswordHash = class(TDECHashAuthentication); {$IF CompilerVersion < 28.0} /// /// Class helper for implementing array concatenation which is not available /// in Delphi XE6 or lower. /// /// /// SHall be removed as soon as the minimum supported version is XE7 or higher. /// TArrHelper = class class procedure AppendArrays(var A: TArray; const B: TArray); end; {$IFEND} implementation uses DECUtil; class function TDECHashAuthentication.IsPasswordHash: Boolean; begin Result := self.InheritsFrom(TDECPasswordHash); end; class function TDECHashAuthentication.KDFInternal(const Data; DataSize: Integer; const Seed; SeedSize, MaskSize: Integer; KDFType: TKDFType): TBytes; var I, n, Rounds, DigestBytes : Integer; Dest : PByteArray; Count : UInt32; HashInstance : TDECHashAuthentication; begin SetLength(Result, 0); DigestBytes := DigestSize; Assert(MaskSize >= 0); Assert(DataSize >= 0); Assert(SeedSize >= 0); Assert(DigestBytes >= 0); HashInstance := TDECHashAuthenticationClass(self).Create; try Rounds := (MaskSize + DigestBytes - 1) div DigestBytes; SetLength(Result, Rounds * DigestBytes); Dest := @Result[0]; if (KDFType = ktKDF2) then n := 1 else n := 0; for I := 0 to Rounds-1 do begin Count := SwapUInt32(n); HashInstance.Init; if (KDFType = ktKDF3) then begin HashInstance.Calc(Count, SizeOf(Count)); HashInstance.Calc(Data, DataSize); end else begin HashInstance.Calc(Data, DataSize); HashInstance.Calc(Count, SizeOf(Count)); end; HashInstance.Calc(Seed, SeedSize); HashInstance.Done; Move(HashInstance.Digest[0], Dest[(I) * DigestBytes], DigestBytes); inc(n); end; SetLength(Result, MaskSize); finally HashInstance.Free; end; end; class function TDECHashAuthentication.MGF1(const Data; DataSize, MaskSize: Integer): TBytes; begin Result := KDF1(Data, DataSize, NullStr, 0, MaskSize); end; class function TDECHashAuthentication.MGF1(const Data: TBytes; MaskSize: Integer): TBytes; begin Result := KDFInternal(Data[0], Length(Data), NullStr, 0, MaskSize, ktKDF1); end; class function TDECHashAuthentication.KDF1(const Data; DataSize: Integer; const Seed; SeedSize, MaskSize: Integer): TBytes; begin Result := KDFInternal(Data, DataSize, Seed, SeedSize, MaskSize, ktKDF1); end; class function TDECHashAuthentication.KDF1(const Data, Seed: TBytes; MaskSize: Integer): TBytes; begin if (length(Seed) > 0) then Result := KDFInternal(Data[0], length(Data), Seed[0], length(Seed), MaskSize, ktKDF1) else Result := KDFInternal(Data[0], length(Data), NullStr, 0, MaskSize, ktKDF1); end; class function TDECHashAuthentication.KDF2(const Data; DataSize: Integer; const Seed; SeedSize, MaskSize: Integer): TBytes; begin Result := KDFInternal(Data, DataSize, Seed, SeedSize, MaskSize, ktKDF2); end; class function TDECHashAuthentication.KDF2(const Data, Seed: TBytes; MaskSize: Integer): TBytes; begin if (length(Seed) > 0) then Result := KDFInternal(Data[0], Length(Data), Seed[0], Length(Seed), MaskSize, ktKDF2) else Result := KDFInternal(Data[0], Length(Data), NullStr, 0, MaskSize, ktKDF2); end; class function TDECHashAuthentication.KDF3(const Data; DataSize: Integer; const Seed; SeedSize, MaskSize: Integer): TBytes; begin Result := KDFInternal(Data, DataSize, Seed, SeedSize, MaskSize, ktKDF3); end; class function TDECHashAuthentication.KDF3(const Data, Seed: TBytes; MaskSize: Integer): TBytes; begin if (length(Seed) > 0) then Result := KDFInternal(Data[0], Length(Data), Seed[0], Length(Seed), MaskSize, ktKDF3) else Result := KDFInternal(Data[0], Length(Data), NullStr, 0, MaskSize, ktKDF3); end; class function TDECHashAuthentication.KDFx(const Data; DataSize: Integer; const Seed; SeedSize, MaskSize: Integer; Index: UInt32 = 1): TBytes; // DEC's own KDF, even stronger var I, J : Integer; Count : UInt32; R : Byte; HashInstance : TDECHashAuthentication; begin Assert(MaskSize >= 0); Assert(DataSize >= 0); Assert(SeedSize >= 0); Assert(DigestSize > 0); SetLength(Result, MaskSize); Index := SwapUInt32(Index); HashInstance := TDECHashAuthenticationClass(self).Create; try for I := 0 to MaskSize - 1 do begin HashInstance.Init; Count := SwapUInt32(I); HashInstance.Calc(Count, SizeOf(Count)); HashInstance.Calc(Result[0], I); HashInstance.Calc(Index, SizeOf(Index)); Count := SwapUInt32(SeedSize); HashInstance.Calc(Count, SizeOf(Count)); HashInstance.Calc(Seed, SeedSize); Count := SwapUInt32(DataSize); HashInstance.Calc(Count, SizeOf(Count)); HashInstance.Calc(Data, DataSize); HashInstance.Done; R := 0; for J := 0 to DigestSize - 1 do R := R xor HashInstance.Digest[J]; Result[I] := R; end; finally HashInstance.Free; end; end; class function TDECHashAuthentication.KDFx(const Data, Seed: TBytes; MaskSize: Integer; Index: UInt32 = 1): TBytes; begin if (length(Seed) > 0) then Result := KDFx(Data[0], Length(Data), Seed[0], Length(Seed), MaskSize, Index) else Result := KDFx(Data[0], Length(Data), NullStr, Length(Seed), MaskSize, Index) end; class function TDECHashAuthentication.MGFx(const Data; DataSize, MaskSize: Integer; Index: UInt32 = 1): TBytes; begin Result := KDFx(Data, DataSize, NullStr, 0, MaskSize, Index); end; class function TDECHashAuthentication.MGFx(const Data: TBytes; MaskSize: Integer; Index: UInt32 = 1): TBytes; begin Result := KDFx(Data[0], Length(Data), NullStr, 0, MaskSize, Index); end; class function TDECHashAuthentication.HMAC(const Key, Text: RawByteString): TBytes; begin result := HMAC(BytesOf(Key), BytesOf(Text)); end; class function TDECHashAuthentication.HMAC(const Key, Text: TBytes): TBytes; const CONST_UINT_OF_0x36 = $3636363636363636; CONST_UINT_OF_0x5C = $5C5C5C5C5C5C5C5C; var HashInstance: TDECHashAuthentication; InnerKeyPad, OuterKeyPad: array of Byte; I, KeyLength, BlockSize, DigestLength: Integer; begin HashInstance := TDECHashAuthenticationClass(self).Create; try BlockSize := HashInstance.BlockSize; // 64 for sha1, ... DigestLength := HashInstance.DigestSize; KeyLength := Length(Key); SetLength(InnerKeyPad, BlockSize); SetLength(OuterKeyPad, BlockSize); I := 0; if KeyLength > BlockSize then begin Result := HashInstance.CalcBytes(Key); KeyLength := DigestLength; end else Result := Key; while I <= KeyLength - SizeOf(NativeUInt) do begin PNativeUInt(@InnerKeyPad[I])^ := PNativeUInt(@Result[I])^ xor NativeUInt(CONST_UINT_OF_0x36); PNativeUInt(@OuterKeyPad[I])^ := PNativeUInt(@Result[I])^ xor NativeUInt(CONST_UINT_OF_0x5C); Inc(I, SizeOf(NativeUInt)); end; while I < KeyLength do begin InnerKeyPad[I] := Result[I] xor $36; OuterKeyPad[I] := Result[I] xor $5C; Inc(I); end; while I <= BlockSize - SizeOf(NativeUInt) do begin PNativeUInt(@InnerKeyPad[I])^ := NativeUInt(CONST_UINT_OF_0x36); PNativeUInt(@OuterKeyPad[I])^ := NativeUInt(CONST_UINT_OF_0x5C); Inc(I, SizeOf(NativeUInt)); end; while I < BlockSize do begin InnerKeyPad[I] := $36; OuterKeyPad[I] := $5C; Inc(I); end; HashInstance.Init; HashInstance.Calc(InnerKeyPad[0], BlockSize); if Length(Text) > 0 then HashInstance.Calc(Text[0], Length(Text)); HashInstance.Done; Result := HashInstance.DigestAsBytes; HashInstance.Init; HashInstance.Calc(OuterKeyPad[0], BlockSize); HashInstance.Calc(Result[0], DigestLength); HashInstance.Done; Result := HashInstance.DigestAsBytes; finally HashInstance.Free; end; end; class function TDECHashAuthentication.PBKDF2(const Password, Salt: TBytes; Iterations: Integer; KeyLength: Integer): TBytes; const CONST_UINT_OF_0x36 = $3636363636363636; CONST_UINT_OF_0x5C = $5C5C5C5C5C5C5C5C; var Hash: TDECHashAuthentication; I, J, C: Integer; BlockCount, HashLengthRounded, SaltLength: Integer; PassLength, DigestLength, BlockSize: Integer; InnerKeyPad, OuterKeyPad: TBytes; SaltEx, T, U, TrimmedKey: TBytes; begin Hash := TDECHashAuthenticationClass(self).Create; try // Setup needed parameters DigestLength := Hash.DigestSize; HashLengthRounded := DigestLength - SizeOf(NativeUInt) + 1; BlockCount := Trunc((KeyLength + DigestLength - 1) / DigestLength); BlockSize := Hash.BlockSize; PassLength := Length(Password); SaltLength := Length(Salt); SaltEx := Salt; SetLength(SaltEx, SaltLength + 4); // reserve 4 bytes for INT_32_BE(i) SetLength(T, DigestLength); // Prepare Key for HMAC calculation // PrepareKeyForHMAC; I := 0; if PassLength > BlockSize then begin TrimmedKey := Hash.CalcBytes(Password); PassLength := DigestLength; end else TrimmedKey := Password; SetLength(InnerKeyPad, BlockSize); SetLength(OuterKeyPad, BlockSize); while I < PassLength do begin InnerKeyPad[I] := TrimmedKey[I] xor $36; OuterKeyPad[I] := TrimmedKey[I] xor $5C; Inc(I); end; while I < BlockSize do begin InnerKeyPad[I] := $36; OuterKeyPad[I] := $5C; Inc(I); end; // Calculate DK for I := 1 to BlockCount do begin SaltEx[SaltLength + 0] := Byte(I shr 24); // INT_32_BE(i) SaltEx[SaltLength + 1] := Byte(I shr 16); SaltEx[SaltLength + 2] := Byte(I shr 8); SaltEx[SaltLength + 3] := Byte(I shr 0); FillChar(T[0], DigestLength, 0); // reset Ti / F U := SaltEx; // initialize U to U1 = Salt + INT_32_BE(i) // Calculate F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc for C := 1 to Iterations do begin Hash.Init; Hash.Calc(InnerKeyPad[0], BlockSize); Hash.Calc(U[0], Length(U)); Hash.Done; U := Hash.DigestAsBytes; Hash.Init; Hash.Calc(OuterKeyPad[0], BlockSize); Hash.Calc(U[0], DigestLength); Hash.Done; U := Hash.DigestAsBytes; // Ui // F = U1 ^ U2 ^ ... ^ Uc J := 0; while J < HashLengthRounded do begin PNativeUInt(@T[J])^ := PNativeUInt(@T[J])^ xor PNativeUInt(@U[J])^; Inc(J, SizeOf(NativeUInt)); end; while J < DigestLength do begin T[J] := T[J] xor U[J]; Inc(J); end; end; {$IF CompilerVersion >= 28.0} Result := Result + T; // DK += F , DK = DK || Ti {$ELSE} TArrHelper.AppendArrays(Result, T); {$IFEND} end; finally Hash.Free; end; // Trim to the needed key length SetLength(Result, KeyLength); end; class function TDECHashAuthentication.PBKDF2(const Password, Salt: RawByteString; Iterations: Integer; KeyLength: Integer): TBytes; begin result := PBKDF2(BytesOf(Password), BytesOf(Salt), Iterations, KeyLength); end; { TArrHelper } {$IF CompilerVersion < 28.0} class procedure TArrHelper.AppendArrays(var A: TArray; const B: TArray); var i, L: Integer; begin L := Length(A); SetLength(A, L + Length(B)); for i := 0 to High(B) do A[L + i] := B[i]; end; {$IFEND} end.