AES算法加密解密工具类util之改进之动态AES密钥加密

来源:互联网 发布:软件自动化测试工具 编辑:程序博客网 时间:2024/06/14 00:15

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_incrementprimary key,username varchar(255) null comment '用户名',password varchar(255) null comment '密码',create_time datetime null comment '创建时间',constraint idx_usernameunique (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. */@Servicepublic 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@EnableSchedulingpublic 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-6a5cd7a46039encrypt.aes.timeout=1redis.key=aes:encrypt:keyredis.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

原创粉丝点击