认证系统包含两个方面:密码存储方案和认证协议,密码如何存储会限制可供选择的认证协议。
实际工程实现中,密码如何存储经历很多变迁,可惜的是业界并不总是能吸取教训的,2002年发布的MacOSX10.2使用的密码加密算法跟1979年Unix第七版的算法是一样的,而且没有采用类似/etc/shadow的机制,这个机制是UNIX在1987年引入的,到了MacOSX10.3的时候,Apple更改了算法,引入了shadow机制,但却没有使用salt。大公司尚且如此马虎,何况互联网界多如牛毛的中小公司了,时至今日,肯定有一小撮公司使用明文存储密码,一大撮公司用通用摘要算法或者不用salt,肯定有好大一批公司把用户详细信息跟密码存储在一起,webserver运行的账户www或者nobody能读取所有密码。
存储明文密码对认证协议的限制是最宽泛的,但此种方案显然是无法满足上面说的核心需求。
早期UNIX限制密码长度,采用加密算法如DES对原始密码加密保存,现在的认证系统都使用摘要算法,对密码以及一个随机串(称为salt)应用一个摘要算法,比如MD5,SHA1,BCrypt,然后保存摘要值以及salt。这种方式是最广泛采用的,但在选择摘要算法时要避免通用摘要算法,因为他们的设计考虑了要快速运算,不利于提高暴力破解的难度,应该选用特别针对密码设计的摘要算法,这类算法被称为keyderivationfunction(KDF),其原理都是通过可配置的迭代次数调节计算量,比通用摘要算法慢几个数量级。
下面的三种KDF,理论上安全强度SCrypt>BCrypt>PBKDF2,推荐使用BCrypt。
PBKDF2的缺陷是容易被特制的芯片破解,比如使用ASIC或者GPU,所需的电路和RAM都很小。
BCrypt被特制芯片破解的难度稍微大点,所需的电路和RAM比PBKDF2要多些,但依然是固定的。
SCrypt特意增大了运算所需内存,因此提高了特制芯片破解的成本,此算法被莱特币(Litecoin)所使用,但似乎在互联网界并不广泛,可能是大家刚转向BCrypt,没工夫搭理尚显年青(2012年被提出)的SCrypt,或者是采用SCrypt的性价比对互联网企业不合算。
在OTP(One-timepassword)算法中,需要在服务端和客户端储存一些参数或者状态,随后认证过程中双方才能使用同样的秘钥函数计算出当前秘钥。
用户注册时选择密码并被服务端保存,随后用户访问这个服务时就要经过认证协议。密码如何存储是服务内部的问题,除非服务被攻破,存储的密码泄露,否则还是不大容易捅篓子的,而认证协议解决的是在网络两头双方的信息交换,这就太容易出问题了,所以出现繁多的认证协议也就不足为怪。
从认证涉及的各方来看,认证协议分为双方认证协议,客户端和服务端,这是最常见的,还有三方认证协议,除了客户端和服务端还引入了一个双方都信任的第三方,比如Kerberos,CAS(CentralAuthenticationService),OpenID。下面只看双方认证协议,这也是三方认证的基础。
双方认证按照认证时提供的凭证个数分为单因子认证(SFA,single-factorauthentication)和多因子认证(MFA,multi-factorauthentication),而多因子认证里以双因子认证最为常见。
所谓多因子,指认证时需要提供如下凭证:
也有人提第四个因子,somebodyyouknow。
双因子认证很容易在实现时引入漏洞:丢失密码后使用手机发短信即可重置密码(要求额外提供身份证号是不保险的,身份证号并不是保密信息),或者手机上的应用长期缓存第一个因子,只需要提供第二个因子。在安全和方便之间总是难以皆大欢喜。
具体的认证协议五花八门,可以一刀切分为两类:需要向对方发送密码的,不管是固定密码还是一次一密;不需要向对方发送密码的。
这种协议都需要SSL/TLS之类的协议护驾,否则毫无安全可言。
ChallengeResponseAuthenticationMechansim,所谓的challenge就是服务端发给客户端一个随机字符串,客户端需要用密码或者密码的摘要值对其进行HMAC-MD5运算,然后把结果发送给服务端,服务端对随机串做相同运算并比对结果。
此协议只是客户端向服务端认证,没有服务端向客户端认证,因此一般需要SSL/TLS护驾,让客户端验证服务端证书。具体实现时那个challenge往往有比较固定的模式,没有SSL/TLS信道加密的话,通讯数据包被窃听后易受词典攻击。
在服务端,密码要么是存为明文,要么是存为MD5摘要值或者中间运算结果,一是容易被暴力破解,二是存储的值在CRAM-MD5认证协议里跟密码等价,所以拿到这个摘要值其实就是获得了此用户的权限。
相比CRAM-MD5,在认证过程中允许客户端提供一个随机串添加在服务器给定的随机串上,因此避免了恶意的服务端做选择明文攻击(CRAM-MD5中对选定明文,客户端返回的摘要值是确定的,因此可以被词典攻击)。
DIGEST-MD5支持互相认证,但协议本身选项比较多,容易实现不当,互操作性比较差。
虽然攻击难度比CRAM-MD5大,但一般也需要用SSL/TLS保护信道以免窃听。
跟CRAM-MD5一样,密码是MD5摘要,而且在认证协议里等价于密码,因此在服务端存储的密码是相当不安全的。
SCRAM需要搭配channelbinding以避免中间人攻击,可以用SSHv2和TLS。所谓通道绑定就是应用层的认证协议利用传输层的加密协议,确认在应用层认证的双方确实是互相通信的双方,避免中间人攻击,注意这里的通道绑定是需要两层协议的具体实现互相支持的,比如上层协议要获取SSHv2的sessionID或者TLS里的握手报文内容(tls-uniquebinding)、X509证书(tls-server-end-pointbinding)参与认证过程,举例来说,在TLS上做通道绑定的SCRAM-SHA-1增强版叫SCRAM-SHA-1-PLUS,其实现需要OpenSSL或者GnuTLS库提供获取握手报文内容、X509证书的API。
与Kerberos和SSLX509不同,SRP并不依赖第三方的受信秘钥服务或者证书分发机构,SRP使用共享密码做互相认证。SRP有大量优良特性:
参考:
OpenSSL>=1.0.1以及Apache2.5mod_ssl,mod_gnutls支持TLS-SRP:
但是很不幸Redhat为了避免可能的专利纠纷删除了Fedora、RHEL中openssl软件包里的srp代码:
AuthenticationandKeyAgreement,用于3G网络中,提供互相认证以及加密通道。
ExtensibleAuthenticationProtocol,EAP是一个认证框架,常用于无线网以及点对点网络中。具体的认证方法称为EAPmethod,目前定义了大约四十种。
EAP-TLS:使用client&serverX509certificates互相认证,并用TLS加密信道
EAP-POTP:使用OTPtoken做双因子认证
EAP-PSK:使用pre-sharedkey做互相认证,认证成功后信道被加密
EAP-PWD:从一系列共享密码中挑选一个做认证,被Android4.0,FreeRADIUS,Radiator支持
EAP-IKEv2
EAP-FAST
EAP-AKA
PEAP:为EAP提供加密保护
基于UDP协议。在使用WPA-Enterprise/WPA2-Enterprise无线网认证方式的地方就需要RADIUS服务。
Cisco开发,基于TCP协议,提供authentication/authorization/accounting.
代替RADIUS,提供authentication,authoriazation,accounting。
上面提到SRP、SCRAM,看起来是很安全了,但是总架不住客户端中了木马导致密码泄露,或者密码比较二被人猜出来,或者一个密码打天下忽然惊闻常去的某网站居然是明文存储密码,等等等等,所以牵涉到用户深度隐私或者钱财的服务必须自觉的支持双因子认证。
一般双因子认证使用这两个因子:knowledgefactor,基本都是指密码了,possessionfactor,电子令牌上或者手机上的Googleauthenticator应用显示的认证码,或者是服务端通过短信发到手机上的认证码,这个认证码就是个one-timepassword,其生成算法是有业界标准的,并不是个简单的随机数。
Wikipedia上对OTP的讲解很清楚:
HOTP是双方定一个种子数字,用同一个摘要函数这个种子求值,对结果再次算摘要值,如此反复,由于摘要函数的特性,很难从下一个值推算出上一个值,所以把这些值倒过来就是一个密码表了,每次用下一个密码。
明白原理后就很容易理解OTP是怎么用的了:
Google的认证系统还有个高级功能,可以为一个账户生成多个副密码,这些密码不需要双因子认证,这个功能是为了给第三方不支持双因子认证的应用访问Google服务。
Googleauthenticator官方自称twp-stepauthentication而非two-factorauthentication,因为有人诟病它的安全性。传统意义上的possessionfactor是很难复制的,要么拥有要么没有,比如RSASecurID就是抗篡改的(tamper-resistant),而Googleauthenticator可以同时在多个设备上运行,只要把种子数字从手机里复制出来,这破坏了“somethingonlytheuserhas”的要求。但总之这种softtoken还是聊胜于无,穷人的福利。
原理很简单,实际应用中也很常见,但做好并不容易,需要挖空心思让机器图形识别困难,但对人肉识别又比较容易,看起来很凌乱的图片,未必难于被机器识别。
依优先级顺序,排在前面的优先级高。
Intranet使用Kerberos和SPNEGO做singlesign-on,这个选择已然定论,支持这些协议的操作系统和应用软件都非常广泛。
估计大伙也是这么想的,所以虽然OpenID想法很好,但大家都把它当做锦上添花的特性,不会作为主要的认证方式。
可以看出SSL/TLS协议并不一定需要x509证书,可惜所有Web浏览器只支持X509证书方式的认证,并且对客户端的x509证书认证操作比较麻烦,需要用户自己在浏览器设置里导入客户端自己的证书,所以为了应付Web浏览器,还是需要结合TLSX509servercert做服务端认证然后加密连接,然后再用HTML表单以及JavaScript做SRP认证(这一步不依赖加密连接),如果是本地应用,可以直接上TLS-SRP。
TLS-SRP要求OpenSSL>=1.0.1或者GnuTLS。OpenSSL1.0.1在2012年3月14日发布。
SRP在服务端保存的是verifier,而非password或者password的等价物,verifier类似公钥认证里的公钥,所以泄露了也太大问题。
使用带有TLSchannelbinding的SCRAM-SHA-1-PLUS。
实现简单,理解容易,业界最广泛使用的方案。用x509证书验证服务端,然后在加密连接上传输密码或者其摘要值给服务端以验证客户端。密码存储使用BCrypt。
使用TLS的注意事项:
如果服务端是一个集群,那么TLSsessionID需要搭配memcached做共享的sessioncache,而sessionticketextension需要集群所有机器使用同样的ticketkey。
认证协议是个理解起来伤脑筋,要想实现无误也很费神的事情,有人就构建了许多框架或者API来容纳各种认证协议:GSSAPI(GenericSecurityServicesApplicationProgrammingInterface),SASL(SimpleAuthenticationandSecurityLayer),SSPI(SecuritySupportProviderInterface),其中应用最广的当属SASL,众多网络协议以及Linux下无数应用都支持SASL,不过最遗憾的是HTTP协议以及众多web浏览器不支持它。
SASL主流实现有四个:
这些SASL实现可以从文件、OpenLDAP、关系数据库读取密码信息并进行验证,也能更改密码,列举用户名,在实现认证系统时最好基于某个SASL实现。
非Web浏览器场合,可以直接上TLS-SRP,不需要X509证书。
下面是分别用OpenSSL和GnuTLS演示TLS-SRP。
需要用正规CA签名的X509证书,因为这个证书用来验证服务端身份。
各种SASL实现都支持PLAIN机制,其实自己实现也非常简单了,唯一要注意的是最好把认证服务跟业务逻辑所在服务分开,避免业务逻辑所在服务出篓子被人爬下整个密码库。
OTP的原理并不复杂,自己实现一个也不难,下面是许多现成的实现供参考。
没有一个提供backupcode特性,当然,这个不在OTP原理里头,只是具体实现时的一个方便用户的特性。实现时可以参考Googleauthenticator和oath-toolkit。
使用oath-toolkit和Googleauthenticator可以验证两者是一致的,Google返回的seed值是16个字符的base32编码的字符串,实际上Googleauthenticator不要求必需是16个字符。
$oathtool-b--totp'bkuq7tyasdbujlda'#字符串的空格被忽略,大小写无关200157$oathtool-b--totp'bkuq7tyasdbujlda'200157#验证0将那串base32编码字符串输入Googleauthenticator里,可以验证它的结果跟oathtool生成的认证码确实是一致的。Googleauthenticator可以用于Google之外的服务。