一.加解密算法生态圈
目前的数据加密技术根据加密密钥类型可分私钥加密(对称加密)系统和公钥加密(非对称加密)系统。对称加密算法是较传统的加密体制,通信双方在加/解密过程中使用他们共享的单一密钥,鉴于其算法简单和加密速度快的优点,目前仍然是主流的密码体制之一。最常用的对称密码算法是数据加密标准(DES)算法,但是由于DES密钥长度较短,已经不适合当今分布式开放网络对数据加密安全性的要求。最后,一种新的基于Rijndael算法对称高级数据加密标准AES取代了数据加密标准DES。非对称加密由于加/解密钥不同(公钥加密,私钥解密),密钥管理简单,也得到广泛应用。RSA是非对称加密系统最著名的公钥密码算法。
二.AES加密算法及算法流程
美国国家标准和技术研究所(NIST)经过三轮候选算法筛选,从众多的分组密码中选中Rijndael算法作为高级加密标准(AES)。Rijndael密码是一个迭代型分组密码,分组长度和密码长度都是可变的,分组长度和密码长度可以独立的指定为128比特,192比特或者256比特。AES的加密算法的数据处理单位是字节,128位的比特信息被分成16个字节,按顺序复制到一个4*4的矩阵中,称为状态(state),AES的所有变换都是基于状态矩阵的变换。
用Nr表示对一个数据分组加密的轮数(加密轮数与密钥长度的关系如表1所示)。在轮函数的每一轮迭代中,包括四步变换,分别是字节代换运算(ByteSub())、行变换(ShiftRows())、列混合(MixColumns())以及轮密钥的添加变换AddRoundKey()[3],其作用就是通过重复简单的非线形变换、混合函数变换,将字节代换运算产生的非线性扩散,达到充分的混合,在每轮迭代中引入不同的密钥,从而实现加密的有效性。
表1 是三种不同类型的AES加密密钥分组大小与相应的加密轮数的对照表。加密开始时,输入分组的各字节按表2 的方式装入矩阵state中。如输入ABCDEFGHIJKLMNOP,则输入块影射到如表2的状态矩阵中。
表1:
1 2 3 4 | |AES类型| 密钥长度 | 分组长度 | 加密轮数| |AES-128| 4字 | 4字 | 10 | |AES-192| 6字 | 4字 | 12 | |AES-256| 8字 | 4字 | 14 | |
表2:
1 2 3 4 | | A | E | I | M | | B | F | J | N | | C | G | K | O | | D | H | L | P | |
- 字节代换运算(ByteSub())
字节代换运算是一个可逆的非线形字节代换操作,对分组中的每个字节进行,对字节的操作遵循一个代换表,即S盒。S盒由有限域 GF(28)上的乘法取逆和GF(2)上的仿射变换两步组成。 - 行变换ShiftRows()
行变换是一种线性变换,其目的就是使密码信息达到充分的混乱,提高非线形度。行变换对状态的每行以字节为单位进行循环右移,移动字节数根据行数来确定,第0行不发生偏移,第一行循环右移一个字节,第二行移两个,依次类推。 - 列混合变换MixColumns()
列变换就是从状态中取出一列,表示成多项式的形式后,用它乘以一个固定的多项式a(x),然后将所得结果进行取模运算,模值为 x4+1。其中a(x)={03}x3+{02}x2+{01}x+{02},
这个多项式与x4+1互质,因此是可逆的。列混合变换的算术表达式为:s’(x)= a(x) s(x),其中, s(x)表示状态的列多项式。 - 轮密钥的添加变换AddRoundKey()
在这个操作中,轮密钥被简单地异或到状态中,轮密钥根据密钥表获得,其长度等于数据块的长度Nb。
三.Java代码实现
Java 实现AES有多种实现方式,这些实现方式在实现细节上有很大的不同,例如密钥的位数,工作模式,填充方式等。如果加解密双方不对细节进行约定,很容易造成解密错误。本节中,提供了一种通用的Java实现方式,加解密双方在生成密钥的使用使用了相同的方式,能够保证加密的数据正确的被解密。
1.ECB加密模式
package com.jd.app.server.soa.bingo.portal.util;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
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.NoSuchAlgorithmException;
import java.security.SecureRandom;
/**
* 实现AES加解密
*
* @author: martin
* @date: 2018/8/21 15:12
* @description:
*/
public class AESUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(AESUtil.class);
private static final String KEY_ALGORITHM = "AES";
private static final String CHAR_SET = "UTF-8";
/**
* AES的密钥长度
*/
private static final Integer SECRET_KEY_LENGTH = 128;
/**
* 加解密算法/工作模式/填充方式
*/
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
/**
* AES加密操作
*
* @param content 待加密内容
* @param password 加密密码
* @return 返回Base64转码后的加密数据
*/
public static String encrypt(String content, String password) {
if (StringUtils.isAnyEmpty(content, password)) {
LOGGER.error("AES encryption params is null");
return null;
}
try {
//创建密码器
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
byte[] byteContent = content.getBytes(CHAR_SET);
//初始化为加密密码器
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(password));
byte[] encryptByte = cipher.doFinal(byteContent);
return Base64.encodeBase64String(encryptByte);
} catch (Exception e) {
LOGGER.error("AES encryption operation has exception,content:{},password:{}", content, password, e);
}
return null;
}
/**
* AES解密操作
*
* @param encryptContent 加密的密文
* @param password 解密的密钥
* @return
*/
public static String decrypt(String encryptContent, String password) {
if (StringUtils.isAnyEmpty(encryptContent, password)) {
LOGGER.error("AES decryption params is null");
return null;
}
Cipher cipher = null;
try {
cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
//设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(password));
//执行解密操作
byte[] result = cipher.doFinal(Base64.decodeBase64(encryptContent));
return new String(result, CHAR_SET);
} catch (Exception e) {
LOGGER.error("AES decryption operation has exception,content:{},password:{}", encryptContent, password, e);
}
return null;
}
private static SecretKeySpec getSecretKey(final String password) throws NoSuchAlgorithmException {
//生成指定算法密钥的生成器
KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
keyGenerator.init(SECRET_KEY_LENGTH, new SecureRandom(password.getBytes()));
//生成密钥
SecretKey secretKey = keyGenerator.generateKey();
//转换成AES的密钥
return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);
}
public static void main(String[] args) throws Exception {
String str = "Hello World";
System.out.println("str:" + str);
String encryptStr = encrypt(str, "aa7889d3-435b-4ed2-99f9-08035661eda9");
System.out.println("encrypt:" + encryptStr);
String decryptStr = decrypt(encryptStr, "aa7889d3-435b-4ed2-99f9-08035661eda9");
System.out.println("decryptStr:" + decryptStr);
}
}
2.CBC加密模式
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* @author: martin
* @date: 2018/8/21 20:11
* @description:
*/
public class AESCBCUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(AES4MEncrypt.class);
private static final String ENCODING = "GBK";
private static final String KEY_ALGORITHM = "AES";
/**
* 加解密算法/工作模式/填充方式
*/
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
/**
* 填充向量
*/
private static final String FILL_VECTOR = "1234560405060708";
/**
* 加密字符串
*
* @param content 字符串
* @param password 密钥KEY
* @return
* @throws Exception
*/
public static String encrypt(String content, String password) {
if (StringUtils.isAnyEmpty(content, password)) {
LOGGER.error("AES encryption params is null");
return null;
}
byte[] raw = hex2byte(password);
SecretKeySpec skeySpec = new SecretKeySpec(raw, KEY_ALGORITHM);
Cipher cipher = null;
try {
cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
IvParameterSpec iv = new IvParameterSpec(FILL_VECTOR.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] anslBytes = content.getBytes(ENCODING);
byte[] encrypted = cipher.doFinal(anslBytes);
return byte2hex(encrypted).toUpperCase();
} catch (Exception e) {
LOGGER.error("AES encryption operation has exception,content:{},password:{}", content, password, e);
}
return null;
}
/**
* 解密
*
* @param content 解密前的字符串
* @param password 解密KEY
* @return
* @throws Exception
* @author cdduqiang
* @date 2014年4月3日
*/
public static String decrypt(String content, String password) {
if (StringUtils.isAnyEmpty(content, password)) {
LOGGER.error("AES decryption params is null");
return null;
}
try {
byte[] raw = hex2byte(password);
SecretKeySpec skeySpec = new SecretKeySpec(raw, KEY_ALGORITHM);
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
IvParameterSpec iv = new IvParameterSpec(FILL_VECTOR.getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] encrypted1 = hex2byte(content);
byte[] original = cipher.doFinal(encrypted1);
return new String(original, ENCODING);
} catch (Exception e) {
LOGGER.error("AES decryption operation has exception,content:{},password:{}", content, password, e);
}
return null;
}
public static byte[] hex2byte(String strhex) {
if (strhex == null) {
return null;
}
int l = strhex.length();
if (l % 2 == 1) {
return null;
}
byte[] b = new byte[l / 2];
for (int i = 0; i != l / 2; i++) {
b[i] = (byte) Integer.parseInt(strhex.substring(i * 2, i * 2 + 2), 16);
}
return b;
}
public static String byte2hex(byte[] b) {
String hs = "";
String stmp = "";
for (int n = 0; n < b.length; n++) {
stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
if (stmp.length() == 1) {
hs = hs + "0" + stmp;
} else {
hs = hs + stmp;
}
}
return hs.toUpperCase();
}
public static void main(String[] args) throws Exception {
String str = "Hello world";
//必须为16位
String key = "9230967890982316";
//生成加密密钥
String key2 = byte2hex(key.getBytes());
System.out.println(key2);
String encryptStr = encrypt(str, key2);
System.out.println(encryptStr);
System.out.println(decrypt(encryptStr, key2));
}
}
关于AES的加密模式参考文章: