TForge 0.65 released.
The release features a hash library with fluent coding support. While I was writing the library I was inspired by usability of the Python’s hashlib.
Current release supports:
- Cryptographic hash algorithms:
- MD5
- SHA1
- SHA256
- Non-cryptographic hash algorithms:
- CRC32
- Jenkins-One-At-Time
- Hash-based MAC algorithm (HMAC)
- Key derivation algorithms:
- PBKDF1
- PBKDF2
Let us consider a common problem: calculate MD5 and SHA1 digests of a file. The simplest way to do it is:
program HashFile;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes, tfTypes, tfHashes;
procedure CalcHash(const FileName: string);
begin
Writeln('MD5: ', THash.MD5.UpdateFile(FileName).Digest.ToHex);
Writeln('SHA1: ', THash.SHA1.UpdateFile(FileName).Digest.ToHex);
end;
begin
try
if ParamCount = 1 then begin
CalcHash(ParamStr(1));
end
else
Writeln('Usage: > HashFile filename');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
The above application demonstrates the beauty of fluent coding. The code is compact and clear – you create an instance of a hash algorithm, feed a file data to it, generate resulting digest and convert it to hexadecimal; the instance is freed automatically, no need for explicit call of the Free method.
But the above code is not optimal – it reads a file twice. A better solutions involves some coding:
procedure CalcHash(const FileName: string);
const
BufSize = 16 * 1024;
var
MD5, SHA1: THash;
Stream: TStream;
Buffer: array[0 .. BufSize - 1] of Byte;
N: Integer;
begin
MD5:= THash.MD5;
SHA1:= THash.SHA1;
try
Stream:= TFileStream.Create(FileName,
fmOpenRead or fmShareDenyWrite);
try
repeat
N:= Stream.Read(Buffer, BufSize);
if N <= 0 then Break
else begin
MD5.Update(Buffer, N);
SHA1.Update(Buffer, N);
end;
until False;
finally
Stream.Free;
end;
Writeln('MD5: ', MD5.Digest.ToHex);
Writeln('SHA1: ', SHA1.Digest.ToHex);
finally
MD5.Burn;
SHA1.Burn;
end;
end;
The code also demonstrate the use of Burn method; it is not needed here and could be safely removed with corresponding try/finally block but can be useful in other cases – it destroys all sensitive data in an instance. The use of Burn method is optional – it is called anyway when an instance is freed, but explicit call of the Burn method gives you full control over erasing the sensitive data.
The Free method does not free an instance; it only decrements the instance’s reference count, and since the compiler can create hidden references to the instance the moment when the reference count turns zero and the instance is freed is generally controlled by the compiler.
The use of non-cryptographic hash algorithms has one caveat – since they actually return an integer value the bytes of a digest are reversed. The idiomatic way to get the correct result is to cast the digest to integer type:
Writeln('CRC32: ',
IntToHex(LongWord(THash.CRC32.UpdateFile(FileName).Digest), 8));
This issue is fixed in ver. 0.68 – see https://sergworks.wordpress.com/2014/12/27/endianness-issue/
HMAC algorithm generates digest using a cryptographic hash algorithm and a secret key. Here is an example of calculating SHA1-HMAC digest of a file:
procedure SHA1_HMAC_File(const FileName: string;
const Key: ByteArray);
begin
Writeln('SHA1-HMAC: ',
THMAC.SHA1.ExpandKey(Key).UpdateFile(FileName).Digest.ToHex);
end;
begin
..
SHA1_HMAC_File(ParamStr(1),
ByteArray.FromText('My Secret Key'));
Key derivation algorithms generate keys from user passwords by applying hash algorithms. PBKDF1 applies a cryptographic hash algorithm directly, PBKDF2 uses HMAC. Here are usage examples:
procedure DeriveKeys(const Password, Salt: ByteArray);
begin
Writeln('PBKDF1 Key: ',
THash.SHA1.DeriveKey(Password, Salt,
10000, // number of rounds
16 // key length in bytes
).ToHex);
Writeln('PBKDF2 Key: ',
THMAC.SHA1.DeriveKey(Password, Salt,
10000, // number of rounds
32 // key length in bytes
).ToHex);
end;
begin
..
DeriveKeys(ByteArray.FromText('User Password'),
ByteArray.FromText('Salt'));
Configuration and Installation
The release contains 2 runtime packages TForge and THashes. You should build them, first TForge, next THashes.
For Delphi users:
The packages are in Packages\DXE subfolder (Delphi XE only).
You should make the folder Source\Include available via project’s search path before you can build the packages. To do it open “Project Options” dialog for each package, set “Build Configuration” to “Base” and replace the path to TFL.inc by your path (depends on where you unpacked the downloaded archive):
Now use the project manager and build the packages in “Debug” and “Release” configurations:
Optionally, to enable Ctrl-click navigation in the editor, add paths to the source code units to the Browsing path:
To make the packages available to an application open the application’s “Project Options” dialog, set “Build Configuration” to “Base” and add the next path to search path:
For FPC/Lazarus users:
The packages are in Packages\FPC subfolder.
I don’t know much about Lazarus packages. Here are the package configuration options I used: