如何衡量密码的强度?

时间:2009-10-23 17:17:40

标签: php javascript password-protection

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

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

6 个答案:

答案 0 :(得分: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;
};

答案 1 :(得分:7)

从根本上说,你想要防止主要类型的攻击

  • 字典攻击
  • 暴力攻击

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

快速谷歌搜索提出了解决暴力攻击(复杂密码)的解决方案,但没有针对字典攻击。来自this list of strength checkers的PHP密码强度计运行检查服务器端,因此可以扩展以检查字典。

编辑:

顺便说一下......你还应该限制每个用户的登录尝试次数。这将使两种类型的攻击不太可能发生。有效但非用户友好的是在X次尝试失败后锁定帐户并要求重置密码。更加用户友好但更省力的是节省登录尝试之间的时间。在最初的几次登录尝试之后,您还可以要求CAPTCHA(在编辑太多之后,Stack Overflow需要这样做,或者对于非常新的用户)。

答案 2 :(得分:3)

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

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

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

答案 3 :(得分:2)

正如Daren Schwenke指出的那样,你最好自己处理安全问题,而不是把它放在用户手中

但是,向用户提供一些关于密码强度的提示是很好的,因为获取密码的最佳方式仍然是社交能力

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

基本上你必须检查的是共同意义:检查密码是否包含合理数量的字母,数字和非字母字符。

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

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

你不需要检查神似的密码(是否有大写字母,哪些位置是特殊的字符等),你的用户不在银行/军事/秘密服务/月份蟒蛇电影行业,是吗?

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

无论如何,验证密码并移动服务器端的所有安全代码。如果你可以委托认证(例如:开放ID),甚至更好。

答案 4 :(得分:0)

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

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

这是检查

的标准列表

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

希望有所帮助。

答案 5 :(得分:0)

不要自己动手!

Cryptography experts discourage roll-your-own cryptography原因应该是显而易见的。

出于同样的原因,不应该尝试将自己的解决方案用于测量密码强度的问题;这是一个非常严重的加密问题。

为了这个目的,不要为了创作一些大规模的正则表达而进入丑陋的事业;您可能无法考虑影响密码整体强度的几个因素。

这是一个难题

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

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

想象一下,对有问题的密码进行点John the Ripper(或类似的工具);破解一个像样的密码可能需要几天,破解一个好的密码可能需要几个月的时间,直到太阳烧毁以破解一个特殊的密码。这不是衡量密码强度的实用方法。

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

可行的解决方案

Openwall提供了一个名为passwdqc的独立实用程序(可能代表密码质量检查器)。 Openwall开发商,Solar Designer,似乎确实是一位真正的加密专家(他的作品不言自明),因此有资格创作这样的工具。

对于我的特定用例,这是一个更有吸引力的解决方案,而不是使用生活在网络黑暗角落的错误构思的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攻击)并且需要客户端和服务器之间的安全通信,以及接收与传输未散列密码相关的风险。