在 《如何安全存储口令? 了解下 Hash 加盐的原理》 这篇文章中, 从原理, 用途, 特点等方面讲解了安全存储口令的一种方案, 这就是 Hash 加盐, 为了更好的理解, 这篇文章从实战的角度, 设计一个可实践的技术方案.
以企业邮箱应用来说, 企业用户 (每个用户有一个 email 地址), 企业管理员, 客服人员, 销售人员都有一个登录口令; 这些用户可以通过 IMAP,POP,SMTP,web(包括 webmail, 客服管理平台, 销售管理平台, 企业管理平台) 登录校验口令.
Hash 加盐虽然不是口令保护最好的技术手段, 但如果实施得当, 也是比较安全的, 除了 Hash 加盐算法, 基本的解决方案是:
存储分离
授权分层
要达到的最终目的: 即使口令系统的某个部分, 或者全部被攻击了, 攻击者也很难反解出明文口令.
那么如何设计呢? 先上一张图, 然后基于这张图细细道来.
1: 首先口令存储 (库) 和 Salt 存储 (库) 是物理分割的, 也就是说不能是同一个库, 不同的库也不能在一台物理机器上, 为什么要这么做呢? 比如口令库被脱库了, 但 Salt 库没有泄漏, 那么也很难破解出口令明文.
同时不同角色的口令 (比如用户口令, 企业管理员口令...) 最好也是物理分离的; 但这些角色的 Salt 可以在一个库中, 毕竟把系统搞的太复杂也比较难维护.
必须注意的是口令, Salt 库一定不能和其他库混在一起, 从 DBA 的角度看, 口令, Salt 库安全级别显然更高, 尤其加上主辅同步, 这些库存储的地方设备越少越好, 减少泄漏的风险.
2: 那么 Salt 如何设计呢?
几个原则:
不可预测, 不可重复, 每个口令的 Salt 独立.
Salt 的长度尽量和 Hash 算法的长度一致.
关于如何得到 Salt, 后面的伪代码会说明.
3:Salt 加密
Salt 看上去好像是加密的, 但必须指出的是, Hash 运算不是加密算法, Salt 值仍然是明文的. 那么将 Salt 值加密后再存储的时候, 是否更安全呢?
为了加密 Salt, 不管是对称加密算法, 还是非对称加密算法, 都需要密钥(称为 Salt 密钥), 这个密钥的存储是非常重要的, 如果泄漏了, 加密基本上也是无用的.
对 Salt 加密还可以使用 HMAC 算法, 即在 Hash 的时候, 同时对 Salt 和 Salt 密钥进行 Hash 运算.
看上去这二种 Salt 加密算法都能满足需求(也同样面临 Salt 密钥的管理问题), 那么应该采用哪一种呢?
建议采用加密算法保护 Salt, 因为可以定时修改 Salt 密钥, 当然在修改的时候, 必须重新计算 Salt 的加密值, 并更新到存储中(比如 MySQL).
而如果采用 HMAC 算法, 因为它无法反解出明文的 Salt 值, 自然无法重置 Salt 加密值.
如果担心 Salt 密钥因为泄漏而存在安全风险, 可以定时修改 Salt 密钥.
4: 以邮箱应用来说, 应用层服务很多, 开发语言也很多, 如果每个服务都独自校验口令, 那么风险显然很大, 而且从历史经验来看, 口令泄漏的风险都来源于此.
那么由统一的 API 层注册, 校验口令显然非常重要, 从安全, 可扩展的角度看, 统一的 API 封装非常重要, 从运维, 开发的角度看, API 的管理也很重要:
比如放在单独的集群中, 开发者不能有权限登录.
口令和 Salt 的库只能授权给 API 集群, 比如通过 MySQL 的 IP 限制.
或者 API 只能提供内网服务.
一切的一切, 都是为了隔离.
在 API 层上, 如果 Salt 是加密的, 为了注册, 校验口令, 必须能够访问 Salt 密钥, 千万不能在代码中硬编码 Salt 密钥, 如果没有专门的硬件保护 Salt 密钥, 那么本质上只要 API 机器被攻破, 这个 Salt 密钥也就相当于明文了. 为了减轻风险, 可以定时修改 salt 密钥.
5: 接下来, Web,SMTP 访问必须具备授权机制, 比如 SMTP 服务显然不能访问企业管理员的口令, 通过可分配权限的机制限制应用层对 API 的访问.
这样的好处除了权限控制, 假想下, 如果 Web 层的服务器被攻击了, 它也很难攻击 API 集群, 至少很难攻破 API 集群服务器, 当然它可以通过模拟的手段, 从 Web 层发起攻击.
至于 Web,SMTP 如何与 API 通信, 如何更安全, 有很多技术解决方案, 比如 HTTPS, 或者动态的 token, 但如果应用层机器被暴力攻破了, 这些防护手段也是没用的.
从上面的描述, 可以看出, 这是基本的三层应用架构, 其实技术之间都是互通的, 接下去简单用伪代码描述下如何注册, 校验口令.
注册口令:
- #POST 提交 user 和明文口令
- $user="";
- $user_password="";
- # 取得随机的 Salt
- $user_salt = openssl_random_pseudo_bytes(16);
- # 生成口令密文值
- $user_hash = sha1($user_password . $user_salt ) ;
- # 将 salt 存储到 Salt 库
- $id=insert_saltdb($user,$user_hash);
- # 将口令密文值存储到口令库, 通过 ID 关联 Salt 库和口令库
- insert_saltdbinsert_passworddb($id,$user_salt);
校验口令:
- #POST 提交 user 和明文口令
- $user="";
- $user_password="";
- # 从口令库中取出 Salt 和关联 ID
- $user_data = select_passworddb($user);
- $id = $user_data["id"];
- $user_verify_password = $user_data["hash"];
- #Salt 库和口令库通过 ID 关联
- $user_salt = select_saltdb($id);
- if ($user_verify_password == sha1($user_password . $user_salt )) {
- echo "校验成功";
- }
口令保护系列文章:
如何安全存储口令? 了解下 Hash 加盐的原理
来源: http://www.tuicool.com/articles/2EFfUnv