真实、可量化的密码强度以及如何衡量密码强度

服务用户账户因为不能被锁定,所以成为暴力密码破解攻击的最好目标。理想情况下,所有的账户都应该使用强密码,但服务账户(或者其他不被锁定的账户)还是应该特别注意。

有些读者可能已经在笔者的网站上看过那篇关于密码的论文。在那篇文章里,提出了一种与简单复杂度规则不同的密码评价方法。这篇文章收到很多读者的积极反馈,本人觉得它是对本章内容的很好补充。笔者也会在本书附上相关的源代码,如果想自己编写密码强度检查工具,可以查考这些源代码。接下来将介绍这种密码评价方法,以及这种方法是如何工作的。

管理员总是告诉用户:密钥必须足够复杂(用户的感觉就是麻烦),但是他们没有告诉我们如何才能设置出好的或强壮的密码。笔者见过的一些密码强度计量工具都是尽最大努力给出评级,糟糕的、好的、更好的或者其他一些模棱两可的评语,都没有对密码强度给出定量结果,只是基于复杂度给出假设——越复杂的密码越好。虽然这是事实,但是我们仍然得不到任何信息用来判断密码应该多复杂才算好,或者为什么需要这样做。

正因为如此,我想做一些不同的事情,所以决定增加可衡量的、并且有价值的属性,通过这一属性可以计算出破解密码的密钥空间(所有可能用来组成密码的字符个数)需要多长时间,而密码是基于使用的基本字符组成的。密码强度关心的就是如何使密码更难或不可能被破解,越长和越复杂的密码,破解需要的时间就越长。就像安全组合锁,使用的锁越多就越安全。

在笔者看来,要真正选择好的密码,就应该总是假设对攻击者最有利的情况和对你最不利的情况。当攻击者暴力破解密码的时候,所用的方法基本上就是枚举所有可能的字符组合:a–z、A–Z、0–9,以及键盘上第一排的那些字符——!、@、#、$、%、^、*、(、)、-、=、_、+,还有其他能打印的可见字符,比如_、{、>,等等。当然,攻击者使用的方法并不总是类似这样教科书一般的方法,但这是他们考虑问题的典型思路。如果输入abcd作为密码,4个小写字母,攻击者不知道密码的结构,所以必须假设密码可以是任何情况,这意味着必须选择使用哪种方法攻击密码(方法有很多),以及选择哪些字符进行密码组合枚举。如果攻击者已经知道密码的组成结构,密码就更容易破解。攻击者了解的事情越多,对他们减少密钥空间就越有利。

一种新方法

所有攻击者要做的事情就是枚举所有可能的字符组合,直到猜到密码为止。也就是说,从字符a开始循环,每循环一次,增加一个字符,也许不该多说这一句,因为这看起来简单得微不足道。无论如何,在整个密钥空间中猜解由4个小写英文字母组成的密码需要迭代475 254次,在互联网上可以看到,许多暴力破解程序或白皮书都介绍说,对由4个小写英文字母组成的密码进行猜解需要迭代456976(也就是264次方)次,但这是错误的。只有从一开始就认定密码必须是4个字母,需要的猜解次数才是264次方。也就是说,枚举密码是从aaaa开始,而不是从a开始。实际上,在得到aaaa之前必须先经过aaaaaa,所以正确的计算方法是使用26^4+26^3+26^2+26^1作为计算公式。475254456976这两个数字看起来差不多,但是对于密码是10个字母的情况,这两个数字的差别就是56亿。所以从一开始,系统使用的公式就是错误的,笔者希望计算从a开始的完整的暴力破解所需要的密码猜测次数,攻击者通常也是这样做的。

为了确定密码枚举使用的基础字符,需要观察密码并确定密码使用的最小基础密钥空间。如果使用abcd作为密码,只需要使用a-z作为基础字符。也就是说,只需要使用从a到z的小写英文字母尝试破解密码。如果使用abcd1作为密码,就需要使用a-z的字母和0到9的数字(基础字符增加到36个)。如果使用abcD作为密码,就需要使用az的小写英文字母和AZ的大写英文字母,基础字符是52个。如果使用abcD1,那么基础字符就是62个,增加!或&字符可以使基础字符增加到76个。如果还使用了[或~字符,那么最终的基础字符就是95个。

显然现在不得不做一些假设,除此之外也没有其他方法。得到攻击者必须使用的基础字符很有意义,再结合得到的输入密码的结构信息,将使之更有意义。但因为现在做的事情是衡量密码的强度,不希望得到有利的任何东西,所以假设所有情况都对破解者有利。假设破解者不得不做一些假设,比如他知道一些本人知道的东西(听起来有点古怪)。为此,将基础字符分成几个组,分别是a-zA-Z09!=,剩下的其他字符形成一个组。如果输入A,那么不得不假设你的密码可能也使用了小写字母。由于考虑了小写字母的情况,因此单独的A也增加到52种可能性。如果使用a1,那么09的数字也要加进来,基础字符就是36(26+10)个。如果是A1,那么基础字符是62(52+10)个。单个!也有76种可能性,所以A!的基础字符就是76个。单个[95种可能性,所以A[的基础字符就是96个。最后,如果使用Ab44!作为密码,那么整个密钥空间就是76^5+76^4+76^3+76^2+76^1,总共2 569 332 380种密码组合。作为参考,通常将使用的基础字符分成以下几组,每个组都有它们对应的密钥空间:

Base 10: 0123456789
Base 26: abcdefghijklmnopqrstuvwxyz
Base 36: abcdefghijklmnopqrstuvwxyz 0123456789
Base 52: abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUV
–– WXYZ
Base 62: abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUV
–– WXYZ 0123456789
Base 76: abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUV
–– WXYZ 0123456789 !@#$%^&*()-=_+
Base 95: abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUV
–– WXYZ 0123456789 !@#$%^&*()-=_+ []\"{}j;':,./<>?'_

以上分组只是组织字符集合的方式,分组的根据是笔者见过的不同攻击者的实现方法,以及人们使用特殊字符的习惯。人们总是趋向于将键盘上最上面一排的特殊字符连在一起使用,就像T$i*D3这样,作为对比的T$i]D3就很少用。黑客手册通常关注以ASCII顺序枚举密码,从不提针对特殊字符的顺序检查,但此处以普通人创建密码的方式考虑如何分组基础字符,尽管是由计算机进行破解。没有绝对正确的方法,也不存在某种黑客一定会照着做的规则。但是当考虑如何应用密码策略时,就需要知道这些事情。对此必须重点关注,笔者没有忘记A-Z和0-9的组合,只是没有发现所有大写字母和数字的价值,因为一般很少看到此类密码(译者注:全部由大写字母和数字组成的密码)。笔者看到的密码一般都是小写字母和数字,这是因为默认情况下很多人都使用这样的密码。这并不是说密码不可能是大写字母和数字的组合,这里选择的是包含了62个基础字符的集合,大写字母和数字组合的情况与小写字母和数字组合的情况在数学处理上一样。所以,只有当真的确定密钥空间就是大写字母加数字时,才可以选择对应的基础字符集合。

现在可以看看图1显示的密码强度结果(这个应用程序是本人编写的,包含在本书的下载资料中)。

图1  裁剪过的密码强度计算程序的运行截图

根据选择的基础字符集合的信息,决定使用最高级别的工业破解标准(Class F),也就是每秒钟进行100万次破解尝试。这是很大的数字,再次强调一下,总是选择最糟糕的情况,这样才能知道密码到底多强壮。以外还会计算对整个密钥空间进行密码测试所需要的时间,并显示这个时间。在前面的例子中,使用过Ab44!作为密码,为破解这个密码,对整个密钥空间进行的测试耗时为2.56933238秒!这个结果提醒我们,尽管这个密码被认为是相对复杂的密码,有大写字母、小写字母、数字和特殊字符,但是2.5秒真的不算多。当然,大多数破解者的业余设备每秒只能发起几百上千次尝试,但是必须确定,这只是此类分析方法展示出来的力量的冰山一角。

不要再告诉用户使用强壮的密码(不管这意味着什么)了,相反,要求他们的密码必须能够经受起一定时间长度(由你决定)的Class F攻击。这种方法使我们有了衡量的基础。再看看图1,使用aaaaaaNotGood作为密码,它由13个大小写混合的字符组成。使用Class F强度的攻击破解这个密码的密钥空间,需要耗时650 000年,看起来很强壮,对吧?

好吧,这个想法让人开始思考,从破解者的角度看,这个问题限制了所能看到的事情。现在,650 000年对大家来说是最好的情况(对破解者来说是最糟糕的情况),因为这代表了对整个密钥空间的枚举。所以这时候,笔者决定编写算法来计算破解实际密码需要的枚举次数,而不是整个密钥空间。笔者编写的算法能正常工作,但速度不是特别快,现在使用Will Fischer想出来的公式,这个公式比较好,在这个问题上,Will给了我很大的帮助,再次感谢。

因为我们已经知道密码,基本上逐列计算每一列从a开始枚举到这个字母所需要的迭代次数,计算的基础是定义在内存里的带索引字符串,索引字符串从a开始。使用字符串可以很容易确定当前是什么字符,对于密码中的每个字符,一列一列地处理。最后按列计算出总的枚举次数,再与Class F进行对比,计算出时间。这样的话,对整个密钥空间枚举需要650 000年,对aaaaaaNotGood枚举就减少到12 637.66年。减少这么多时间的原因是直接从6个小写字母a开始枚举。如果将密码的第一个字母换成H,需要计算的时间就从12 637.66年(398 541 262 291 912 000 000次枚举组合)变成421 660.40年(13 297 482 476 338 200 000 000次枚举组合)。这就是从a变成H的巨大差异,对整个密钥空间的枚举也增加到20 724 145 598 800 400 000 000次,使用Class F的破解需要657 158.35年时间。

从实际应用的角度看,应该能够选择自己的破解级别(听起来就像选学校),如果选择从最糟糕的情况构建密码强度,选择每秒钟10亿次枚举,那就真的不用担心那些来自慢平台的攻击。当然,可以看到因选择自己的破解级别而带来的价值。

使用这个例子是为了让你能够看到来自真实世界的攻击是什么样子,毕竟美国国家安全局不会试图获取每个人的数据。当没有选择密码强度,并且还想知道通常每天要面对多少自攻击者的风险时,以上示例还是有价值的。举个例子,可以看看图2中显示的来自Cisco产品的截图。

图2  开发人员提供的用户界面可以显式地限制密码的长度和复杂度

在上述产品中,必须创建管理用户,但是注意提示信息:“你的密码必须小于8个字符,并且不能包含空格和特殊字符。”在这种情况下,用户被迫使用蹩脚的密码,即便选择对破解者最不利的情况,也就是每秒10 000(打印机都有可能使用空闲时间做到这一点)Class A攻击,最多需要61个小时就可以破解62个基础字符(a-z、A-Z0-9)的密钥空间。对于类似AaZzaa99这样的密码,只需26个小时即可破解。当考虑这种情况时,使用用户定义的破解级别就更显得意义重大。

这也给了你深入了解安全明确的其他限制和默认密码的机会。举个例子,我们曾经对邮件列表的成员关系提醒做过快速搜索,这个查询将提供对存档电子邮件的各种链接,邮件列表的管理员没有考虑他们的提醒程序如何通知用户,包含有完整用户名和密码的信息也被存放到这个列表中并且通过你最喜欢的搜索引擎建立索引。因为这些都是明文,所以也不需要破解。可以看到大多数都是简单的由8个小写字母组成的随机密码。如果对整个密钥空间进行暴力破解,即使采用最慢的破解级别,也只需要217秒,大约是听完AC/DC乐队的“Have a Drink on Me”所需要的时间。

字典攻击和其他攻击方法

精英类型的黑客首先要说的是,“如果从字母Z开始,并且倒退着来呢?”好吧,如果这是你认为合理,并且关注的事情,那就倒着枚举。就算按照字母位置倒着枚举字符,甚至可以一切都从M开始,这个公式依然能以相同的方式工作。当然,攻击者也可能决定从中间字母开始,他们抱着万分之一的希望,期望你也是这么做的,还有许多诸如此类的方法,总之,这是导致收益递减的参数。

笔者认为,同样的道理也适用于字典攻击,我和Will把这称为“香蕉狗综合征”。可以使用包含10000个单词的字典攻击那些对安全一无所知的人,他们通常使用bananadog等单词作为密码。假设把这些词两两组合在一起(产生含有1亿个单词的词典),即便这些人聪明地使用bananadog作为密码,攻击者也只需要毫秒级的时间就可以破解。暴力破解bananadog的密钥空间需要大约94.11分钟,在这段时间里,攻击者可以枚举大约5 646 683 826 134个最多由9个小写字母组成的密码。对于任何人,bananadog攻击总是被限制在总数为1亿个的可能密码中,并且会错过类似bannaadig这样的密码。攻击者想要得到密码,他们不达目的誓不罢休。正因为这样,笔者并不担心以bananadog作为密码的人,如果你是这种类型的人,我怀疑你根本不可能读本书。

Will提出一种很好的观点,就是如果作为攻击者只需要花费几毫秒的时间就可以从字典攻击中获得那个未知的密码,有什么理由不这么做吗?笔者完全赞同这种观点,作为这个逻辑的延伸,需要在密码强度检查工具中增加一些合理的检查,去除类似bananadog这样的“杂草”。如果准备这么做,就必须先做几个不同级别的假设,这意味着实际应用的字典攻击的逻辑是非常主观的,但是当前的实现方法围绕着暴力破解的顺序(关于从a到z、从A到Z、从0到9,等等)所做的假设是公平的。需要将这些词连接起来,给类似BananaDog这样的密码评分吗?需要做三次连接,给类似DogBananaDog这样的密码评分吗?如果是BananaDog1,又怎么处理?或是B4n4n4D0g呢?笔者发现字典攻击的逻辑太随机了。理论上做这个检查是可以的,但是并不知道如何以工具的形式实现;所以把这个问题放在了次要位置上,任何人如果有好的建议,请直接联系thor@hammerofgod.com,我都会回复的。

笔者认为这种方法比其他衡量密码强度的方法更有价值,图3给出了一个未公开的测试网站作为演示示例。

 

图3  密码强度计量器显示由20z组成的密码得分是0,非常脆弱

这个系统根据一系列满足最低要求的规则对强度进行评级,显然20z是非常脆弱的密码。但是如果计算一下,就会发现要破解这个密钥空间需要20 725 274 851 017 800 000 000 000 000次枚举,需要657 194 154 332.12年的时间。在图4中,给出了另一个不同的密码。

 

图4  密码强度计量器显示:由有大写字母、小写字母和数字组成的5字符密码获得了50%

的评分,并被评价为Good

重申一下,这个假设就是因为Aa!1_20z更复杂,是个强壮的密码,但是用数学来衡量,只需要2 569 332 380次枚举就可以破解这个密码的密钥空间,需要的时间是2.56933238秒。这就是为什么一直提醒大家使用密语而不是密码,因为可以很容易地记住这个密码。但是每次输入Aa!1_的时候,不得不反复检查,确认没有输错。所以现在有了量化的标准来衡量密码的复杂性,问题是密码可以安全多少年?

查看代码

下面就是密码检查工具的C#源代码: 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace PasswordChecker
{
public partial class frmPassword : Form
{

// Assumptions:
// lower is only lower
// upper is only upper
// numbers are only numbers
// Bangs assumes upper and lower and numbers
// Spec assumes upper lower numbers and Bang

public static int iLength;
public static double uCombinations;
public static double uPerSecond;
public static string sPassword;
public static int iBase = 0;
public static int iaz = 0;
public static int iAZ = 0;
public static int iNum = 0;
public static int iBang = 0;
public static int iSpec = 0;
public static string brute = "abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"0123456789!@#$%^&*()-=+" +
" []\""+"{}j;': " +",./<>?'  ”;

public static double dSeconds;
public static double dMinutes;
public static double dHours;
public static double dDays;
public static double dYears;

public frmPassword()
{
InitializeComponent();
uPerSecond = 1000000000;
uCombinations = 0;
txtPerSecond.Text = "1,000,000,000";
txtPassword.Focus();
}

private void frmPassword_Load(object sender, EventArgs e)
{
}

private void txtPassword_TextChanged(object sender,EventArgs e)
{
GetBase();
}

public void GetBase()
{
sPassword = txtPassword.Text;
iLength = txtPassword.TextLength;
iaz = 0; iAZ = 0; iNum = 0; iBang = 0; iSpec = 0;
dSeconds = 0; dHours = 0; dDays = 0; dYears = 0;
uCombinations = 0;

MatchCollection az = Regex.Matches(sPassword, @"[a-z]");
MatchCollection AZ = Regex.Matches(sPassword, @"[A-Z]");
MatchCollection Num = Regex.Matches(sPassword, @"[0-9]");
MatchCollection Bang = Regex.Matches(sPassword, @"[!@#$%
–– ^&*\(\)\-_=+]");
MatchCollection Spec = Regex.Matches(sPassword, @"[\[\]
–– \{\}\;\:\'\,\.\<\>\/\j\\\'\ \?\ ]\""");

if (az.Count > 0) {iaz = 26;}
if (AZ.Count > 0) { iAZ = 26; }
if (Num.Count > 0) { iNum = 10; }

if (Bang.Count > 0)
{
iBang = (26 + 26 + 10 + 14);


iaz = 0;
iAZ = 0;
iNum = 0;
}
if (Spec.Count > 0)
{
iSpec = (26 + 26 + 10 + 14 + 20);
iBang = 0;
iaz = 0;
iAZ = 0;
iNum = 0;
}

iBase = iaz + iAZ + iNum + iBang + iSpec;
txtBase.Text = "Dervies " + Convert.ToString(iBase) + "
–– of 96";
txtLength.Text = Convert.ToString(sPassword.Length);

for (int i = 1; i <= sPassword.Length; i++)
{
uCombinations = uCombinations + System.Math.Pow(iBase, i);
}

txtCombinations.Text = Convert.ToString(uCombinations);

dSeconds = uCombinations / uPerSecond;
dMinutes = dSeconds / 60;
dHours = dSeconds / 60;
dDays = dHours / 24;
dYears = dDays / 365;

txtMinutes.Text = string.Format("{0:n}", dMinutes);
txtHours.Text = string.Format("{0:n}", dHours);
txtDays.Text = string.Format("{0:n}", dDays);
txtYears.Text = string.Format("{0:n}", dYears);
}
}
}


 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值