密码的意义在于,给我们一种方法区分不同的人:知道某些信息的,和不知道的。因此密码的信息量越大,区分两者的能力越好。
理论上说,对于不知道密码结构的攻击者,最多需要尝试多少次才能解开密码,其次数对二的对数即是密码的信息量。信息量越大的密码,在穷举攻击上难度越高。一个令人望而生畏的密码反而不会被穷举。
信息量是相对的,authentication这个单词有14个字母,看似强度很大(65-70bits)。但是一般攻击者都会试图用常用单词和变形表进行攻击,因此实际信息量的估计不超过英语总单词量(14bits以下)。更精确的说,密码的强度应当是攻击者选用的所有方法下的信息熵。从普遍角度来说,当然密码本身的信息越随机,强度越接近理论值,记忆越困难。
多少位密码是安全的这个问题其实很难说,取决于两个因素。
一般来说,损失越大,能够接受的破解概率越低。例如你也许不会介意自己的订票密码被万分之一的概率破解,但是却未必能容忍自己的银行卡被万分之一的概率破解。
重试速度则取决于系统的性能,机制等。网络系统往往可以忍受1req/s的重试很长时间,一年的攻击量就是25bit左右。但是如果使用了抗重试算法则会降低到1req/min以下。
本地密码则更多的取决于性能。使用强力计算机并行计算往往可以提供1+Mreq/s的尝试,因此本地文件的密码一定要比网络密码强这是常识。
安全的密码至少要满足,你的密码强度比上单位时间的攻击量,大于你能够忍受的破解概率。
例如,时间长达一年,每秒攻击一次,一年可以实施25bits左右次的攻击。对于40bits的密码,破解概率就是1/30k(15bits=30k)。
网络上使用的密码,强度从低到高可以分为20/30/40/50/60/70几个档。
如果要对抗政府机关,请选择128bits密钥。
个人推荐,密码的选择范围为0-9a-z,即小写字母和数字。排除掉o和l。选择范围为34个,信息量5bits。
排除o和l是因为,某些字体下这两个会像0和1。当然你也可以排除掉0和1。
符号在某些键盘上会比较难输入,大写字符亦然。尤其在手机上,每输入一个大写都要额外的按Shift,因此相当于多了一次按键。而且在记忆上又需要加上“xx大写X”的前缀,记忆不便。
汉字的选择是8000以上,所以可以提供13bits的强度。这样的密码,只要三个字就基本可以满足强密码。
听起来挺好,问题是汉字的输入长度,至少是2-3键一字。三个字差不多6-9键,和8个字符没什么区别。如果使用常用词组,还会降低安全性。
如果系统支持的话,对于中国人来说,使用汉字密码确实很方便,容易记忆。但是设计系统的时候最好不要扯到这种东西,容易出错而且不容易调试。
用户的密码有n种可能性,攻击者每次尝试的破解概率是多少呢?m次尝试的破解概率是多少呢?
如果你的答案是m/n,考虑一下,你每实验个几次用户就改一次密码会如何?
如果用户改密码足够频繁,答案应该是1-((n-1)/n)^m。直观点带入个数据看看,n=2,m=2。原本是100%破解出,现在只有75%。
如果n=1000000。当m=1000000时,实际破解概率只有六成出头。
上面的数学问题说明几点。
9! = 362880 = 18.5bits。其实会略高一些,因为还有P(8,9), P(7,9)等。但是大多数时候不会使用一些特别的绕法。所以大约是16-20bits,5-6位数字的强度。
所谓抗重试算法,就是能在不干扰正常登录的情况下,降低重试者的频率。
最简单的算法是5次失败后锁定五分钟,这时的重试频率就降低到1req/min了。另一种方法是失败一次后double登录时间间隔。例如第一次失败后3秒内不能登录,第二次失败后10秒,类推。
抗重试算法的核心值在于失败次数和锁定时间。过长的锁定时间会使得攻击者可以人肉尝试你的登录,把正常用户锁在系统外面。过低的重试次数又会导致客户不慎输错后被锁定。而短的锁定时间和很高的重试次数则会抵消抗重试的效用。
另一些方案则采用验证码来对抗软件自动重试。
首先应当注意,如果打开验证码不一定需要跟着打开失败锁定,但是如果打开失败锁定就必须打开验证码。否则有人会以高频进行重试,导致用户自己无法登录。
其次,注意验证的次序,首先检测验证码,然后检查是否锁定,最后检查是否登录成功。如果失败,返回原因一概是登录失败。从安全角度来说,最好不要说明问题的细节(例如是否存在用户)。否则容易造成信息泄漏。例如使用最简单密码尝试多个帐号,从返回中发现是帐号是否存在(登录失败或者不存在本用户)。或者先检测登录是否成功后检测验证码,登录失败时返回为“密码错误”而非“验证码错误”,导致虽然无法登录,但是可以继续暴力破解密码。
不要耍小聪明,用IP来限制重试。攻击者可能利用上万IP来把你的限制绕过去。要假定攻击者可以做到一些还算正常的异常事件。
不要把计数放到session中,攻击者可能通过放弃session的方式绕过。
另外,抗重试算法必须贯彻到系统的每个密码框上。改密的时候需要重新输入密码是常识吧。那里是否有抗重试?短信令牌是否做了抗重试?
如果你的网站需要被某些残障人士使用,或者政府规定你的网站必须能够被这些人所用。那么单纯的图片验证码就会产生麻烦。
即使抛开上述不论,验证码也是个很麻烦的问题。太强的验证码会让用户失去耐性,太简单的则会让OCR绕过你的防御。
推荐的方法之一是使用reCAPTCHA。
警告是抗重试算法的升级。简单来说,除了维护一个锁定解除后失败计数外,还维护一个连续失败次数的计数。如果这个计数大于一定值,就给客户发邮件报警,强制客户设定更强级别的密码。
警告和调整可以有效的降低密码位数。例如我们能够忍受万分之一的破解概率,连续失败计数是1000次。那么简单来说,25bits的密码就足够保护系统的安全。因为攻击者试验了1000次后,密码强度就会提高到一个他无法接受的值,所以总破解概率只有1000/(2^25),约1/33000,正好在万分之一以内。
当然,实践中并不推荐这么低安全的密码。但是正确的警告可以大幅增强用户密码的安全性是毋庸置疑的。警告和调整的假定是,不是每个帐号都会被暴力攻击的。所以只需要提高正在被暴力攻击的账户的安全级别,而保持普通帐号的安全性恰好在忍受范围以内即可。
另外注意两个细节。
有些设计的不正确的警告和调整算法容易被一种叫做“静默攻击”的方式攻击。具体来说,就是一定时间内将失败次数重置。这导致警告始终发不出去。
我们以银行卡密码为例,分析一下安全性。当然我假定银行卡磁条是会被漏出去的。
银行卡密码一般是6位,必须是数字,共计可以提供20bits的安全。一般一天可以失败五次(或者三次,但是下面的计算过程不变)。作为攻击者,你可以失败四次而不触发警报。一年就可以重试1460次,破解概率为1/685。考虑到一般密码是生日的变形,概率会更高一些。如果你的密码恰好是生日的变形,那么破解概率会升高到1/45。
这是一个不可接受的值。
而且最关键在于——银行系统不能提高密码强度。即使收到攻击警告也只能修改密码,或者干脆冻结户头。这样每次的破解概率始终固定,可以通过大量尝试来提升总体破解概率。即使不断修改密码,也只能让连续百万次攻击的成功率降低到63%。换言之,如果攻击者手里有七百张银行卡,每张卡每天试验4次密码,一年内有六成的概率猜到一个人的密码。考虑到很多人使用了生日作为密码,实际上风险会远远高出这个值。
正确设计的计数是不重置,或者以很长间隔重置的。一般来说,正常用户每年的错误概率是固定的。常用的密码不容易出错,不常用的密码很少输入。因此一个用户一年的输入错误数大约也就是几百,加上一些路过攻击,几千次出错一年是一个正常的值。
另外,这个计数不被密码重设所重置。
密码系统本身有几个缺陷。
自动化密码重设机制是上面第三个缺陷的解决方案。这种方案利用一个可以收取信息的工具来收取验证信息,从而证明你就是自己。
注意,这里必须保证“只有用户本人能够接收到用户本人的信息”。如果你的手机已经越狱,那么SMS并不总是可靠。而且大部分邮箱都使用明文发送报文内容,如果你的邮件服务商被人监听或者邮件服务商本身不可信的话,那么账户被盗是必然的。
理论上说,地址也是可以的,只要能够保证只有你自己能够接受到自己的信件,而且还能够接受缓慢的邮递时间。
这里要特别注意几点。
早些年,自动化密码重设机制往往都伴有一个“安全问题”,只有答对了安全问题才能寻回密码。
这个的设计原因是因为,如果没有“安全问题”,只要始终去识别验证码就可以持续的发寻回密码邮件,会对系统造成干扰。而且在某些时候,邮箱会被短暂的破解。如果没有安全问题,帐号就会很快丢失。
但是安全问题的设计其实很扯淡——大部分时候,如果我能记得安全问题,我就能记得密码。实际上在大部分系统上的问题都是——压根不记得我的安全问题里面回答了什么。
而且现在随着SNS的流行,找到安全问题的答案越来越容易了——母亲的姓名和生日?小学老师的姓名?这些都不难被找到或者通过简单的社会工程学弄到。如果选择使用安全问题的话,一定不要选择填写一些公开或半公开信息,例如上面的亲属生日等等。因为很容易被找到。
当然,由于密码重置攻击的结果只是造成系统讨厌的垃圾邮件而已,所以使用这种攻击的人并不是特别多。至于“邮箱属于用户本人”基本已经成为了共识。大部分用户可以认同“邮箱被盗就等于所有账户被盗”。因此个人意见,安全问题不做也行。
同样是对暂时破解邮箱(或者手机——这更频繁)的解决,慢重置利用减缓重置速度的方法来抵抗攻击者。这个思路不仅适用于自动重置,而且适用于人工重置过程。
想象你被劫持了,绑匪弄不到你的银行卡密码,但是他们拥有你的身份证和银行卡。他们是否可以通过身份证代办的方式来重置你的密码(假定密码重置不需要本人亲自到场——很多时候本人亲自到场是有困难的)呢?
银行对抗这一问题的方法是,首先必须挂失。在挂失一周后才能重置密码。当然,在大部分业务里面一周太长了。但是长达小时级别的通告-重置周期是值得考虑的。几个小时的时间足够真正的用户看到重置动作通知,并做出合适的反应。立刻进行重置的话,用户不一定总是能看到。甚至在看到并产生反应的时间内,攻击者就造成了损失。
慢重置的问题在于对于重置的响应很慢,这会使得正常用户很不愉快。所以建议只有真正需要高安全性的系统才使用这种方案。
社会关系验证出名的有facebook和qq两家,其中qq的实现还是有问题的。
所谓社会关系验证,实际上就是从你的好友中挑选N个人,如果你能够证明其中M个确实是你的好友,你就可以通过验证。
证明的方法很多,例如你通过别的方法通知对方,让他们知道那个验证请求确实是你。facebook采用的是一堆图片里面找到哪个人是对方,有的时候碰到一堆乱标的图片会很火大。
注意,这里挑选N个人,必须是系统随机选择,而不能是客户自己来选择。否则就会产生M个好友一加就能抢掉你的帐号的问题。
自动化密码重设系统实际上等同于密码验证,因此如果自动化密码重设系统存在缺陷,密码系统再强也没用。所以自动化密码重设系统的强度必须大于或者等于密码验证。而实现上说,大部分系统的强度是远不及密码系统的。
从设计者角度来说,最好的办法就是不做自动化密码重置。以下是几种替代流程,当然,他们各有缺陷。
从用户来说一定要小心。随着你的手机和邮箱绑定了越来越多的寻回,一旦邮箱和手机丢失是一件非常恐怖的事情(尤其是手机,因为很多邮箱都绑定到了手机)。所以如果绑定了大量寻回,邮箱必须强密码,手机必须打开加密(丢失后整个机器无法读取),而且千万别越狱。
增强验证是试图解决密码的缺陷一,使得在非安全环境中密码即使泄漏也不会造成帐号被盗。大部分增强验证都是基于TOTP(time-based one time password)的。
首先,服务器和某个安全的设备共享一个密钥,并且保持时间同步。然后在登录时,安全的设备显示密钥加密过的当前时间,只要和服务器上的密钥加密过的当前时间在一定差以内,就算通过。
问题在于安全设备。目前gauth使用手机,这其实未必安全。真正的安全设备必须是特别设计实现的硬件,例如工行的电子令牌。
google authentication就不说了,目前受到广泛应用。
国内做OTP有两家比较大的公司,坚石诚信和飞天诚信。
yubico公司出品的yubikey是个很有趣的产品。这个产品一个U盘模样的硬件,把自己伪装成一块键盘。当用户需要输入OTP时,只需要按一下按钮,yubikey就会自动模拟按出对应的键。因此这个产品可以免去用户手工输入的繁琐,而且支持几乎所有操作系统。
有些时候,我们可以利用自动化密码重设的机制来增强验证。例如登录时需要输入手机短信令牌等等。
实际上这种设计并无必要。因为自动化密码重设机制等效于密码本身,利用密码来增强密码是没有道理的。以手机短信令牌为例,我们可以想想。如果丢失手机,那么得到手机的人可以重置密码来完成登录。如果手机没有丢失(即手机始终是安全的),那么可以只用手机而不需要密码,强度并没有降低。正常情况下我们不这么做的理由是因为收SMS看手机会比较麻烦。可既然每次登录都必须输入验证码,为什么还需要密码呢?
一个比较可行的方案是,利用一种渠道来重置密码,而利用另一个来增强验证。那么这两个通讯渠道必须都保证安全。
客户证书可以为我们提供一个强验证机制,并且可以保证通讯过程中的安全。不过遗憾的是,大部分客户证书的实施开销都很高。
软件证书相对容易使用,但是很容易被从计算机中窃取。硬件证书可以规避窃取问题(如同TOTP一样),但是成本很高。而且大部分硬件证书都有兼容性问题,无法用于windows以外的系统,甚至IE以外的浏览器。
客户证书最麻烦的问题,在于大量客户证书的发行和管理很麻烦,而且通讯过程中的密码学计算需要大量资源。
独立硬件的增强认证系统的麻烦在于,需要随身携带令牌防丢失。而且为了防止丢失——这几乎是必然的——所以需要一些额外的验证码作为丢失时的报废手段。而这些密码一旦丢失,其结果和令牌泄漏是一样的。不过幸好,大多数情况下都不需要输入紧急密码,更不会在非安全环境输入。
而且TOTP必须每次都看一下手机或者令牌(yubikey例外),这对需要频繁输入的密码是很不利的。
即使不对每个网站使用不同密码,至少也要将网站分组,使用不同密码。绝对不能对所有网站使用同一密码。
上文可以看到,不同密级的密码长度是不固定的。建议普通网站8个字符,高安全性网站10个字符,最高安全性网站12个字符。(每个字符5bits计)
对不提供防重试,警告功能的网站提升密码强度,至少要提升10个bits的安全性。
定期修改密码的主要目标是抵抗偶然的密码丢失,例如非安全环境,肩窥,或者偶然被人猜到。
周期取决于偶然事件发生的概率。即使没有什么你可以察觉的偶然事件,三年修改一次密码也是个良好的习惯。
虽然听起来很愚蠢,但是大部分brute force程序都会从11xxxx或者00xxxx,aaxxxx开始枚举,而不是按照数字或者字母频度表排序。因此在密码的前面使用01a其实略微增加了被枚举的概率。
以下是一些著名的使用了gauth的网站。
以下是一些自己实现了OTP的例子:
这个逻辑在“利用自动化密码重设的机制来增强验证”里面出现过。不需要用同等安全性的方式来加强安全性。
例如手机宝令和短信令牌。在手机安全的情况下,两者都是安全的。当手机不安全了(被植入程序或者丢失),两者都不安全。因此同时启用两者并不增强安全性,只增加麻烦。
实际上只要引入其中比较好用的一个就行了。相比宝令,短信受环境影响更大,因此建议关闭短信令牌,只启用宝令。
主要说的是主邮箱和手机。用于安全性加强的联系方式往往会被用户配置为互相加强的情况,这是非常危险的。安全中最危险的模型就是环状模型,一旦一个点漏了整个环都会崩溃。
例如两个邮箱,A邮箱可以被B重置,B邮箱可以被A重置,又没有其他的保险措施。于是当其中任意一个发生泄漏的时候,所有的帐号就像被接到了一起的鞭炮一样,一个也跑不了。
通常而言,建议的方式是星型汇聚——多个安全的联系方式用同一个基础作为寻回。一般来说,就是多个邮箱利用同一个手机做寻回。或者多个安全方式之间不做寻回加强——因为反而不安全。
对于星型汇聚,当这个点出问题时所有帐号都会面临风险这点不变。但是当某个邮箱出问题时,其他邮箱并不面临风险,同时(可能)可以通过手机寻回密码。
对于不做寻回加强,万一邮箱被盗,相关帐号就无法恢复了。但是至少这风险不会蔓延到其他邮箱的帐号上。
——致命性标靶的面积总是越小越好。