你的密码真的安全么?
主页
- 微信公众号:密码应用技术实战
- 博客园首页:https://www.cnblogs.com/informatics/
引言
微软(MicroSoft)竟然在近三年的时间里泄露了高达38TB
的内部数据,包括密钥、员工个人备份和三万条内部消息,而原因竟然是一个微不足道的设置失误!
9月18日,云安全公司Wiz
爆出微软AI研究团队在Github上的开源数据发布中的惊人漏洞。事件起因是微软的AI开发人员在Github上提供了一个链接,用于下载开源代码和AI模型,但因为一个过于宽松
的共享访问签名(SAS)令牌,链接不仅授权任何人访问存储账户的全部内容,而且权限竟然是“完全控制”,让任何人都可以删除、替换或添加恶意内容!
这次泄露的数据包括两名微软员工的个人备份、用于微软服务的密码和秘钥,以及来自359名微软员工的三万条内部群聊消息。令人震惊的是,这个泄露问题已经存在自2020年,直到今年6月Wiz才揭示了这一严重漏洞,微软才在两天后撤销了问题的SAS令牌。这起事件也再次引发了对数据安全
的关切
数据安全是一个系统性的工程
,涉及到数据生命周期
的各个环节、以及用户的数据安全保护意识
。
用户密码作为一种特殊的用户数据,一旦泄露可能对用户隐私信息、用户财产带来重大损失。
在《2FA双因素认证 - 原理和应用》一文中我们已简单介绍过用用户密码
, 但着重介绍了第二个因素
。本文我们将从”用户密码攻击模型“开始,并使用技术手段对”用户密码生命周期“的安全保护进行展开介绍。
本文内容组织:
- 用户密码生命周期
- 用户密码攻击模型
- 用户密码安全保护&实践
- 参考资料
用户密码生命周期
用户密码(或口令) 作为用户登录网站、主机、手机APP的用户凭证
被广泛使用,如:
- 用户需要登录淘宝App,才能购物
- 用户需要登录微信,才能与好友进行聊天、发朋友圈
- 开发&运维人员需要通过密码才能登录到远程主机,进行服务运维等。
在以上场景中,用户都需要输入自己的密码登录服务,才能进行敏感操作。而密码泄露
将会导致攻击者伪装成合法用户,进而窃取用户敏感信息、盗走用户个人财产。因此保证用户密码
的安全至关重要。
要保护用户密码,我们首先要了解用户密码的整个生命周期,如下:
用户密码生命周期主要包含:
- 生成密码、使用密码、修改密码、删除密码(用户操作)
- 重置密码(管理员操作)
- 存储密码、校验密码(服务端程序自动执行)
在业务上一般涉及“注册”和“登录”两个流程:
-
用户注册
用户首次使用APP或首次访问网站时,一般需要注册,注册过程中需要用户设置密码。这里涉及到用户密码的安全生成、安全传输和安全存储。
注:在企业级应用中,用户注册可能由管理员协助完成,用户密码由管理员提供给普通用户
-
用户登录
用户注册后,需要进行登录才能正常进行APP或网站操作。这里涉及到用户密码的安全传输和校验
注:由于修改密码、重置密码比较简单,且与生成密码和存储密码类似,后文不做过多赘述;同时删除密码场景较少,也不做赘述。
用户密码攻击模型
针对用户密码生命周期,密码安全主要涉及以下几个方面:
- 密码生成
- 密码传输
- 密码存储
- 密码校验
在介绍具体密码安全措施前,我们首先要了解用户密码攻击模型
,只有了解了该模型,我们才知道我们要面对的攻击者到底是谁,也才能对症下药的制定防护措施。用户密码攻击模型,如下:
- 【01】暴力破解者。暴力破解者一般指攻击者通过不断尝试密码进行登录,从而找到合法用户密码。此类方式一般配合社会工程学,在已知用户部分信息的前提下来实施。
- 【02】网络窃听者。在用户登录和注册时,都需要将密码传输给服务端,在传输过程中网络窃听者可以使用网络嗅探工具,如wireshark获取网络数据,从而窃取用户密码
- 【03】内部泄密者。当服务端收到用户密码时,内部泄密者可以通过服务日志或直接从数据库窃取用户密码,并将密码私下售卖给攻击者。
易受攻击模型(简单密码&明文传输&明文存储):
安全模型(复杂密码&安全通道&密文存储):
用户密码安全保护&实践
密码生成安全
密码生成安全一般用来防止暴力破解者
, 生成的密码越复杂(长度越长,字符种类越多等),攻击者暴力破解的可能性越小。常见的密码生成安全规则:
- 长度: 密码应该足够长,一般至少包含 12 个字符。较长的密码更难被破解。
- 复杂性: 密码应该包含不同类型的字符,包括大写字母、小写字母、数字和特殊符号(如!、@、#、$等)。这样的密码更难以猜测或破解。
- 避免常见单词: 避免使用常见的词语、短语、名字或日期作为密码。犯罪分子通常会使用字典攻击来尝试这些常见的密码。
- 不要使用个人信息: 避免使用与您相关的个人信息,如生日、家庭地址、电话号码等。这些信息容易被猜测或通过社交工程攻击获取。
- 不要重复密码: 不要在多个账户中重复使用相同的密码。如果一个账户被破解,其他账户也会受到威胁。
假设密码生成规则为:至少包含数字、大写字母、小写字母以及特殊字符种类中的minClasses
个,并且密码长度为length
,则密码生成的代码,如下:
// 生成指定长度的用户密码
func GenerateRandomPassword(length int, minClasses int) (string, error) {
// 定义密码字符集
digits := "0123456789"
upperLetters := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
lowerLetters := "abcdefghijklmnopqrstuvwxyz"
specialChars := "!@#$%^&*()_+[]{}|;:,.<>?`~"
charSets := []string{digits, upperLetters, lowerLetters, specialChars}
selectedSets := make([]byte, 0, minClasses)
// 设置随机数种子
rand.Seed(time.Now().UnixNano())
// 从每个字符集中随机选择一个字符
charSetsUsedFlag := make(map[int]bool)
for i := 0; i < minClasses; i++ {
charSetsIndex := rand.Intn(len(charSets))
if charSetsUsedFlag[charSetsIndex] {
i--
continue
} else {
charSetsUsedFlag[charSetsIndex] = true
}
charSet := charSets[charSetsIndex]
selectedSets = append(selectedSets, charSet[rand.Intn(len(charSet))])
}
// 剩余字符随机选择
for i := 0; i < length-minClasses; i++ {
charSet := charSets[rand.Intn(len(charSets))]
selectedSets = append(selectedSets, charSet[rand.Intn(len(charSet))])
}
return string(selectedSets), nil
}
密码传输安全
密码传输安全用来保护通信数据的安全,防止密码在从客户端 -> 服务端
过程中被网络窃听者获取。传输安全比较成熟,一般借助TLS来实现。TLS原理&实践我们在之前的文章已经介绍过,这里不在赘述。
详情见:
密码存储安全&校验
密码存储安全一般用来保护用户密码不被网站提供者内部人员
窃取,一般的数据安全是很难做到防护内部人员的,因为数据已经存储在服务端,网站提供网络服务必须使用明文数据,但是密码
作为一种特殊数据,可以做到在不泄露明文密码的前提下,实现对用户身份的校验。
相信很多人已经听过类似技术或算法,我们通常称作为密码哈希函数
。如:pbkdf2
、scrypt
、bcrypt
等。
密码哈希函数 | 用途 | 安全性 | 广泛性 | 性能 | 易用性 |
---|---|---|---|---|---|
pbkdf2 | 密钥派生/密码保护 | 相对较低。PBKDF2的安全性依赖于迭代次数,如果迭代次数设置得不够高,它容易受到暴力破解攻击 | 相对广泛应用,因为它是一个老牌的密码哈希函数 | 相对较高,因为它使用的是标准的哈希算法,如SHA-256 | 易于使用和实现 |
bcrypt | 密钥保护 | 高。bcrypt使用了适应性哈希算法,它自动增加计算时间,使得暴力破解更加困难 | 在 Web 开发中相当常见 | 相对较低,因为它被设计成计算缓慢,以防止暴力破解 | 易于使用,但需要正确配置工作因子以平衡性能和安全性 |
scrypt | 密钥派生/密码保护 | 较高。scrypt类似于bcrypt,但比其更耗费内存,增加了抵抗硬件攻击的能力 | 逐渐变得更流行,尤其在密码管理和身份验证中 | 较低,因为它需要大量的内存 | 相对较易使用,但也需要正确配置参数以平衡性能和安全性 |
argon2 | 密钥派生/密码保护 | 最高。Argon2是最新的密码哈希函数,被认为是目前最安全的选项,它抵抗各种攻击,包括时间攻击和侧信道攻击 | 逐渐被接受,尤其在密码学领域中,但在一些旧系统中可能不常见 | 相对较低,因为它旨在增加计算时间 | 相对较易使用,但需要正确配置参数以平衡性能和安全性 |
注意:
- 除了
bcrypt
外,其他密码哈希函数,也用于基于密码的密钥派生
,派生出来的密钥,一般用于数据加密。(密钥派生不是本文关注的内容) - 密码哈希函数在生成密文密码过程中,使用了哈希函数,由于哈希函数具有不可逆性,内部人员无法通过密文密码还原用户明文密码,因此即使密文密码泄露,也不会对用户其他信息(非本网站数据)进行非法访问。(据不完全统计,绝大部分用户会在多个APP或网站上使用相同的密码)
使用demo程序生成随机密码&校验
由于密码哈希函数在使用过程中,基本都需要配置各种参数,并且这些参数需要在服务端进行保存,以便于进行后续用户登录验证,在实现上并不是特别灵活。
因此本人在业余时间通过github开源了集成所有主流密码哈希函数
并进行了易用性封装的实现代码(golang语言),代码仓库为github.com/warm3snow/practical-crypto
。
下面我们使用该仓库进行demo演示:
- 打开终端并执行:
# 下载代码库&切换到工作目录
➜ git clone https://github.com/warm3snow/practical-crypto.git
➜ cd practical-crypto/kdf
- 查看代码结构
➜ kdf git:(master) ✗ tree
.
├── argon2impl # argon2的kdf接口实现
│ ├── argon2impl.go
│ └── argon2impl_test.go
├── bcryptimpl # bcrypt的kdf接口实现
│ ├── bcryptimpl.go
│ └── bcryptimpl_test.go
├── kdf.go # 密码哈希函数通用接口
├── kdf_test.go # kdf单元测试
├── pbkdf2impl # pbkdf2的kdf接口实现
│ ├── pbkdf2impl.go
│ └── pbkdf2impl_test.go
└── scryptimpl # scrypt的kdf接口实现
├── scryptimpl.go
└── scryptimpl_test.go
- 执行测试程序
➜ kdf git:(master) ✗ go test -test.run=TestKDF
明文密码: 0%B+&[J]
密文密码: $bcrypt$2a$10$EBXiMyoZeuO/POjuXHNAyeIPRanE0YaJyLrEddTFbywlNiCXDPLrq
验证密文密码: true
密文密码: $pbkdf2$GClZdx8hyy3iqC4hQZkCTw==$zATpvtegzY0Q49te5vmpSgiJtQ+6B4Ub55oQbYaZaOI=$4096:32
验证密文密码: true
密文密码: $scrypt$rQRKOR9lOxcDHLP+G97Uew==$VtS1Vx2ayCbx6lPhqmrRGDbi/ILceonDR+Kp+F3lJ5Q=$32768:8:1:32
验证密文密码: true
密文密码: $argon2$TQ6r/snRqrrfbC4r352S5g==$XkH5p9EqEq10IQomLhzeu8T6dPN4O8vUu0fO3BmjgBc=$3:32768:4:32
验证密文密码: true
PASS
ok github.com/warm3snow/practical-crypto/kdf 1.295s
从执行结果上看:
- 测试程序首先生成了明文密码
0%B+&[J]
,该密码生成规则请参考本章节#密码生成安全 - 基于
明文密码
和密码哈希函数
,我们生成了密文密码, 格式为$<kdfName>$<salt>$<key>$<others>
,其中kdfName表示密码哈希函数;salt表示随机盐值,用于增强安全性,抵御彩虹攻击;key: 表示密文密码;others: 密码哈希函数需要用到的其他安全参数,不同算法使用的参数不同、参数个数也可能不一样,多个参数使用:
隔开 - 所有的
密码校验
全部成功
总结
本文主要介绍了用户密码在生成、使用、传输和存储整个生命周期的安全风险,并针对不同环节提出了应对措施&最佳实践。安全是个大问题,不仅需要通过技术来降低风险,也需要各个环节参与者能够切实考虑到安全风险。 最近微软数据泄漏事件表明,即使是这种国际化大公司也有做的不到位的地方,因此在程序编码、服务搭建以及人员安全培训上都需要不断加强,本文仅从用户密码角度进行了探讨,而密码安全是数据安全的重要一环,也需要重视,希望读着通过本文可以有个初步的认识。
注:本文未对kdf接口定义&实现进行详细介绍,感兴趣的读着可以参考开源代码。
参考资料
- TLS原理&实践: https://www.cnblogs.com/informatics/collections/3758
- practical-crypto开源代码:https://github.com/warm3snow/practical-crypto/tree/master/kdf
- pbkdf2 RFC 2898: https://datatracker.ietf.org/doc/html/rfc2898
- scrypt RFC 7914: https://datatracker.ietf.org/doc/html/rfc7914
- Argon2 RFC 7693: https://datatracker.ietf.org/doc/html/rfc7693
- bcrypt wiki: https://en.wikipedia.org/wiki/Bcrypt