AES算法加密解密工具类util之改进之动态AES密钥加密
对于AES算法,我想很多博友都知晓是干嘛用的,本博文就不详细介绍了。作为一种常用的加密算法,AES加密解密我觉得要点在于其key(密钥),一般项目应用中,aesKey是固定的。本文将基于传统的aes加密解密的写法,介绍一种“基于redis缓存动态aes密钥”的方法。
顾名思义,动态aes密钥,其实就是使得key动态隔一段在变化,而且又不影响原有存在的密码,即在动态自动更换密钥时,需要使用原有的key进行解密再使用新生成的aesKey进行加密,并将新的aesKey进行存储。
以上即为缓存动态密钥进行加密解密的思路。下面首先介绍一下固定aesKey的写法:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.UUID;
/**
* Created by debug on 2017/10/15.
*/
public class TestUtil {
private static final Logger log= LoggerFactory.getLogger(TestUtil.class);
public static void main(String[] args){
/*EnumSet<SourceEnum> set=EnumSet.allOf(SourceEnum.class);
for(SourceEnum e:set){
log.debug("enum值: {},{} ",e.toString(),e.getCode());
}*/
String aesKey= "36c82834-3fe4-4305-b6e0-39d52e5113d4";
String password="123456";
String resPass=new String(encryptAES(password,aesKey));
log.debug("aesKey={} 加密的结果: {} ",aesKey,resPass);
String srcPass=new String(decryptAES(encryptAES(password,aesKey),aesKey));
log.debug("解密的结果: {} ",srcPass);
}
/**
* AES加密
*
* @param content 需要加密的内容
* @param key 加密密钥
* @return
*/
public static byte[] encryptAES(String content, String key) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128, new SecureRandom(key.getBytes()));
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec keySpec = new SecretKeySpec(enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");// 创建密码器
byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);// 初始化
byte[] result = cipher.doFinal(byteContent);
return result; // 加密
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* AES解密
* @param content 待解密内容
* @param key 解密密钥
* @return
*/
public static byte[] decryptAES(byte[] content, String key) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128, new SecureRandom(key.getBytes()));
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec keySpec = new SecretKeySpec(enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");// 创建密码器
cipher.init(Cipher.DECRYPT_MODE, keySpec);// 初始化
byte[] result = cipher.doFinal(content);
return result; // 加密
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
如果,你只是需要一种比较安全,简单的aes加密解密写法,你可以参考上面的代码即可(不过,还是希望使用前 自己改造一番,比如不要硬编码之类的)
下面就结合“用户注册与登录”的应用场景讲解“缓存动态密钥加密解密”:
(1)首先创建 注册的user表以及动态变化的aesKey表
create table tb_user
(
id int auto_increment
primary key,
username varchar(255) null comment '用户名',
password varchar(255) null comment '密码',
create_time datetime null comment '创建时间',
constraint idx_username
unique (username)
)
;
create table tb_encrypt_key
(
id int auto_increment comment '主键'
primary key,
alg_key varchar(255) not null comment '加密key',
create_time datetime null comment '创建时间'
)
;
(2)逆向生成mapper与model我就不多说了(不懂的话,可以加入后面的羣或者看我以往的博客: mybatis逆向 生成model与mapper工具的博文)
(3)开发UserService与UserController组件
package com.debug.springboot.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
/**
* v1、对key进行Base64编码 作为key - 只是增加了原始key的复杂度
* v2、对明文密码已经处理完成的二进制串进行过Base64编码解码-可以解码
* Created by debug on 2017/10/21.
*/
@Service
public class UserService {
//AES 算法
private static final String Encrypt_Alg="AES";
//加密串时选用的字符编码
private static final String Char_Unicode="UTF-8";
//keyGen位数
private static final Integer Key_Size=128;
private static final Logger log= LoggerFactory.getLogger(UserService.class);
/**
* 加密密码
* @param passwordStr
* @return
*/
public String encryptPassword(String passwordStr,String key){
byte[] encryptBytes=encrypt(passwordStr,key);
String encryptStr=parseByte2HexStr(encryptBytes);
return encryptStr;
}
/**
* 解密密码
* @param passwordHex
* @return
*/
public String decryptPassword(String passwordHex,String key){
byte[] decryptBytes=decrypt(parseHexStr2Byte(passwordHex),key);
String decryptStr=new String(decryptBytes);
return decryptStr;
}
/**
* 加密
* @param content
* @return
*/
private byte[] encrypt(String content,String aesKey) {
try{
KeyGenerator kgen = KeyGenerator.getInstance(Encrypt_Alg);
kgen.init(Key_Size,new SecureRandom(Base64.getEncoder().encode(aesKey.getBytes()))); //v3
kgen.init(Key_Size,new SecureRandom(aesKey.getBytes()));
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(enCodeFormat,Encrypt_Alg);
/**创建密码器**/
Cipher cipher = Cipher.getInstance(Encrypt_Alg);
byte[] byteContent = content.getBytes(Char_Unicode);
/**初始化密码器**/
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] result = cipher.doFinal(byteContent); //v1
//byte[] result = Base64.getEncoder().encode(cipher.doFinal(byteContent)); //v2
return result;
}catch(Exception e) {
log.error("加密发生异常: {} ",content,e.fillInStackTrace());
}
return null;
}
/**
* 解密
* @param content
* @return
*/
private byte[] decrypt(byte[] content,String aesKey) {
try{
KeyGenerator kgen = KeyGenerator.getInstance(Encrypt_Alg);
kgen.init(Key_Size,new SecureRandom(Base64.getEncoder().encode(aesKey.getBytes()))); //--解密不了 v3
kgen.init(Key_Size,new SecureRandom(aesKey.getBytes()));
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(enCodeFormat,Encrypt_Alg);
/**创建密码器**/
Cipher cipher = Cipher.getInstance(Encrypt_Alg);
/**初始化密码器**/
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] result = cipher.doFinal(content); //v1
//byte[] result=cipher.doFinal(Base64.getDecoder().decode(content)); //v2
return result;
}catch(Exception e) {
log.debug("解密过程发生异常: {} ",content,e.fillInStackTrace());
}
return null;
}
/**
* 将二进制转换成十六进制
* @param buf
* @return
*/
private String parseByte2HexStr(byte buf[]) {
StringBuffer sb = new StringBuffer();
for(int i = 0; i < buf.length; i++) {
String hex = Integer.toHexString(buf[i] & 0xFF);
if(hex.length() == 1) {
hex = '0'+ hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
/**
* 将十六进制转换为二进制
* @param hexStr
* @return
*/
private byte[] parseHexStr2Byte(String hexStr) {
if(hexStr.length() < 1) {
return null ;
}else{
byte[] result =new byte[hexStr.length() / 2];
for(int i = 0; i < hexStr.length() / 2; i++) {
int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
int low = Integer.parseInt(hexStr.substring(i * 2 + 1,i * 2 + 2),16);
result[i] = (byte) (high * 16 + low);
}
return result;
}
}
}
上面我还尝试了两种写法,就是加上Base64编码解码增加复杂度而已。
package com.debug.springboot.controller;
import com.debug.springboot.mapper.TbEncryptKeyMapper;
import com.debug.springboot.mapper.TbUserMapper;
import com.debug.springboot.model.TbEncryptKey;
import com.debug.springboot.model.TbUser;
import com.debug.springboot.response.BaseResponse;
import com.debug.springboot.response.Status;
//import com.debug.springboot.utils.AESUtil;
import com.debug.springboot.service.UserService;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* Created by debug on 2017/10/20.
*/
@RestController
@EnableScheduling
public class UserController {
private static final Logger log= LoggerFactory.getLogger(UserController.class);
private static final String prex="user";
@Autowired
private UserService userService;
@Autowired
private TbEncryptKeyMapper keyMapper;
@Autowired
private TbEncryptKeyMapper encryptKeyMapper;
@Autowired
private TbUserMapper userMapper;
@Autowired
private Environment env;
@Autowired
private StringRedisTemplate redisTemplate;
//AES加密需要的key
//@Value("${encrypt.aes.key}")
private String aesKey;
@PostConstruct
public void init() throws Exception{
aesKey=keyMapper.selectNewestKey();
log.debug("当前的key: {} ",aesKey);
}
//此即为动态更换aes密钥并进行缓存的核心代码
@Scheduled(cron = "0 0/45 * * * ?")
@Transactional(rollbackFor = Exception.class)
public void scheduledUpdateKey() throws Exception{
if (!redisTemplate.hasKey(env.getProperty("redis.key"))){
String newKey= UUID.randomUUID().toString();
redisTemplate.opsForValue().set(env.getProperty("redis.key"),newKey,env.getProperty("redis.key.timeout",Long.class), TimeUnit.MINUTES);
TbEncryptKey keyEntity=new TbEncryptKey();
keyEntity.setCreateTime(new Date());
keyEntity.setAlgKey(newKey);
List<TbUser> userAll=userMapper.selectAll();
for(TbUser u:userAll){
log.debug("当前用户:{} key:{} 密码: {} ",u.getUsername(),aesKey,u.getPassword());
String uPass=userService.decryptPassword(u.getPassword(),aesKey);
log.debug("解密: {} ",uPass);
u.setPassword(userService.encryptPassword(uPass,newKey));
userMapper.updateByPrimaryKeySelective(u);
}
log.debug("oldKey={} 已过期, newKey={} ",aesKey,newKey);
aesKey=newKey;
encryptKeyMapper.insertSelective(keyEntity);
log.debug("现在真正的key: {} ",aesKey);
}
}
/**
* 用户注册
* @param userName
* @param password
* @return
* @throws Exception
*/
@RequestMapping(value = prex+"/register",method = RequestMethod.POST)
public BaseResponse register(@RequestParam("userName") String userName,@RequestParam("password") String password) throws Exception{
if (Strings.isNullOrEmpty(userName) || Strings.isNullOrEmpty(password)){
return new BaseResponse(Status.Invalid_Params);
}
if (userMapper.selectByUserName(userName)!=null){
return new BaseResponse(Status.User_Name_Has_Exist);
}
BaseResponse response=new BaseResponse(Status.Success);
try {
TbUser user=new TbUser();
user.setUsername(userName);
//user.setPassword(AESUtil.encryptPassword(password));
user.setPassword(userService.encryptPassword(password,this.aesKey));
user.setCreateTime(new Date());
userMapper.insertSelective(user);
Map<String,Object> dataMap= Maps.newHashMap();
//dataMap.put("pass",AESUtil.decryptPassword(user.getPassword()));
dataMap.put("pass",userService.decryptPassword(user.getPassword(),this.aesKey));
response.setData(dataMap);
}catch (Exception e){
log.error("用户注册发生异常:{},{} ",userName,password,e.fillInStackTrace());
return new BaseResponse(Status.Fail);
}
return response;
}
/**
* 用户登录
* @param userName
* @param password
* @return
* @throws Exception
*/
@RequestMapping(value = prex+"/login",method = RequestMethod.POST)
public BaseResponse login(@RequestParam("userName") String userName,@RequestParam("password") String password) throws Exception{
if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(password)){
return new BaseResponse(Status.Invalid_Params);
}
TbUser user=userMapper.selectByUserName(userName);
if (user==null){
return new BaseResponse(Status.User_Not_Exist);
}
BaseResponse response=new BaseResponse(Status.Success);
try {
String passwordHexStr=user.getPassword();
//String passwordSrcHexStr=AESUtil.encryptPassword(password);
String passwordSrcHexStr=userService.encryptPassword(password,this.aesKey);
log.debug("user的16进制密码串: {} 待比较的16进制密码串:{} ",passwordHexStr,passwordSrcHexStr);
if (passwordSrcHexStr.equals(passwordHexStr)){
Map<String,Object> dataMap=Maps.newHashMap();
dataMap.put("userName",user.getUsername());
//dataMap.put("password",AESUtil.decryptPassword(passwordHexStr));
dataMap.put("password",userService.decryptPassword(passwordHexStr,this.aesKey));
response.setData(dataMap);
}else{
return new BaseResponse(Status.User_Password_Not_Match);
}
}catch (Exception e){
log.error("用户登录发生异常:{},{} ",userName,password,e.fillInStackTrace());
return new BaseResponse(Status.Fail);
}
return response;
}
}
其中,需要使用到的配置文件内容:
#加密 encrypt.aes.key=b6eb4c32-6441-4b63-aae0-6a5cd7a46039 encrypt.aes.timeout=1 redis.key=aes:encrypt:key redis.key.timeout=1
(4)最后,当然是模拟用户登录与注册的postman测试,看看效果:
其中,注册返回的response的data中pass字段其实经过解密后的,可以从这段代码看出:
dataMap.put("pass",userService.decryptPassword(user.getPassword(),this.aesKey));
最后,当然是登录啦:从代码可以看出,登录是对request接收到的password进行加密,然后去数据库对应userName的加密串进行匹配,
如果相同,那就说明用户输入的密码是正确的(这也是目前大多数应用中“登录”逻辑的写法)
好了,没什么介绍的了,总结一下:其实这种写法,在基于传统的固定key的安全写法上,变得更为安全,代码中隔一段时间更换
key的写法我觉得才是核心所在,实际应用中,你可以设置为一个月或者两个月动态更换一次,这个具体可以根据业务来定。最后
晒一下我经常变换的aesKey的记录表:
就先介绍到这里吧,如果有问题可以加入羣讨论: java开源技术交流:583522159 鏖战八方(开源羣):391619659