好的,所以也许这个答案晚了一年,但我会试一试。在您自己的回答中,您注意到crypt()
使用的是 FreeBSD MD5,它还在运行哈希之前对 salt 进行了一些有趣的转换,因此我将要给您的结果永远不会与 a 的结果完全匹配打电话给md5()
. 也就是说,您看到的输出与您习惯的格式之间的唯一区别是您看到的输出编码如下
$1$ # this indicates that it is MD5
Vf/.4.1. # these eight characters are the significant portion of the salt
$ # this character is technically part of the salt, but it is ignored
CgCo33eb # the last 22 characters are the actual hash
iHVuFhpw # they are base64 encoded (to be printable) using crypt's alphabet
S.kMI0 # floor(22 * 6 / 8) = 16 (the length in bytes of a raw MD5 hash)
据我所知,crypt 使用的字母表如下所示:
./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
因此,考虑到所有这些,以下是如何将 22 个字符的 crypt-base64 散列转换为 32 个字符的 base16(十六进制)散列:
首先,您需要一些东西来将 base64(带有自定义字母)转换为原始的 16 字节 MD5 散列。
define('CRYPT_ALPHA','./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz');
/**
* Decodes a base64 string based on the alphabet set in constant CRYPT_ALPHA
* Uses string functions rather than binary transformations, because said
* transformations aren't really much faster in PHP
* @params string $str The string to decode
* @return string The raw output, which may include unprintable characters
*/
function base64_decode_ex($str) {
// set up the array to feed numerical data using characters as keys
$alpha = array_flip(str_split(CRYPT_ALPHA));
// split the input into single-character (6 bit) chunks
$bitArray = str_split($str);
$decodedStr = '';
foreach ($bitArray as &$bits) {
if ($bits == '$') { // $ indicates the end of the string, to stop processing here
break;
}
if (!isset($alpha[$bits])) { // if we encounter a character not in the alphabet
return false; // then break execution, the string is invalid
}
// decbin will only return significant digits, so use sprintf to pad to 6 bits
$decodedStr .= sprintf('%06s', decbin($alpha[$bits]));
}
// there can be up to 6 unused bits at the end of a string, so discard them
$decodedStr = substr($decodedStr, 0, strlen($decodedStr) - (strlen($decodedStr) % 8));
$byteArray = str_split($decodedStr, 8);
foreach ($byteArray as &$byte) {
$byte = chr(bindec($byte));
}
return join($byteArray);
}
现在您已经获得了原始数据,您需要一种方法将其转换为您期望的 base-16 格式,这再简单不过了。
/**
* Takes an input in base 256 and encodes it to base 16 using the Hex alphabet
* This function will not be commented. For more info:
* @see http://php.net/str-split
* @see http://php.net/sprintf
*
* @param string $str The value to convert
* @return string The base 16 rendering
*/
function base16_encode($str) {
$byteArray = str_split($str);
foreach ($byteArray as &$byte) {
$byte = sprintf('%02x', ord($byte));
}
return join($byteArray);
}
最后,由于 crypt 的输出包含很多我们不需要(实际上也不能使用)这个过程的数据,一个简短而巧妙的函数不仅可以将这两者联系在一起,而且允许直接输入输出从地穴。
/**
* Takes a 22 byte crypt-base-64 hash and converts it to base 16
* If the input is longer than 22 chars (e.g., the entire output of crypt()),
* then this function will strip all but the last 22. Fails if under 22 chars
*
* @param string $hash The hash to convert
* @param string The equivalent base16 hash (therefore a number)
*/
function md5_b64tob16($hash) {
if (strlen($hash) < 22) {
return false;
}
if (strlen($hash) > 22) {
$hash = substr($hash,-22);
}
return base16_encode(base64_decode_ex($hash));
}
鉴于这些功能,您的三个示例的 base16 表示是:
3ac3b4145aa7b9387a46dd7c780c1850
6f80dba665e27749ae88f58eaef5fe84
ec5f74086ec3fab34957d3ef0f838154
当然,重要的是要记住它们总是有效的,只是格式不同。