ActFramework中存储与验证用户密码的机制与应用

@oschina的这篇博客详细讲述了保护密码的机制. 作为应用程序开发者理解这些原理是非常重要的, 但是没有理由在每个项目中依据文中所述去实现自己的保护机制, 框架应该在这方面做出足够的支持.

ActFramework提供简单有效的API来帮助用户处理安全性问题, 其中包括了密码保护与验证. 下面的代码演示如何在应用中使用框架提供的机制:

代码演示

  1. public class User {
  2. private String email;
  3. // 保存password hash而不是明文
  4. private String passhash;
  5. /**
  6. * 使用`act.Act.crypto().passwordHash(String)`来生成password hash
  7. * @param password the password text
  8. */
  9. public void setPassword(String password) {
  10. this.passhash = Act.crypto().passwordHash(password);
  11. }
  12. ...
  13. public static class Dao extends EbeanDao<User> {
  14. ...
  15. /**
  16. * 验证用户的方法: 使用email搜索用户, 然后对password做匹配
  17. * @param email an email
  18. * @param password a password
  19. * @return a user if the email and password match, else null
  20. */
  21. public final User authenticate(String email, String password) {
  22. User user = findOneBy("email", email);
  23. if (null == user) {
  24. return null;
  25. }
  26. return Act.crypto().verifyPassword(password, this.passhash) ? user : null;
  27. }
  28. }
  29. }

算法

ActFramework采用公认最好bcrypt算法处理密码保存与验证

问题

1. 盐在哪里?

Bcrypt采用随机生成盐并且将盐和hash存放在一起

2. authenticate方法为什么不生成hash然后再从数据库中寻找用户

上面的public final User authenticate(String email, String password)这样写不是更简单吗:

  1. public final User authenticate(String email, String password) {
  2. String hash = Act.crypto().passwordHash(password);
  3. return findOneBy("email, passhash", email.toLowerCase(), hash);
  4. }

答案是不行. 因为Bcrypt每次都随机生成salt和hash值,所以即便用户使用相同的密码,两次调用Act.crypto().passwordHash(password)生成的值都是不一样的. 必须用emailUser从数据库里面取出之后再使用Act.crypto().verifyPassword(String, String) API来比较

3. 有没有时间攻击防范

JFinal最新版提供了slowEquals方法用于防范这篇博客中讲述的时间攻击问题. ActFramework有这方面的防范措施吗?

答案是必须的, 在Act.crypto().verifyPassword(String)API里面调用Bcrypt的匹配函数, 用的就是JFinal实现的slowEquals逻辑. 值得一提的是和JFinal的实现相比, Bcrypt做了一点优化, 如果字符串长度不匹配的话, 直接短路返回false, 而不会继续slow equals处理.

链接