PHP Conference Japan 2024

sha1

(PHP 4 >= 4.3.0, PHP 5, PHP 7, PHP 8)

sha1计算字符串的 sha1 散列值

警告

由于该散列算法的快速特性,不建议使用此函数来保护密码。有关详细信息和最佳实践,请参阅密码散列常见问题解答

说明

sha1(string $string, bool $binary = false): string

使用» 美国安全散列算法 1 计算 string 的 sha1 散列值。

参数

string

输入字符串。

binary

如果可选的 binary 设置为 true,则返回长度为 20 的原始二进制格式的 sha1 摘要,否则返回值为一个 40 字符的十六进制数字。

返回值

以字符串形式返回 sha1 散列值。

范例

示例 #1 sha1() 例子

<?php
$str
= 'apple';

if (
sha1($str) === 'd0be2dc421be4fcd0172e5afceea3970e2f3d940') {
echo
"您想要青苹果还是红苹果?";
}
?>

参见

添加注释

用户贡献的注释 31 条注释

up
124
nathan
16 年前
下面建议对密码进行双重散列不是一个好主意。在散列之前向密码添加可变盐(例如用户名或其他每个帐户都不同的字段)要好得多。

双重散列比常规散列的安全性*更差*。您实际上正在做的是获取一些输入 $passwd,将其转换为仅包含字符 [0-9][A-F] 的恰好 32 个字符的字符串,然后对*该*字符串进行散列。您刚刚*大大*增加了散列冲突的几率(即我猜到一个短语,该短语将散列为与您的密码相同的值的几率)。

sha1(md5($pass)) 甚至更没有意义,因为您输入 128 位的信息来生成 256 位的散列,因此 50% 的结果数据是冗余的。您根本没有提高安全性。
up
7
Helpful Harry
19 年前
看看这些随机化的 sha1 密码存储函数,它们输出一个 50 个字符的字符串,前 40 个字符是基于后 10 个字符的 sha1 输出 - 后 10 个字符是一个随机种子

要对密码进行编码,请使用密码运行 pw_encode,它每次都会返回一个不同的伪随机字符串 - 存储此值。

要检查密码,请使用密码尝试和存储的值运行 pw_check,如果匹配则返回 true,否则返回 false

这些函数消除了在密码列表上运行字典匹配的烦人问题

<?php

function pw_encode($password)
{
for (
$i = 1; $i <= 10; $i++)
$seed .= substr('0123456789abcdef', rand(0,15), 1);
return
sha1($seed.$password.$seed).$seed;
}

function
pw_check($password, $stored_value)
{
if (
strlen($stored_value) != 50)
return
FALSE;
$stored_seed = substr($stored_value,40,10);
if (
sha1($stored_seed.$password.$stored_seed).$stored_seed == $stored_value)
return
TRUE;
else
return
FALSE;
}

?>
up
4
bobm at hp dot com
21 年前
要在 PHP5 之前实现原始二进制格式,您可以这样做...

$raw = pack("H*", sha1($str));

此致,

Bob Mader
up
4
marcin at marcinwolny dot net
11 年前
请记住,MD5 的安全性低于 SHA1。
较旧的 CPU 计算 MD5 的速度是 SHA1 的两倍多。并行计算中的 GPU 处理 MD5 的速度是 SHA1 的 3 倍多!

两个 Radeon 79xx 系列 GPU 可以在...大约 6 秒内计算出 6 个字符的小写 MD5 密码的彩虹表!

来源:http://www.codinghorror.com/blog/2012/04/speed-hashing.html
up
4
Andre D
16 年前
这是我之前发布的 getDigestNotation() 函数的更好版本。(第一个版本在参数检查中有一个错误。)

<?php
function getDigestNotation($rawDigest, $bitsPerCharacter, $chars = NULL)
{
if (
$chars === NULL || strlen($chars) < 2) {
$chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,';
}

if (
$bitsPerCharacter < 1) {
// $bitsPerCharacter 必须至少为 1
$bitsPerCharacter = 1;

} elseif (
strlen($chars) < pow(2, $bitsPerCharacter)) {
// $chars 的字符长度对于 $bitsPerCharacter 来说太小
// 将 $bitsPerCharacter 设置为 $chars 长度允许的最大值
$bitsPerCharacter = 1;
$minCharLength = 2;

while (
strlen($chars) >= ($minCharLength *= 2)) {
$bitsPerCharacter++;
}

unset(
$minCharLength);
}

$bytes = unpack('C*', $rawDigest);
$byteCount = count($bytes);

$out = '';
$byte = array_shift($bytes);
$bitsRead = 0;

for (
$i = 0; $i < $byteCount * 8 / $bitsPerCharacter; $i++) {

if (
$bitsRead + $bitsPerCharacter > 8) {
// 此字节中没有足够的位用于当前字符
// 获取剩余位并获取下一个字节
$oldBits = $byte - ($byte >> 8 - $bitsRead << 8 - $bitsRead);

if (
count($bytes) == 0) {
// 最后一位;匹配最后一个字符并退出循环
$out .= $chars[$oldBits];
break;
}

$oldBitCount = 8 - $bitsRead;
$byte = array_shift($bytes);
$bitsRead = 0;

} else {
$oldBitCount = 0;
}

// 仅从此字节读取所需的位
$bits = $byte >> 8 - ($bitsRead + ($bitsPerCharacter - $oldBitCount));
$bits = $bits - ($bits >> $bitsPerCharacter - $oldBitCount << $bitsPerCharacter - $oldBitCount);
$bitsRead += $bitsPerCharacter - $oldBitCount;

if (
$oldBitCount > 0) {
// 位来自不同的字节,将 $oldBits 添加到 $bits
$bits = ($oldBits << $bitsPerCharacter - $oldBitCount) | $bits;
}

$out .= $chars[$bits];
}

return
$out;
}
?>
up
2
novum123 在 ribbonbazaar 点 com
18 年前
关于字典攻击,我想到了以下函数

<?php
function twistSTR($array){
$twisted="";
$array_strlen=array();

foreach (
$array as $element){
$array_strlen[]=strlen($element);
}

for (
$i=0; $i<max($array_strlen); $i++){
foreach (
$array as $element){
if (
$i<strlen($element)){
$twisted=$twisted.$element{$i};
}
}
}

return
$twisted;
}
?>

twistSTR 函数接受一个字符串数组作为输入,并在所有其他字符串中交替每个字符串的每个字符。例如

<?php
echo twistSTR(array("this","and","that"));//输出: tathnhidast
?>

它可以按以下方式应用

<?php
if ($un===$_POST["username"] && $pwd===sha1(twistSTR(array($salt,$_POST["password"])))){
?>

反向工程实际输出并不是特别困难,但这不是重点。重点是,当密码被输入到那些数据库中时,它们将输入例如“thisandthat”,而不是“tathnhidast”。
up
3
ranko84 在 gmail 点 com
15 年前
小更新……,更像是对 obscure 函数的修复,替换
<?php
if ($keepLength != NULL)
{
if (
$hSLength != 0)
{
$hPassHash = substr($hPassHash, $hLPosition, -$hRPosition);
}
}
?>

替换为

<?php
if ($keepLength != NULL)
{
if (
$hSLength != 0)
{
if (
$hRPosition == 0)
{
$hPassHash = substr($hPassHash, $hLPosition);
}
else
{
$hPassHash = substr($hPassHash, $hLPosition, -$hRPosition);
}
}
}
?>

我收到了几个解释如何使用它的请求,所以这可能会有点长。

问题
1. 在大多数使用哈希和盐的解决方案中,您必须在数据库中增加一行,该行最好是随机的,用于该哈希数据的盐。如果攻击者设法获取您的数据库,他们将获得哈希数据和与原始数据一起使用的盐以使其变得模糊,然后破解该哈希数据就像您没有向其中添加任何盐一样。
2. 我偶然发现了一些函数,它们会对数据进行哈希处理,然后将盐输入到哈希中的随机位置并将其存储在数据库中,但他们仍然必须写下用于加扰盐的随机参数,以便在验证数据时可以重用它。获取简单的数据库转储在这里没有多大帮助,但如果他们也能设法获得模糊函数,他们可以很容易地看到什么是盐,什么是哈希。

解决方案
1. 当您可以将盐输入到哈希中时,为什么要使用额外的行来存储盐。我不确定攻击者如何确定他们面对的是哪种类型的哈希,但我猜这与哈希长度有关。在这种情况下,为什么让攻击者的工作更轻松,并在数据库中存储 data_hash+salt,他们可以仅凭其长度就假设其中包含盐。
$keepLength 背后的原因。如果将其设置为 1,则哈希数据加盐的 strlen 将等于哈希数据的 strlen,从而使攻击者认为没有盐。
如果将 $keepLength 保留为 NULL,则最终结果的 strlen 将为 strlen(used_hash_algorithm)+$hSLength。
$minhPass 用于为必须哈希的字符串保留足够的空间,因此使用此函数的人不会因设置过高的盐长度 ($hSLength) 而意外删除它,例如……如果您将其设置为 30000,它将继续正常工作。

2. 如果您考虑一下,进行输入时的常量但可变的值将是正在输入的相同数据。
如果我们尝试对密码进行哈希处理,并且用户 A 的密码为“notme”,密码 strlen 等于 5,如果我们使用函数的默认参数,将 $keepLength 设置为 1,则过程将为
随机生成盐值,对其进行哈希,将哈希后盐值的前 5 个字符添加到明文密码的开头,将哈希后盐值的后 5 个字符添加到明文密码的结尾,然后进行哈希。用哈希后盐值的前 5 个字符替换哈希后密码的前 5 个字符,对哈希后密码的后 5 个字符执行相同的操作,返回哈希后的密码。
如果字符串长度超过 10 个字符,函数将使用简单的数学方法将其减少到小于 10 的数字,嗯... 小于 $hSLength 中指定的数字。
好处是每次用户输入正确的密码时,其长度都相同,因此无需将其写在任何地方。

那么最终实现了什么?
1. 攻击者可能不知道哈希值已加盐,并且您的数据库中没有额外的行说明这是此哈希的盐值。
2. 如果他确实发现它是加盐的,他将不知道什么是哈希后的密码,什么是盐值。
3. 如果他设法访问了这个晦涩的函数,唯一可能对他有帮助的是 $hSLength 的值,如果 $hSLength 设置为 10,他将不得不破解哈希字符串的 10 种变体,因为他不知道他试图破解的用户的密码有多长。
例如,第一个变体是没有最后 10 个字符的哈希密码,第二个变体是没有第一个字符和最后 9 个字符的哈希密码...
4. 即使他有足够的能力破解所有 10 种变体,他可能得到的最终字符串不一定与原始用户的密码长度完全相同,在这种情况下,攻击者再次失败。
up
2
mark at dot BANSPAM dot pronexus dot nl
20 年前
正在寻找一个简单的函数来实现 HMAC-SHA1,但不想使用整个 PEAR Message 库?

//根据 RFC2104 计算 HMAC-SHA1
// http://www.ietf.org/rfc/rfc2104.txt
function hmacsha1($key,$data) {
$blocksize=64;
$hashfunc='sha1';
if (strlen($key)>$blocksize)
$key=pack('H*', $hashfunc($key));
$key=str_pad($key,$blocksize,chr(0x00));
$ipad=str_repeat(chr(0x36),$blocksize);
$opad=str_repeat(chr(0x5c),$blocksize);
$hmac = pack(
'H*',$hashfunc(
($key^$opad).pack(
'H*',$hashfunc(
($key^$ipad).$data
)
)
)
);
return bin2hex($hmac);
}

这对客户端身份验证非常有用。另请参阅 http://cookies.lcs.mit.edu/pubs/webauth:tr.pdf
您可以选择将 $hashfunc 更改为 'md5',使其成为 HMAC-MD5 函数 ;-)
如果您想要原始输出或 base64 输出而不是十六进制输出,只需更改最后一行 return 语句。

此致,
Mark

附言:"$hmac =" 行曾经是 1 行,但我不得不将其分割以适合此处 ;)
up
2
Dan
19 年前
我注意到网站现在开始要求使用特定长度的密码,这些密码必须至少包含 1 个非字母数字字符。这本身就使得字典攻击毫无用处。我的网站也要求这样做。它使用 md5,并在 md5 中附加一个站点代码。包含该站点密钥的 include 文件位于公共文件夹之外。我确信我已经做了足够多的工作来阻止坏人。
up
4
Gregory Boshoff
18 年前
请注意,sha1 算法已被攻破,政府机构不再使用。

从 PHP 5.1.2 开始,可以使用一组新的哈希函数。

https://php.net/manual/en/function.hash.php

新的 hash() 函数支持一系列新的哈希方法。

echo hash('sha256', 'The quick brown fox jumped over the lazy dog.');

建议开发人员开始使用更强的 sha-2 哈希方法(例如 sha256、sha384、sha512 或更好的方法)来确保其应用程序的未来安全性。

从 PHP 5.1.2 开始,hash_algos() 返回一个数组,其中包含系统特定或已注册的哈希算法方法,这些方法可供 PHP 使用。

print_r(hash_algos());
up
1
匿名
15 年前
另一种将盐值直接包含在哈希中的加盐哈希解决方案,同时保持结果的长度相同。如果要生成哈希,请在没有第二个参数的情况下调用该函数。如果要根据哈希检查密码,请使用哈希作为第二个参数。在这种情况下,该函数在成功时返回哈希本身,在失败时返回布尔值 false。您还可以将哈希算法指定为第三个参数(否则将使用 SHA-1)。

<?php
function __hash($password, $obscured = NULL, $algorithm = "sha1")
{
// 是否使用用户指定的算法
$mode = in_array($algorithm, hash_algos());
// 生成随机盐值
$salt = uniqid(mt_rand(), true);
// 对其进行哈希
$salt = $mode ? hash($algorithm, $salt) : sha1($salt);
// 获取长度
$slen = strlen($salt);
// 计算我们将使用的盐值的实际长度
// 哈希的 1/8 到 1/4,密码越短,盐值越长
$slen = max($slen >> 3, ($slen >> 2) - strlen($password));
// 如果我们正在根据哈希检查密码,请从中获取实际盐值,否则只需将我们已有的盐值剪切到合适的大小
$salt = $obscured ? __harvest($obscured, $slen, $password) : substr($salt, 0, $slen);
// 对密码进行哈希 - 这可能是不必要的
$hash = $mode ? hash($algorithm, $password) : sha1($password);
// 将盐值放入其中
$hash = __scramble($hash, $salt, $password);
// 然后再次对其进行哈希
$hash = $mode ? hash($algorithm, $hash) : sha1($hash);
// 剪切结果,以便我们可以添加盐值并保持相同的长度
$hash = substr($hash, $slen);
// ... 这样做
$hash = __scramble($hash, $salt, $password);
// 并返回结果
return $obscured && $obscured !== $hash ? false : $hash;
}
?>

它使用随机的可变长度盐值,具体取决于密码的长度。__scramble() 和 __harvest() 函数分别用于将盐值放入哈希或将其取出。您可以编写自己的函数,当然,结果的强度在很大程度上取决于它们。它们可以相对简单,但仍然非常安全

<?php
function __scramble($hash, $salt, $password)
{
return
substr($hash, 0, strlen($password)) . $salt . substr($hash, strlen($password));
}

function
__harvest($obscured, $slen, $password)
{
return
substr($obscured, min(strlen($password), strlen($obscured) - $slen), $slen);
}
?>

或者它们可以非常复杂(我最喜欢的那种)

<?php
function __scramble($hash, $salt, $password)
{
$k = strlen($password); $j = $k = $k > 0 ? $k : 1; $p = 0; $index = array(); $out = ""; $m = 0;
for (
$i = 0; $i < strlen($salt); $i++)
{
$c = substr($password, $p, 1);
$j = pow($j + ($c !== false ? ord($c) : 0), 2) % (strlen($hash) + strlen($salt));
while (
array_key_exists($j, $index))
$j = ++$j % (strlen($hash) + strlen($salt));
$index[$j] = $i;
$p = ++$p % $k;
}
for (
$i = 0; $i < strlen($hash) + strlen($salt); $i++)
$out .= array_key_exists($i, $index) ? $salt[$index[$i]] : $hash[$m++];
return
$out;
}

function
__harvest($obscured, $slen, $password)
{
$k = strlen($password); $j = $k = $k > 0 ? $k : 1; $p = 0; $index = array(); $out = "";
for (
$i = 0; $i < $slen; $i++)
{
$c = substr($password, $p, 1);
$j = pow($j + ($c !== false ? ord($c) : 0), 2) % strlen($obscured);
while (
in_array($j, $index))
$j = ++$j % strlen($obscured);
$index[$i] = $j;
$p = ++$p % $k;
}
for (
$i = 0; $i < $slen; $i++)
$out .= $obscured[$index[$i]];
return
$out;
}
?>
up
1
sinatosk at gmail dot com
20 年前
这里有一个 SHA1 函数,可以完全独立工作。这是为那些使用低于 PHP 4.3.0 版本的用户准备的。它的工作方式与 PHP5 相同(能够返回原始输出)。

<?php

/*
** Date modified: 1st October 2004 20:09 GMT
*
** PHP implementation of the Secure Hash Algorithm ( SHA-1 )
*
** This code is available under the GNU Lesser General Public License:
** http://www.gnu.org/licenses/lgpl.txt
*
** Based on the PHP implementation by Marcus Campbell
** http://www.tecknik.net/sha-1/
*
** This is a slightly modified version by me Jerome Clarke ( [email protected] )
** because I feel more comfortable with this
*/

function sha1_str2blks_SHA1($str)
{
$strlen_str = strlen($str);

$nblk = (($strlen_str + 8) >> 6) + 1;

for (
$i=0; $i < $nblk * 16; $i++) $blks[$i] = 0;

for (
$i=0; $i < $strlen_str; $i++)
{
$blks[$i >> 2] |= ord(substr($str, $i, 1)) << (24 - ($i % 4) * 8);
}

$blks[$i >> 2] |= 0x80 << (24 - ($i % 4) * 8);
$blks[$nblk * 16 - 1] = $strlen_str * 8;

return
$blks;
}

function
sha1_safe_add($x, $y)
{
$lsw = ($x & 0xFFFF) + ($y & 0xFFFF);
$msw = ($x >> 16) + ($y >> 16) + ($lsw >> 16);

return (
$msw << 16) | ($lsw & 0xFFFF);
}

function
sha1_rol($num, $cnt)
{
return (
$num << $cnt) | sha1_zeroFill($num, 32 - $cnt);
}

function
sha1_zeroFill($a, $b)
{
$bin = decbin($a);

$strlen_bin = strlen($bin);

$bin = $strlen_bin < $b ? 0 : substr($bin, 0, $strlen_bin - $b);

for (
$i=0; $i < $b; $i++) $bin = '0'.$bin;

return
bindec($bin);
}

function
sha1_ft($t, $b, $c, $d)
{
if (
$t < 20) return ($b & $c) | ((~$b) & $d);
if (
$t < 40) return $b ^ $c ^ $d;
if (
$t < 60) return ($b & $c) | ($b & $d) | ($c & $d);

return
$b ^ $c ^ $d;
}

function
sha1_kt($t)
{
if (
$t < 20) return 1518500249;
if (
$t < 40) return 1859775393;
if (
$t < 60) return -1894007588;

return -
899497514;
}

function
sha1($str, $raw_output=FALSE)
{
if (
$raw_output === TRUE ) return pack('H*', sha1($str, FALSE));

$x = sha1_str2blks_SHA1($str);
$a = 1732584193;
$b = -271733879;
$c = -1732584194;
$d = 271733878;
$e = -1009589776;

$x_count = count($x);

for (
$i = 0; $i < $x_count; $i += 16)
{
$olda = $a;
$oldb = $b;
$oldc = $c;
$oldd = $d;
$olde = $e;

for (
$j = 0; $j < 80; $j++)
{
$w[$j] = ($j < 16) ? $x[$i + $j] : sha1_rol($w[$j - 3] ^ $w[$j - 8] ^ $w[$j - 14] ^ $w[$j - 16], 1);

$t = sha1_safe_add(sha1_safe_add(sha1_rol($a, 5), sha1_ft($j, $b, $c, $d)), sha1_safe_add(sha1_safe_add($e, $w[$j]), sha1_kt($j)));
$e = $d;
$d = $c;
$c = sha1_rol($b, 30);
$b = $a;
$a = $t;
}

$a = sha1_safe_add($a, $olda);
$b = sha1_safe_add($b, $oldb);
$c = sha1_safe_add($c, $oldc);
$d = sha1_safe_add($d, $oldd);
$e = sha1_safe_add($e, $olde);
}

return
sprintf('%08x%08x%08x%08x%08x', $a, $b, $c, $d, $e);
}

?>
up
1
rsemirag at yahoo dot com
20 年前
如果你正在努力为 LDAP 生成 SHA 编码的密码(PHP < 5.0),你最终需要的是这个

$userpassword = base64_encode(pack("H*", sha1($pass)));

我在 OpenLDAP 常见问题解答中找到了这个(非常感谢 Google 和 Ace),尽管我使用的是 iPlanet LDAP 服务器。

Ray Semiraglio
up
0
hkmaly
7 年前
注意:在你想到使用 sha1 加密码作为防止他人篡改消息的方法之前,请阅读维基百科上的“长度扩展攻击”和“基于哈希的消息认证码”页面。简而言之,简单的构造可能非常不安全。如果可用,请使用 hash_hmac 或正确重新实现 HMAC,不要走捷径,就像 mark at dot BANSPAM dot pronexus dot nl 的评论中已经显示的那样。
up
0
php at REMOVEMEkennel17 dot co dot uk
19 年前
应该注意的是,sha1("") 不返回空字符串。这意味着如果你正在运行一个不需要用户设置密码的系统,以下代码将不会按预期工作

<?php
if ($StoredPassword == sha1($NewPassword))
// 密码正确
?>

如果 $StoredPassword 和 $NewPassword 都为空,那么密码应该被视为正确,但因为 sha1("") != "",它将被视为错误。为了获得正确的行为,你需要使用

<?php
if (($StoredPassword == "" && $NewPassword == "") || ($StoredPassword == sha1($NewPassword)))
// 密码正确
?>

(注意:我使用自定义的 IsBlank() 函数而不是与空字符串进行比较,因此 NULL 值也会匹配。)

作为参考,这里有一些特殊值通过 sha1()。请注意 sha1("") == sha1(NULL) == sha1(false),并且 sha1(0) != sha1(false)

"" -> da39a3ee5e6b4b0d3255bfef95601890afd80709
NULL -> da39a3ee5e6b4b0d3255bfef95601890afd80709
0 -> b6589fc6ab0dc82cf12099d1c2d40ab994e8410c
1 -> 356a192b7913b04c54574d18c28d46e6395428ab
false -> da39a3ee5e6b4b0d3255bfef95601890afd80709
true -> 356a192b7913b04c54574d18c28d46e6395428ab
up
0
WTM
19 年前
实际上,Helpful Harry 的帖子除了最简单的破解尝试外,不会提高你的安全性。由于随机种子附加到密码哈希的末尾,如果你窃取了哈希密码,你就窃取了种子。

这意味着你可以编写一个简单的 php 程序来从循环中调用 Harry 包含的 pw_check 函数,向其提供字典单词或随机字符。

当然,如果你修改程序以更复杂的方式使用种子,“他们”将不得不了解新函数的操作。但话又说回来,如果有人可以窃取你的密码数据库,他们可能也可以窃取你的网站代码(或猜测它)。
up
-1
mgcummings at yahoo dot com
13 年前
我想我可以节省其他人一些时间,弄清楚如何仅使用 PHP 生成像 MySQL5 PASSWORD() 那样的哈希。

$hash = '*' . sha1(sha1($pass), TRUE));
up
-1
erling dot westenvik at gmail dot com
18 年前
关于 php at REMOVEMEkennel17 dot co dot uk 的以下说明

“为了获得正确的行为”这句话,如果改成“为了获得想要的(但不推荐的)行为”可能会更好。

始终尊重函数的预期数据类型:sha1 期望一个字符串作为输入,并在退出时返回一个字符串。NULL、TRUE 和 FALSE 不是字符串数据类型。" " 字符串与 "any" 一样是一个字符串。如果遵循 sha1("") 应该返回 "" 的“逻辑”,那么 sha1("a") 应该返回什么?"b"?"c"?

一个允许空密码的认证系统实际上并不是一个认证系统。你所描述的只是一种告诉应用程序你想在某个特定上下文中查看数据的方式,例如按用户名排序等。为此目的创建其他工具,并让认证系统处理它应该做的事情:授予用户访问受限数据的权限并阻止其他用户查看相同的数据。

不要以明文形式存储密码,而是对它们进行加盐和加密。这样,即使使用空“密码”,<?php $sStoredPwd === sha1($sStoredSalt . $_POST["sTypedPwd"]); ?> 也是完全合理的。除了用户本身之外,没有其他人,甚至程序员,应该知道密码或能够猜到它。如果用户忘记了密码,则必须生成一个新密码。

此致,
二菱
up
-1
Andr D
16 年前
有时你希望摘要既有可读的表示法(例如十六进制),也有原始二进制。有时你希望摘要采用十六进制以外的表示法。

以下 getDigestNotation() 函数接受一个二进制字符串并以 2、4、8、16、32 或 64 进制表示法返回它。它适用于 sha1()、md5()、hash() 或任何其他可以输出原始二进制字符串的函数。

它的工作原理类似于 php.ini 配置选项中的 session.hash_bits_per_character。

您可以指定每个位置使用哪些字符,或者使用默认值,该默认值与 session.hash_bits_per_character (0-9, a-z, A-Z, "-", ",") 匹配。每个字符使用的实际位数范围 ($bitsPerCharacter) 是 1 到 6;您可以使用更多位数,但您必须提供自己的基本字符串 ($chars),该字符串的长度至少为 pow(2, $bitsPerCharacter) 个字符。因此,即使每个字符使用 7 位,您也需要指定一个长度为 128 个字符的 $chars 值,这超出了可打印 ASCII 字符的数量。

输出的基数与 $bitsPerCharacter 的值相关,如下所示:
1:base-2 (二进制)
2:base-4
3:base-8 (八进制)
4:base-16 (十六进制)
5:base-32
6:base-64

<?php
$raw
= sha1(uniqid(mt_rand(), TRUE), TRUE);

echo
getDigestNotation($raw, 6);

function
getDigestNotation($rawDigest, $bitsPerCharacter, $chars = NULL)
{
if (
$chars === NULL || strlen($chars) < 2) {
$chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,';
}

if (
$bitsPerCharacter < 1) {
// $bitsPerCharacter 必须至少为 1
$bitsPerCharacter = 1;

} elseif (
strlen($chars) < pow(2, $bitsPerCharacter)) {
// $chars 的字符长度对于 $bitsPerCharacter 来说太小
// 将 $bitsPerCharacter 设置为 $chars 长度允许的最大值
$bitsPerCharacter = 1;

do {
$bitsPerCharacter++;
} while (
strlen($chars) > pow(2, $bitsPerCharacter));
}

$bytes = unpack('C*', $rawDigest);
$byteCount = count($bytes);

$out = '';
$byte = array_shift($bytes);
$bitsRead = 0;

for (
$i = 0; $i < $byteCount * 8 / $bitsPerCharacter; $i++) {

if (
$bitsRead + $bitsPerCharacter > 8) {
// 此字节中剩余的位数不足以表示当前字符
// 获取剩余的位数并获取下一个字节
$oldBits = $byte - ($byte >> 8 - $bitsRead << 8 - $bitsRead);

if (
count($bytes) == 0) {
// 最后一位;匹配最后一个字符并退出循环
$out .= $chars[$oldBits];
break;
}

$oldBitCount = 8 - $bitsRead;
$byte = array_shift($bytes);
$bitsRead = 0;

} else {
$oldBitCount = 0;
}

// 仅从此字节中读取所需的位数
$bits = $byte >> 8 - ($bitsRead + ($bitsPerCharacter - $oldBitCount));
$bits = $bits - ($bits >> $bitsPerCharacter - $oldBitCount << $bitsPerCharacter - $oldBitCount);
$bitsRead += $bitsPerCharacter - $oldBitCount;

if (
$oldBitCount > 0) {
// 位数来自不同的字节,将 $oldBits 添加到 $bits
$bits = ($oldBits << $bitsPerCharacter - $oldBitCount) | $bits;
}

$out .= $chars[$bits];
}

return
$out;
}
?>

最后,根据摘要长度,最后一个字符的剩余位数可能少于 $bitsPerCharacter,因此最后一个字符会更小。当 session.hash_bits_per_character 使用 5 或 6 时,PHP 的会话 ID 生成器也会发生同样的情况。
up
-1
ranko84 在 gmail 点 com
16 年前
感谢您的反馈。我希望这应该可以解决问题。
我想我还没有完全理解这句话:“在这种情况下,您将需要盐与用户名和密码一起驻留在数据库中。” 就像,您指的是以前的方法,这种方法还是这个函数。
盐已经与用户名、密码或您决定哈希的任何字符串一起驻留在数据库中。此函数只是根据用户输入的字符串(密码)的长度对其进行加扰,以便攻击者难以找出什么是盐,什么是哈希,即使攻击者怀疑存在盐($keepLength 背后的原因,或定义 $hSLength,您可以在其中将其设置为 24,使攻击者认为他面对的是 sha256,而不是 sha1)。

<?php
function obscure ($hString, $hDecode = NULL, $hSLength = 10, $keepLength = NULL, $minhPass = 10, $hMethod = sha1)
{
// 如果
$hDecode NULL
if ($hDecode == NULL)
{
// 循环16次
for ($i = 0; $i<16; $i++)
{

$hSalt = rand(33, 255);
$hRandomSalt .= chr($hSalt);
}
$hRandomSalt = hash($hMethod, $hRandomSalt);
}
else
{
$hRandomSalt = $hDecode;
}

// 如果
$keepLength 不为 NULL
if ($keepLength != NULL)
{
// 如果
$hSLength 大于 ($hRandomSalt 的长度 - $minhPass)
if ($hSLength > (strlen($hRandomSalt) - $minhPass))
{
$hSLength = (strlen($hRandomSalt) - $minhPass);
}
}
// 否则,如果
$hSLength 小于 0
else if ($hSLength < 0)
{
$hSLength = 0;
}

$hLPosition = strlen($hString);

// 当
$hLPosition 大于 $hSLength
while ($hLPosition > $hSLength)
{
$hNumber = substr($hLPosition, -1);

$hLPosition = $hLPosition * ($hNumber/10);
}

$hLPosition = (integer)$hLPosition;
$hRPosition = $hSLength - $hLPosition;

$hFSalt = substr($hRandomSalt, 0, $hLPosition);
$hLSalt = substr($hRandomSalt, -$hRPosition, $hRPosition);

$hPassHash = hash($hMethod, ($hLSalt . $hString . $hFSalt));

// 如果
$keepLength 不为 NULL
if ($keepLength != NULL)
{
// 如果
$hSLength 不等于 0
if ($hSLength != 0)
{
$hPassHash = substr($hPassHash, $hLPosition, -$hRPosition);
}
}

// 返回
$hFSalt . $hPassHash . $hLSalt
return $hFSalt . $hPassHash . $hLSalt;
}
?>
up
-1
rich dot sage 在 gmail dot com
14 年前
如果您使用 Dovecot 进行邮件检索,并且想要自己生成 SHA1 密码,您需要将 raw_output 值设置为 true,然后对输出进行 base64 编码。

<?php
function makeDovecotPassword($input)
{
// 返回 '{SHA}' . base64 编码的 SHA1 哈希值
return '{SHA}' . base64_encode(sha1($input, true));
}
?>
up
-4
php 在 wbhostmax dot de
12 年前
<?php
function DoubleSaltedHash($pw, $salt) {
// 返回双重加盐的 SHA1 哈希值
return sha1($salt.sha1($salt.sha1($pw)));
}

// 生成盐值的函数
function generate_salt() {
$dummy = array_merge(range('0', '9'));
mt_srand((double)microtime()*1000000);
// 循环数组长度的两倍次数
for ($i = 1; $i <= (count($dummy)*2); $i++)
{
$swap = mt_rand(0,count($dummy)-1);
$tmp = $dummy[$swap];
$dummy[$swap] = $dummy[0];
$dummy[0] = $tmp;
}
// 返回截取前9位的 SHA1 哈希值
return sha1(substr(implode('',$dummy),0,9));
}
$pw="geheim"
$salt=generate_salt();
echo
"hash:".DoubleSaltedHash($pw, $salt);

?>

这是我加密密码的方式。
up
-1
labarks
21 年前
将此代码追加到您的 sha1lib 文件中,以使其更具可移植性。如果您的 PHP 版本支持 sha1(),那么它将尝试使用 Mhash,否则将使用 sha1lib。如果您想显示正在使用的哈希函数,请使用 $sha1。

if ( function_exists('sha1') )
$sha1 = "sha1";

if ( !function_exists('sha1') && function_exists('mhash'))
{
function sha1($hash_source)
{
$hash = mhash(MHASH_SHA1, $hash_source);
$hex_hash = bin2hex($hash);
return $hex_hash;
}
$sha1 = "Mhash";
}
if ( !function_exists('sha1') && !function_exists('mhash'))
{
function
{
$library = new Sha1Lib();

return $raw_output ? $library->str_sha1($string) : $library->hex_sha1($string);
}
$sha1 = "sha1lib";
}
up
-1
NoName
17 years ago
Regarding the twistSTR - the problem is that currently it is relatively easy to generate a collision for any alphanumeric plaintext of a given, short length via e.g. a rainbow table. You're bettter off using a sufficiently lengthy and random salt.
up
-2
mVamer
15 年前
If I correctly understand what ranko84 is on about, this would be a simpler function with roughly the same result.

<?php
function obscure($password, $algorythm = "sha1")
{
// 获取一个随机盐,或验证一个盐。
// 由 (grosbedo AT gmail DOT com) 添加
if ($salt == NULL)
{
$salt = hash($algorythm, uniqid(rand(), true));
}

// 确定哈希的长度。
$hash_length = strlen($salt);

// 确定密码的长度。
$password_length = strlen($password);

// 确定密码的最大长度。仅当用户输入非常长的密码时才需要。
// 无论如何,盐最多为最终结果的一半。哈希越长,密码/盐可以越长。
$password_max_length = $hash_length / 2;

// 根据密码的长度缩短盐。
if ($password_length >= $password_max_length)
{
$salt = substr($salt, 0, $password_max_length);
}
else
{
$salt = substr($salt, 0, $password_length);
}

// 确定盐的长度。
$salt_length = strlen($salt);

// 确定加盐的哈希密码。
$salted_password = hash($algorythm, $salt . $password);

// 如果我们将盐添加到哈希密码中,我们将得到一个比普通哈希密码更长的哈希。我们不希望那样;
// 它会向攻击者泄露提示。因为密码和密码的长度是已知的,所以我们可以丢弃加盐密码的
// 前几个字符。这样,盐和加盐密码的长度与没有盐的普通哈希密码的长度相同。
$used_chars = ($hash_length - $salt_length) * -1;
$final_result = $salt . substr($salted_password, $used_chars);

return
$final_result;
}
?>
up
-2
svn 在 datapirate 点 de
19 年前
想使用 SHA-2 算法吗?试试这个

http://www.adg.us/computers/sha.html 下载 Tar 包
编译(可能会出现一些警告)并测试它

cc -O2 -DSHA2_UNROLL_TRANSFORM -Wall -o sha2 sha2prog.c sha2.c
./sha2test.pl

将其复制到 /usr/local/bin/(不要忘记检查权限)

这里有两个可以使用的函数

function sha2($bits, $string){
$sha2bin="/usr/local/bin/sha2";
$echocmd="echo";
if(!in_array($bits, array(256, 384, 512)))return(false);
$r=exec($echocmd." ".escapeshellarg($string)."|".$sha2bin." -q -".$bits, $sha2);
return($sha2[0]);
}

function sha2_file($bits, $filename){
$sha2bin="/usr/local/bin/sha2";
if(!in_array($bits, array(256, 384, 512)))return(false);
if(!file_exists($filename)||!is_readable($filename))return(false);
$r=exec($sha2bin." -q -".$bits." ".escapeshellarg($filename), $sha2);
return($sha2[0]);
}

并像下面这样使用它

<?php
$str
= 'apple';
if (
sha2(256, $str) === '303980bcb9e9e6cdec515230791af8b0ab1aaa244b58a8d99152673aa22197d0') {
echo
"你想要一个青苹果还是红苹果?";
exit;
}
?>
up
-4
paul
15 年前
我相信这提供了使用随机盐的最佳保护,必须存储该盐以便以后进行验证。

如果没有提供盐(可以通过将输出分成两半并取前半部分来检索),那么它将生成一个随机盐,对其进行哈希处理,将其放置在哈希密码中相对于密码长度的位置(介于0和哈希类型(sha1?md5?)的长度之间),然后对整个字符串进行哈希处理。

这将导致使用根据密码长度动态放置的盐的密码哈希。然后将使用的盐附加到完成的哈希的前面,以便以后可以检索到它以进行验证。

鉴于用户将选择一个典型的密码,长度在5到15个字符之间,这使得他们需要尝试额外的10倍的字典攻击次数,因为它可以放置在任何位置,因为这也是一个随机生成的盐,这意味着每个密码实例至少需要10次字典攻击(可能多达40次),以尝试破解你的sha1加密密码。

如果你每个月都更改密码,即使有人通过本地漏洞查看了你的文件,破解你密码所需的时间也会远远超过你更改密码的频率。

没有什么是安全的,但这应该会让他们花费比你更改密码的时间更长的时间来破解。至少按照今天的技术是这样。

保罗

<?php
function createHash($inText, $saltHash=NULL, $mode='sha1'){
// 对文本进行哈希 //
$textHash = hash($mode, $inText);
// 设置盐在哈希中出现的位置 //
$saltStart = strlen($inText);
// 如果没有提供盐,则创建一个随机盐 //
if($saltHash == NULL) {
$saltHash = hash($mode, uniqid(rand(), true));
}
// 在密码长度位置将盐添加到文本哈希中并对其进行哈希 //
if($saltStart > 0 && $saltStart < strlen($saltHash)) {
$textHashStart = substr($textHash,0,$saltStart);
$textHashEnd = substr($textHash,$saltStart,strlen($saltHash));
$outHash = hash($mode, $textHashEnd.$saltHash.$textHashStart);
} elseif(
$saltStart > (strlen($saltHash)-1)) {
$outHash = hash($mode, $textHash.$saltHash);
} else {
$outHash = hash($mode, $saltHash.$textHash);
}
// 将盐放在哈希的前面 //
$output = $saltHash.$outHash;
return
$output;
}
?>
up
-3
brian_bisaillon 在 rogers 点 com
20 年前
创建 SSHA 密码的源代码...

public function HashPassword($password)
{
mt_srand((double)microtime()*1000000);
$salt = mhash_keygen_s2k(MHASH_SHA1, $password, substr(pack('h*', md5(mt_rand())), 0, 8), 4);
$hash = "{SSHA}".base64_encode(mhash(MHASH_SHA1, $password.$salt).$salt);
return $hash;
}

验证 SSHA 密码的源代码...

public function ValidatePassword($password, $hash)
{
$hash = base64_decode(substr($hash, 6));
$original_hash = substr($hash, 0, 20);
$salt = substr($hash, 20);
$new_hash = mhash(MHASH_SHA1, $password . $salt);
if (strcmp($original_hash, $new_hash) == 0)
... 如果密码有效,则执行某些操作 ...
else
echo '未授权:您提供的凭据已被拒绝授权。请使用有效的用户名和密码登录。';
... 确保清除您的会话数据 ...
}

注意:如果我没记错的话,该格式与 OpenLDAP 的 SSHA 方案兼容。
up
-1
alex at milivojevic dot org
19 年前
关于我之前的评论,如果你想安全起见,只使用 ASCII 可打印字符作为种子(对于 SSHA 种子来说应该没有影响),可以使用类似这样的代码:

<?php
$salt
= substr(base64_encode(pack("H*", sha1(mt_rand()))), 0, 4);
?>
up
-4
cneeds athome dot co dot bw
7 年前
迄今为止,我发现的最安全的发送密码方式

是首先向服务器请求并接收一个一次性代码,

然后使用该一次性代码对发送回服务器的密码进行掩码(哈希)处理。
up
-3
jcastromail at yahoo dot es
8 年前
大家好

关于 sha1 的复杂性,sha1 每生成 1.4615016373309029182036848327163e+48(2 ^ 160 位)个不同的代码。因此,使用相同哈希值的几率非常小。

sha1(和 md5)的“问题”在于生成速度。然而,速度与要加密的文本长度成正比。

但是,使用 SALT 可以将安全性提高十倍,即使对于弱密码也是如此。

粗略地说,一个 6 个字符的密码可以在一分钟内被破解(如果它存储在 md5 或 sha 中)。然而,一个 7 个字符的密码需要一个小时,一个 8 个字符的密码需要一年,而一个超过 8 个字符的密码实际上是无法破解的。

但是,如果我们使用 SALT(顺便说一句,是一个秘密的 SALT),那么即使是一个 3 个字符的密码也将非常安全。

sha1('SALT SECRET TEXT!!@@@aaa0000'.'123');

双重 sha1 将确保更高的安全性

sha1(sha1('SALT SECRET TEXT'.'123',false),false)

这将需要一个 20 个字符的彩虹表,大到即使对于一千台服务器运行一年来说也足够安全。
To Top