一、前言

常见的加解密算法中存在AES(对称加解密算法)和RSA(非对称加解密算法)两种。简单来说对称加密就是加密的秘钥与解密的秘钥使用的为相同的一个,对称加密解密的秘钥不能够公开,非对称加密的加密秘钥与解密秘钥不同,加密秘钥可以公开,而解密秘钥不能公开。一般来说对称加密的加解密速度比非对称加解密的速度要快。
以下摘自百度百科的一些介绍

1、AES的介绍:

对称/分组秘钥分为流加密(如OFB、CFB等)和块加密(如ECB、CBC等)。对于块加密(分组加密)如果要加密超过块大小的数据,还涉及到填充和链加密模式。

2、ECB、CBC模式介绍

(1)ECB(Electronic Code Book电子密码本)模式
ECB模式是最早采用和最简单的模式,它将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密。
优点:
1)简单
2)有利于并行计算
3)误差不会被传送
缺点:
1)不能隐藏明文
2)可能对明文进行主动攻击
此模式适用于加密小消息。
(2)CBC(Cipher Block Chaining,加密块链)模式
优点:
1)不容易主动攻击,安全性好于ECB
2)适合创术长消息,是SSL、IPSec的标准
缺点:
1)不利于并行计算
2)误差传递,一个明文单元损坏影响多个单元
3)唯一的IV(偏移量)

二、Java代码

1、有指定的秘钥和偏移量

(1)程序代码

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;

/**
 * @author Jacob
 */
public class AesTestOne {

  /**
   * AES 对称加密(RSA非对称加密)
   * CBC 有向量 (ECB 无向量)
   * PKCS5Padding 填充模式(NoPadding 无填充)
   */
  private static final String ALG_AES_CBC_PKCS5 = "AES/CBC/PKCS5Padding";

  private static final String ALGORITHM = "AES";

  private static final Charset UTF8 = StandardCharsets.UTF_8;

  private String aesKey = "12e476beac1a4g20";  // 指定好的秘钥,非Base64和16进制

  private String aesIv = "2e119e58a526bc64";   // 偏移量

  private SecretKeySpec skeySpec;

  private IvParameterSpec iv;

  /**
   * 解密方法
   * @param cipherStr Base64编码的加密字符串
   * @return 解密后的字符串(UTF8编码)
   * @throws Exception 异常
   */
  public String decrypt(String cipherStr) throws Exception{
    // step 1 获得一个密码器
    Cipher cipher = Cipher.getInstance(ALG_AES_CBC_PKCS5);
    // step 2 初始化密码器,指定是加密还是解密(Cipher.DECRYPT_MODE 解密; Cipher.ENCRYPT_MODE 加密)
    // 加密时使用的盐来够造秘钥对象
    skeySpec = new SecretKeySpec(aesKey.getBytes(),ALGORITHM);
    // 加密时使用的向量,16位字符串
    iv = new IvParameterSpec(aesIv.getBytes());
    cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
    // 对加密报文进行base64解码
    byte[] encrypted1 = Base64.decodeBase64(cipherStr);
    // 解密后的报文数组
    byte[] original = cipher.doFinal(encrypted1);
    // 输出utf8编码的字符串,输出字符串需要指定编码格式
    return new String(original, UTF8);
  }

  /**
   * 加密
   * @param plainText 明文
   * @return Base64编码的密文
   * @throws Exception  加密异常
   */
  public String encrypt(String plainText) throws Exception{
    Cipher cipher = Cipher.getInstance(ALG_AES_CBC_PKCS5);
    skeySpec = new SecretKeySpec(aesKey.getBytes(),ALGORITHM);
    iv = new IvParameterSpec(aesIv.getBytes());
    cipher.init(Cipher.ENCRYPT_MODE, skeySpec,iv);
    // 这里的编码格式需要与解密编码一致
    byte [] encryptText = cipher.doFinal(plainText.getBytes(UTF8));
    return Base64.encodeBase64String(encryptText);
  }

  public static void main(String[] args) {
    AesTestOne testOne = new AesTestOne();
    String plainText = "明文报文,进行对称AES算法加密传输";
    String cipherStr;
    try {
      System.out.println("被加解密的报文:[ " + plainText + " ]");
      cipherStr = testOne.encrypt(plainText);
      System.out.println("AES 加密后的Base64报文:[ " + cipherStr + "]");
      System.out.println("对加密后的报文解密后的明文为:[ " + testOne.decrypt(cipherStr) + " ]");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

(2)程序远行结果

在这里插入图片描述

(3)说明

  • 如果是通过KeyGenerator生成的秘钥的话,他返回的是一个字节数组,需要进行Base64或者转换为16进制进行保存(或者将字节数组存放到文件或者数据库中,但不具有一般可读性)

  • Cipher 提供了多种方式的加密,通过Cipher.getInstance(“AES/CBC/PKCS5Padding”);参数按照 算法、模式、添加模式的方式来获取密码器。

  • 常见构造密码器参数

常见参数位数
AES/CBC/NoPadding128
AES/CBC/PKCS5Padding128
AES/ECB/NoPadding128
AES/ECB/PKCS5Padding128
DES/CBC/NoPadding56
DES/CBC/PKCS5Padding56
DES/ECB/NoPadding56
DES/ECB/PKCS5Padding56
DESede/CBC/NoPadding168
DESede/CBC/PKCS5Padding168
DESede/ECB/NoPadding168
DESede/ECB/PKCS5Padding168
RSA/ECB/PKCS1Padding2024 2048
RSA/ECB/OAEPWithSHA-1AndMGF1Padding1024 2048
RSA/ECB/OAEPWithSHA-256AndMGF1Padding1024 2048

2、使用生成的固定秘钥进行加解密

(1)生成固定秘钥

package cn.zlc.des.main;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import org.apache.commons.codec.binary.Base64;

/**
 * @author Jacob
 */
public class AesTestTwo {

  private static final String ALGORITHM = "AES";

  private static final Charset UTF8 = StandardCharsets.UTF_8;

  public SecretKey geneKey() throws Exception {
    KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
    SecureRandom random = new SecureRandom();
    // 固定种子,每次生成的秘钥不会变
    random.setSeed("690303".getBytes());
    // 指定长度为128{128(24),256(32)}
    keyGenerator.init(128, random);
    // 随机源,每次生成的秘钥都会变,需要保存秘钥
    //keyGenerator.init(new SecureRandom());
    return keyGenerator.generateKey();
  }

  public static void main(String[] args) {
    AesTestTwo aesTestTwo = new AesTestTwo();
    try {
       // 对秘钥进行了Base64编码
       String base64AesKey = Base64.encodeBase64String(aesTestTwo.geneKey().getEncoded());
       System.out.println("生成的秘钥为[ " + base64AesKey + " ]");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

或者使用下面的方式

public SecretKey geneKey() throws Exception {
    KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
    // 固定种子,每次生成的秘钥不会变
    // 指定长度为128{128(24),256(32)}如果不指定长度,默认128
    keyGenerator.init(new SecureRandom("690303".getBytes()));
    // 随机源,每次生成的秘钥都会变,需要保存秘钥
    //keyGenerator.init(new SecureRandom());
    return keyGenerator.generateKey();
  }

生成的Base64秘钥为:

HmA2k7mq6xguhdsL5CpExQ==

(2)使用生成固定的秘钥进行加解密

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;

/**
 * @author Jacob
 */
public class AesTestTwo {

  private static final String ALG_AES_CBC_PKCS5 = "AES/CBC/PKCS5Padding";

  private static final String ALGORITHM = "AES";

  private static final Charset UTF8 = StandardCharsets.UTF_8;

  public SecretKey geneKey() throws Exception {
    KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
    // 固定种子,每次生成的秘钥不会变
    // 指定长度为128{128(24),256(32)}如果不指定长度,默认128
    keyGenerator.init(new SecureRandom("690303".getBytes()));
    // 随机源,每次生成的秘钥都会变,需要保存秘钥
    //keyGenerator.init(new SecureRandom());
    return keyGenerator.generateKey();
  }

  /**
   * 解密方法
   * @param cipherStr Base64编码的加密字符串
   * @return 解密后的字符串(UTF8编码)
   * @throws Exception 异常
   */
  public String decrypt(String cipherStr) throws Exception{
    // step 1 获得一个密码器
    Cipher cipher = Cipher.getInstance(ALGORITHM);
    // step 2 初始化密码器,指定是加密还是解密(Cipher.DECRYPT_MODE 解密; Cipher.ENCRYPT_MODE 加密)
    AesTestTwo aesTestTwo = new AesTestTwo();
    // 初始化密码器
    cipher.init(Cipher.DECRYPT_MODE, aesTestTwo.geneKey());
    // 对加密报文进行base64解码
    byte[] encrypted1 = Base64.decodeBase64(cipherStr);
    // 解密后的报文数组
    byte[] original = cipher.doFinal(encrypted1);
    // 输出utf8编码的字符串,输出字符串需要指定编码格式
    return new String(original, UTF8);
  }

  /**
   * 加密
   * @param plainText 明文
   * @return Base64编码的密文
   * @throws Exception  加密异常
   */
  public String encrypt(String plainText) throws Exception{
    Cipher cipher = Cipher.getInstance(ALGORITHM);
    // 如果没有指定向量,解密也不能指定向量(偏移量)
    AesTestTwo aesTestTwo = new AesTestTwo();
    cipher.init(Cipher.ENCRYPT_MODE, aesTestTwo.geneKey());
    // 这里的编码格式需要与解密编码一致
    byte [] encryptText = cipher.doFinal(plainText.getBytes(UTF8));
    return Base64.encodeBase64String(encryptText);
  }
  
  public static void main(String[] args) {
    AesTestTwo aesTestTwo = new AesTestTwo();
    String plainText = "明文报文,进行对称AES算法加密传输";
    String cipherStr;
    try {
       // 对秘钥进行了Base64编码
       String base64AesKey = Base64.encodeBase64String(aesTestTwo.geneKey().getEncoded());
       System.out.println("生成的秘钥为[ " + base64AesKey + " ]");
       System.out.println("被加解密的报文:[ " + plainText + " ]");
       cipherStr = aesTestTwo.encrypt(plainText);
       System.out.println("AES 加密后的Base64报文:[ " + cipherStr + "]");
       System.out.println("对加密后的报文解密后的明文为:[ " + aesTestTwo.decrypt(cipherStr) + " ]");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

程序运行结果如下:
在这里插入图片描述

(3)说明

  • 如果使用AES/CBC/PKCS5Padding ,需要指定一个向量
  • KeyGenerator如果使用固定的随机源,则每次生成的秘钥都是一样的

3、每次生成一个新的秘钥进行加解密

(1)生成秘钥

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import org.apache.commons.codec.binary.Base64;

/**
 * @author Jacob
 */
public class AesTestThree {

  private static final String ALGORITHM = "AES";

  private static final Charset UTF8 = StandardCharsets.UTF_8;

  public SecretKey geneKey() throws Exception {
    KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
    // 指定长度为128{128(24),256(32)}如果不指定长度,默认128
    keyGenerator.init(new SecureRandom());
    return keyGenerator.generateKey();
  }

  public static void main(String[] args) {
    AesTestThree aesTestThree = new AesTestThree();
    try {
      // 对秘钥进行了Base64编码
      String base64AesKey = Base64.encodeBase64String(aesTestThree.geneKey().getEncoded());
      System.out.println("生成的秘钥为[ " + base64AesKey + " ]");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

远行两次main方式,第一次生成的秘钥Base64与第二次生成的Base64秘钥不一样

2mqQuBmuKBZvNPsxS7uxfw==
fLM8degdiHb1jZMv62E+tw==

(2)添加保存和读取秘钥的方法

(可以保存在数据库或者文件中)

/**
   * 保存 AesKey 方法(可实现自己的处理逻辑)
   * @param secretKey 秘钥
   * @throws IOException IO异常
   */
  public void savaAesKey(SecretKey secretKey) throws IOException {
    File aesKeyFilesPath = new File("D://aesKeys");
    aesKeyFilesPath.mkdirs();
    File aesKeyFile = new File(aesKeyFilesPath, "aes.key");
    if (! aesKeyFile.exists()){
      aesKeyFile.createNewFile();
    }
    Path aesKeyFilePath = Paths.get(aesKeyFile.getAbsolutePath());
    Files.write(aesKeyFilePath, secretKey.getEncoded());
  }

  /**
   * 读取文件中的AesKey
   * @param filePath 秘钥文件路径
   * @return Aes秘钥
   * @throws IOException IO异常
   */
  public SecretKey readSecretKeyFromFile(String filePath) throws IOException {
    Path path = Paths.get(filePath);
    byte [] aesKeyBytes = Files.readAllBytes(path);
    return new SecretKeySpec(aesKeyBytes, ALGORITHM);
  }

(3)使用保存在文件中的秘钥进行加解密

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;

/**
 * @author Jacob
 */
public class AesTestThree {

  private static final String ALGORITHM = "AES";

  private static final Charset UTF8 = StandardCharsets.UTF_8;

  public SecretKey geneKey() throws Exception {
    KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
    // 指定长度为128{128(24),256(32)}如果不指定长度,默认128
    keyGenerator.init(new SecureRandom());
    return keyGenerator.generateKey();
  }

  /**
   * 保存 AesKey 方法(可实现自己的处理逻辑)
   * @param secretKey 秘钥
   * @throws IOException IO异常
   */
  public void savaAesKey(SecretKey secretKey) throws IOException {
    File aesKeyFilesPath = new File("D://aesKeys");
    aesKeyFilesPath.mkdirs();
    File aesKeyFile = new File(aesKeyFilesPath, "aes.key");
    if (! aesKeyFile.exists()){
      aesKeyFile.createNewFile();
    }
    Path aesKeyFilePath = Paths.get(aesKeyFile.getAbsolutePath());
    Files.write(aesKeyFilePath, secretKey.getEncoded());
  }

  /**
   * 读取文件中的AesKey
   * @param filePath 秘钥文件路径
   * @return Aes秘钥
   * @throws IOException IO异常
   */
  public SecretKey readSecretKeyFromFile(String filePath) throws IOException {
    Path path = Paths.get(filePath);
    byte [] aesKeyBytes = Files.readAllBytes(path);
    return new SecretKeySpec(aesKeyBytes, ALGORITHM);
  }

  /**
   * 解密方法
   * @param cipherStr Base64编码的加密字符串
   * @return 解密后的字符串(UTF8编码)
   * @throws Exception 异常
   */
  public String decrypt(String cipherStr) throws Exception{
    // step 1 获得一个密码器
    Cipher cipher = Cipher.getInstance(ALGORITHM);
    // step 2 初始化密码器,指定是加密还是解密(Cipher.DECRYPT_MODE 解密; Cipher.ENCRYPT_MODE 加密)
    AesTestThree aesTestThree = new AesTestThree();
    // 初始化密码器
    cipher.init(Cipher.DECRYPT_MODE, aesTestThree.readSecretKeyFromFile("D:\\aesKeys\\aes.key"));
    // 对加密报文进行base64解码
    byte[] encrypted1 = Base64.decodeBase64(cipherStr);
    // 解密后的报文数组
    byte[] original = cipher.doFinal(encrypted1);
    // 输出utf8编码的字符串,输出字符串需要指定编码格式
    return new String(original, UTF8);
  }

  /**
   * 加密
   * @param plainText 明文
   * @return Base64编码的密文
   * @throws Exception  加密异常
   */
  public String encrypt(String plainText) throws Exception{
    Cipher cipher = Cipher.getInstance(ALGORITHM);
    AesTestThree aesTestThree = new AesTestThree();
    SecretKey secretKey = aesTestThree.geneKey();
    // 保存一下密码
    aesTestThree.savaAesKey(secretKey);
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);
    // 这里的编码格式需要与解密编码一致
    byte [] encryptText = cipher.doFinal(plainText.getBytes(UTF8));
    return Base64.encodeBase64String(encryptText);
  }

  public static void main(String[] args) {
    AesTestThree aesTestThree = new AesTestThree();
    String plainText = "明文报文,进行对称AES算法加密传输";
    String cipherStr;
    try {
      SecretKey secretKey = aesTestThree.geneKey();
      // 对秘钥进行了Base64编码
      String base64AesKey = Base64.encodeBase64String(secretKey.getEncoded());
      System.out.println("生成的秘钥为[ " + base64AesKey + " ]");
      aesTestThree.savaAesKey(secretKey);
      System.out.println("被加解密的报文:[ " + plainText + " ]");
      cipherStr = aesTestThree.encrypt(plainText);
      System.out.println("AES 加密后的Base64报文:[ " + cipherStr + "]");
      System.out.println("对加密后的报文解密后的明文为:[ " + aesTestThree.decrypt(cipherStr) + " ]");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

程序运行结果:
在这里插入图片描述
本文首发于香菜喵,打开微信随时随地读,文章下方 ↓ ↓ ↓

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐