在 PHP 中读取 ASP 散列密码
Reading ASP hashed password in PHP
我正在开发一个新的 PHP 项目,该项目使用为 ASP.NET 创建的现有数据库。我无法访问 ASP 源代码,所以我不知道密码是如何散列的。
我只需要一种方法来比较用户从 PHP 登录到数据库中存储的密码,因此现有用户(和新的 ASP 脚本注册)不必创建两个两个脚本的密码。
我知道它们已经以 sha1/base64 形式散列,但研究让我意识到 ASP.NET 使用生成 SALT 的 SqlMembershipProvider 或 membershipprovider,我想这是我的问题。
我需要一种方法让 PHP 验证 ASP 散列密码。
更新 1:
这是来自数据库的散列密码,供测试用户使用:
AHmLnE/qf1Jb9ABf6uIHEomuQCN8e0Xt8Bpl8Ty4fzdicsnut6nK/Rv/ZlfJDOK9Pg==
密码是1234
更新 2:
在尝试了下面@DeadSpace 的回答后,我得到了这个(不起作用):
<?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));
}
输出:
Hashed: AHmLnE/qf1Jb9ABf6uIHEomuQCN8e0Xt8Bpl8Ty4fzdicsnut6nK/Rv/ZlfJDOK9Pg==
Class test: AHmLnE/qf1Jb9ABf6uIHEmNZcjhUOFMxREhQOGQrTFMzb0VpL2c9PQ==
another way: AHmLnE/qf1Jb9ABf6uIHEp3Abm4NCdtNaQ/iXjxShfVK9SDoAiCfYJ7Pbz0UUnDZ
ASP.Net 是开源的,所以它的源代码是可用的 here.
这是它如何散列密码的简化版本。
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);
}
其中 CompareBytes 定义为:
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;
}
要在 PHP 中实现 Rfc2898DeriveBytes,您可以查看 João Santos 的 article。不过我还没有亲自测试代码。
<?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');
}
}
嗯,
None 世界各地的 pbkdf2 函数对我有用,我总是得到错误的哈希值。 php 中的 Rfc2898DeriveBytes 结果不同于 asp/c#.
所以我想,"The shortest distance between two points is a straight line"。
我最终在 c# 中创建了 CLI,它接受参数并使用来自 PasswordHasher Class 的 VerifyHashedPassword(string, string) 函数,然后在 php 中使用 exec("some.exe $thehash $password", $output)
函数执行它,并且获取 $output.
这种方式非常有效,因为我在 windows 中 运行 php。
我正在开发一个新的 PHP 项目,该项目使用为 ASP.NET 创建的现有数据库。我无法访问 ASP 源代码,所以我不知道密码是如何散列的。
我只需要一种方法来比较用户从 PHP 登录到数据库中存储的密码,因此现有用户(和新的 ASP 脚本注册)不必创建两个两个脚本的密码。
我知道它们已经以 sha1/base64 形式散列,但研究让我意识到 ASP.NET 使用生成 SALT 的 SqlMembershipProvider 或 membershipprovider,我想这是我的问题。
我需要一种方法让 PHP 验证 ASP 散列密码。
更新 1:
这是来自数据库的散列密码,供测试用户使用:
AHmLnE/qf1Jb9ABf6uIHEomuQCN8e0Xt8Bpl8Ty4fzdicsnut6nK/Rv/ZlfJDOK9Pg==
密码是1234
更新 2:
在尝试了下面@DeadSpace 的回答后,我得到了这个(不起作用):
<?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));
}
输出:
Hashed: AHmLnE/qf1Jb9ABf6uIHEomuQCN8e0Xt8Bpl8Ty4fzdicsnut6nK/Rv/ZlfJDOK9Pg==
Class test: AHmLnE/qf1Jb9ABf6uIHEmNZcjhUOFMxREhQOGQrTFMzb0VpL2c9PQ==
another way: AHmLnE/qf1Jb9ABf6uIHEp3Abm4NCdtNaQ/iXjxShfVK9SDoAiCfYJ7Pbz0UUnDZ
ASP.Net 是开源的,所以它的源代码是可用的 here.
这是它如何散列密码的简化版本。
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);
}
其中 CompareBytes 定义为:
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;
}
要在 PHP 中实现 Rfc2898DeriveBytes,您可以查看 João Santos 的 article。不过我还没有亲自测试代码。
<?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');
}
}
嗯,
None 世界各地的 pbkdf2 函数对我有用,我总是得到错误的哈希值。 php 中的 Rfc2898DeriveBytes 结果不同于 asp/c#.
所以我想,"The shortest distance between two points is a straight line"。
我最终在 c# 中创建了 CLI,它接受参数并使用来自 PasswordHasher Class 的 VerifyHashedPassword(string, string) 函数,然后在 php 中使用 exec("some.exe $thehash $password", $output)
函数执行它,并且获取 $output.
这种方式非常有效,因为我在 windows 中 运行 php。