SHA1 hashing in Delphi XE
I'm in the process of implementing XML digital signatures. I'm starting with little steps, so right now I want to solve the problem of SHA-1 hashing.
There are lots of questions about this in SO:
- Digitially Sign Key with Lockbox
- Encryption library for Delphi
- Convert this php digital signing to Delphi
- Delphi: is there a version of LockBox for Delphi-XE
- Delphi 2010 Cryptography libraries
...and probably more. However, I'm using Delphi XE. So far, I've tried LockBox 2 (both the Songbeamer and Sourceforge versions), Lock Box 3, DCPCrypto2 and some others (Hashes is an easy to use unit which uses Windows crypto functions)
I prepared a small test rig that gives me the following:
LockBox2
FAILED: 1 ('abc')
Got: '9f04f41a848514162050e3d68c1a7abb441dc2b5'
Expected: 'a9993e364706816aba3e25717850c26c9cd0d89d'
FAILED: 2 ('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq')
Got: '51d7d8769ac72c409c5b0e3f69c60adc9a039014'
Expected: '84983e441c3bd26ebaae4aa1f95129e5e54670f1'
LockBox3
FAILED: 1 ('abc')
Got: '9f04f41a848514162050e3d68c1a7a开发者_开发问答bb441dc2b5'
Expected: 'a9993e364706816aba3e25717850c26c9cd0d89d'
FAILED: 2 ('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq')
Got: '51d7d8769ac72c409c5b0e3f69c60adc9a039014'
Expected: '84983e441c3bd26ebaae4aa1f95129e5e54670f1'
DCPCrypto2
FAILED: 1 ('abc')
Got: '9f04f41a848514162050e3d68c1a7abb441dc2b5'
Expected: 'a9993e364706816aba3e25717850c26c9cd0d89d'
FAILED: 2 ('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq')
Got: '51d7d8769ac72c409c5b0e3f69c60adc9a039014'
Expected: '84983e441c3bd26ebaae4aa1f95129e5e54670f1'
Hashes
Test 1 passes
Test 2 passes
Have you succeeded in compile the mentioned libraries under Delphi XE and make them give the appropriate values? I'm particularly interested in DCPCrypt2 SelfTest
procedure.
Edit: I've added this answer with the fixed source code. Thank you all for your help, it is most appreciated.
Leonardo, i think which your problem is the UNICODE
when you uses a function to hash a string
you are passing a array (buffer) of bytes. so when you pass the abc
string in Delphi XE, your are hashing a buffer like this 61 00 62 00 63 00
(Hex representation)
check this sample application which uses the Windows crypto functions from the Jwscl library
(JEDI Windows Security Code Lib)
program Jwscl_TestHash;
{$APPTYPE CONSOLE}
uses
JwsclTypes,
JwsclCryptProvider,
Classes,
SysUtils;
function GetHashString(Algorithm: TJwHashAlgorithm; Buffer : Pointer;Size:Integer) : AnsiString;
var
Hash: TJwHash;
HashSize: Cardinal;
HashData: Pointer;
i : Integer;
begin
Hash := TJwHash.Create(Algorithm);
try
Hash.HashData(Buffer,Size);
HashData := Hash.RetrieveHash(HashSize);
try
SetLength(Result,HashSize*2);
BinToHex(PAnsiChar(HashData),PAnsiChar(Result),HashSize);
finally
TJwHash.FreeBuffer(HashData);
end;
finally
Hash.Free;
end;
end;
function GetHashSHA(FBuffer : AnsiString): AnsiString;
begin
Result:=GetHashString(haSHA,@FBuffer[1],Length(FBuffer));
end;
function GetHashSHA_Unicode(FBuffer : String): String;
begin
Result:=GetHashString(haSHA,@FBuffer[1],Length(FBuffer)*SizeOf(Char));
end;
begin
try
Writeln(GetHashSHA('abc'));
Writeln(GetHashSHA('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'));
Writeln(GetHashSHA_Unicode('abc'));
Writeln(GetHashSHA_Unicode('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'));
Readln;
except
on E:Exception do
begin
Writeln(E.Classname, ':', E.Message);
Readln;
end;
end;
end.
this return
abc
AnsiString
A9993E364706816ABA3E25717850C26C9CD0D89D
abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq
AnsiString
84983E441C3BD26EBAAE4AA1F95129E5E54670F1 for
abc
unicode
9F04F41A848514162050E3D68C1A7ABB441DC2B5
abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq
Unicode
51D7D8769AC72C409C5B0E3F69C60ADC9A039014
My Cygwin command-prompt tells me it is indeed Unicode that's confusing you:
~$ printf 'a\0b\0c\0' | sha1sum
9f04f41a848514162050e3d68c1a7abb441dc2b5 *-
~$ printf 'abc' | sha1sum
a9993e364706816aba3e25717850c26c9cd0d89d *-
Could the expected value be for an ANSI string and the hash you are getting is for a unicode string?
Okay, so it was Unicode issues. Just in case you want to know, this is my Unit1.pas source. You need a form with a memo and a button. Requires DCPCrypt2, LockBox2, LockBox3 and the Hashes unit.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, LbCipher, LbClass, StdCtrls, DCPcrypt2, DCPsha1, Hashes,
uTPLb_CryptographicLibrary, uTPLb_BaseNonVisualComponent, uTPLb_Hash;
type
THashProc = reference to procedure(src: AnsiString; var output: AnsiString);
TForm1 = class(TForm)
Memo1: TMemo;
btnTest: TButton;
function Display(Buf: TBytes): String;
procedure LockBox2Test;
procedure LockBox3Test;
procedure DCPCrypto2Test;
procedure HashesTest;
procedure btnTestClick(Sender: TObject);
private
{ Private declarations }
procedure RunTests(Name: String; HashFunc: THashProc);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses uTPLb_StreamUtils;
{$R *.dfm}
procedure TForm1.btnTestClick(Sender: TObject);
begin
LockBox2Test;
LockBox3Test;
DCPCrypto2Test;
HashesTest;
end;
procedure TForm1.DCPCrypto2Test;
begin
RunTests('DCPCrypto2', procedure(src: AnsiString; var output: AnsiString)
var
Digest: TSHA1Digest;
Bytes : TBytes;
SHA1 : TDCP_sha1;
begin
SHA1 := TDCP_sha1.Create(nil);
SHA1.Init;
SHA1.UpdateStr(src);
SHA1.Final(Digest);
SHA1.Destroy;
SetLength(Bytes, 20);
Move(Digest, Bytes[0], 20);
output := Form1.Display(Bytes);
end);
end;
function TForm1.Display(Buf: TBytes): String;
var
i: Integer;
begin
Result := '';
for i := 0 to 19 do
Result := Result + Format('%0.2x', [Buf[i]]);
Result := LowerCase(Trim(Result));
end;
procedure TForm1.HashesTest;
begin
RunTests('Hashes', procedure(src: AnsiString; var output: AnsiString)
begin
output := CalcHash2(src, haSHA1)
end)
end;
procedure TForm1.LockBox2Test;
begin
RunTests('LockBox2', procedure(src: AnsiString; var output: AnsiString)
var
Digest: TSHA1Digest;
Bytes : TBytes;
SHA1 : TLbSHA1;
begin
SHA1 := TLbSHA1.Create(nil);
SHA1.HashStringA(src);
SHA1.GetDigest(Digest);
SHA1.Destroy;
SetLength(Bytes, 20);
Move(Digest, Bytes[0], 20);
output := Form1.Display(Bytes);
end);
end;
procedure TForm1.LockBox3Test;
begin
RunTests('LockBox3', procedure(src: AnsiString; var output: AnsiString)
var
Digest: TSHA1Digest;
bytes : TBytes;
P, Sz: integer;
aByte: byte;
s: string;
SHA1 : THash;
Lib : TCryptographicLibrary;
begin
Lib := TCryptographicLibrary.Create(nil);
SHA1 := THash.Create(nil);
SHA1.CryptoLibrary := Lib;
SHA1.HashId := 'native.hash.SHA-1';
SHA1.Begin_Hash;
SHA1.HashAnsiString(src);
if not assigned(SHA1.HashOutputValue) then
output := 'nil'
else
begin
SetLength(Bytes, 20);
Sz := SHA1.HashOutputValue.Size;
if Sz <> 20 then
output := Format('wrong size: %d', [Sz])
else
begin
P := 0;
SHA1.HashOutputValue.Position := 0;
while SHA1.HashOutputValue.Read(aByte, 1) = 1 do
begin
bytes[P] := aByte;
Inc(P);
end;
output := Form1.Display(Bytes);
end;
end;
SHA1.Destroy;
Lib.Destroy;
end)
end;
procedure TForm1.RunTests(Name: String; HashFunc: THashProc);
var
i: Integer;
Tests: array [1 .. 2, 1 .. 2] of AnsiString;
src, res: AnsiString;
expected: String;
begin
// http://www.nsrl.nist.gov/testdata/
Tests[1][1] := 'abc';
Tests[1][2] := 'a9993e364706816aba3e25717850c26c9cd0d89d';
Tests[2][1] := 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq';
Tests[2][2] := '84983e441c3bd26ebaae4aa1f95129e5e54670f1';
Memo1.Lines.Add('');
Memo1.Lines.Add('**' + Name + '**');
Memo1.Lines.Add('');
for i := 1 to 2 do
begin
src := Tests[i][1];
expected := Tests[i][2];
HashFunc(src, res);
res := Trim(LowerCase(res));
if res = expected then
begin
Memo1.Lines.Add(Format(' Test %d passes', [i]))
end
else
begin
Memo1.Lines.Add(Format(' FAILED: %d (''%s'') ', [i, src]));
Memo1.Lines.Add(Format(' Got: ''%s''', [res]));
Memo1.Lines.Add(Format(' Expected: ''%s''', [expected]));
end;
end;
end;
end.
精彩评论