Check Bitcoin wallet.dat passphrase hash in php

Viewed 4295

I want to match a passphrase against the hash generated with bitcoin2john.py from a specific Bitcoin wallet.dat file.

So first I have created a wallet.dat with the Passphrase "bit".

bitcoin2john.py generates this output for this wallet.dat file: $bitcoin$64$12c098515dc4f4140786e352f05d3065f17a2ca8f15c5f1c93923dc7146380c6$16$146b99a74fa7b536$135174$2$00$2$00

It is possible to crack that hash with hashcat. Haschcat returns the correct passphrase for it: $bitcoin$64$12c098515dc4f4140786e352f05d3065f17a2ca8f15c5f1c93923dc7146380c6$16$146b99a74fa7b536$135174$2$00$2$00:bit

Now I want to do that in PHP (and have been failing for days):

<?php

  $testPassphrase = 'bit';

  /*
   * Hash from wallet.dat with passphrase: "bit"
   * (Could be cracked by hashcat)
   */
  $passHash = '$bitcoin$64$12c098515dc4f4140786e352f05d3065f17a2ca8f15c5f1c93923dc7146380c6$16$146b99a74fa7b536$135174$2$00$2$00';
  
  /*
   * Remove hash identifier (not needed)
   */
  $passHash = str_replace('$bitcoin$', '', $passHash);
  
  /*
   * Split hash into pieces
   * ([0] = ??, [1] = passphrase hash, [2] = salt len, [3] = salt, [4] = iteration count, [5] = salt position, [6] = ??, [7] == ??, [8] == ??)
   */
  $passHashArray = explode('$', $passHash);
  
  /*
   * Combine passphrase and salt
   */
  $passToHash = $testPassphrase.$passHashArray[3];
   
   /*
    * Hash $passToHash $passHasArray[4] times with SHA512
    */
   for($i = 0; $i < $passHashArray[4]; $i++){
     $passToHash = hash('SHA512', $passToHash, true);
   }
   
   /*
    * Get Key and Iv from $passToHash for final encryption
    */
   $key = substr($passToHash, 0, 32);
   $iv = substr($passToHash, 32, 16);
   
   /*
    * final passphrase encryption
    */
   if(in_array('aes-256-cbc', openssl_get_cipher_methods())){
     $testPassphrase = openssl_encrypt($testPassphrase, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
   }

   /*
    * show result (should be: $passHashArray[1] = "12c098515dc4f4140786e352f05d3065f17a2ca8f15c5f1c93923dc7146380c6")
    */
   echo bin2hex($testPassphrase);

?>

I went through the Bitcoin Core code to see how it's done.

In wallet.ccp (https://github.com/bitcoin/bitcoin/blob/2556a973ed5839c81ad23cc4d3f187f3a777483c/src/wallet/wallet.cpp) in line 387 the function ChangeWalletPassphrase shows the steps. And in crypter.ccp (https://github.com/bitcoin/bitcoin/blob/c7ad94428ab6f54661d7a5441e1fdd0ebf034903/src/wallet/crypter.cpp) in line 13 and 39 the functions BytesToKeySHA512AES and SetKeyFromPassphrase are used.

This is what my PHP samle code does until "final passphrase encryption".

As far as I have found out through Internet research, the key and iv generated with this are then used to encrypt the passphrase with AES-256-CBC. You can also find this in the Bitcoin Core code. But there it looks as if not the passphrase but the MasterKey (_vMasterKey) is encrypted. But I don't have the MasterKey and so Hashcat (from my test scenario) doesn't have it. However, if I encrypt the passphrase, I cannot generate the correct hash.

After days of trial and error with my code, I'm now looking for help here. Thanks in advance for your help!

1 Answers

Here we go!

<?php

  $testPassphrase = 'bit';

  /*
   * Hash from wallet.dat with passphrase: "bit"
   * (Could be cracked by hashcat)
   */
  $passHash = '$bitcoin$64$12c098515dc4f4140786e352f05d3065f17a2ca8f15c5f1c93923dc7146380c6$16$146b99a74fa7b536$135174$2$00$2$00';
  
  /*
   * Remove hash identifier (not needed)
   */
  $passHash = str_replace('$bitcoin$', '', $passHash);
  
  /*
   * Split hash into pieces
   * ([0] = ??, [1] = master key, [2] = salt len, [3] = salt, [4] = iteration count, [5] = salt position, [6] = ??, [7] == ??, [8] == ??)
   */
  $passHashArray = explode('$', $passHash);
  
  /*
   * Combine passphrase and salt
   */
  $passToHash = $testPassphrase.hex2bin($passHashArray[3]);
   
   /*
    * Hash $passToHash $passHasArray[4] times with SHA512
    */
   for($i = 0; $i < $passHashArray[4]; $i++){
     $passToHash = hash('SHA512', $passToHash, true);
   }
   
   /*
    * Get Key and Iv from $passToHash for final encryption
    */
   $key = substr($passToHash, 0, 32);
   $iv = substr($passToHash, 32, 16);
   
   /*
    * final passphrase encryption
    */
   if(in_array('aes-256-cbc', openssl_get_cipher_methods())){
     if(openssl_decrypt(hex2bin($passHashArray[1]), 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv)){
       echo 'password correct';
     }else{
       echo 'decrypt failed';
     }
   }

?>
Related