Java实现AES加密

简介: Java实现AES加密

1 前言

基于合规、外部网络攻击等环境要求,需要企业梳理敏感数据,有选择性地加密敏感字段内容、脱敏前端敏感字段内容,保证数据库敏感数据的安全、前端页面的数据防泄露。敏感数据以密文的形式存储,能保证即使在存储介质被窃听或者数据文件被非法复制的情况下,敏感数据仍然是安全。

2 技术选型

对称加密 or 非对称加密

在对称加密中,加密和解密使用的是同一份密钥。而非对称加密中,加密和解密使用的是不同的密钥 非对称加密中的密钥分为公钥和私钥。任何人都可以通过公钥进行信息加密,但是只有用户私钥的人才能完成信息解密。

本次功能是向所有端提供公共sdk,加密和解密使用者在同一端,所以选择对称加密,AES是对称加密的典型工具,本次选择AES加密工具

3 AES加密

### AES加密简介

AES加密算法是密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。

AES 是一个新的可以用于保护电子数据的加密算法。明确地说,AES 是一个迭代的、对称密钥分组的密码,它可以使用128、192 和 256 位密钥,并且用 128 位(16字节)分组加密和解密数据。与公共密钥密码使用密钥对不同,对称密钥密码使用相同的密钥加密和解密数据。通过分组密码返回的加密数据 的位数与输入数据相同。迭代加密使用一个循环结构,在该循环中重复置换(permutations )和替换(substitutions)输入数据。

AES加密有很多轮的重复和变换。大致步骤如下:

  1. 密钥扩展(KeyExpansion)。
  2. 初始轮(Initial Round)。
  3. 重复轮(Rounds),每一轮又包括:SubBytes、ShiftRows、MixColumns、AddRoundKey。
  4. 最终轮(Final Round),最终轮没有MixColumns。

AES128 vs AES256

AES128AES256区别是,AES的性能更快,因为底层是迭代加密原理,AES128的迭代次数AES256少,速度更快,而AES256的安全性相对更好

在早期JDK版本中,由于受美国的密码出口条例约束,Java中涉及加解密功能的API被限制出口,jdk1.8.99版本后才允许AES256的使用,如果使用AES256加密,如果集成AES jar包的项目jdk版本低于jdk1.8.99那么就要对jdk进行修改,所以本次选择AES128

4 AES加密实现

public class AESUtils {

    private static Logger log = LoggerFactory.getLogger(AESUtils.class);

    private static final String encodeRules = "dst-aes";
    private static final String AES = "AES";
    private static final String SHA_1_PRNG = "SHA1PRNG";
    private static final String UTF_8 = "utf-8";

    /**
     * 加密
     * 1.构造密钥生成器
     * 2.根据ecnodeRules规则初始化密钥生成器
     * 3.产生密钥
     * 4.创建和初始化密码器
     * 5.内容加密
     * 6.返回字符串
     */
    public static String AESEncode(String content) {
        try {
            KeyGenerator keygen = KeyGenerator.getInstance(AES);
            SecureRandom random = SecureRandom.getInstance(SHA_1_PRNG);
            random.setSeed(encodeRules.getBytes());
            keygen.init(128, random);
            SecretKey original_key = keygen.generateKey();
            byte[] raw = original_key.getEncoded();
            SecretKey key = new SecretKeySpec(raw, AES);
            Cipher cipher = Cipher.getInstance(AES);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] byte_encode = content.getBytes(UTF_8);
            byte[] byte_AES = cipher.doFinal(byte_encode);
            String AES_encode = Base64.getEncoder().encodeToString(byte_AES);
            return AES_encode;
        } catch (Exception e) {
            log.error("加密数据失败,异常信息",e);
            throw new RuntimeException(e);
        }
    }




    /**
     * 解密
     * 解密过程:
     * 1.同加密1-4步
     * 2.将加密后的字符串反纺成byte[]数组
     * 3.将加密内容解密
     */
    public static String AESDecode(String content) {
        try {
            KeyGenerator keygen = KeyGenerator.getInstance(AES);
            SecureRandom random = SecureRandom.getInstance(SHA_1_PRNG);
            random.setSeed(encodeRules.getBytes());
            keygen.init(128, random);
            SecretKey original_key = keygen.generateKey();
            byte[] raw = original_key.getEncoded();
            SecretKey key = new SecretKeySpec(raw, AES);
            Cipher cipher = Cipher.getInstance(AES);
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] byte_content = new BASE64Decoder().decodeBuffer(content);
            byte[] byte_decode = cipher.doFinal(byte_content);
            String AES_decode = new String(byte_decode, UTF_8);
            return AES_decode;
        } catch (Exception e) {
            log.error("解密数据失败,异常信息",e);
            throw new RuntimeException(e);
        }
    }
}

实现比较简单,首先是输入种子,根据AES算法生成秘钥,再根据秘钥加密数据就可以了,解密时也一样,测试一下

public static void main(String[] args) {

    // 1 身份证号加密

    System.*out*.println("未加密身份证号/211011199202292013");

    String enCodeIdnumber = AESUtils.*AESEncode*("211011199202292013");

    System.*out*.println("加密后身份证号/" + enCodeIdnumber +"/位数" + enCodeIdnumber.length());

    String decodeIdnumber = AESUtils.*AESDecode*(enCodeIdnumber);

    System.*out*.println("解密后身份证号/" + decodeIdnumber);

    // 2 手机号加密

    System.*out*.println("未加密手机号/18218424921");

    String enCodeIdnumber = AESUtils.*AESEncode*("18218424921");

    System.*out*.println("加密后手机号/" + enCodeIdnumber +"/位数" + enCodeIdnumber.length());

    String decodeIdnumber = AESUtils.*AESDecode*(enCodeIdnumber);

    System.*out*.println("解密后手机号/" + decodeIdnumber);

    // 3 银行卡号加密

    System.*out*.println("未加密银行卡号/6217902000023951234");

    String enCodeIdnumber = AESUtils.*AESEncode*("6217902000023951234");

    System.*out*.println("加密后银行卡号/" + enCodeIdnumber +"/位数" + enCodeIdnumber.length());

    String decodeIdnumber = AESUtils.*AESDecode*(enCodeIdnumber);

    System.*out*.println("解密后银行卡号/" + decodeIdnumber);

}

控制台结果

未加密身份证号/211011199202292013

加密后身份证号/Go5Kb2L515RN1OkuRoiWgiFQV8oq57P74pa/do91WXo=:c2pLTFkwMDAwMDAwMDAwMA==/位数69

解密后身份证号/211011199202292013

未加密手机号/18218424921

加密后手机号/271M5stznel/jKhSGagjmQ==:c2pLTFkwMDAwMDAwMDAwMA==/位数49

解密后手机号/18218424921

未加密银行卡号/6217902000023951234

加密后银行卡号/knbBiyfYbAd1hmyUZwiq6mn0CAxuTlPBSG509j7jvbI=:c2pLTFkwMDAwMDAwMDAwMA==/位数69

解密后银行卡号/6217902000023951234

这时候就实现了AES的加密,但是还没结束,公司的不止java团队还有PHP团队,他们也要用AES加密,且要和java这边的加密解密保持一致,由于双方不清楚对方的代码,所以对接起来有些困难,而且AES加密有很多种,比如AES-128-ECB,AES-128-CBC等,最终为了兼容PHP端,将实现改为了AES-128-CBC方式

5 兼容PHP加密

实现AES-128-CBC加密

/**
 * @Description: AES加密类 128 CBC位加密
 * @author: qianyun
 * @date: 2022/6/1 14:19
 */
public class AESUtils {

    private static String CIPHER_NAME = "AES/CBC/PKCS5PADDING";

    /**
     * 128 bits
    */
    private static int CIPHER_KEY_LEN = 16;
    private static Logger log = LoggerFactory.getLogger(AESUtils.class);

    private static final String AES = "AES";
    private static final String UTF_8 = "utf-8";

    static {
        AESConfig.readConfigFile();
    }


    /**
     * AES加密数据
     * @author yangjiawen
     * @date 2022/6/6 10:18
     * @param strToEncrypt: 加密字符串
     * @return String
    */
    public static String encrypt(String strToEncrypt) {
        String key = AESConfig.key;
        String iv = key.substring(0,5);
        iv += "00000000000";
        try {
            IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(UTF_8));
            SecretKeySpec secretKey = new SecretKeySpec(fixKey(key).getBytes(UTF_8), AES);
            Cipher cipher = Cipher.getInstance(AESUtils.CIPHER_NAME);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
            byte[] encryptedData = cipher.doFinal((strToEncrypt.getBytes()));
            String encryptedDataInBase64 = Base64.getEncoder().encodeToString(encryptedData);
            String ivInBase64 = Base64.getEncoder().encodeToString(iv.getBytes(UTF_8));
            return encryptedDataInBase64 + ":" + ivInBase64;
        } catch (Exception e) {
            log.error("加密数据失败,异常信息",e);
            throw new RuntimeException(e);
        }
    }

    private static String fixKey(String key) {
        if (key.length() < AESUtils.CIPHER_KEY_LEN) {
            int numPad = AESUtils.CIPHER_KEY_LEN - key.length();
            for (int i = 0; i < numPad; i++) {
                //0 pad to len 16 bytes
                key += "0";
            }
            return key;
        }
        if (key.length() > AESUtils.CIPHER_KEY_LEN) {
            //truncate to 16 bytes
            return key.substring(0, CIPHER_KEY_LEN);
        }
        return key;
    };

    /**
     * AES解密数据
     * @author yangjiawen
     * @date 2022/6/6 10:19
     * @param data: 加密后数据
     * @return String
    */
    public static String decrypt(String data) {
        String key = AESConfig.key;
        try {
            String[] parts = data.split(":");
            IvParameterSpec iv = new IvParameterSpec(Base64.getDecoder().decode(parts[1]));
            SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(UTF_8), AES);
            Cipher cipher = Cipher.getInstance(AESUtils.CIPHER_NAME);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
            byte[] decodedEncryptedData = Base64.getDecoder().decode(parts[0]);
            byte[] original = cipher.doFinal(decodedEncryptedData);
            return new String(original);
        } catch (Exception e) {
            log.error("解密数据失败,异常信息",e);
            throw new RuntimeException(e);
        }
    }


}

经过测试,双方的秘钥相同,加密后结果相同,这样一个AES加密就完成了。

6 结语

随着信息技术的发展,数据安全越来越重要,一些敏感数据还是需要加密的方式来存储,所以对一些加密技术进行一些了解很有好处,对称加密和非对称加密在银行等项目中是经常被使用的技术,跨语言的加解密也是一个经常遇到的问题,实现很简单,因为很多实现细节都已经封装好了,文章没有写的很深入,如果有兴趣可以研究研究底层。

相关文章
|
3天前
|
存储 安全 数据安全/隐私保护
打造安全防线!Python AES&RSA加密工具,黑客绕道走的秘籍
【9月更文挑战第9天】随着数字化时代的到来,信息安全问题日益凸显。本文将介绍如何使用Python结合AES与RSA两种加密算法,构建强大的加密工具。AES以其高效性和强安全性著称,适用于大量数据的快速加密;RSA作为非对称加密算法,在加密小量数据及实现数字签名方面表现卓越。通过整合两者,可以构建既安全又灵活的加密系统。首先,需要安装pycryptodome库。接着,实现AES加密与解密功能,最后利用RSA加密AES密钥,确保其安全传输。这种设计不仅提高了数据传输效率,还增强了密钥交换的安全性,为敏感数据提供坚实保护。
128 43
|
2天前
|
安全 算法 数据安全/隐私保护
深度揭秘!Python加密技术的背后,AES与RSA如何守护你的数据安全
【9月更文挑战第10天】随着数字化时代的到来,数据安全成为企业和个人面临的重大挑战。Python 作为功能强大的编程语言,在数据加密领域扮演着重要角色。AES 和 RSA 是两种主流加密算法,分别以对称和非对称加密方式保障数据安全。AES(Advanced Encryption Standard)因其高效性和安全性,在数据加密中广泛应用;而 RSA 则利用公钥和私钥机制,在密钥交换和数字签名方面表现卓越。
13 3
|
2天前
|
安全 Java 数据安全/隐私保护
- 代码加密混淆工具-Java 编程安全性
在Java编程领域,保护代码安全与知识产权至关重要。本文探讨了代码加密混淆工具的重要性,并介绍了五款流行工具:ProGuard、DexGuard、Jscrambler、DashO 和 Ipa Guard。这些工具通过压缩、优化、混淆和加密等手段,提升代码安全性,保护知识产权。ProGuard 是开源工具,用于压缩和混淆Java代码;DexGuard 专为Android应用程序设计,提供强大加密功能;Jscrambler 基于云,保护Web和移动应用的JavaScript及HTML5代码;DashO 支持多种Java平台和
16 1
|
4天前
|
安全 数据安全/隐私保护 Python
情书也能加密?Python AES&RSA,让每一份数据都充满爱的密码
【9月更文挑战第8天】在这个数字化时代,情书不再局限于纸笔,也可能以电子形式在网络中传递。为了确保其安全,Python提供了AES和RSA等加密工具,为情书编织爱的密码。首先,通过安装pycryptodome库,我们可以利用AES对称加密算法高效保护数据;接着,使用RSA非对称加密算法加密AES密钥和IV,进一步增强安全性。即使情书被截获,没有正确密钥也无法解读内容。让我们用Python为爱情编织一张安全的网,守护每份珍贵情感。
15 2
|
11天前
|
安全 Java 应用服务中间件
网络安全的护城河:漏洞防御与加密技术深入浅出Java并发编程
【8月更文挑战第31天】在数字世界的棋盘上,每一次点击都可能是一步棋。网络安全的战场无声却激烈,漏洞如同裂缝中的风,悄无声息地侵袭着数据的堡垒。本文将揭示网络漏洞的隐蔽角落,探讨如何通过加密技术筑起防线,同时提升个人和组织的安全意识,共同守护我们的数字家园。
|
17天前
|
JavaScript 数据安全/隐私保护 Python
网易云音乐搜索接口JS逆向: Params、encSecKey加密和AES实战
网易云音乐搜索接口JS逆向: Params、encSecKey加密和AES实战
20 4
|
存储 算法 前端开发
一文带你学会国产加密算法SM4的java实现方案
今天给大家带来一个国产SM4加密解密算法的java后端解决方案,代码完整,可以直接使用,希望给大家带来帮助,尤其是做政府系统的开发人员,可以直接应用到项目中进行加密解密。
2855 1
|
8天前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
64 6
【Java学习】多线程&JUC万字超详解
|
2天前
|
Java 调度 开发者
Java并发编程:深入理解线程池
在Java的世界中,线程池是提升应用性能、实现高效并发处理的关键工具。本文将深入浅出地介绍线程池的核心概念、工作原理以及如何在实际应用中有效利用线程池来优化资源管理和任务调度。通过本文的学习,读者能够掌握线程池的基本使用技巧,并理解其背后的设计哲学。
|
1天前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。