介绍
在涉及到密码或者隐私信息的存储问题上,应该对隐私数据进行加密或者摘要存储,而不是存储敏感信息。比如账号或者隐私信息泄露,都会造成不可逆的影响,因此我们在编码或者存储数据的过程中需要对隐私数据进行不可逆的加密方式进行存储,对密码我们需要进行不可逆的加密存储,对于需要还原的隐私数据需要进行编码解码的操作。 在Shiro框架中提供了 Base64 和 16进制的编码与解码的操作API,可以满足我们日常的工作需求,对于加密提供了散列加密算法API。
编码/解码
Base64
shiro提供了Base64对字节数组的编码解码API,可以将字节数据编码成字符串或者字节数组。
String str = "hello";
byte[] base64Encoded1 = Base64.encode(str.getBytes());
String base64Encoded2 = Base64.encodeToString(str.getBytes());
String str1 = Base64.decodeToString(base64Encoded1);
String str2 = Base64.decodeToString(base64Encoded2);
16进制
String str = "hello";
byte[] hex16Encode1 = Hex.encode(str.getBytes());
String hex16Encode2 = Hex.encodeToString(str.getBytes());
String str1 = new String(Hex.decode(hex16Encode1.getBytes()));
String str2 = new String(Hex.decode(hex16Encode2.getBytes()));
散列算法
散列算法的宗旨就是:构造冲突较低的散列地址,保证散列表中数据的离散度。 散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如 MD5、SHA 等。一般进行散列时最好提供一个 salt(盐)和加密次数(也就是Hash的次数来减少密码被破解的概率),但是使用散列算法对数据进行散列加密并不是绝对安全的,可以到一些 md5 解密网站很容易的通过散列值得到原始数据,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如设置salt和加密次数;这样散列的要素会变多,这样生成的散列值相对来说更难破解。
Shiro中提供了通用的散列支持,可以自定义散列算法,原始数据,salt,散列次数
String str = "admin";
String salt = "123456";
//进行散列加密
String simpleHash = new SimpleHash("SHA-1", str, salt, 4).toString();
为了方便使用,Shiro 提供了 HashService,默认提供了 DefaultHashService 实现。
DefaultHashService hashService = new DefaultHashService(); //默认算法SHA-512
hashService.setHashAlgorithmName("SHA-1"); 可以设置算法
hashService.setPrivateSalt(new SimpleByteSource("123456")); //私盐,默认无
hashService.setGeneratePublicSalt(true);//是否生成公盐,默认false
hashService.setRandomNumberGenerator(new SecureRandomNumberGenerator());//用于生成公盐。默认就这个
hashService.setHashIterations(2); //生成Hash值的迭代次数
HashRequest request = new HashRequest.Builder()
.setAlgorithmName("MD5")
.setSource(ByteSource.Util.bytes("hello"))
.setSalt(ByteSource.Util.bytes("123"))
.setIterations(2)
.build();
String hex = hashService.computeHash(request).toHex();
使用逻辑:
- 首先创建一个 DefaultHashService,默认使用 SHA-512 算法;
- 以通过 hashAlgorithmName 属性修改算法;
- 可以通过 privateSalt 设置一个私盐,其在散列时自动与用户传入的公盐混合产生一个新盐;
- 可以通过 generatePublicSalt 属性在用户没有传入公盐的情况下是否生成公盐;
- 可以设置 randomNumberGenerator 用于生成公盐;
- 可以设置 hashIterations 属性来修改默认加密迭代次数;
- 需要构建一个 HashRequest,传入算法、数据、公盐、迭代次数。
加密解密
Shiro 提供了 PasswordService 及 CredentialsMatcher 用于提供加密密码及验证密码服务。
public interface PasswordService {
//输入明文密码得到密文密码
String encryptPassword(Object plaintextPassword) throws IllegalArgumentException;
}
public interface CredentialsMatcher {
//匹配用户输入的token的凭证(未加密)与系统提供的凭证(已加密)
boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info);
}
上面是Shiro定义的接口,如果我们需要自己实现密码加密验证
配置加密算法
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 设置加密算法
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
// 设置散列次数
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
在Reaml中使用
在Ream中使用自定义的加密算法
@Bean
public Realm myRealm(){
JwtRealm jwtRealm = new JwtRealm();
// 使用自定义的加密算法
jwtRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return jwtRealm;
}
使用自定义加密算法进行加密对比
@Override
protected AuthenticationInfo doGetAuthenticationInfo (Authenticationloken authenticationloken) throw AuthenticationException(
/认证Token转换为常用的用户名密码Token
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken:ShiroUser shiroUser = shiroUserService. findByUserName(token. getUsername ());
if(shiroUser == null) (
return null;)
//进行验证,将正确数据给shiro处理 传入 用户名、密码、盐、Reaml名称
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(shiroUser
shiroUser.getPassword(),
ByteSource.Util.bvtes(shiroUser.getSalt )),
this.getName());
//认证成功,返回一个封装后的简单认证信息对象,失败抛出AuthenticationException异常
return authenticationInfo;