浅谈Shiro框架中的加密算法,以及校验

在涉及到密码存储问题上,应该加密/生成密码摘要存储,而不是存储明文密码。为什么要加密:网络安全问题是一个很大的隐患,用户数据泄露事件层出不穷,比如12306账号泄露。


Shiro提供了base6416进制字符串编码/解码的API支持,方便一些编码解码操作,想了解自己百度API操作用法。


看一张图,了解Shiro提供的加密算法



本文重点讲shiro提供的第二种:不可逆加密。

        散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5、SHA等。一般进行散列时最好提供一salt(盐),比如加密密码“admin”,产生的散列值是“21232f297a57a5a743894a0e4a801fc3”,可以到一些md5解密网站很容易的通过散列值得到密码“admin”,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如用户名和ID(即盐);这样散列的对象是“密码+用户名+ID”,这样生成的散列值相对来说更难破解。


常见的算法有:MD5,SHA算法:

        MD5算法是1991年发布的一项数字签名加密算法,它当时解决了MD4算法的安全性缺陷,成为应用非常广泛的一种算法。作为Hash函数的一个应用实例。

        SHA诞生于1993年,全称是安全散列算法(Secure Hash Algorithm),由美国国家安全局(NSA)设计,之后被美国标准与技术研究院(NIST)收录到美国的联邦信息处理标准(FIPS)中,成为美国国家标准,SHA(后来被称作SHA-0)于1995被SHA-1(RFC3174)替代。SHA-1生成长度为160bit的摘要信息串,虽然之后又出现了SHA-224、SHA-256、SHA-384和SHA-512等被统称为“SHA-2”的系列算法,但仍以SHA-1为主流。


数据库User设计:

  1. CREATE TABLE `sys_users` (  
  2.   `id` bigint(20) NOT NULL AUTO_INCREMENT,  
  3.   `username` varchar(100) DEFAULT NULL,  
  4.   `password` varchar(100) DEFAULT NULL,  
  5.   `salt` varchar(100) DEFAULT NULL,  
  6.   `locked` tinyint(1) DEFAULT '0',  
  7.   PRIMARY KEY (`id`),  
  8.   UNIQUE KEY `idx_sys_users_username` (`username`)  
  9. ENGINE=InnoDB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8;  
  10.   
  11.   
  12. 说明:id主键字段  
  13.       username 登录的用户名  
  14.       passowrd 登录的密码      
  15.       salt     盐              
  16.       locked   锁定  默认为0(false)表示没有锁  

用户表User:


  1. package com.lgy.model;  
  2.   
  3. import org.springframework.util.CollectionUtils;  
  4. import org.springframework.util.StringUtils;  
  5.   
  6. import java.io.Serializable;  
  7. import java.util.ArrayList;  
  8. import java.util.List;  
  9.   
  10. public class User implements Serializable {  
  11.     private static final long serialVersionUID = -651040446077267878L;  
  12.       
  13.     private Long id; //编号  
  14.     private Long organizationId; //所属公司  
  15.     private String username; //用户名  
  16.     private String password; //密码  
  17.     private String salt; //加密密码的盐  
  18.     private List<Long> roleIds; //拥有的角色列表  
  19.     private Boolean locked = Boolean.FALSE;  
  20.   
  21.     public User() {  
  22.     }  
  23.   
  24.     public User(String username, String password) {  
  25.         this.username = username;  
  26.         this.password = password;  
  27.     }  
  28.   
  29.     public Long getId() {  
  30.         return id;  
  31.     }  
  32.   
  33.     public void setId(Long id) {  
  34.         this.id = id;  
  35.     }  
  36.   
  37.     public Long getOrganizationId() {  
  38.         return organizationId;  
  39.     }  
  40.   
  41.     public void setOrganizationId(Long organizationId) {  
  42.         this.organizationId = organizationId;  
  43.     }  
  44.   
  45.     public String getUsername() {  
  46.         return username;  
  47.     }  
  48.   
  49.     public void setUsername(String username) {  
  50.         this.username = username;  
  51.     }  
  52.   
  53.     public String getPassword() {  
  54.         return password;  
  55.     }  
  56.   
  57.     public void setPassword(String password) {  
  58.         this.password = password;  
  59.     }  
  60.   
  61.     public String getSalt() {  
  62.         return salt;  
  63.     }  
  64.   
  65.     public void setSalt(String salt) {  
  66.         this.salt = salt;  
  67.     }  
  68.   
  69.     //证书凭证  
  70.     public String getCredentialsSalt() {  
  71.         return username + salt;  
  72.     }  
  73.   
  74.     public List<Long> getRoleIds() {  
  75.         if(roleIds == null) {  
  76.             roleIds = new ArrayList<Long>();  
  77.         }  
  78.         return roleIds;  
  79.     }  
  80.   
  81.     public void setRoleIds(List<Long> roleIds) {  
  82.         this.roleIds = roleIds;  
  83.     }  
  84.   
  85.   
  86.     public String getRoleIdsStr() {  
  87.         if(CollectionUtils.isEmpty(roleIds)) {  
  88.             return "";  
  89.         }  
  90.         StringBuilder s = new StringBuilder();  
  91.         for(Long roleId : roleIds) {  
  92.             s.append(roleId);  
  93.             s.append(",");  
  94.         }  
  95.         return s.toString();  
  96.     }  
  97.   
  98.     public void setRoleIdsStr(String roleIdsStr) {  
  99.         if(StringUtils.isEmpty(roleIdsStr)) {  
  100.             return;  
  101.         }  
  102.         String[] roleIdStrs = roleIdsStr.split(",");  
  103.         for(String roleIdStr : roleIdStrs) {  
  104.             if(StringUtils.isEmpty(roleIdStr)) {  
  105.                 continue;  
  106.             }  
  107.             getRoleIds().add(Long.valueOf(roleIdStr));  
  108.         }  
  109.     }  
  110.       
  111.     public Boolean getLocked() {  
  112.         return locked;  
  113.     }  
  114.   
  115.     public void setLocked(Boolean locked) {  
  116.         this.locked = locked;  
  117.     }  
  118.   
  119.     @Override  
  120.     public boolean equals(Object o) {  
  121.         if (this == o) return true;  
  122.         if (o == null || getClass() != o.getClass()) return false;  
  123.   
  124.         User user = (User) o;  
  125.   
  126.         if (id != null ? !id.equals(user.id) : user.id != null) return false;  
  127.   
  128.         return true;  
  129.     }  
  130.   
  131.     @Override  
  132.     public int hashCode() {  
  133.         return id != null ? id.hashCode() : 0;  
  134.     }  
  135.   
  136.     @Override  
  137.     public String toString() {  
  138.         return "User{" +  
  139.                 "id=" + id +  
  140.                 ", organizationId=" + organizationId +  
  141.                 ", username='" + username + '\'' +  
  142.                 ", password='" + password + '\'' +  
  143.                 ", salt='" + salt + '\'' +  
  144.                 ", roleIds=" + roleIds +  
  145.                 ", locked=" + locked +  
  146.                 '}';  
  147.     }  
  148. }  

-------------------------------------------------------------------------------------------加密----------------------------------------------

正如前面散列算法的说法:加密采用的是MD5或者SHA算法和salt盐结合产生不可逆的加密。

什么是盐?

      抛开盐不说: 

     例如用户名admin        密码123,通过md5加密密码得到新的密码值为21232f297a57a5a743894a0e4a801fc3,这样通过数字字典很容易就知道md5加密后的密码为123.

     若加入一些系统已经知道的干扰数据,这些干扰的数据就是盐。则密码就是由  sale(盐) + 通过盐生成的密码组成,这样同一个密码加密生成的密码是各不相同的达到不可逆加密。



对密码进行盐加密的工具:

这个是jdbc.properties配置文件,里面有shiro加密中需要配的算法名称和迭代次数。算法名称可以为md5,sha-1,sha-256.

若填的算法名称不是加密算法如aaa,则会报错:Caused by: Java.security.NoSuchAlgorithmException: abc MessageDigest not available

  1. #dataSource configure  
  2. connection.url=jdbc:mysql://localhost:3306/shiro-demo  
  3. connection.username=root  
  4. connection.password=  
  5.   
  6. #druid datasource  
  7. druid.initialSize=10  
  8. druid.minIdle=10  
  9. druid.maxActive=50  
  10. druid.maxWait=60000  
  11. druid.timeBetweenEvictionRunsMillis=60000  
  12. druid.minEvictableIdleTimeMillis=300000  
  13. druid.validationQuery=SELECT 'x'  
  14. druid.testWhileIdle=true  
  15. druid.testOnBorrow=false  
  16. druid.testOnReturn=false  
  17. druid.poolPreparedStatements=true  
  18. druid.maxPoolPreparedStatementPerConnectionSize=20  
  19. druid.filters=wall,stat  
  20.   
  21. #shiro  
  22. password.algorithmName=sha-1  
  23. password.hashIterations=2  

密码加密工具类:
  1. package com.lgy.service;  
  2.   
  3. import org.apache.shiro.crypto.RandomNumberGenerator;  
  4. import org.apache.shiro.crypto.SecureRandomNumberGenerator;  
  5. import org.apache.shiro.crypto.hash.SimpleHash;  
  6. import org.apache.shiro.util.ByteSource;  
  7. import org.springframework.beans.factory.annotation.Value;  
  8. import org.springframework.stereotype.Service;  
  9.   
  10. import com.lgy.model.User;  
  11.       
  12. @Service  
  13. public class PasswordHelper {  
  14.   
  15.     private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();  
  16.   
  17.     @Value("${password.algorithmName}")  
  18.     private String algorithmName;  
  19.     @Value("${password.hashIterations}")  
  20.     private int hashIterations;  
  21.   
  22.     public void encryptPassword(User user) {  
  23.   
  24.         user.setSalt(randomNumberGenerator.nextBytes().toHex());  
  25.   
  26.         String newPassword = new SimpleHash(  
  27.                 algorithmName,           //加密算法  
  28.                 user.getPassword(),      //密码  
  29.                 ByteSource.Util.bytes(user.getCredentialsSalt()),  //salt盐   username + salt  
  30.                 hashIterations   //迭代次数  
  31.                 ).toHex();  
  32.   
  33.         user.setPassword(newPassword);  
  34.     }  
  35. }  

密码中干扰的值是username+salt组成, salt是用RandomNumberGererator随机生成的值。可以自定义,也可以不需要salt这个字段。这样在数据库中生成的数据有:

同样的密码123456,得到的密码值是不一样的!

用户名                                    密码                                                              盐值

admin c4270458aca71740949bead254d6e9fb          228723e1ecce4511f2ff3a02a1a6a57b

feng 2053ad769d326bc6b36f97aac53b72a6a        cf12465e22601b8399439e526499f5c


---------------------------------------------------------------------------解密-----------------------------------------------------------------


shiro框架的解密是通过:HashedCredentialsMatcher实现密码验证服务

a.首先配置自己的realm:     

  1. <!-- Realm实现 -->  
  2. <bean id="userRealm" class="com.lgy.realm.UserRealm">  
  3.     <!-- 密码验证方式 -->  
  4.     <property name="credentialsMatcher" ref="credentialsMatcher"/>  
  5.     <property name="cachingEnabled" value="false"/>  
  6.     <!--<property name="authenticationCachingEnabled" value="true"/>-->  
  7.     <!--<property name="authenticationCacheName" value="authenticationCache"/>-->  
  8.     <!--<property name="authorizationCachingEnabled" value="true"/>-->  
  9.     <!--<property name="authorizationCacheName" value="authorizationCache"/>-->  
  10. </bean>  
  1. <!-- 凭证匹配器 -->  
  2. <bean id="credentialsMatcher" class="com.lgy.credentials.RetryLimitHashedCredentialsMatcher">  
  3.     <constructor-arg ref="cacheManager"/>  
  4.     <property name="hashAlgorithmName" value="sha-1"/>  
  5.     <property name="hashIterations" value="2"/>  
  6.     <property name="storedCredentialsHexEncoded" value="true"/>  
  7. </bean>  

密码验证方式是自定义实现的,RetryLimitHashedCredentialsMatcher实现类如下:
  1. package com.lgy.credentials;  
  2.   
  3. import org.apache.shiro.authc.AuthenticationInfo;  
  4. import org.apache.shiro.authc.AuthenticationToken;  
  5. import org.apache.shiro.authc.ExcessiveAttemptsException;  
  6. import org.apache.shiro.authc.credential.HashedCredentialsMatcher;  
  7. import org.apache.shiro.cache.Cache;  
  8. import org.apache.shiro.cache.CacheManager;  
  9.   
  10. import java.util.concurrent.atomic.AtomicInteger;  
  11. public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {  
  12.   
  13.     private Cache<String, AtomicInteger> passwordRetryCache;  
  14.   
  15.     public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {  
  16.         passwordRetryCache = cacheManager.getCache("passwordRetryCache");  
  17.     }  
  18.   
  19.     @Override  
  20.     public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {  
  21.         String username = (String)token.getPrincipal();  
  22.         //retry count + 1  
  23.         AtomicInteger retryCount = passwordRetryCache.get(username);  
  24.         if(retryCount == null) {  
  25.             retryCount = new AtomicInteger(0);  
  26.             passwordRetryCache.put(username, retryCount);  
  27.         }  
  28.         if(retryCount.incrementAndGet() > 5) {  
  29.             //if retry count > 5 throw  
  30.             throw new ExcessiveAttemptsException();  
  31.         }  
  32.   
  33.         boolean matches = super.doCredentialsMatch(token, info);  
  34.         if(matches) {  
  35.             //clear retry count  
  36.             passwordRetryCache.remove(username);  
  37.         }  
  38.         return matches;  
  39.     }  
  40. }  

这里要注意认证凭证中的2个参数值的设置要与加密时的一致,分别是算法名称)和迭代次数.

userRealm类如下:

  1. @Override  
  2. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {  
  3.     String username = (String)token.getPrincipal();  
  4.     User user = userService.findByUsername(username);  
  5.     if(user == null) {  
  6.         throw new UnknownAccountException();//没找到帐号  
  7.     }  
  8.     if(Boolean.TRUE.equals(user.getLocked())) {  
  9.         throw new LockedAccountException(); //帐号锁定  
  10.     }  
  11.     //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现  
  12.     SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(  
  13.             user.getUsername(), //用户名  
  14.             user.getPassword(), //密码  
  15.             ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt  
  16.             getName()  //realm name  
  17.     );  
  18.     return authenticationInfo;  
  19. }  

通过SimpleAuthenticationInfo将盐值以及用户名和密码信息封装到AuthenticationInfo中,进入证书凭证类中进行校验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值