8

我一直在寻找一种有效的算法,可以让我准确了解密码的强度。

我发现几个不同的网站使用了几种不同的算法,因为我在不同的网站上获得了不同的密码强度等级。

4

6 回答 6

19

这已经发展成为我在 PHP/MySQL 中使用密码的最佳实践的一般头脑转储。这里提出的想法通常不是我自己的,而是我迄今为止发现的最好的。


确保您将 SSL 用于涉及用户信息的所有操作。所有涉及这些表单的页面都应该检查它们是通过 HTTPS 调用的,否则拒绝工作。

您可以通过简单地限制允许的失败登录次数来消除大多数攻击。

允许使用相对较弱的密码,但存储每个用户的登录失败次数,如果超过,则需要通过电子邮件进行验证码或密码验证。我将最大失败次数设置为 5。

需要仔细考虑向用户呈现登录失败,以免向攻击者提供信息。由于用户不存在而导致登录失败应返回与由于密码错误导致登录失败相同的消息。提供不同的消息将允许攻击者确定有效的用户登录。

还要确保在使用有效密码登录失败的次数过多以及登录次数过多和密码错误导致失败时返回完全相同的消息。提供不同的消息将允许攻击者确定有效的用户密码。相当多的用户在被迫重置密码时只会将其恢复为原来的状态。

不幸的是,限制每个 IP 地址允许的登录次数是不切实际的。一些提供商,如 AOL 和大多数公司代理他们的网络请求。施加此限制将有效地消除这些用户。


我发现在提交之前检查字典单词效率低下,因为您必须在 javascript 中向客户端发送字典,或者在每个字段更改时发送 ajax 请求。我做了一段时间,它工作正常,但不喜欢它产生的流量。

检查固有的弱密码减去字典单词是使用一些简单的 javascript 的实用客户端。

提交后,我检查字典单词和包含密码的用户名,反之亦然服务器端。非常好的字典很容易下载,并且对它们的测试很简单。这里的一个问题是,要测试字典中的单词,您需要向数据库发送一个查询,该数据库再次包含密码。我解决这个问题的方法是事先用简单的加密和末端定位的 SALT 对我的字典进行加密,然后测试加密的密码。不理想,但比纯文本更好,并且仅在您的物理机器和子网上的人的网络上。

一旦您对他们选择的密码感到满意,他们首先使用 PHP 对其进行加密,然后存储。下面的密码加密功能也不是我的主意,但解决了很多问题。在 PHP 中进行加密可以防止共享服务器上的人截获您未加密的密码。为每个用户添加不会更改的内容(我使用电子邮件,因为这是我的站点的用户名)并添加哈希(SALT 是我为每个站点更改的短常量字符串)增加了对攻击的抵抗力。因为 SALT 位于密码中,并且密码可以是任意长度,所以几乎不可能用彩虹表来攻击它。或者,这也意味着人们无法更改他们的电子邮件,并且您无法在不使每个人的密码无效的情况下更改 SALT。

编辑:我现在建议使用PhPass而不是在这里滚动您自己的功能,或者干脆完全忘记用户登录并改用OpenID

function password_crypt($email,$toHash) {
  $password = str_split($toHash,(strlen($toHash)/2)+1);
  return hash('sha256', $email.$password[0].SALT.$password[1]); 
}

我的 Jqueryish 客户端密码计。目标应该是一个 div。它的宽度将在 0 到 100 之间变化,背景颜色将根据脚本中表示的类而变化。再次大部分是从我发现的其他东西中偷来的:

$.updatePasswordMeter = function(password,username,target) {
$.updatePasswordMeter._checkRepetition = function(pLen,str) {
res = ""
for ( i=0; i<str.length ; i++ ) {
    repeated=true;
    for (j=0;j < pLen && (j+i+pLen) < str.length;j++)
        repeated=repeated && (str.charAt(j+i)==str.charAt(j+i+pLen));
    if (j<pLen) repeated=false;
    if (repeated) {
        i+=pLen-1;
        repeated=false;
    }
    else {
        res+=str.charAt(i);
    };
};
return res;
};
var score = 0;
var r_class = 'weak-password';
//password < 4
if (password.length < 4 || password.toLowerCase()==username.toLowerCase()) { 
target.width(score + '%').removeClass("weak-password okay-password good-password strong-password"
).addClass(r_class);
  return true;
} 
//password length
score += password.length * 4;
score += ( $.updatePasswordMeter._checkRepetition(1,password).length - password.length ) * 1;
score += ( $.updatePasswordMeter._checkRepetition(2,password).length - password.length ) * 1;
score += ( $.updatePasswordMeter._checkRepetition(3,password).length - password.length ) * 1;
score += ( $.updatePasswordMeter._checkRepetition(4,password).length - password.length ) * 1;
//password has 3 numbers
if (password.match(/(.*[0-9].*[0-9].*[0-9])/))  score += 5; 

//password has 2 symbols
if (password.match(/(.*[!,@,#,$,%,^,&,*,?,_,~].*[!,@,#,$,%,^,&,*,?,_,~])/)) score += 5; 

//password has Upper and Lower chars
if (password.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/))  score += 10; 

//password has number and chars
if (password.match(/([a-zA-Z])/) && password.match(/([0-9])/))  score += 15; 
//
//password has number and symbol
if (password.match(/([!,@,#,$,%,^,&,*,?,_,~])/) && password.match(/([0-9])/))  score += 15; 

//password has char and symbol
if (password.match(/([!,@,#,$,%,^,&,*,?,_,~])/) && password.match(/([a-zA-Z])/))  score += 15; 

//password is just a nubers or chars
if (password.match(/^\w+$/) || password.match(/^\d+$/) )  score -= 10; 

//verifing 0 < score < 100
score = score * 2;
if ( score < 0 )  score = 0;
if ( score > 100 )  score = 100;
if (score > 25 ) r_class = 'okay-password';
if (score > 50  ) r_class = 'good-password';
if (score > 75 ) r_class = 'strong-password';
target.width(score + '%').removeClass("weak-password okay-password good-password strong-password"
).addClass(r_class);
return true;
};
于 2009-10-23T17:31:40.713 回答
7

从根本上要防止主要类型的攻击

  • 字典攻击
  • 蛮力攻击

为了防止第一个,您要考虑包含常用单词的密码较弱。为了防止第二种情况,您希望鼓励使用合理长度的密码(通常 8 个以上字符)和相当大的字符集(包括字母、数字和特殊字符)。如果您认为小写和大写字母不同,则会大大增加字符集。但是,这会为某些用户社区带来可用性问题,因此您需要平衡该考虑因素。

一个快速的谷歌搜索出现了解决暴力攻击(复杂密码)而不是字典攻击的解决方案。此强度检查器列表中的PHP 密码强度计运行检查服务器端,因此可以将其扩展为检查字典。

编辑:

顺便说一句……您还应该限制每个用户的登录尝试次数。这将降低这两种攻击的可能性。有效但不友好的方法是在 X 次错误尝试后锁定帐户并要求重置密码。更用户友好但更多的努力是限制登录尝试之间的时间。您还可以在前几次登录尝试后要求验证码(这是 Stack Overflow 在编辑过多或非常新的用户后需要的东西)。

于 2009-10-23T17:21:18.440 回答
3

基本上,您可能想使用正则表达式来验证密码的长度和复杂性。

可以在此处找到使用 javascript 执行此操作的一个很好的示例:

http://marketingtechblog.com/programming/javascript-password-strength/

于 2009-10-23T17:25:39.650 回答
3

正如Daren Schwenke 指出的那样,您最好自己处理安全问题,而不是将其交到用户手中

但是最好向用户提供一些关于他的密码强度的提示,因为获得密码的最佳方式仍然是社交工程

所以你可以破解一个小的客户端脚本,它可以实时检查用户密码强度作为礼貌指标。它不会阻挡任何东西,但当它变成绿色时会给他一种温暖的感觉:-)

基本上你必须检查的是常识:检查密码是否包含合理数量的字母、数字和非字母字符。

你可以很容易地破解你自己的算法:只需 10 / 10 标记:

  • 0 是零长度密码;
  • 密码中每 8 个字符 +2(15 应该是安全长度);
  • +1 使用一个字母,+2 使用 2 个字母;
  • +1 使用数字,+2 使用 2 个数字;
  • +1 表示使用非字母字符,+2 表示 2。

您不需要检查神一样的密码(是否有大写字母,特殊字符的位置等),您的用户不在银行/军队/秘密服务/月度蟒蛇电影行业,是吗?

您可以在一个小时内编写代码,而无需疯狂的 javascript 技能。

无论如何,验证密码并移动服务器端的所有安全代码。如果您可以委托身份验证(例如:开放 ID),那就更好了。

于 2009-10-24T18:22:02.160 回答
0

我想不出一个特定的算法来检查密码的强度。我们所做的是我们定义了几个标准,当密码符合一个标准时,我们将其分数加 1。当密码达到阈值时,密码为强密码。否则就是弱。

如果具有不同的阈值,您可以定义许多不同的强度级别,或者您可以为特定标准定义不同的值。例如,如果密码有 5 个字符,我们加 1,但如果它有 10 个字符,则我们加 2。

这是要检查的标准列表

长度(8 到 12 可以,越多越好) 包含小写字母 包含大写字母 大写字母不是第一个。包含数字 包含符号 最后一个字符不是类似人类的符号(例如:. 或 !) 看起来不像字典单词。一些明智的密码破解包含单词和字母替代库(如 Library --> L1br@ry )

希望有所帮助。

于 2009-10-23T17:44:10.503 回答
0

不要自己动手!

密码学专家不鼓励使用自己的密码学,原因显而易见。

出于同样的原因,人们不应该尝试使用自己的解决方案来解决测量密码强度的问题。这在很大程度上是一个密码学问题。

不要为了这个目的而编写一些庞大的正则表达式。您可能无法考虑影响密码整体强度的几个因素。

这是一个难题

测量密码强度的问题本身就存在相当大的困难。我对这个主题进行的研究越多,我就越意识到这是一个“单向”问题;也就是说,无法衡量有效破解密码的“难度”(计算成本) 。相反,提供复杂性要求并衡量密码满足这些要求的能力会更有效。

当我们从逻辑上考虑这个问题时,“可破解性指数”没有多大意义,听起来很方便。驱动计算的因素太多,其中大部分与用于破解过程的计算资源有关,因此是不切实际的。

想象一下,让开膛手约翰(或类似工具)与有问题的密码对抗;破解一个像样的密码可能需要几天的时间,破解一个好的密码可能需要几个月的时间,直到太阳熄灭才能破解一个特殊的密码。这不是衡量密码强度的实用方法。

从另一个方向处理这个问题要容易得多:如果我们提供一组复杂性要求,就可以非常快速地判断密码的相对强度。显然,所提供的复杂性要求必须随着时间的推移而发展,但如果我们以这种方式处理问题,则需要考虑的变量要少得多。

可行的解决方案

Openwall 提供了一个名为passwdqc(大概代表Password Quality Checker)的独立实用程序。Openwall 开发人员 Solar Designer 似乎确实是一位真正的密码学专家(他的作品不言自明),因此有资格创作这样的工具。

对于我的特定用例,这是一个比在 Web 的某个黑暗角落使用构思不当的 JavaScript 片段更具吸引力的解决方案。

为您的特定需求建立参数是最困难的部分。实施是容易的部分。

一个实际的例子

我在 PHP 中提供了一个简单的实现来提供一个快速启动。标准免责声明适用。

此示例假设我们将整个密码列表提供给 PHP 脚本。不用说,如果您使用真实密码(例如,从密码管理器中删除的密码)执行此操作,则在密码处理方面应格外小心。简单地将未加密的密码转储写入磁盘会危及密码的安全性!

passwords.csv

"Title","Password"
"My Test Password","password123"
"Your Test Password","123456!!!"
"A strong password","NFYbCoHC5S7dngitqCD53tvQkAu3dais"

password-check.php

<?php

//A few handy examples from other users:
//http://php.net/manual/en/function.str-getcsv.php#117692

$csv = array_map('str_getcsv', file('passwords.csv'), [',']);

array_walk($csv, function(&$a) use ($csv) {
    $a = array_combine($csv[0], $a);
});

//Remove column header.

array_shift($csv);

//Define report column headers.

$results[] = [
    'Title',
    'Result',
    'Exit Code',
];

$i = 1;

foreach ($csv as $p) {
    $row['title'] = $p['Title'];

    //If the value contains a space, it's considered a passphrase.

    $isPassphrase = stristr($p['Password'], ' ') !== false ? true : false;

    $cmd = 'echo ' . escapeshellarg($p['Password']) . ' | pwqcheck -1 min=32,24,22,20,16 max=128';

    if ($isPassphrase) {
        $cmd .= ' passphrase=3';
    }
    else {
        $cmd .= ' passphrase=0';
    }

    $output = null;
    $exitCode = null;

    $stdOut = exec($cmd, $output, $exitCode);

    //Exit code 0 represents an unacceptable password (not an error).
    //Exit code 1 represents an acceptable password (it meets the criteria).

    if ($exitCode === 0 || $exitCode === 1) {
        $row['result'] = trim($stdOut);
        $row['exitCode'] = $exitCode;
    }
    else {
        $row['result'] = 'An error occurred while calling pwqcheck';
        $row['exitCode'] = null;
    }

    $results[$i] = $row;

    $i++;
}

$reportFile = 'report.csv';

$fp = @fopen($reportFile, 'w');

if ($fp !== false) {
    foreach ($results as $p) {
        fputcsv($fp, $p);
    }

    fclose($fp);
}
else {
    die($reportFile . ' could not be opened for writing (destination is not writable or file is in use)');
}

exit;

结果report.csv

Title,Result,"Exit Code"
"My Test Password","Bad passphrase (too short)",1
"Your Test Password","Bad passphrase (too short)",1
"A strong password",OK,0

包起来

我还没有在网上找到更彻底的解决方案;不用说,我欢迎任何其他建议。

显然,这种方法对于某些用例(例如,在“客户端”实现的“密码强度计”)并不理想。即便如此,使用上面概述的方法对返回通过/失败响应的服务器端资源进行 AJAX 调用是微不足道的,但这种方法应该假设存在滥用的可能性(例如,DoS 攻击)并且需要客户端和服务器之间的安全通信,以及接受与传输未散列密码相关的风险。

于 2016-01-25T21:16:18.413 回答