Guest User

BCrypt for Delphi

a guest
May 3rd, 2012
2,201
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Delphi 26.57 KB | None | 0 0
  1. unit Bcrypt;
  2.  
  3. {
  4.     Bcrypt is an algorithm designed for hashing passwords, and only passwords.
  5.         i.e. It's not a generic, high-speed, generic hashing algorithm.
  6.               It's computationally and memory expensive
  7.               It's limited to passwords of 55 bytes.
  8.  
  9.     http://static.usenix.org/events/usenix99/provos/provos.pdf
  10.  
  11.     It uses the Blowfish encryption algorithm, but with an "expensive key setup"
  12.     modification, contained in the function EksBlowfishSetup.
  13.  
  14.     Ian Boyd  5/3/2012
  15.     Public Domain
  16.  
  17.     v1.0 - Initial release
  18. }
  19.  
  20. interface
  21.  
  22. uses
  23.     Blowfish, Types, Math, ComObj;
  24.  
  25. type
  26.     UnicodeString = WideString;
  27.  
  28.     TBCrypt = class(TObject)
  29.     private
  30.         class function TryParseHashString(const hashString: AnsiString;
  31.                 out version: string; out Cost: Integer; out Salt{, Hash}: TByteDynArray): Boolean;
  32.     protected
  33.         class function EksBlowfishSetup(const Cost: Integer; salt, key: array of Byte): TBlowfishData;
  34.         class procedure ExpandKey(var state: TBlowfishData; salt, key: array of Byte);
  35.         class function CryptCore(const Cost: Integer; Key: array of Byte; salt: array of Byte): TByteDynArray;
  36.  
  37.         class function FormatPasswordHashForBsd(const cost: Integer; const salt: array of Byte; const hash: array of Byte): AnsiString;
  38.  
  39.         class function BsdBase64Encode(const data: array of Byte; BytesToEncode: Integer): AnsiString;
  40.         class function BsdBase64Decode(const s: AnsiString): TByteDynArray;
  41.  
  42.         class function WideStringToUtf8(const Source: UnicodeString): AnsiString;
  43.  
  44.         class function SelfTestA: Boolean; //known test vectors
  45.         class function SelfTestB: Boolean; //BSD's base64 encoder/decoder
  46.         class function SelfTestC: Boolean; //unicode strings in UTF8
  47.         class function SelfTestD: Boolean; //different length passwords
  48.         class function SelfTestE: Boolean; //salt rng
  49.  
  50.         class function GenRandomBytes(len: Integer; const data: Pointer): HRESULT;
  51.     public
  52.         //Hashes a password into the OpenBSD password-file format (non-standard base-64 encoding). Also validate that BSD style string
  53.         class function HashPassword(const password: UnicodeString): AnsiString; overload;
  54.         class function CheckPassword(const password: UnicodeString; const expectedHashString: AnsiString): Boolean; overload;
  55.  
  56.         //If you want to handle the cost, salt, and encoding yourself, you can do that.
  57.         class function HashPassword(const password: UnicodeString; const salt: array of Byte; const cost: Integer): TByteDynArray; overload;
  58.         class function CheckPassword(const password: UnicodeString; const salt, hash: array of Byte; const Cost: Integer): Boolean; overload;
  59.         class function GenerateSalt: TByteDynArray;
  60.  
  61.         class function SelfTest: Boolean;
  62.     end;
  63.  
  64. implementation
  65.  
  66. uses
  67.     Windows, SysUtils,
  68. {$IFDEF UnitTests}TestFramework, {$ENDIF}
  69.     ActiveX;
  70.  
  71. const
  72.     BCRYPT_COST = 10; //cost determintes the number of rounds. 10 = 2^10 rounds (1024)
  73.     BCRYPT_SALT_LEN = 16; //bcrypt uses 128-bit (16-byte) salt (This isn't an adjustable parameter, just a name for a constant)
  74.  
  75.     BsdBase64EncodeTable: array[0..63] of Char =
  76.             { 0:} './'+
  77.             { 2:} 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'+
  78.             {28:} 'abcdefghijklmnopqrstuvwxyz'+
  79.             {54:} '0123456789';
  80.  
  81.             //the traditional base64 encode table:
  82.             //'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
  83.             //'abcdefghijklmnopqrstuvwxyz' +
  84.             //'0123456789+/';
  85.  
  86.     BsdBase64DecodeTable: array[#0..#127] of Integer = (
  87.             {  0:} -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  // ________________
  88.             { 16:} -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  // ________________
  89.             { 32:} -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  0,  1,  // ______________./
  90.             { 48:} 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, -1, -1, -1, -1, -1, -1,  // 0123456789______
  91.             { 64:} -1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,  // _ABCDEFGHIJKLMNO
  92.             { 80:} 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, -1, -1, -1, -1, -1,  // PQRSTUVWXYZ_____
  93.             { 96:} -1, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,  // _abcdefghijklmno
  94.             {113:} 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, -1, -1, -1, -1, -1); // pqrstuvwxyz_____
  95.  
  96.     TestVectors: array[1..20, 1..3] of string = (
  97.             ('',                                   '$2a$06$DCq7YPn5Rq63x1Lad4cll.',    '$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'),
  98.             ('',                                   '$2a$08$HqWuK6/Ng6sg9gQzbLrgb.',    '$2a$08$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye'),
  99.             ('',                                   '$2a$10$k1wbIrmNyFAPwPVPSVa/ze',    '$2a$10$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW'),
  100.             ('',                                   '$2a$12$k42ZFHFWqBp3vWli.nIn8u',    '$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO'),
  101.             ('a',                                  '$2a$06$m0CrhHm10qJ3lXRY.5zDGO',    '$2a$06$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe'),
  102.             ('a',                                  '$2a$08$cfcvVd2aQ8CMvoMpP2EBfe',    '$2a$08$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V.'),
  103.             ('a',                                  '$2a$10$k87L/MF28Q673VKh8/cPi.',    '$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u'),
  104.             ('a',                                  '$2a$12$8NJH3LsPrANStV6XtBakCe',    '$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS'),
  105.             ('abc',                                '$2a$06$If6bvum7DFjUnE9p2uDeDu',    '$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i'),
  106.             ('abc',                                '$2a$08$Ro0CUfOqk6cXEKf3dyaM7O',    '$2a$08$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm'),
  107.             ('abc',                                '$2a$10$WvvTPHKwdBJ3uk0Z37EMR.',    '$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi'),
  108.             ('abc',                                '$2a$12$EXRkfkdmXn2gzds2SSitu.',    '$2a$12$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q'),
  109.             ('abcdefghijklmnopqrstuvwxyz',         '$2a$06$.rCVZVOThsIa97pEDOxvGu',    '$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC'),
  110.             ('abcdefghijklmnopqrstuvwxyz',         '$2a$08$aTsUwsyowQuzRrDqFflhge',    '$2a$08$aTsUwsyowQuzRrDqFflhgekJ8d9/7Z3GV3UcgvzQW3J5zMyrTvlz.'),
  111.             ('abcdefghijklmnopqrstuvwxyz',         '$2a$10$fVH8e28OQRj9tqiDXs1e1u',    '$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq'),
  112.             ('abcdefghijklmnopqrstuvwxyz',         '$2a$12$D4G5f18o7aMMfwasBL7Gpu',    '$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG'),
  113.             ('~!@#$%^&*()      ~!@#$%^&*()PNBFRD', '$2a$06$fPIsBO8qRqkjj273rfaOI.',    '$2a$06$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO'),
  114.             ('~!@#$%^&*()      ~!@#$%^&*()PNBFRD', '$2a$08$Eq2r4G/76Wv39MzSX262hu',    '$2a$08$Eq2r4G/76Wv39MzSX262huzPz612MZiYHVUJe/OcOql2jo4.9UxTW'),
  115.             ('~!@#$%^&*()      ~!@#$%^&*()PNBFRD', '$2a$10$LgfYWkbzEvQ4JakH7rOvHe',    '$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS'),
  116.             ('~!@#$%^&*()      ~!@#$%^&*()PNBFRD', '$2a$12$WApznUOJfkEGSmYRfnkrPO',    '$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC')
  117.     );
  118.  
  119.  
  120. {$IFDEF UnitTests}
  121. type
  122.     TBCryptTests = class(TTestCase)
  123.     public
  124.         procedure SelfTest;
  125.  
  126.         //These are just too darn slow (as they should be) for continuous testing
  127.         procedure SelfTestA_KnownTestVectors;
  128.         procedure SelfTestC_UnicodeStrings;
  129.         procedure SelfTestD_VariableLengthPasswords;
  130.     published
  131.         procedure SelfTestB_Base64EncoderDecoder;
  132.     end;
  133. {$ENDIF}
  134.  
  135. const
  136.     advapi32 = 'advapi32.dll';
  137.  
  138. function CryptAcquireContextW(out phProv: THandle; pszContainer: PWideChar; pszProvider: PWideChar; dwProvType: DWORD; dwFlags: DWORD): BOOL; stdcall; external advapi32;
  139. function CryptReleaseContext(hProv: THandle; dwFlags: DWORD): BOOL; stdcall; external advapi32;
  140. function CryptGenRandom(hProv: THandle; dwLen: DWORD; pbBuffer: Pointer): BOOL; stdcall; external advapi32;
  141.  
  142. { TBCrypt }
  143.  
  144. class function TBCrypt.HashPassword(const password: UnicodeString): AnsiString;
  145. var
  146.     cost: Integer;
  147.     salt: TByteDynArray;
  148.     hash: TByteDynArray;
  149. begin
  150. {   bcrypt was designed for OpenBSD, where hashes in the password file have a
  151.     certain format.
  152.  
  153.     The convention used in BSD when generating password hash strings is to format it as:
  154.             $version$salt$hash
  155.  
  156.     MD5 hash uses version "1":
  157.             "$"+"1"+"$"+salt+hash
  158.  
  159.     bcrypt uses version "2a", but also encodes the cost
  160.  
  161.             "$"+"2a"+"$"+rounds+"$"+salt+hash
  162.  
  163.     e.g.
  164.             $2a$10$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm
  165.             $==$==$======================-------------------------------
  166.  
  167.     The benfit of this scheme is:
  168.             - the number of rounds
  169.             - the salt used
  170.  
  171.     This means that stored hashes are backwards and forwards compatible with
  172.     changing the number of rounds
  173. }
  174.     salt := GenerateSalt();
  175.     cost := BCRYPT_COST;
  176.  
  177.     //utf8 := TBCrypt.WideStringToUtf8(password);
  178.     hash := TBCrypt.HashPassword(password, salt, cost);
  179.  
  180.     Result := FormatPasswordHashForBsd(cost, salt, hash);
  181. end;
  182.  
  183. class function TBCrypt.GenerateSalt: TByteDynArray;
  184. var
  185.     type4Uuid: TGUID;
  186.     salt: TByteDynArray;
  187. begin
  188.     //Salt is a 128-bit (16 byte) random value
  189.     SetLength(salt, BCRYPT_SALT_LEN);
  190.  
  191.     //Type 4 UUID (RFC 4122) is a handy source of (almost) 128-bits of random data (actually 120 bits)
  192.     //But the security doesn't come from the salt being secret, it comes from the salt being different each time
  193.     OleCheck(CoCreateGUID(Type4Uuid));
  194.  
  195.     Move(type4Uuid.D1, salt[0], BCRYPT_SALT_LEN); //16 bytes
  196.  
  197.     Result := salt;
  198. end;
  199.  
  200. class function TBCrypt.HashPassword(const password: UnicodeString; const salt: array of Byte; const cost: Integer): TByteDynArray;
  201. var
  202.     key: TByteDynArray;
  203.     len: Integer;
  204.     utf8Password: AnsiString;
  205. begin
  206.     //pseudo-standard dictates that unicode strings are converted to UTF8 (rather than UTF16, UTF32, UTF16LE, etc)
  207.     utf8Password := TBCrypt.WideStringToUtf8(password);
  208.  
  209.     //key is 56 bytes.
  210.     //bcrypt version 2a defines that we include the null terminator
  211.     //this leaves us with 55 characters we can include
  212.     len := Length(utf8Password);
  213.     if len > 55 then
  214.         len := 55;
  215.  
  216.     SetLength(key, len+1); //+1 for the null terminator
  217.  
  218.     if Length(utf8Password) > 0 then
  219.         Move(utf8Password[1], key[0], len);
  220.  
  221.     //set the final null terminator
  222.     key[len] := 0;
  223.  
  224.     Result := TBCrypt.CryptCore(cost, key, salt);
  225. end;
  226.  
  227. class function TBCrypt.CryptCore(const Cost: Integer; key, salt: array of Byte): TByteDynArray;
  228. var
  229.     state: TBlowfishData;
  230.     i: Integer;
  231.     plainText: array[0..23] of Byte;
  232.     cipherText: array[0..23] of Byte;
  233.  
  234. const
  235.     magicText: AnsiString = 'OrpheanBeholderScryDoubt'; //the 24-byte data we will be encrypting 64 times
  236. begin
  237.     state := TBCrypt.EksBlowfishSetup(cost, salt, key);
  238.  
  239.     //Conceptually we are encrypting "OrpheanBeholderScryDoubt" 64 times
  240.     Move(magicText[1], plainText[0], 24);
  241.  
  242.     for i := 1 to 64 do
  243.     begin
  244.         //The painful thing is that the plaintext is 24 bytes long; this is three 8-byte blocks.
  245.         //Which means we have to do the EBC encryption on 3 separate sections.
  246.         BlowfishEncryptECB(state, Pointer(@plainText[ 0]), Pointer(@cipherText[ 0]));
  247.         BlowfishEncryptECB(state, Pointer(@plainText[ 8]), Pointer(@cipherText[ 8]));
  248.         BlowfishEncryptECB(state, Pointer(@plainText[16]), Pointer(@cipherText[16]));
  249.  
  250.         Move(cipherText[0], plainText[0], 24);
  251.     end;
  252.  
  253.     SetLength(Result, 24);
  254.     Move(cipherText[0], Result[0], 24);
  255. end;
  256.  
  257.  
  258. class function TBCrypt.EksBlowfishSetup(const Cost: Integer; salt, key: array of Byte): TBlowfishData;
  259. var
  260.     rounds: Cardinal; //rounds = 2^cost
  261.     i: Integer;
  262.     Len: Integer;
  263. const
  264.     zero: array[0..15] of Byte = (0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0);
  265. begin
  266.     //Expensive key setup
  267.     if (cost < 4) or (cost > 31) then
  268.         raise Exception.Create('Blowfish: Cost ('+IntToStr(Cost)+') must be between 4..31');
  269.  
  270.     Len := Length(key);
  271.     if (Len > 56) then
  272.         raise Exception.Create('Blowfish: Key must be between 1 and 56 bytes long');
  273.  
  274.     if Length(salt) <> BCRYPT_SALT_LEN then
  275.         raise Exception.Create('Blowfish: salt must be 16 bytes');
  276.  
  277.     //Copy S and P boxes into local state
  278.     BlowfishInitState(Result);
  279.  
  280.     Self.ExpandKey(Result, salt, key);
  281.  
  282.     //rounds = 2^cost
  283.     rounds := 1 shl cost;
  284.  
  285.     for i := 1 to rounds do
  286.     begin
  287.         Self.ExpandKey(Result, zero, key);
  288.         Self.ExpandKey(Result, zero, salt);
  289.     end;
  290.  
  291.     //Result := what it is
  292. end;
  293.  
  294. class procedure TBCrypt.ExpandKey(var State: TBlowfishData; salt, key: array of Byte);
  295. var
  296.     i, j, k: Integer;
  297.     A: DWord;
  298.     KeyB: PByteArray;
  299.     Block: array[0..7] of Byte;
  300.     Len: Integer;
  301.     saltHalf: Integer;
  302. begin
  303.     //ExpandKey phase of the Expensive key setup
  304.     Len := Length(key);
  305.     if (Len > 56) then
  306.         raise Exception.Create('Blowfish: Key must be between 1 and 56 bytes long');
  307.  
  308.     {
  309.         XOR all the subkeys in the P-array with the encryption key
  310.         The first 32 bits of the key are XORed with P1, the next 32 bits with P2, and so on.
  311.         The key is viewed as being cyclic; when the process reaches the end of the key,
  312.         it starts reusing bits from the beginning to XOR with subkeys.
  313.     }
  314.     if len > 0 then
  315.     begin
  316.         KeyB := PByteArray(@key[0]);
  317.         k := 0;
  318.         for i := 0 to 17 do
  319.         begin
  320.             A :=      KeyB[(k+3) mod Len];
  321.             A := A + (KeyB[(k+2) mod Len] shl 8);
  322.             A := A + (KeyB[(k+1) mod Len] shl 16);
  323.             A := A + (KeyB[k]             shl 24);
  324.             State.PBoxM[i] := State.PBoxM[i] xor A;
  325.             k := (k+4) mod Len;
  326.         end;
  327.     end;
  328.  
  329.     //Blowfsh-encrypt the first 64 bits of its salt argument using the current state of the key schedule.
  330.     BlowfishEncryptECB(State, @salt[0], @Block);
  331.  
  332.     //The resulting ciphertext replaces subkeys P1 and P2.
  333.     State.PBoxM[0] := Block[3] + (Block[2] shl 8) + (Block[1] shl 16) + (Block[0] shl 24);
  334.     State.PBoxM[1] := Block[7] + (Block[6] shl 8) + (Block[5] shl 16) + (Block[4] shl 24);
  335.  
  336.     saltHalf := 1;
  337.     for i := 1 to 8 do
  338.     begin
  339.         //That same ciphertext is also XORed with the second 64-bits of salt
  340.         for k := 0 to 7 do
  341.             block[k] := block[k] xor salt[(saltHalf*8)+k]; //Salt is 0..15 (0..7 is first block, 8..15 is second block)
  342.         saltHalf := saltHalf xor 1;
  343.  
  344.         //and the result encrypted with the new state of the key schedule
  345.         BlowfishEncryptECB(State, @Block, @Block);
  346.  
  347.         // The output of the second encryption replaces subkeys P3 and P4. (P[2] and P[3])
  348.         State.PBoxM[i*2] :=   Block[3] + (Block[2] shl 8) + (Block[1] shl 16) + (Block[0] shl 24);
  349.         State.PBoxM[i*2+1] := Block[7] + (Block[6] shl 8) + (Block[5] shl 16) + (Block[4] shl 24);
  350.     end;
  351.  
  352.     //When ExpandKey finishes replacing entries in the P-Array, it continues on replacing S-box entries two at a time.
  353.     for j := 0 to 3 do
  354.     begin
  355.         for i := 0 to 127 do
  356.         begin
  357.             //That same ciphertext is also XORed with the second 64-bits of salt
  358.             for k := 0 to 7 do
  359.                 block[k] := block[k] xor salt[(saltHalf*8 mod 16)+k]; //Salt is 0..15 (0..7 is first block, 8..15 is second block)
  360.             saltHalf := saltHalf xor 1;
  361.  
  362.             //and the result encrypted with the new state of the key schedule
  363.             BlowfishEncryptECB(State, @Block, @Block);
  364.  
  365.             // The output of the second encryption replaces subkeys S1 and P2. (S[0] and S[1])
  366.             State.SBoxM[j, i*2] :=   Block[3] + (Block[2] shl 8) + (Block[1] shl 16) + (Block[0] shl 24);
  367.             State.SBoxM[j, i*2+1] := Block[7] + (Block[6] shl 8) + (Block[5] shl 16) + (Block[4] shl 24);
  368.         end;
  369.     end;
  370. end;
  371.  
  372. class function TBCrypt.CheckPassword(const password: UnicodeString; const salt, hash: array of Byte; const Cost: Integer): Boolean;
  373. var
  374.     candidateHash: TByteDynArray;
  375.     len: Integer;
  376. begin
  377.     Result := False;
  378.  
  379.     candidateHash := TBCrypt.HashPassword(password, salt, cost);
  380.  
  381.     len := Length(hash);
  382.     if Length(candidateHash) <> len then
  383.         Exit;
  384.  
  385.     Result := CompareMem(@candidateHash[0], @hash[0], len);
  386. end;
  387.  
  388. class function TBCrypt.TryParseHashString(const hashString: AnsiString;
  389.         out version: string; out Cost: Integer; out Salt: TByteDynArray{; out Hash: TByteDynArray}): Boolean;
  390. var
  391.     work: AnsiString;
  392.     s: AnsiString;
  393. begin
  394.     Result := False;
  395.  
  396.     {
  397.         Pick apart our specially formatted hash string
  398.  
  399.         $2a$nn$(22 character salt, b64 encoded)(32 character hash, b64 encoded)
  400.  
  401.         We also need to accept version 2, the original version
  402.     }
  403.     if Length(hashString) < 28 then
  404.         Exit;
  405.  
  406.     //get the version prefix (we support "2a" and the older "2", since they are the same thing)
  407.     if AnsiSameText(Copy(hashString, 1, 4), '$2a$') then
  408.     begin
  409.         version := Copy(hashString, 2, 2);
  410.         work := Copy(hashString, 5, 25);
  411.     end
  412.     else if AnsiSameText(Copy(hashString, 1, 3), '$2$') then
  413.     begin
  414.         version := Copy(hashString, 2, 1);
  415.         work := Copy(hashString, 4, 25);
  416.     end
  417.     else
  418.         Exit;
  419.  
  420.     //Next two characters must be a length
  421.     s := Copy(work, 1, 2);
  422.     cost := StrToIntDef(s, -1);
  423.     if cost < 0 then
  424.         Exit;
  425.  
  426.     //Next is a separator
  427.     if work[3] <> '$' then
  428.         Exit;
  429.  
  430.     //Next 22 are the salt
  431.     s := Copy(work, 4, 22);
  432.     Salt := BsdBase64Decode(s); //salt is always 16 bytes
  433.  
  434. {   //next 32 is hash
  435.     s := Copy(work, 26, 32);
  436.     SetLength(Hash, 24); //hash is always 24 bytes}
  437.  
  438.     Result := True;
  439. end;
  440.  
  441. class function TBCrypt.CheckPassword(const password: UnicodeString; const expectedHashString: AnsiString): Boolean;
  442. var
  443.     version: string;
  444.     cost: Integer;
  445.     salt: TByteDynArray;
  446.     hash: TByteDynArray;
  447.     actualHashString: string;
  448. begin
  449.     if not TryParseHashString(expectedHashString,
  450.             {out}version, {out}cost, {out}salt) then
  451.         raise Exception.Create('Invalid hash string');
  452.  
  453.     hash := TBCrypt.HashPassword(password, salt, cost);
  454.  
  455.     actualHashString := FormatPasswordHashForBsd(cost, salt, hash);
  456.  
  457.     Result := (actualHashString = expectedHashString);
  458. end;
  459.  
  460. class function TBCrypt.BsdBase64Encode(const data: array of Byte; BytesToEncode: Integer): AnsiString;
  461.  
  462.     function EncodePacket(b1, b2, b3: Byte; Len: Integer): AnsiString;
  463.     begin
  464.         Result := '';
  465.  
  466.         Result := Result + BsdBase64EncodeTable[b1 shr 2];
  467.         Result := Result + BsdBase64EncodeTable[((b1 and $03) shl 4) or (b2 shr 4)];
  468.         if Len < 2 then Exit;
  469.  
  470.         Result := Result + BsdBase64EncodeTable[((b2 and $0f) shl 2) or (b3 shr 6)];
  471.         if Len < 3 then Exit;
  472.  
  473.         Result := Result + BsdBase64EncodeTable[b3 and $3f];
  474.     end;
  475.  
  476. var
  477.     i: Integer;
  478.     len: Integer;
  479.     b1, b2: Integer;
  480. begin
  481.     Result := '';
  482.  
  483.     len := BytesToEncode;
  484.     if len = 0 then
  485.         Exit;
  486.  
  487.     if len > Length(data) then
  488.         raise Exception.Create('Invalid length');
  489.  
  490.     //encode whole 3-byte chunks  TV4S 6ytw fsfv kgY8 jIuc Drjc 8deX 1s.
  491.     i := Low(data);
  492.     while len >= 3 do
  493.     begin
  494.         Result := Result+EncodePacket(data[i], data[i+1], data[i+2], 3);
  495.         Inc(i, 3);
  496.         Dec(len, 3);
  497.     end;
  498.  
  499.     if len = 0 then
  500.         Exit;
  501.  
  502.     //encode partial final chunk
  503.     Assert(len < 3);
  504.     if len >= 1 then
  505.         b1 := data[i]
  506.     else
  507.         b1 := 0;
  508.     if len >= 2 then
  509.         b2 := data[i+1]
  510.     else
  511.         b2 := 0;
  512.     Result := Result+EncodePacket(b1, b2, 0, len);
  513. end;
  514.  
  515. class function TBCrypt.SelfTest: Boolean;
  516. begin
  517.     Result :=
  518.             SelfTestA and  //known test vectors
  519.             SelfTestB and  //the base64 encoder/decoder
  520.             SelfTestC and  //unicode strings
  521.             SelfTestD;     //different length passwords
  522. end;
  523.  
  524. class function TBCrypt.FormatPasswordHashForBsd(const cost: Integer; const salt, hash: array of Byte): AnsiString;
  525. var
  526.     saltString: AnsiString;
  527.     hashString: AnsiString;
  528. begin
  529.     saltString := BsdBase64Encode(salt, Length(salt));
  530.     hashString := BsdBase64Encode(hash, Length(hash)-1); //Yes, everything except the last byte
  531.         //OpenBSD, in the pseudo-base64 implementation, doesn't include the last byte of the hash
  532.         //Nobody knows why, but that's what all exists tests do - so it's what i do
  533.  
  534.     Result := Format('$2a$%.2d$%s%s', [cost, saltString, hashString]);
  535. end;
  536.  
  537. class function TBCrypt.BsdBase64Decode(const s: AnsiString): TByteDynArray;
  538.  
  539.     function Char64(character: AnsiChar): Integer;
  540.     begin
  541.         if (Ord(character) > Length(BsdBase64DecodeTable)) then
  542.         begin
  543.             Result := -1;
  544.             Exit;
  545.         end;
  546.  
  547.         Result := BsdBase64DecodeTable[character];
  548.     end;
  549.  
  550.     procedure Append(value: Byte);
  551.     var
  552.         i: Integer;
  553.     begin
  554.         i := Length(Result);
  555.         SetLength(Result, i+1);
  556.         Result[i] := value;
  557.     end;
  558.  
  559. var
  560.     i: Integer;
  561.     len: Integer;
  562.     c1, c2, c3, c4: Integer;
  563. begin
  564.     SetLength(Result, 0);
  565.  
  566.     len := Length(s);
  567.     i := 1;
  568.     while i <= len do
  569.     begin
  570.         // We'll need to have at least 2 character to form one byte.
  571.         // Anything less is invalid
  572.         if (i+1) > len then
  573.         begin
  574.             raise Exception.Create('Invalid base64 hash string');
  575. //          Break;
  576.         end;
  577.  
  578.         c1 := Char64(s[i]);
  579.         Inc(i);
  580.         c2 := Char64(s[i]);
  581.         Inc(i);
  582.  
  583.         if (c1 = -1) or (c2 = -1) then
  584.         begin
  585.             raise Exception.Create('Invalid base64 hash string');
  586. //          Break;
  587.         end;
  588.  
  589.         //Now we have at least one byte in c1|c2
  590.         // c1 = ..111111
  591.         // c2 = ..112222
  592.         Append( ((c1 and $3f) shl 2) or (c2 shr 4) );
  593.  
  594.         //If there's a 3rd character, then we can use c2|c3 to form the second byte
  595.         if (i > len) then
  596.             Break;
  597.         c3 := Char64(s[i]);
  598.         Inc(i);
  599.  
  600.         if (c3 = -1) then
  601.         begin
  602.             raise Exception.Create('Invalid base64 hash string');
  603. //          Break;
  604.         end;
  605.  
  606.         //Now we have the next byte in c2|c3
  607.         // c2 = ..112222
  608.         // c3 = ..222233
  609.         Append( ((c2 and $0f) shl 4) or (c3 shr 2) );
  610.  
  611.         //If there's a 4th caracter, then we can use c3|c4 to form the third byte
  612.         if i > len then
  613.             Break;
  614.         c4 := Char64(s[i]);
  615.         Inc(i);
  616.  
  617.         if (c4 = -1) then
  618.         begin
  619.             raise Exception.Create('Invalid base64 hash string');
  620. //          Break;
  621.         end;
  622.  
  623.         //Now we have the next byte in c3|c4
  624.         // c3 = ..222233
  625.         // c4 = ..333333
  626.         Append( ((c3 and $03) shl 6) or c4 );
  627.     end;
  628. end;
  629.  
  630. class function TBCrypt.WideStringToUtf8(const Source: UnicodeString): AnsiString;
  631. var
  632.     cpStr: AnsiString;
  633.     strLen: Integer;
  634.     dw: DWORD;
  635. const
  636.     CodePage = CP_UTF8;
  637. begin
  638.     if Length(Source) = 0 then
  639.     begin
  640.         Result := '';
  641.         Exit;
  642.     end;
  643.  
  644.     // Determine real size of destination string, in bytes
  645.     strLen := WideCharToMultiByte(CodePage, 0,
  646.             PWideChar(Source), Length(Source), //Source
  647.             nil, 0, //Destination
  648.             nil, nil);
  649.     if strLen = 0 then
  650.     begin
  651.         dw := GetLastError;
  652.         raise EConvertError.Create('[WideStringToUtf8] Could not get length of destination string. Error '+IntToStr(dw)+' ('+SysErrorMessage(dw)+')');
  653.     end;
  654.  
  655.     // Allocate memory for destination string
  656.     SetLength(cpStr, strLen);
  657.  
  658.     // Convert source UTF-16 string (WideString) to the destination using the code-page
  659.     strLen := WideCharToMultiByte(CodePage, 0,
  660.             PWideChar(Source), Length(Source), //Source
  661.             PChar(cpStr), strLen, //Destination
  662.             nil, nil);
  663.     if strLen = 0 then
  664.     begin
  665.         dw := GetLastError;
  666.         raise EConvertError.Create('[WideStringToUtf8] Could not convert utf16 to utf8 string. Error '+IntToStr(dw)+' ('+SysErrorMessage(dw)+')');
  667.     end;
  668.  
  669.     Result := cpStr
  670. end;
  671.  
  672.  
  673. class function TBCrypt.SelfTestB: Boolean;
  674. var
  675.     i: Integer;
  676.     salt: AnsiString;
  677.     encoded: AnsiString;
  678.     data: TByteDynArray;
  679.     recoded: AnsiString;
  680. begin
  681.     for i := Low(TestVectors) to High(TestVectors) do
  682.     begin
  683.         salt := TestVectors[i,2];
  684.  
  685.         encoded := Copy(salt, 8, 22); //salt is always 22 characters
  686.  
  687.         data := TBCrypt.BsdBase64Decode(encoded);
  688.  
  689.         recoded := TBCrypt.BsdBase64Encode(data, Length(data));
  690.         if (recoded <> encoded) then
  691.             raise Exception.Create('BSDBase64 encoder self-test failed');
  692.     end;
  693.  
  694.     Result := True;
  695. end;
  696.  
  697. class function TBCrypt.SelfTestA: Boolean;
  698. var
  699.     i: Integer;
  700.  
  701.     procedure t(const password: AnsiString; const HashSalt: AnsiString; const ExpectedHashString: AnsiString);
  702.     var
  703.         version: string;
  704.         cost: Integer;
  705.         salt: TByteDynArray;
  706.         hash: TByteDynArray;
  707.         actualHashString: AnsiString;
  708.     begin
  709.         //Extract "$2a$06$If6bvum7DFjUnE9p2uDeDu" rounds and base64 salt from the HashSalt
  710.         if not TBCrypt.TryParseHashString(HashSalt, {out}version, {out}cost, {out}salt) then
  711.             raise Exception.Create('bcrypt self-test failed: invalid versionsalt "'+HashSalt+'"');
  712.  
  713.         hash := TBCrypt.HashPassword(password, salt, cost);
  714.         actualHashString := TBCrypt.FormatPasswordHashForBsd(cost, salt, hash);
  715.  
  716.         if actualHashString <> ExpectedHashString then
  717.             raise Exception.CreateFmt('bcrypt self-test failed. actual hash "%s" did not match expected hash "%s"', [actualHashString, ExpectedHashString]);
  718.     end;
  719.  
  720. begin
  721.     for i := Low(TestVectors) to High(TestVectors) do
  722.     begin
  723.         t(TestVectors[i,1], TestVectors[i,2], TestVectors[i,3] );
  724.     end;
  725.  
  726.     Result := True;
  727. end;
  728.  
  729. class function TBCrypt.SelfTestC: Boolean;
  730. var
  731.     s: UnicodeString;
  732.     hash: AnsiString;
  733. const
  734.     n: UnicodeString=''; //n=nothing.
  735.             //Work around bug in Delphi compiler when building widestrings
  736.             //http://stackoverflow.com/a/7031942/12597
  737. begin
  738.     {
  739.         We test that the it doesn't choke on international characters
  740.         This was a bug in a version of bcrypt somewhere, that we do not intend to duplicate
  741.     }
  742.     s := n+#$03C0+#$03C0+#$03C0+#$03C0+#$03C0+#$03C0+#$03C0+#$03C0; //U+03C0: Greek Small Letter Pi
  743.     hash := TBCrypt.HashPassword(s);
  744.     if not TBCrypt.CheckPassword(s, hash) then
  745.         raise Exception.Create('Failed to validate unicode string "'+s+'"');
  746.  
  747.  
  748.     s := n+#$03C0+#$03C0+#$03C0+#$03C0+#$03C0+#$03C0+#$03C0+#$03C0; //U+03C0: Greek Small Letter Pi
  749.     hash := TBCrypt.HashPassword(s);
  750.     if not TBCrypt.CheckPassword(s, hash) then
  751.         raise Exception.Create('Failed to validate unicode string "'+s+'"');
  752.  
  753.     Result := True;
  754. end;
  755.  
  756. { TBCryptTests }
  757.  
  758. {$IFDEF UnitTests}
  759. procedure TBCryptTests.SelfTest;
  760. begin
  761.     CheckTrue(TBCrypt.SelfTest);
  762. end;
  763. {$ENDIF}
  764.  
  765. class function TBCrypt.SelfTestD: Boolean;
  766. var
  767.     i: Integer;
  768.     password: string;
  769.     hash: string;
  770. begin
  771.     for i := 0 to 56 do
  772.     begin
  773.         password := Copy('The quick brown fox jumped over the lazy dog then sat on a log', 1, i);
  774.         hash := TBCrypt.HashPassword(password);
  775.         if (hash = '') then
  776.             raise Exception.Create('hash creation failed');
  777.     end;
  778.  
  779.     Result := True;
  780. end;
  781.  
  782. {$IFDEF UnitTests}
  783. procedure TBCryptTests.SelfTestA_KnownTestVectors;
  784. begin
  785.     CheckTrue(TBCrypt.SelfTestA);
  786. end;
  787.  
  788. procedure TBCryptTests.SelfTestB_Base64EncoderDecoder;
  789. begin
  790.     CheckTrue(TBCrypt.SelfTestB);
  791. end;
  792.  
  793. procedure TBCryptTests.SelfTestC_UnicodeStrings;
  794. begin
  795.     CheckTrue(TBCrypt.SelfTestC);
  796. end;
  797.  
  798. procedure TBCryptTests.SelfTestD_VariableLengthPasswords;
  799. begin
  800.     CheckTrue(TBCrypt.SelfTestD);
  801. end;
  802. {$ENDIF}
  803.  
  804. class function TBCrypt.GenRandomBytes(len: Integer; const data: Pointer): HRESULT;
  805. var
  806.     hProv: THandle;
  807. const
  808.     PROV_RSA_FULL = 1;
  809.     CRYPT_VERIFYCONTEXT = DWORD($F0000000);
  810.     CRYPT_SILENT         = $00000040;
  811. begin
  812.     if not CryptAcquireContextW(hPRov, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT or CRYPT_SILENT) then
  813.     begin
  814.         Result := HResultFromWin32(GetLastError);
  815.         Exit;
  816.     end;
  817.     try
  818.         if not CryptGenRandom(hProv, len, data) then
  819.         begin
  820.             Result := HResultFromWin32(GetLastError);
  821.             Exit;
  822.         end;
  823.     finally
  824.         CryptReleaseContext(hProv, 0);
  825.     end;
  826.  
  827.     Result := S_OK;
  828. end;
  829.  
  830. class function TBCrypt.SelfTestE: Boolean;
  831. var
  832.     salt: TByteDynArray;
  833. begin
  834.     salt := TBCrypt.GenerateSalt;
  835.     if Length(salt) <> BCRYPT_SALT_LEN then
  836.         raise Exception.Create('BCrypt selftest failed; invalid salt length');
  837.  
  838.     Result := True;
  839. end;
  840.  
  841. initialization
  842. {$IFDEF UnitTests}
  843.     RegisterTest('Library', TBCryptTests.Suite);
  844. {$ENDIF}
  845.  
  846. end.
Add Comment
Please, Sign In to add comment