Reading ASP hashed password in PHP

915 views Asked by At

I am working on a new PHP project that uses an exist database created for ASP.NET. I can't reach the ASP source code, so I don't know how the passwords are hashed.

All I need is a way to compare users logins from PHP to the stored password in database, so already existing users (and new ASP script registers) don't have to create two passwords for both scripts.

I know they've been hashed in sha1/base64 form, but researches led me to realize that ASP.NET uses either SqlMembershipProvider or membershipprovider that generates SALT, which is my problem I guess.

I need a way to make PHP verify the ASP hashed password.

UPDATE 1:

This is a hashed password from the database, for a test user: AHmLnE/qf1Jb9ABf6uIHEomuQCN8e0Xt8Bpl8Ty4fzdicsnut6nK/Rv/ZlfJDOK9Pg==

the password is 1234

UPDATE 2:

after trying @DeadSpace 's answer below, I ended up with this (not working):

<?php
include "SymmetricEncryption.php";

$hash = "AHmLnE/qf1Jb9ABf6uIHEomuQCN8e0Xt8Bpl8Ty4fzdicsnut6nK/Rv/ZlfJDOK9Pg=="; // password is : 1234

echo "Hashed: ".  $hash . "<br>";
$salt = substr(base64_decode($hash), 0, 16); 
//$salt = substr(base64_decode($hash), 1, 16); // C# = Buffer.BlockCopy(src, 1, dst, 0, 16);

$hasher = new SymmetricEncryption();

echo "Class test: ". base64_encode($salt. $hasher->encrypt('', '1234', $salt) ) . "<br>"; 



/***** another faield approach *****/


//Not working either :(

echo "another way: ". base64_encode($salt. pbkdf2('SHA1', '1234', $salt, 1000, 32, true)) ;

function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
    $algorithm = strtolower($algorithm);
    if(!in_array($algorithm, hash_algos(), true))
        trigger_error('PBKDF2 ERROR: Invalid hash algorithm.', E_USER_ERROR);
    if($count <= 0 || $key_length <= 0)
        trigger_error('PBKDF2 ERROR: Invalid parameters.', E_USER_ERROR);

    if (function_exists("hash_pbkdf2")) {
        // The output length is in NIBBLES (4-bits) if $raw_output is false!
        if (!$raw_output) {
            $key_length = $key_length * 2;
        }

        return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
    }

    $hash_length = strlen(hash($algorithm, "", true));
    $block_count = ceil($key_length / $hash_length);

    $output = "";
    for($i = 1; $i <= $block_count; $i++) {
        // $i encoded as 4 bytes, big endian.
        $last = $salt . pack("N", $i);
        // first iteration
        $last = $xorsum = hash_hmac($algorithm, $last, $password, true);
        // perform the other $count - 1 iterations
        for ($j = 0; $j < $count; $j++) {
            $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
        }
        $output .= $xorsum;
    }

    if($raw_output)
        return substr($output, 0, $key_length);
    else
        return bin2hex(substr($output, 0, $key_length));
}

Output:

Hashed: AHmLnE/qf1Jb9ABf6uIHEomuQCN8e0Xt8Bpl8Ty4fzdicsnut6nK/Rv/ZlfJDOK9Pg==
Class test: AHmLnE/qf1Jb9ABf6uIHEmNZcjhUOFMxREhQOGQrTFMzb0VpL2c9PQ==
another way: AHmLnE/qf1Jb9ABf6uIHEp3Abm4NCdtNaQ/iXjxShfVK9SDoAiCfYJ7Pbz0UUnDZ
2

There are 2 answers

0
Dohab On BEST ANSWER

Well,

None of the pbkdf2 functions around the world work for me, I always get the wrong hash. Rfc2898DeriveBytes results in php are different than asp/c#.

So I thought, "The shortest distance between two points is a straight line".

I ended up creating CLI in c# that accepts arguments and uses VerifyHashedPassword(string, string) function from PasswordHasher Class, then execute it in php with exec("some.exe $thehash $password", $output) function, and grab the $output.

This way works like a charm, since I'm running php in windows.

1
ZerosAndOnes On

ASP.Net is open source, so its source code is available here.


Here is a simplified version of how it hashes passwords.

public static bool VerifyHashedPassword(string hashedPassword, string password)
{
    byte[] buffer4;
    if (hashedPassword == null)
    {
        return false;
    }
    if (password == null)
    {
        throw new ArgumentNullException("password");
    }
    byte[] src = Convert.FromBase64String(hashedPassword);
    if ((src.Length != 49) || (src[0] != 0))
    {
        return false;
    }
    byte[] dst = new byte[16];
    /*Buffer.BlockCopy(Array src, int sourceOffset, Array destination,
      int DestionationOffset, int count)*/
    Buffer.BlockCopy(src, 1, dst, 0, 16);
    byte[] buffer3 = new byte[32];
    Buffer.BlockCopy(src, 17, buffer3, 0, 32);

    using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, dst, 1000))
    {
        buffer4 = bytes.GetBytes(32);
    }
    return CompareBytes(buffer3, buffer4);
}

Where CompareBytes is defined as:

static bool CompareBytes(byte[] a1, byte[] a2)
{
    if (a1.Length != a2.Length)
        return false;

    for (int i = 0; i < a1.Length; i++)
        if (a1[i] != a2[i])
            return false;

    return true;
}

For implementing Rfc2898DeriveBytes in PHP you can look into João Santos's article. I haven't personally tested the code though.

<?php
class SymmetricEncryption {
    private $cipher;
    public function __construct($cipher = 'aes-256-cbc') {
        $this->cipher = $cipher;
    }
    private function getKeySize() {
        if (preg_match("/([0-9]+)/i", $this->cipher, $matches)) {
            return $matches[1] >> 3;
        }
        return 0;
    }
    private function derived($password, $salt) {
        $AESKeyLength = $this->getKeySize();
        $AESIVLength = openssl_cipher_iv_length($this->cipher);
        $pbkdf2 = hash_pbkdf2("SHA1", $password, mb_convert_encoding($salt, 'UTF-16LE'), 1000, $AESKeyLength + $AESIVLength, TRUE);
        $key = substr($pbkdf2, 0, $AESKeyLength);
        $iv =  substr($pbkdf2, $AESKeyLength, $AESIVLength);
        $derived = new stdClass();
        $derived->key = $key;
        $derived->iv = $iv;
        return $derived;
    }
    function encrypt($message, $password, $salt) {
        $derived = $this->derived($password, $salt);
        $enc = openssl_encrypt(mb_convert_encoding($message, 'UTF-16', 'UTF-8'), $this->cipher, $derived->key, NULL, $derived->iv);
        return $enc;
    }
    function decrypt($message, $password, $salt) {
        $derived = $this->derived($password, $salt);
        $dec = openssl_decrypt($message, $this->cipher, $derived->key, NULL, $derived->iv);
        return mb_convert_encoding($dec, 'UTF-8', 'UTF-16');
    }
}