0

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
7
  • well that certainly is a problem. unless you know how they hashed the password, seems like your best bet is to reset all your user password and re-hashed it and save back on the DB Commented Oct 15, 2017 at 8:19
  • This way will prevent asp from verifying new hashed passwords! i guess!. Commented Oct 15, 2017 at 8:20
  • there you have it, unless you know the hashed function on ASP side, doing it from PHP will not be possible. Commented Oct 15, 2017 at 8:37
  • The salts are usually kept alongside the password usually concatenated with them. PHPs password_hash also creates a salt. You just need to bring the password in the appropriate format for password_verify to work. Check more details on php.net/manual/en/book.password.php Commented Oct 15, 2017 at 9:00
  • Can you show us a (non-valuable) ASP hash, so someone can guess what algorithm was used? Sometimes seeing the string length is valuable. Commented Oct 15, 2017 at 10:46

2 Answers 2

2

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');
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Here is another implementation of ASP.NET Core Hasher for Laravel: gist.github.com/tonila/5719aea8ad57df6821d7acdd1ed4ef1a
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.