From 46bec8c24d738cba18a61f943e438702dff1fcff Mon Sep 17 00:00:00 2001 From: sikofitt Date: Wed, 22 Feb 2017 17:20:45 -0800 Subject: [PATCH] Implement UTF8 support --- BCrypt.pas | 31 ++-- tests/BCryptHashTest.pas | 332 ++++++++++++++++++++++----------------- tests/PHPBCryptTest.php | 9 ++ 3 files changed, 211 insertions(+), 161 deletions(-) diff --git a/BCrypt.pas b/BCrypt.pas index 9c79014..0efd129 100644 --- a/BCrypt.pas +++ b/BCrypt.pas @@ -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; diff --git a/tests/BCryptHashTest.pas b/tests/BCryptHashTest.pas index be3dff0..132906b 100644 --- a/tests/BCryptHashTest.pas +++ b/tests/BCryptHashTest.pas @@ -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. diff --git a/tests/PHPBCryptTest.php b/tests/PHPBCryptTest.php index 1c6828c..d86363c 100644 --- a/tests/PHPBCryptTest.php +++ b/tests/PHPBCryptTest.php @@ -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')) {