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

View File

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