└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # 第三代树洞加密算法概述 2 | 3 | 第三代树洞中,我们采用了全新的加密体系。 4 | 5 | **我们将通过用户设定的密码来加密用户的个人信息**,通过非对称密钥及Shamir密钥分发算法,实现了如下关键特性: 6 | 7 | - 服务器如果发生数据泄漏,将不会泄露发帖者个人信息。也就是说,即便网络黑客通过攻击手段获取了服务器中的全部数据,也无法将用户的发帖与邮箱对应。 8 | - 从密码学上要求一定数量的"密钥保管员"同意才可以解密用户个人信息。 9 | - 用户的密码明文不上传服务器,不会离开用户的设备。 10 | 11 | 本文档中,首先介绍了一些基础的密码学知识,然后详述了第三代树洞的加密体系,最后讨论了一些实现细节。 12 | 13 | ## 密码学基础知识 14 | 15 | ### 对称加密与非对称加密 16 | 17 | #### 什么是对称加密 18 | 19 | 在对称加密算法中,**加密和解密使用的是同一把钥匙**,即:使用相同的密码进行加密和解密; 20 | 21 | 加密过程如下: 22 | 23 | 加密:原文 + 密匙 = 密文 24 | 25 | 解密:密文 - 密匙 = 原文 26 | 27 | ![img](https://pic2.zhimg.com/80/v2-caf177200aa029f8c3075a73a274c7c9_720w.jpg) 28 | 29 | 一个最经典的对称加密算法叫凯撒算法,据说是凯撒发明并用来传递军令的。移位密码非常简单,就是对明文做相同的偏移并取模,即E(x)=x+b(mod m)。 30 | 31 | ``` 32 | 选择密钥k,0 自从阿里巴巴和四十大盗的故事流传出来后,后世的强盗们考虑加强藏宝洞的安保措施,他们找到了一家安保公司,提出这样一个要求:n个强盗每人都有一把钥匙,任意k个强盗聚在一起都可以打开藏宝洞的锁,k-1或更少的钥匙都无法开启。 75 | 76 | 这个要求其实很巧妙,n个人最坏情况即使丢失了n-k把钥匙,依旧能够打开锁;而少于k个人聚在一起或者密钥被窃取,也不会造成财产损失。它既能提供冗余性,也能防共谋。 77 | 78 | Shamir算法巧妙地构造了一个k-1次随机多项式,将核心密钥编码到多项式的常数项,在这个k-1次曲面上随机选择n个点分发给n个参与者即完成了密钥分发的过程。更详细的科普可见[Wikipedia](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing)。 79 | 80 | ## 树洞加密算法 81 | 82 | 第三代树洞的加密体系用到了全部以上三种算法。具体的做法如下: 83 | 84 | 1. 设密钥保管员数量是n个,每个密钥保管员会在自己的**永久离线的保密设备**中生成1个非对称密钥对,命名为`private_key_1, public_key_1, private_key_2, ..., private_key_n, public_key_n`,然后将`public_key_1, ..., public_key_n`保存到服务器上。 85 | 2. 用户的客户端会向服务器上传用户的邮箱(email)、密码哈希值(password_hash,等于sha256(sha256(password))),也就是说用户的密码明文从来不会离开用户的设备。 86 | 3. 服务器上有关用户邮箱的部分只存储`email_encrypted = AESEncrypt(email, password_hash)`。 87 | - 这样用户登录时,后端可以通过对比`email_encrypted`和服务器中的数据是否相同,以验证用户的密码。 88 | - 如果需要变更密码,对比`email_encrypted`然后改为新的`email_encrypted`即可,然后执行步骤4-6。 89 | 4. 在验证完用户邮箱后,服务器的内存和数据库中会删除所有的用户邮箱明文。 90 | 5. 对于每个用户的`password_hash`,我们使用Shamir算法生成n个密钥切片`key_share_1, ..., key_share_n`,大于等于k个切片才可以还原出`password_hash`。生成后,服务器的内存中就删除`password_hash`的全部存在。 91 | 6. 在服务器上我们通过非对称加密得到`encrypted_key_share_i = RSAEncrypt(key_share_i, public_key_1), i = 1, 2, ..., n`,然后服务器的内存中就删除`key_share_i, i = 1, 2, ..., n`的全部存在。服务器中会将`encrypted_key_share_i, i = 1, 2, ..., n`存储到数据库中。 92 | 93 | 总结来说,对于每一个用户,与用户注册邮箱相关的内容,服务器只存储 `email_encrypted`, `encrypted_key_share_i, i = 1, 2, ..., n`。 94 | 95 | ## 实现细节 96 | 97 | ### 如何判断用户的邮箱是否注册过 98 | 99 | 我们需要单独维护一列已注册邮箱的哈希值列表,这个列表中只记录邮箱哈希值,不与用户信息和用户发帖关联。 100 | 101 | ### 用户忘记密码怎么办 102 | 103 | 由于服务器上的用户邮箱被用户密码加密了起来,从邮箱解密对应的用户需要k个密钥保管员交出私钥遍历全部数据库,这会让用户找回密码变得困难。 104 | 105 | 一个可行的解决方案是,对每一个用户生成一个随机字符串`forget_pw_nonce`,在用户注册成功后发送到用户邮箱,并提醒用户不要删除。这会引入新的安全风险,即: 如果用户的邮件系统和服务器中的数据同时被泄露则会导致发帖与用户邮箱的关联。这是安全性与便捷性的一个取舍。 106 | 107 | ### 遇到特殊情况,密钥保管员如何解密 108 | 109 | 1. 对于一个特定用户,从数据库中读取出`encrypted_key_share_i, i = 1, 2, ..., n`, `email_encrypted`。此后的步骤都可以在线下离线完成。 110 | 2. n个密钥保管员中的k个如果同意,就从`encrypted_key_share_i`中解密出`key_share_i` 111 | 3. 从k个`key_share_i`中通过Shamir算法解密可以得到`password_hash` 112 | 4. 由于`email_encrypted = AESEncrypt(email, password_hash)`,解密就可以计算出`email = AESDecrypt(email_encrypted, password_hash)` 113 | 5. 这样就获得了`email` 114 | 115 | ### 为什么即使服务器全部数据泄露也无法解密用户邮箱明文 116 | 117 | 因为从`encrypted_key_share_i`中解密出`key_share_i`的过程需要`private_key_i`,而`private_key_i`只存在于密钥保管员的**永久离线设备**上。 118 | --------------------------------------------------------------------------------