在编程中,对于敏感数据需要加密保存,不恰当的加密会引发安全问题。常见的AES加密,明文和密钥相同的情况下每次加密出来的密文值都一样,上线使用后运维人员很容易根据密文值“猜”出明文,不利于用户数据的保护,下面将实现CBC模式的AES加密,随机生成iv值,加密后iv拼接在密文值后面,这样每次加密后,即使明文和密钥相同,得到的密文值也是不一样的。
1. maven依赖codec
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>
2. java实现
package com.realjt.common.encrypt;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
public class App
{
/**
* 加密算法,CBC模式
*/
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
/**
* 用于生成随机iv
*
* @param length
* @return
* @throws NoSuchAlgorithmException
*/
private static byte[] getSecureRandom(int length) throws NoSuchAlgorithmException
{
byte[] randomByte = new byte[length];
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.nextBytes(randomByte);
return randomByte;
}
/**
* 不同长度的种子seed,生成一样长度的key
*
* @param seed
* @return
* @throws NoSuchAlgorithmException
*/
private static byte[] getSecretKey(byte[] seed) throws NoSuchAlgorithmException
{
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(seed);
keyGenerator.init(256, secureRandom); // 256 bits or 128 bits,192bits
SecretKey secretKey = keyGenerator.generateKey();
byte[] result = secretKey.getEncoded();
return result;
}
private static byte[] aesCipher(byte[] source, byte[] key, byte[] iv, int mode)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
{
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(mode, secretKeySpec, ivParameterSpec);
return cipher.doFinal(source);
}
private static String encrypt(String source, String key) throws Exception
{
try
{
byte[] iv = getSecureRandom(16);
byte[] resultByte = aesCipher(source.getBytes(StandardCharsets.UTF_8), getSecretKey(key.getBytes()), iv,
Cipher.ENCRYPT_MODE);
Base64.Encoder encoder = Base64.getEncoder();
// iv值拼接在密文值后面
return encoder.encodeToString(resultByte).concat(new String(Hex.encodeHex(iv)));
}
catch (NoSuchAlgorithmException e)
{
throw new Exception(e.getMessage());
}
catch (InvalidKeyException e)
{
throw new Exception(e.getMessage());
}
catch (NoSuchPaddingException e)
{
throw new Exception(e.getMessage());
}
catch (InvalidAlgorithmParameterException e)
{
throw new Exception(e.getMessage());
}
catch (IllegalBlockSizeException e)
{
throw new Exception(e.getMessage());
}
catch (BadPaddingException e)
{
throw new Exception(e.getMessage());
}
}
private static String decrypt(String source, String key) throws Exception
{
try
{
// 先分别解析密文值和iv值
String message = source.substring(0, source.length() - 32);
String iv = source.substring(source.length() - 32, source.length());
Base64.Decoder decoder = Base64.getDecoder();
byte[] resultByte = aesCipher(decoder.decode(message), getSecretKey(key.getBytes()),
Hex.decodeHex(iv.toCharArray()), Cipher.DECRYPT_MODE);
return new String(resultByte, StandardCharsets.UTF_8);
}
catch (InvalidKeyException e)
{
throw new Exception(e.getMessage());
}
catch (NoSuchAlgorithmException e)
{
throw new Exception(e.getMessage());
}
catch (NoSuchPaddingException e)
{
throw new Exception(e.getMessage());
}
catch (InvalidAlgorithmParameterException e)
{
throw new Exception(e.getMessage());
}
catch (IllegalBlockSizeException e)
{
throw new Exception(e.getMessage());
}
catch (BadPaddingException e)
{
throw new Exception(e.getMessage());
}
catch (DecoderException e)
{
throw new Exception(e.getMessage());
}
}
public static void main(String[] args)
{
// key长度随意
String key = "1dc5d72e5b3a097cca875cce84c402946";
String source = "RealJt";
String encryptResult = null;
try
{
encryptResult = encrypt(source, key);
System.out.println(encryptResult);
}
catch (Exception e)
{
System.out.println("encrypt error.");
}
try
{
System.out.println(decrypt(encryptResult, key));
}
catch (Exception e)
{
System.out.println("decrypt error.");
}
}
}
3. 三次执行结果
gbPCHi7UstvXf3ZZnWWukg==da7334f3dd6fbcb3d2a1943df3796a6d
RealJt
nenW5+GtNFdAG2NuwtIsiw==4250897195018f7376de0b4e13d7b0fa
RealJt
HR3jI7uSGaa1Or1Fv/pXHw==e04027fddf4f3be36fdd6cc59278510e
RealJt
4. 注意
需要在${JAVA_HOME}/jre/lib/security目录下部署JCE的两个jar包,US_export_policy.jar和local_policy.jar