Implement UTF8 support

This commit is contained in:
R. Eric Wheeler 2017-02-22 17:20:45 -08:00
parent dae6e78b8a
commit 46bec8c24d
3 changed files with 211 additions and 161 deletions

View File

@ -11,13 +11,16 @@
*} *}
unit BCrypt; unit BCrypt;
{$mode objfpc}{$H+} {$mode objfpc}{$H+}
{$codepage utf8} {$CODEPAGE UTF-8}
interface interface
uses uses
SysUtils, sysutils,
Classes; classes
;
const const
// bcrypt uses 128-bit (16-byte) salt // bcrypt uses 128-bit (16-byte) salt
@ -219,6 +222,7 @@ type
BCryptSalt, BCryptSalt,
BCryptHash : AnsiString; BCryptHash : AnsiString;
end; end;
UTF8String = type AnsiString(CP_UTF8);
EHash = class(EArgumentException); EHash = class(EArgumentException);
@ -228,7 +232,7 @@ private
FPBox: array[0..17] of DWord; FPBox: array[0..17] of DWord;
function BsdBase64Encode(const RawByteData: TBytes; CharacterLength: Sizeint): AnsiString; function BsdBase64Encode(const RawByteData: TBytes; CharacterLength: Sizeint): AnsiString;
function BsdBase64Decode(const EncodedString : AnsiString): TBytes; function BsdBase64Decode(const EncodedString : AnsiString): TBytes;
function Crypt(const Password, Salt : AnsiString; Cost : Byte; HashType : THashTypes) : AnsiString; function Crypt(const Password : UTF8String; const Salt : AnsiString; Cost : Byte; HashType : THashTypes) : AnsiString;
function CryptRaw(const HashKey, Salt: TBytes; Cost : Byte): TBytes; function CryptRaw(const HashKey, Salt: TBytes; Cost : Byte): TBytes;
procedure EKSKey(const Salt, HashKey: TBytes); procedure EKSKey(const Salt, HashKey: TBytes);
procedure Encipher(var lr: array of DWord; const offset: SizeInt); procedure Encipher(var lr: array of DWord; const offset: SizeInt);
@ -247,10 +251,10 @@ private
public public
constructor Create; overload; constructor Create; overload;
destructor Destroy; override; destructor Destroy; override;
function CreateHash(const Password : AnsiString) : AnsiString; overload; function CreateHash(const Password : UTF8String) : AnsiString; overload;
function CreateHash(const Password : AnsiString; HashType : THashTypes) : AnsiString; overload; function CreateHash(const Password : UTF8String; HashType : THashTypes) : AnsiString; overload;
function CreateHash(const Password : AnsiString; HashType : THashTypes; Cost : Byte) : AnsiString; overload; function CreateHash(const Password : UTF8String; HashType : THashTypes; Cost : Byte) : AnsiString; overload;
function VerifyHash(const Password, Hash : AnsiString) : Boolean; function VerifyHash(const Password : UTF8STring; const Hash : AnsiString) : Boolean;
function NeedsRehash(const BCryptHash : AnsiString) : Boolean; overload; function NeedsRehash(const BCryptHash : AnsiString) : Boolean; overload;
function NeedsRehash(const BCryptHash : AnsiString; Cost : Byte) : Boolean; overload; function NeedsRehash(const BCryptHash : AnsiString; Cost : Byte) : Boolean; overload;
function HashGetInfo(const Hash : AnsiString) : RTPasswordInformation; function HashGetInfo(const Hash : AnsiString) : RTPasswordInformation;
@ -263,6 +267,7 @@ Uses
constructor TBCryptHash.Create; constructor TBCryptHash.Create;
begin begin
inherited Create; inherited Create;
end; end;
@ -687,16 +692,16 @@ begin
Result := RandomFileBuffer; Result := RandomFileBuffer;
end; { TBCryptHash.unixRandomBytes } end; { TBCryptHash.unixRandomBytes }
function TBCryptHash.CreateHash(const Password : AnsiString) : AnsiString; overload; function TBCryptHash.CreateHash(const Password : UTF8String) : AnsiString; overload;
begin begin
Result := CreateHash(Password, bcPHP, BCRYPT_DEFAULT_COST); Result := CreateHash(Password, bcPHP, BCRYPT_DEFAULT_COST);
end; end;
function TBCryptHash.CreateHash(const Password : AnsiString; HashType : THashTypes) : AnsiString; overload; function TBCryptHash.CreateHash(const Password : UTF8String; HashType : THashTypes) : AnsiString; overload;
begin begin
Result := CreateHash(Password, HashType, BCRYPT_DEFAULT_COST); Result := CreateHash(Password, HashType, BCRYPT_DEFAULT_COST);
end; { TBCryptHash.CreateHash } end; { TBCryptHash.CreateHash }
function TBCryptHash.CreateHash(const Password : AnsiString; HashType : THashTypes; Cost : Byte) : AnsiString; overload; function TBCryptHash.CreateHash(const Password : UTF8String; HashType : THashTypes; Cost : Byte) : AnsiString; overload;
var var
PasswordKey, PasswordKey,
SaltBytes, SaltBytes,
@ -714,7 +719,7 @@ begin
Result := FormatPasswordHash(SaltBytes, Hash, Cost, HashType); Result := FormatPasswordHash(SaltBytes, Hash, Cost, HashType);
end; { TBCryptHash.CreateHash } end; { TBCryptHash.CreateHash }
function TBCryptHash.Crypt(const Password, Salt : AnsiString; Cost : Byte; HashType : THashTypes) : AnsiString; function TBCryptHash.Crypt(const Password : UTF8String; const Salt : AnsiString; Cost : Byte; HashType : THashTypes) : AnsiString;
var var
PasswordKey, PasswordKey,
SaltBytes, SaltBytes,
@ -745,7 +750,7 @@ begin
end; end;
end; end;
function TBCryptHash.VerifyHash(const Password, Hash : AnsiString) : Boolean; function TBCryptHash.VerifyHash(const Password : UTF8String; const Hash : AnsiString) : Boolean;
var var
WorkingBcryptHash, Salt : AnsiString; WorkingBcryptHash, Salt : AnsiString;
HashCounter, ResultStatus, BCryptCost : Byte; HashCounter, ResultStatus, BCryptCost : Byte;

View File

@ -2,6 +2,7 @@ Program BCryptHashTest;
{$mode objfpc}{$H+} {$mode objfpc}{$H+}
{$ASSERTIONS ON} {$ASSERTIONS ON}
{$UNITPATH ../} {$UNITPATH ../}
{$CODEPAGE UTF-8}
uses BCrypt, Classes, SysUtils, Crt; uses BCrypt, Classes, SysUtils, Crt;
const const
@ -21,6 +22,10 @@ var
PassedAssertions : Word; PassedAssertions : Word;
Passed : Boolean; Passed : Boolean;
UTF8TestString : UTF8String = 'Τη γλώσσα μου έδωσαν ελληνική';
UTF8TestHash : AnsiString = '$2y$12$RSxqgCt5T4qPXLM3AzKMCueMBZo6cc9o/bN4wqcX6KA6lZnOkqzTG';
UTF8PHPHash : AnsiString = '$2y$12$KrBUSn54WO5C/aw2H3imKurgsnrGq7PsrIZYXusaTNIO.27IGsmkG';
PasswordHashes : array [1..14] of AnsiString = ( PasswordHashes : array [1..14] of AnsiString = (
'$2y$10$LCb3aOt8lAXSzNrEpQKDQO1zc2wCCQltrDwSEbb9JaUo4OKbphC3i', '$2y$10$LCb3aOt8lAXSzNrEpQKDQO1zc2wCCQltrDwSEbb9JaUo4OKbphC3i',
'$2y$11$H7TRTJZqQTzN5RCiwMOne.yjVxyKCd4GyLrBQzV91gK0T4XQeKTNa', '$2y$11$H7TRTJZqQTzN5RCiwMOne.yjVxyKCd4GyLrBQzV91gK0T4XQeKTNa',
@ -104,95 +109,127 @@ for i := 1 to 7 do
WriteLn(' - Pass'); WriteLn(' - Pass');
Inc(PassedAssertions); Inc(PassedAssertions);
end; end;
WriteLn(#10#13'Testing Failures ...'#10#13); WriteLn(#10#13'Testing UTF8 with ', UTF8TestString, ' ... '#10#13);
for i := 1 to 7 do Write('Testing : ', UTF8TestHash);
begin try
Write('Testing : ', PasswordHashFailures[i]); Assert(TBCrypt.VerifyHash(UTF8TestString, UTF8TestHash) = True, 'Should Be True');
try Inc(Assertions);
Assert(TBCrypt.VerifyHash(StaticPassword, PasswordHashFailures[i]) = False, 'Should Be False');
Inc(Assertions);
except
on e: EAssertionFailed do
begin
WriteLn(' - Fail');
Inc(FailedAssertions);
Continue;
end;
end;
WriteLn(' - Pass');
Inc(PassedAssertions); Inc(PassedAssertions);
Writeln(' - Pass');
except
on e: EAssertionFailed do
begin
WriteLn(' - Fail');
Inc(FailedAssertions);
Dec(PassedAssertions);
end;
end;
WriteLn(#10#13'Testing UTF8 PHP Hash with ', UTF8TestString, ' ... '#10#13);
Write('Testing : ', UTF8PHPHash);
try
Assert(TBCrypt.VerifyHash(UTF8TestString, UTF8PHPHash) = True, 'Should Be True');
Inc(Assertions);
Inc(PassedAssertions);
Writeln(' - Pass');
except
on e: EAssertionFailed do
begin
WriteLn(' - Fail');
Inc(FailedAssertions);
Dec(PassedAssertions);
end;
end; end;
WriteLn(#10#13'Testing Rehash True ...'#10#13); WriteLn(#10#13'Testing Failures ...'#10#13);
for i := 1 to 7 do for i := 1 to 7 do
begin begin
Write('Testing : ', PasswordHashes[i]); Write('Testing : ', PasswordHashFailures[i]);
try try
Assert(TBCrypt.NeedsRehash(PasswordHashes[i], 17) = True, 'Should Be True'); Assert(TBCrypt.VerifyHash(StaticPassword, PasswordHashFailures[i]) = False, 'Should Be False');
Inc(Assertions); Inc(Assertions);
except except
on e: EAssertionFailed do on e: EAssertionFailed do
begin begin
WriteLn(' - Fail'); WriteLn(' - Fail');
Inc(FailedAssertions); Inc(FailedAssertions);
Continue; Continue;
end; end;
end; end;
WriteLn(' - Pass'); WriteLn(' - Pass');
Inc(PassedAssertions); Inc(PassedAssertions);
end;
WriteLn(#10#13'Testing Rehash False ...'#10#13); end;
j := 10;
for i := 1 to 7 do WriteLn(#10#13'Testing Rehash True ...'#10#13);
for i := 1 to 7 do
begin
Write('Testing : ', PasswordHashes[i]);
try
Assert(TBCrypt.NeedsRehash(PasswordHashes[i], 17) = True, 'Should Be True');
Inc(Assertions);
except
on e: EAssertionFailed do
begin
WriteLn(' - Fail');
Inc(FailedAssertions);
Continue;
end;
end;
WriteLn(' - Pass');
Inc(PassedAssertions);
end;
WriteLn(#10#13'Testing Rehash False ...'#10#13);
j := 10;
for i := 1 to 7 do
begin begin
Write('Testing : ', PasswordHashes[i]); Write('Testing : ', PasswordHashes[i]);
try try
Assert(TBCrypt.NeedsRehash(PasswordHashes[i], j) = False, 'Should Be False'); Assert(TBCrypt.NeedsRehash(PasswordHashes[i], j) = False, 'Should Be False');
Inc(Assertions); Inc(Assertions);
except except
on e: EAssertionFailed do on e: EAssertionFailed do
begin begin
WriteLn(' - Fail'); WriteLn(' - Fail');
Inc(FailedAssertions); Inc(FailedAssertions);
Inc(j); Inc(j);
Continue; Continue;
end; end;
end; end;
WriteLn(' - Pass'); WriteLn(' - Pass');
Inc(PassedAssertions); Inc(PassedAssertions);
Inc(j); Inc(j);
end; end;
WriteLn(#10#13'Testing HashGetInfo on hash '#10#13, HashToMatch2, ' ...'#10#13); WriteLn(#10#13'Testing HashGetInfo on hash '#10#13, HashToMatch2, ' ...'#10#13);
PasswordInfo := TBCrypt.HashGetInfo(HashToMatch2); PasswordInfo := TBCrypt.HashGetInfo(HashToMatch2);
Passed := True; Passed := True;
With PasswordInfo do With PasswordInfo do
begin begin
Writeln('Algo : ', Algo); Writeln('Algo : ', Algo);
try try
Assert(Algo = bcPHP); Assert(Algo = bcPHP);
Inc(Assertions); Inc(Assertions);
except except
on e: EAssertionFailed do on e: EAssertionFailed do
begin begin
Inc(FailedAssertions); Inc(FailedAssertions);
end; end;
end; end;
WriteLn('AlgoName : ', AlgoName); WriteLn('AlgoName : ', AlgoName);
WriteLn('Cost : ', Cost); WriteLn('Cost : ', Cost);
Write('Salt : ', BCryptSalt); Write('Salt : ', BCryptSalt);
try try
Assert(Length(BCryptSalt) = 22, 'Should Be True'); Assert(Length(BCryptSalt) = 22, 'Should Be True');
Inc(Assertions); Inc(Assertions);
except except
on e: EAssertionFailed do on e: EAssertionFailed do
begin begin
Passed := False; Passed := False;
end; end;
end; end;
if Passed = False then if Passed = False then
begin begin
Writeln(' Length - Fail'); Writeln(' Length - Fail');
Inc(FailedAssertions); Inc(FailedAssertions);
@ -207,51 +244,51 @@ for i := 1 to 7 do
Assert(Length(BCryptHash) = 31, 'Should Be True'); Assert(Length(BCryptHash) = 31, 'Should Be True');
Inc(Assertions); Inc(Assertions);
except except
on e: EAssertionFailed do on e: EAssertionFailed do
begin begin
Passed := False; Passed := False;
end; end;
end; end;
if Passed = False then if Passed = False then
begin begin
Writeln(' Length - Fail'); Writeln(' Length - Fail');
Inc(FailedAssertions); Inc(FailedAssertions);
end else end else
begin begin
Writeln(' Length - Pass'); Writeln(' Length - Pass');
Inc(PassedAssertions); Inc(PassedAssertions);
end; end;
end; end;
WriteLn(#10#13'Testing HashGetInfo on bsd hash '#10#13, BSDHashToMatch, ' ...'#10#13); WriteLn(#10#13'Testing HashGetInfo on bsd hash '#10#13, BSDHashToMatch, ' ...'#10#13);
PasswordInfo := TBCrypt.HashGetInfo(BSDHashToMatch); PasswordInfo := TBCrypt.HashGetInfo(BSDHashToMatch);
Passed := True; Passed := True;
With PasswordInfo do With PasswordInfo do
begin begin
Writeln('Algo : ', Algo); Writeln('Algo : ', Algo);
try try
Assert(Algo = bcBSD); Assert(Algo = bcBSD);
Inc(Assertions); Inc(Assertions);
except except
on e: EAssertionFailed do on e: EAssertionFailed do
begin begin
Inc(FailedAssertions); Inc(FailedAssertions);
end; end;
end; end;
WriteLn('AlgoName : ', AlgoName); WriteLn('AlgoName : ', AlgoName);
WriteLn('Cost : ', Cost); WriteLn('Cost : ', Cost);
Write('Salt : ', BCryptSalt); Write('Salt : ', BCryptSalt);
try try
Assert(Length(BCryptSalt) = 22, 'Should Be True'); Assert(Length(BCryptSalt) = 22, 'Should Be True');
Inc(Assertions); Inc(Assertions);
except except
on e: EAssertionFailed do on e: EAssertionFailed do
begin begin
Passed := False; Passed := False;
end; end;
end; end;
if Passed = False then if Passed = False then
begin begin
Writeln(' Length - Fail'); Writeln(' Length - Fail');
Inc(FailedAssertions); Inc(FailedAssertions);
@ -266,36 +303,36 @@ for i := 1 to 7 do
Assert(Length(BCryptHash) = 31, 'Should Be True'); Assert(Length(BCryptHash) = 31, 'Should Be True');
Inc(Assertions); Inc(Assertions);
except except
on e: EAssertionFailed do on e: EAssertionFailed do
begin begin
Passed := False; Passed := False;
end; end;
end; end;
if Passed = False then if Passed = False then
begin begin
Writeln(' Length - Fail'); Writeln(' Length - Fail');
Inc(FailedAssertions); Inc(FailedAssertions);
end else end else
begin begin
Writeln(' Length - Pass'); Writeln(' Length - Pass');
Inc(PassedAssertions); Inc(PassedAssertions);
end; end;
end; end;
Writeln(#10#13'Testing PasswordInfo with bad Hashes.'#10#13); Writeln(#10#13'Testing PasswordInfo with bad Hashes.'#10#13);
Passed := False; Passed := False;
try try
Write('Short Hash : ', ShortHash); Write('Short Hash : ', ShortHash);
PasswordInfo := TBCrypt.HashGetInfo(ShortHash); PasswordInfo := TBCrypt.HashGetInfo(ShortHash);
Inc(Assertions); Inc(Assertions);
except except
on e: EHash do on e: EHash do
begin begin
Passed := True; Passed := True;
end; end;
end; end;
if Passed = True then if Passed = True then
begin begin
Writeln(' - Pass'); Writeln(' - Pass');
Inc(PassedAssertions); Inc(PassedAssertions);
@ -304,18 +341,18 @@ for i := 1 to 7 do
Writeln(' - Fail'); Writeln(' - Fail');
Inc(FailedAssertions); Inc(FailedAssertions);
end; end;
Passed := False; Passed := False;
try try
Write('Long Hash : ', LongHash); Write('Long Hash : ', LongHash);
PasswordInfo := TBCrypt.HashGetInfo(LongHash); PasswordInfo := TBCrypt.HashGetInfo(LongHash);
Inc(Assertions); Inc(Assertions);
except except
on e: EHash do on e: EHash do
begin begin
Passed := True; Passed := True;
end; end;
end; end;
if Passed = True then if Passed = True then
begin begin
Writeln(' - Pass'); Writeln(' - Pass');
Inc(PassedAssertions); Inc(PassedAssertions);
@ -325,20 +362,19 @@ for i := 1 to 7 do
Inc(FailedAssertions); Inc(FailedAssertions);
end; end;
Writeln(#10#13'Testing hashing ...'#10#13);
Writeln(TBCrypt.CreateHash(StaticPassword));
Writeln(TBCrypt.CreateHash(StaticPassword, bcBSD));
Writeln(TBCrypt.CreateHash(StaticPassword, bcDefault));
Writeln(TBCrypt.CreateHash(StaticPassword, bcPHP));
Writeln(TBCrypt.CreateHash(StaticPassword, bcBSD, 14));
Writeln(TBCrypt.CreateHash(StaticPassword, bcDefault, 14));
Writeln(TBCrypt.CreateHash(StaticPassword, bcPHP, 14));
Writeln(#10#13);
Writeln(#10#13'Testing hashing ...'#10#13); TBCrypt.Free;
Writeln(TBCrypt.CreateHash(StaticPassword)); Writeln('Assertions : ', Assertions);
Writeln(TBCrypt.CreateHash(StaticPassword, bcBSD)); Writeln('Passed Assertions : ', PassedAssertions);
Writeln(TBCrypt.CreateHash(StaticPassword, bcDefault)); Writeln('Failed Assertions : ', FailedAssertions);
Writeln(TBCrypt.CreateHash(StaticPassword, bcPHP)); Writeln;
Writeln(TBCrypt.CreateHash(StaticPassword, bcBSD, 14)); end.
Writeln(TBCrypt.CreateHash(StaticPassword, bcDefault, 14));
Writeln(TBCrypt.CreateHash(StaticPassword, bcPHP, 14));
Writeln(#10#13);
TBCrypt.Free;
Writeln('Assertions : ', Assertions);
Writeln('Passed Assertions : ', PassedAssertions);
Writeln('Failed Assertions : ', FailedAssertions);
Writeln;
end.

View File

@ -14,6 +14,8 @@ assert_options(ASSERT_CALLBACK, 'bcrypt_assert_handler');
$bsdPascalHash = '$2a$12$9NWTTEbRtjLNd4KdW.VtUekFA6pJ3DF23FqdvwwvMtoMD9zqdaZg2'; $bsdPascalHash = '$2a$12$9NWTTEbRtjLNd4KdW.VtUekFA6pJ3DF23FqdvwwvMtoMD9zqdaZg2';
$bsdPascalHashFail = '$2a$12$9NWTTEbRtjLNd4KdW.VtUekFA6pJ3DF23FqdvwwvMtoMD9zqdaZg1'; $bsdPascalHashFail = '$2a$12$9NWTTEbRtjLNd4KdW.VtUekFA6pJ3DF23FqdvwwvMtoMD9zqdaZg1';
$utf8String = 'Τη γλώσσα μου έδωσαν ελληνική';
$utf8PascalHash = '$2y$12$RSxqgCt5T4qPXLM3AzKMCueMBZo6cc9o/bN4wqcX6KA6lZnOkqzTG';
$pascalHashesMT = [ $pascalHashesMT = [
'$2y$10$kJgRFQ993paFLArmPE3gn.8yuUB/SRpaEw7lkJJ1oVqhWVIecI5nO', '$2y$10$kJgRFQ993paFLArmPE3gn.8yuUB/SRpaEw7lkJJ1oVqhWVIecI5nO',
@ -35,6 +37,13 @@ $pascalHashesURandom = [
'$2y$16$Y0QNc8vaJJY5mQO0IkN6oeAxEVjtnHYqk0WeWLPm7bRjxA7fWHRBG', '$2y$16$Y0QNc8vaJJY5mQO0IkN6oeAxEVjtnHYqk0WeWLPm7bRjxA7fWHRBG',
]; ];
print PHP_EOL . 'Testing UTF8Hashe ... ' . str_repeat(PHP_EOL, 2);
print 'Testing : ' . $utf8PascalHash;
print PHP_EOL . ' with : ' . $utf8String;
if(true === assert(password_verify($utf8String, $utf8PascalHash), ' - Fail')) {
print ' - Pass' . PHP_EOL;
}
print PHP_EOL . 'Testing bsdPascalHash ... ' . str_repeat(PHP_EOL, 2); print PHP_EOL . 'Testing bsdPascalHash ... ' . str_repeat(PHP_EOL, 2);
print 'Testing : ' . $bsdPascalHash; print 'Testing : ' . $bsdPascalHash;
if (true === assert(password_verify('password', $bsdPascalHash), ' - Fail')) { if (true === assert(password_verify('password', $bsdPascalHash), ' - Fail')) {