目录:
** 0x00 引子
** 0x01 用户名和密码
** 0x02 密码管理器的基本原理
** 0x03 多因素认证
** 0x04 双因素认证(two-factor-auth)的基本原理。
** 0x05 [OpenID] vs [OAuth]
** 0x06 [IDToken] vs [JWT, JWS, JWE]
** 0x07 如何生成强密码
** 0x08 OpenGPG
0x00 引子
每天,你使用用户名和密码登录网站。
每天,你使用手机验证码重置密码。
每天,你使用微信扫码登录其他软件。
每天,你使用微信授权第三方小程序访问你的个人信息。
每天,你可能会对如何记住众多密码感到棘手。
作为开发者,你应该对安全地做“身份认证”和“授权”,有十分清晰的认识。
0x01 用户名和密码
那么,如何安全地保存密码?
- 客户端,为每个用户,使用密码学安全的伪随机数生成器(Cryptographically secure pseudorandom number generator)生成随机密码password。通过https等安全通道传输username、password到服务端。
- 服务端,为每个用户,生成一个独立的随机盐salt,加盐是为了防御彩虹字典破解。
- 使用标准的加密哈希函数计算密码和盐的的哈希: hashValue=hash(password+salt)
- 加密哈希函数应该选择慢哈希函数(Slow Hash Function),慢加密哈希函数可以是:Argon2, bcrypt, scrypt, or PBKDF2 中的一种,不要自己发明哈希函数。不要使用快哈希函数(Fast Hash Function),例如MD5, SHA1, SHA256, SHA512, RipeMD, WHIRLPOOL, SHA3。使用慢加密哈希函数是为了防御暴力计算破解。
- 在数据库中保存匹配的
以及,如何安全地校验密码?
- 客户端,通过https、ssh等安全通道传输username、passowrd到服务端。
- 服务端,从数据库中取出数据
- 使用和保存时一样的hash函数计算 hashValue'=hash(password+salt)
- 将计算结果hashValue'和保存的hashValue比较,如果一致则校验通过,否则失败。
最后,密码忘记了怎么办?
0. 只能重置密码!
- 用户输入邮件,如果邮件存在,则发送重置链接给那个邮件,如果不存在,不要发送!
- 用户输入手机号码,通过验证码来验证,参考下面的双因素认证一节。
[1] 密码学安全伪随机数生成器
[2] Cryptographically secure pseudo-random number generator, CSPRNG
[3] 盐
[4] Salt
[5] Origin of the term "salt" in computer security
[6] hashing-security
[7] Everything you ever wanted to know about building a secure password reset feature
[8] The Definitive 2019 Guide to Cryptographic Key Sizes and Algorithm Recommendations
0x02 密码管理器的基本原理
密码很多怎么办?用户可以选择使用密码管理器来管理自己的密码。那么,密码管理器的基本原理是怎样的呢?
- 使用一个包含字母,数字,特殊字符的8位以上的强主密码(masterpassword)。
- 使用主密码+用户名等只有你自己知道的信息,通过一个密码哈希函数去生成一个盐(salt)。所谓密码哈希函数就是是满足单向性质(计算哈希容易,反向破解很难,具体来说是NP难度),强碰撞防御的哈希函数。
- 有了主密码和盐,然后可以使用PBKDF2,Bcrypt, Scrypt ,Argon2中的一种Key Derived function(KDF)来生成一个密钥 aesKey = kdf(masterpassword,salt),此时有3个重要的数据:masterpassword, salt,aesKey,这3个数据不会上传到云端。
- 每当注册一个新站点需要一个新密码时,使用密码管理器生成每个站点用的随机字符串强密码,一般是 xxxx-xxxx-xxxx-xxxx格式,形成数据。这个生成过程必须要用密码安全的随机数生成器。
- 密码管理器使用步骤3生成的aesKey,采用AES对称加密算法去加密第4步生成的站点密码数据,上传加密后的密码数据到云端。
- 在其他硬件上登录密码管理器客户端后(最好登录客户端的密码与主密码应该不一样),客户端同步加密后的数据。
- 需要密码时输入主密码(masterpassword),使用主密码+用户名+同样的单向哈希函数生成盐(salt),通过KDF实时生成aeskey,再用aeskey去解密加密后的数据的到。注意,这里用到的主密码是你自己记住的,盐(salt)和aesKey是实时在本地生成的,这也是密码管理器可安全使用的一个关键因素,别人即使获得了加密后的数据,他没有你的主密码,用户名等私人信息,他是很难破解的。这也是为什么主密码本身应该足够强,需要包含字母,数字,特殊字符以及8位以上的原因。
- 如果不同步到云端,那么切换到其他设备时,你要自己手工拷贝加密后的密码数据到新设备的密码管理器里。
- 同步到云端的密码管理器有LastPass/1Password,不同步到云端的密码管理器早期的1Password,现在是否有可选模式不知道,整体上来说许多用户反馈1Password的加密过程更可靠,如果你要选择和对比,可进一步分析。如果你用的是Mac系统,使用safari浏览器内置的密钥管理器记住密码,并使用icloud同步到iphone,也是属于会同步到云端的密码管理器,只是它只支持苹果自己的系统。
[1] Password Manager
[2] 1Password
[3] Lastpass
[4] password-manager-hacking
0x03 多因素认证
我们知道密码在认证身份中的重要作用,但实际上密码只是验证身份的一个因素。身份认证实际上分为:
- 只验证密码的单因子认证,验证的是一个人“只有自己知道”的知识。
- 除了验证密码,还验证只有你个人持有的东西,例如手机,u盾,微信扫码认证,Google设备验证器等。验证的是一个人“只有你拥有的”设备。这就形成了双因子认证。即使都是双因素认证,限制级别不同,也会导致不同级别的安全,例如Google在双因素认证的基础上,又提供❝ 高级保护计划❞服务,用户必须购买两个安全密钥:一个是具备无线网络功能的主密钥,另一个是备用密钥。然后只能在安全密钥设备上访问Google账号,以及访问白名单内的OAuth服务。 (参考[3])
- 进一步,还可以通过生物特征验证你是谁。例如,指纹,人脸识别等,银行客户端和支付宝等就会有这种认证。那么就形成了三因子认证。
[1] WebAuthn
[2] Web Authentication: What It Is and What It Means for Passwords
[3] Google Advanced Protection
0x04 双因素认证(two-factor-auth)的基本原理。
双因素认证的核心目的是确认只有用户拥有的设备,例如忘记密码后通过手机验证码可以快速重置密码,密码+手机就构成了一个双因素认证。而像Google Authenticator则是一个双因素认证的App。双因素认证的基本过程是怎样的呢?
- 个人手机上安装一个生成双因素认证码的App,例如:Google Authenticator App。
- 登陆需要支持双因素认证的网站,该网站生成一个内含共享密钥(shared_secret)的二维码,这个步骤叫做初始安装,每个要使用双因素认证的网站都需要和用户的Google Authenticator做这个安装步骤。
- 手机上的Google Authenticator通过扫描的方式,安全地将网站共享给个人的密钥导入App。
- 手机上的Google Authenticator通过HMAC(shared_secret, 当前Unix时间/30秒取整)生成一个20字节的值。
- 对这20字节的值再做处理,最后得到一个6位数字整数,这个整数是一个一次性密码。
- 在网站上输入这6位数字整数的一次性密码。
- 网站上使用和Google Authenticator共享的密钥shared_secret,同样计算HMAC(shared_secret, 当前Unix时间/30秒取整),并处理后得到同样的6位一次性密码。
- 网站通过校验用户输入的一次性密码和自己生成的一次性密码,确认用户确实拥有这台手机。
- 共享密码只在第一次的时候需要通过扫码的方式从网站共享给了个人手机上的验证器。
- 这里生成一次性密码的算法叫做Time-Based One-Time Password Algorithm,也就是TOTP。
[1] 2-Step Verification with Google Authenticator
[2] HOTP: An HMAC-Based One-Time Password Algorithm
[3] TOTP: Time-Based One-Time Password Algorithm
[4] How Google Authenticator Works, Github: Google Authenticator
[5] Microsoft Authenticator
0x05 [OpenID] vs [OAuth]
每个站点都需要一个用户名密码,于是你会有一堆的用户名密码需要保管,因此从安全的角度来说你需要一个可靠的密码管理器。
但是!但是另外一种做法是你可以减少注册的用户名和密码,通过减少用户名密码的数量来解决用户的痛点,更重要的是在安全的基础上带来更强的能力。
如果我们只要注册一个公共的账号(用户名/密码),让其他网站都能做到下面两个目的:
- 身份验证(Identity, authenticate):不需要知道用户的密码就可以验证用户的身份,从而提供登录服务。
- 授权(authorization):网站A的用户能够在不提供密码给网站B的情况下,安全地授权给网站B在限定时间内去访问用户在网站A上指定资源的权限。
可以看到authenticate和authorization,很接近的两个单词,代表了两件不同的事:A授权给B使用某个资源,但是网站A并不能验证当前访问资源的是A还是B。
OpenID和OAuth正是解决这个问题的两个标准协议,但是他们各自解决的问题不同,我们先梳理下它们之间的关系。
- OAuth解决的是如何授权的问题。OAuth经历了OAuth 1.0和 OAuth2.0。
- OpenID解决的是如何验证身份的问题。OpenID经历了OpenID1.0,OpenID2.0,最新的标准是OpenID Connect(OIDC)。 OpenID 1.0和OpenID 2.0是和OAuth独立的体系,但是OpenID Connect是以OAuth 2.0为基础的协议,可以看作是OAuth的一个应用。
因此,总的来说只要理解了OAuth 2.0的基本原理,就可以理解OpenID Connect。那么就理解了“授权”和“身份验证”两件事最新的是怎么做的即可。
现在,理解下OAuth 2.0的基本做法,先定义几个关键角色:
- 资源持有者(Resource Owner)
- 资源服务器(Resource Server)
- 终端用户(Client)
- 授权服务器(Authorization Server)
我们来看Client是如何获得Resource Owner授权,从而在有效期内使用Resource Server上特定资源的:
- Alice在网站R上有一堆资源。
- Bob想要在网站S上使用Alice在网站R上的资源。
- Bob向Alice请求获得授权。
- Alice使用用户名密码登陆网站R。
- 网站R返回给Alice一个授权码(Authentication Code)。
- Alice向Bob返回授权码。
- Bob使用授权码向网站R请求获得授权令牌(Access Token)。
- 网站R返回给Bob返回授权令牌。
- Bob发送授权令牌给网站S。
- 网站S使用Access Token在有效期内访问Alice在R上的特定资源。
基本过程就是这样的,Bob在这里未必是指另一个人,而可能是一台机器,一个浏览器,一个客户端。OAuth 2.0上解决不同的安全需求也会在协议步骤上有不同的变种。核心原理就是:
- Bob必须通过只有Alice持有的用户名和密码授权获得一个AccessToken。
- 这个AccessToken有过期时间和能访问的特定资源限制。
而前面说了OAuth 2.0只规定了如何授权。通过授权可以做的事情很多,包括在授权的基础上实现身份认证。如果每个OAuth 2.0的使用者可以自己在OAuth 2.0的基础上做这个事情。另一方面,OpenID 1.0和OpenID 2.0虽然目的是身份验证,但是协议本身的过程和OAuth多少有些冗余重复。OpenID Connect就是直接对使用OAuth 2.0做身份认证提供了标准。
OpenID Connect的基本过程如下:
- 终端用户(End User, EU)需要在网站S上登录,网站S叫做依赖方(Relying Party,RP)。
- 网站S向终端用户的开放身份认证提供商(OpenID Provider, OP)请求身份认证。
- 开放身份认证提供商对终端用户进行身份认证,例如要求终端用户输入OpneID的用户名密码等。
- 开放身份认证提供商返回身份认证令牌(IDToken)给网站S,以及一个授权令牌(AccessToken)。
- 网站S使用AccessToken向开放身份认证提供商发送一个请求,以获得终端用户的UserInfo。
- 这样网站S就完成了对终端用户的身份认证,获得了其信息。
小结:
- OAuth2.0完成授权,OpenID Connect基于OAuth2.0完成身份认证。
- 关键字:AccessToken,IDToken。
[1] wiki:OpenID
[2] OAuth
[3] OAuth 2.0
[4] The OAuth 2.0 Authorization Framework
[5] The OAuth 2.0 Authorization Framework: Bearer Token Usage
[6] OpenID Connect
0x06 [IDToken] vs [JWT, JWS, JWE]
在OpenConnect里面,IDToken是身份认证传递的最重要的数据。那么一个IDToken的结构是怎样的呢?
在OpenConnect里面,IDToken采用了JSON Web Token (JWT)格式,这个格式大概如下的JSON格式:
{
"iss": "http://server.example.com",
"sub": "**********",
"aud": "**********",
"nonce": "**********",
"exp": 1311281970,
"iat": 1311280970,
"name": "Jane Doe",
"given_name": "Jane",
"family_name": "Doe",
"gender": "female",
"birthdate": "0000-10-31",
"email": "[email protected]",
"picture": "http://example.com/janedoe/me.jpg"
}
有了JWT,从安全上来说,对JWT有两种操作需求:
- 签名
- 加密
首先,签名的结构包含3部分:Head.Payload.Signature
- Head: {"kid":"...", "alg":"..."},alg表示签名使用的加密算法,还有很多其他可选字段
- Payload: 就是上面的JWT的
- Signature: 使用加密算法对Payload签名
然后把这三分部通过下面的公式用点号拼接在一起:
BASE64URL(UTF8(Head)).BASE64URL(Payload)).BASE64URL(Signature).
所以解码的时候,只要先用Base64解码,再对签名做校验即可。
其次,加密的结构包含5部分: Head.Key.Vector.Payload.Tag,基本做法如下的两步加密:
- 使用对称加密算法Key和初始化向量Vector,对Payload加密
- 使用非对称加密算法对Key加密。
所以这5部分实际上分别是:
- Head: {"alg":"...", "enc":"..."}。enc是对称加密算法,alg是非对称加密算法。
- Encrypted Key,使用非对称加密算法alg对Key加密
- Initialization Vector,对称加密算法的初始化向量
- CipherText,使用Key+Initialization Vector对Payload加密
- Authentication Tag,校验码
然后把这五部分通过下面的公式用点号拼接在一起:
BASE64URL(UTF8(Head)).BASE64URL(EncryptedKey).BASE64URL(InitializaionVector).BASE64URL(CipherText).BASE64URL(AuthenticationTag)
最后,如果你想同时对JWT签名+加密,可以这么做:
- 使用JWS对JWT签名
- 把步骤1的签名数据作为JWE的Payload
小结一下,IDToken,JWT、JWS、JWE的关系是:
- JWT是IDToken的一种。
- JWT可以使用JWS签名。
- JWT可以使用JWE加密。
- JWT可以同时使用JWS+JWE签名和加密。
最后说一句,身份验证和授权机制,在封闭的内部系统里都可以自己做一套,标准化的目的是提供不同服务商之间的互通性,减少巴别塔。理解基本原理可以自由构建,但是理解标准则是另一个目标。
[1] Understanding IDToken
[2] jwt.io
[3] rfc-7519: JSON Web Token (JWT)
[4] rfc-7515: JSON Web Signature (JWS)
[5] rfc-7516: JSON Web Encryption (JWE)
0x07 如何生成强密码
// TODO
0x08 OPenGPG
A和B通信
- A生成一个随机数R,用B的公钥加密这个随机数R,得到S
- A用R作为对称加密算法的密钥,加密信息M,得到E
- A把(S,E)发给B
- B用自己的私钥解密S,得到R
- B用R作为对称加密算法的密钥,解密E,得到M
所以最关键的是A怎样确认B的公钥确实是B的。在公共钥匙设施(Publick Key Infrastructure)的情况下,你需要有一个证书颁发和验证中心,但是这个中心机构是会作恶的。
OpenGPG走的就是【朋友圈认证】机制,只要A的N个可信的朋友圈认证一个公钥确实是B的,A就认可B的这个公钥。同时A也加入了B的可信朋友圈,可以为B的公钥背书。
[1] https://tools.ietf.org/html/rfc4880
--end--