术→技巧, 研发

Google用户账户密码设计实践

钱魏Way · · 1,016 次浏览

账户管理、授权和密码管理可能对于许多开发者来说是一个没有得到足够关注的黑暗角落。以下内容是Google云平台总结的12条最佳实践。

对密码进行Hash

最重要的账户管理规则是安全地存储用户的敏感信息,如密码。在任何时候都不要存储明文的密码,并且存储的密码要是无法解密还原的。常用的方式是使用PBKDF2、Argon2、Scrypt或Bcrypt创建对密码进行hash。另外hash时还要进行加盐处理。千万不要使用已经被弃用的hash技术,如MD5、SHA1,在任何情况下都不应使用可逆加密或尝试创建自己的散列算法。在设计系统时,需要问自己,假使数据库泄露了,泄露后是否会对用户使用的服务或者用户使用的其他网站的服务构成威胁,我们可以采取哪些措施来减少泄漏事件造成的损害?

密码加密时的加盐(salt)是什么?

我们知道,如果直接对密码进行散列,那么黑客可以对一个已知密码进行散列,然后通过对比散列值得到某用户的密码。换句话说,虽然黑客不能取得某特定用户的密码,但他可以知道使用特定密码的用户有哪些。

加Salt可以一定程度上解决这一问题。所谓加Salt,就是加点“佐料”。其基本想法是这样的——当用户首次提供密码时(通常是注册时),由系统自动往这个密码里撒一些“佐料”,然后再散列。而当用户登录时,系统为用户提供的代码撒上同样的“佐料”,然后散列,再比较散列值,已确定密码是否正确。

这里的“佐料”被称作“Salt值”,这个值是由系统随机生成的,并且只有系统知道。这样,即便两个用户使用了同一个密码,由于系统为它们生成的salt值不同,他们的散列值也是不同的。即便黑客可以通过自己的密码和自己生成的散列值来找具有特定密码的用户,但这个几率太小了(密码和salt值都得和黑客使用的一样才行)。

下面详细介绍一下加Salt散列的过程。介绍之前先强调一点,前面说过,验证密码时要使用和最初散列密码时使用“相同的”佐料。所以Salt值是要存放在数据库里的。

如上图所示,注册时:

  • 用户提供密码(以及其他用户信息)
  • 系统为用户生成Salt值
  • 系统将Salt值和用户密码连接到一起
  • 对连接后的值进行散列,得到Hash值
  • 将Hash值和Salt值分别放到数据库中

如上图所示,登录时:

  • 用户提供用户名和密码
  • 系统通过用户名找到与之对应的Hash值和Salt值
  • 系统将Salt值和用户提供的密码连接到一起
  • 对连接后的值进行散列,得到Hash’(注意有个“撇”)
  • 比较Hash和Hash’是否相等,相等则表示密码正确,否则表示密码错误

密码哈希的方法

PBKDF2

PBKDF2(Password-Based Key Derivation Function)是一个用来导出密钥的函数,常用于生成加密的密码。它的基本原理是通过一个伪随机函数(例如HMAC函数),把明文和一个盐值作为输入参数,然后重复进行运算,并最终产生密钥。如果重复的次数足够大,破解的成本就会变得很高。而盐值的添加也会增加“彩虹表”攻击的难度。

上图是14年在一台多GPU的高端PC上进行的测试,可以看到,在4个单词(随机从Diceware列表选择)的情况下,如果每秒能猜2万个密码,则要猜出密码平均需要2890年。如果密码的长度再高点,则这个时间是天文数字了。

PBKDF2函数的定义:DK = PBKDF2(PRF, Password, Salt, c, dkLen)

  • PRF是一个伪随机函数,例如HASH_HMAC函数,它会输出长度为hLen的结果。
  • Password是用来生成密钥的原文密码。
  • Salt是一个加密用的盐值。
  • c是进行重复计算的次数。
  • dkLen是期望得到的密钥的长度。
  • DK是最后产生的密钥。

Argon2

Argon2 是 PHC(Password Hashing Competition[3])的冠军,利用大量内存和大量计算资源进行 Hash 计算。提供三个版本:

  • Argon2d:更快,使用 data-dependent 的内存访问方式,data 是需要 Hash 的 password 和 salt。适合加密货币和不会收到 side-channel timing 攻击的应用。
  • Argon2i:使用 data-independent 的内存访问方式,更适合密码哈希等。他比 Argon2d 慢,因为它需要更多次内存计算(passes)来保护免受 tradeoff 的攻击。
  • Argon2id:是 Argon2i 和 Argon2d 的混合版本,第一次计算用 Argon2i,后续的计算用 Argon2d。如果没有特定的理由,推荐使用 Argon2id。

Argon2 的实现:

  • Primary inputs 是 message P 和 nonce S,代表密码和盐。
  • Secondary inputs 有
    • 并行程度 p
    • Tag length τ, 代表期望返回的结果长度 bytes
    • 内存大小 m kilobytes
    • 迭代次数 t,用于调节运行时间,与内存大小独立
    • version number v
    • secret value K,默认没有 key
    • associated data X
    • Type y of Argon2, 0 代表 Argon2d, 1 代表 Argon2i, 2 代表 Argon2id

特性:

  • 性能。Argon2 填充内存的速度非常快,从而增加了 AT 里的 A。Argon2i 对每个 byte 稳定占用 2 个 CPU cycles,Argon2d 就快 3 倍。
  • Tradeoff 抵御能力。默认 passes 配置下(Argon2d 为 1,Argon2i 为 3),ASIC 设备在减少至 α =1/4 或更少内存的时候,无法减少 time-area 复杂度。如果把 pass 数量提高,时间惩罚会更高。
  • 可扩展性。Argon2 可以同时在时间和内存两个维度扩展,两者互相独立,保证始终能在一定数量的时间内填满一定数量的内存。
  • 并行。Argon2 最多可以使用 2^24 个并发线程。在实验中,8 个线程就已经消耗完了所有的计算资源和带宽。
  • GPU/FPGA/ASIC-unfriendly。Argon2 对 x86 做了高度优化,所以在定制硬件下运行既不会更快也不会更便宜。
  • 支持额外的输入。除了 message 和 nonce,类似 secret key,环境变量,用户数据等内容也可以被输入作为参数。

Bcrypt

Bcrypt是专门为密码存储而设计的算法,基于Blowfish加密算法变形而来,由Niels Provos和David Mazières发表于1999年的USENIX。Bcrypt最大的好处是有一个参数(work factor),可用于调整计算强度,而且work factor是包括在输出的摘要中的。随着攻击者计算能力的提高,使用者可以逐步增大work factor,而且不会影响已有用户的登陆。Bcrypt经过了很多安全专家的仔细分析,使用在以安全著称的OpenBSD中,一般认为它比PBKDF2更能承受随着计算能力加强而带来的风险。bcrypt也有广泛的函数库支持,因此我们建议使用这种方式存储密码。

Bcrypt有四个变量:

  • saltRounds: 正数,代表hash计算次数,数值越高越安全,默认10次。
  • myPassword: 明文密码字符串。
  • salt: 盐,一个128bits随机字符串,22字符
  • myHash: 经过明文密码password和盐salt进行hash,个人的理解是默认10次下 ,循环加盐hash10次,得到myHash

每次明文字符串myPassword过来,就通过10次循环加盐salt加密后得到myHash, 然后拼接BCrypt版本号+salt盐+myHash等到最终的bcrypt密码 ,存入数据库中。这样同一个密码,每次登录都可以根据自省业务需要生成不同的myHash, myHash中包含了版本和salt,存入数据库。

Bcrypt密码图解:

Scrypt

Scrypt是由著名的FreeBSD黑客 Colin Percival为他的备份服务 Tarsnap开发的。

Scrypt不仅计算所需时间长,而且占用的内存也多,使得并行计算多个摘要异常困难,因此利用rainbow table进行暴力攻击更加困难。Scrypt没有在生产环境中大规模应用,并且缺乏仔细的审察和广泛的函数库支持。但是,Scrypt在算法层面只要没有破绽,它的安全性应该高于PBKDF2和Bcrypt。

Scrypt 算法的工作原理:

  • 首先用随机数据填充随机存取存储器 RAM 里面的缓存空间。
  • 再从这块内存区域里虚拟随机地读取数据,同时要求整个缓存都存储在 RAM 里面。

Scrypt算法会产生一个p个块元素的数组,p的值大概比2^31(42亿)小几个数量级,实际使用中可能是十万~百万级别吧?对于每个块元素,都是进行一系列复杂运算生成的哈希值,最后对整个数组再进行PBKDF2-HMAC-SHA256运算得到最终结果。Scrypt算法保证只有将每个元素都存放在内存中,最后才能算出正确的结果,从算法层面保证了对大量内存空间的硬需求,从而提高了运算成本。

如果可能,允许用户使用第三方登陆

第三方登陆服务可以使您可以依赖受信任的外部服务来验证用户的身份。Google,Facebook和Twitter是常用的提供商。您可以使用Firebase Auth等平台在现有内部身份验证系统旁边实施外部身份提供程序。Firebase Auth带来了许多好处,包括更简单的管理,更小的攻击面和多平台SDK。我们将在此列表中触及更多优势。查看我们的案例研究,了解能够在短短一天内集成Firebase Auth的公司。

将用户身份和用户账户概念分隔开

您的用户不是电子邮件地址。他们不是电话号码。它们不是OAUTH响应提供的唯一ID。您的用户是他们在您的服务中独特,个性化数据和的体验。精心设计的用户管理系统在用户配置文件的不同部分之间具有低耦合和高内聚性。保持用户账户和凭证的概念分离将极大地简化实施第三方身份提供商的过程,允许用户更改其用户名并将多个身份链接到单个用户账户。实际上,为每个用户提供内部全局标识符并通过该ID链接其配置文件和身份验证标识可能比将其全部存储在单个记录中更优。

参考链接:用户系统设计:三户模型&三层身份模型

允许多个身份链接到单个用户账户

如果用户在一周前使用其用户名和密码进行了登陆,但在这次使用了Google登陆,用户不会意识到可能会创建重复的账户。同样,用户可能有充分的理由将多个电子邮件地址链接到您的服务。如果您正确地分离了用户身份和身份验证,则将多个身份链接到单个用户将是一个简单的过程。

您的后端需要考虑用户是否有可能在注册过程中完成部分或全部,然后才意识到他们正在使用未链接到系统中现有账户的新第三方身份。这最简单地通过要求用户提供公共识别细节(例如电子邮件地址,电话或用户名)来实现。如果该数据与系统中的现有用户匹配,则要求他们也使用已知身份提供商进行身份验证,并将新ID链接到其现有账户。

不要阻止长密码或复杂密码

NIST最近更新了密码复杂性和强度指南。随着你使用Hash算法对密码进行存储,中间的很多很多可以迎刃而解,hash算法会产生固定长度的输出,因此不必去限制用户韩剧密码的长度,如果必须限制密码长度,则只能根据服务器允许的最大POST大小来设置密码长度。这通常远高于1MB。

Hash出来的密码将由少量已知的ASCII字符组成(如果输出为二进制,则可以通过Base64进行转换)。考虑到这一点,您应该允许您的用户在密码中使用他们想要的任何字符。如果有人想要一个由Klingon、Emoji和两端都有空格的控制字符组成的密码,你应该没有技术理由否认它们。

不要对用户名强加不合理的规则

站点或服务要求用户名超过两个或三个字符,阻止隐藏字符并阻止用户名开头和结尾处的空格并不是不合理的。有些网站确实由这样的要求,例如最小长度为8个字符,或者阻止7位ASCII字母和数字之外的任何字符。对用户名进行严格限制的网站可能会为开发人员提供一些快捷方式,但这样做会以牺牲用户为代价,而极端情况会导致一些用户离开。

在某些情况下,只能做到分配用户名。如果您的服务属于这种情况,请确保指定的用户名是用户友好的,只要他们需要回忆和通信。字母数字ID应避免使用视觉上模糊的符号,例如“Il1O0”。另外建议您对任何随机生成的字符串执行字典扫描,以确保用户名中没有嵌入非预期的信息。这些相同的准则适用于自动生成的密码。

允许用户更改其用户名

在遗留系统或任何提供电子邮件账户的平台中,不允许用户更改其用户名,这种情况非常普遍。有充分理由不自动释放用户名以供重用,但系统的长期用户最终会提出使用其他用户名的充分理由,他们可能不想创建新账户。

您可以通过允许别名并让用户选择主别名来尊重用户更改用户名的愿望。您可以在此功能之上应用所需的任何业务规则。某些组织可能每年仅允许更改一个用户名,或阻止用户显示除主用户名之外的任何内容。电子邮件提供商可能会确保用户在从其账户中分离旧用户名之前充分了解风险,或者可能完全禁止取消旧用户名的链接。为您的平台选择正确的规则,但要确保它们允许您的用户随着时间的推移成长和变化。

让用户可以删除他们的账号

用户由充分的理由永久关闭账户并删除所有个人数据,但是令人惊讶的是大量的服务并没有提供这样的自助服务。这里需要考虑安全性与合规性的平衡,但大多数受监管的环境都提供了有关数据保留的特定指南。避免合规性和黑客攻击问题的常见解决方案是让用户选择是否进行删除。在某些情况下,法律上可能会要求您遵守用户要求及时删除其数据的请求。

慎重的考虑会话长度

安全性和身份验证经常被忽视的一个方面是会话长度。Google花了很多精力确保用户是他们本人,并会根据某些事件或行为进行仔细检查。用户可以采取措施进一步提高安全性。对于非关键分析目的,您的服务可能有充分理由保持会话无限期打开,但应该有阈值,之后您要求输入密码或其他用户验证。

考虑用户在重新进行身份验证之前应该能够处于非活动状态的时间。如果有人执行密码重置,请验证所有活动会话中的用户身份。如果用户更改其配置文件的核心方面或执行敏感操作时,则提示进行身份验证。考虑禁止从多个设备或位置登录同时是否有意义。

当您的服务确实使用户会话到期或需要重新身份验证时,请实时提示用户或提供一种机制来保留自上次身份验证以来未保存的任何活动。用户填写长表格,稍后提交并发现所有输入都已丢失并且必须再次登录,这是非常令人沮丧的。

使用两步验证

在选择两步验证(也称为双因素授权或仅2FA)方法时,请考虑用户对其账户被盗的实际影响。由于存在多个弱点,NIST已弃用SMS 2FA(短信二次验证),但是,它可能是您的用户可以接受的最安全的选项。启用第三方身份提供商并在其2FA上搭载是一种简单的方法,可以在不花费大量精力的情况下提高安全性。

大多数用户为了便利,常常将不同的网络服务设置了同样的账户、同样的密码。这很容易导致在某个地方泄漏了密码之后,他人可以“顺藤摸瓜”使用该账户以及密码轻易的登录到用户注册过的其他网络服务。

如果要将「密码」与「两步验证」做对比。

  • 密码就像是「印章」一样,可能你会在不同网站使用同样的印章,甚至别人可以偷偷使用你的印章。
  • 两步验证就像是「签名」一样,只有你本人才能使用。像提币这种重要的安全操作,就必须要「本人」才可以操作。

两步验证的原理。是手机两步软件

  • 手机软件先根据网站提供的密钥,产生一次性密码
  • 与网站配对绑定后,产生密钥种子。
  • 手机每 30 秒会产生一组一次性的密码,可以通关该网站的验证机制

两步验证软件,通常业界最常见的有这三款

  • Google Authenticator
  • Authy
  • 身份宝

您可以选择一款绑定使用。

使用户ID不区分大小写

您的用户不在乎,甚至可能不记得用户名的确切情况。用户名应完全不区分大小写。将所有用户名和电子邮件地址全部以小写形式存储并将所有输入转换为小写进行比较之前是非常容易实现的。

越来越多的设备是智能手机,其中大多数手机提供纯文本字段的自动更正和自动大小写。在UI级别阻止此行为可能不是理想的或完全有效。

构建安全的身份验证系统

如果您使用的是Firebase Auth等服务,则会自动为您处理许多安全问题。但是,您的服务始终需要正确设计以防止滥用。核心考虑因素包括实施密码重置而不是密码检索,详细记录账户的活动,速率限制登录尝试,在多次不成功的登录尝试后锁定账户,以及对无法识别的设备或长时间闲置的账户要求两步身份验证。安全认证系统还有许多方面,因此请参阅以下部分以获取更多信息的链接。

进一步阅读

有许多优秀的资源可用于指导您完成开发,更新或迁移帐户和身份验证管理系统的过程。我推荐以下内容作为起点:

参考链接:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注