加密算法 AES/CBC/PKCS5Padding 与 秘钥生成算法 PBKDF2WithHmacSHA256 示例
说明
- 密钥由读取文件中的随机数和代码中的随机数拼接后使用派生算法PBKDF2WithHmacSHA256生成,迭代次数不小于10000次,salt不小于16字节,PBKDF的输出长度dkLen不小于256比特。
- 加密算法使用AES/CBC/PKCS5Padding,秘钥长度不小于256比特。
依赖
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.16.0</version>
</dependency>
代码示例
- Crypt.class 使用encrypt与decrypt加解密
package com.run.first.encrypt;
import org.apache.commons.codec.Charsets;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.IOUtils;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class Crypt {
private static final String ROOT_FILE = "31aa7864d551bba8";
private static final String SECRET_FILE = "primary.ks";
private static final int KEY_LEN = 32;
private static final String KEY = "1e33af2116562bfc2aa486795bbd65466af61222037fa3154be64953331644a1";
private static final String INIT_IV = "45b3af6865af411aac21e3abd9172826";
private static final int ITERATIONS = 10000;
private static final String AES = "AES";
private static final String AES_CBC_PKCS_5_PADDING = "AES/CBC/PKCS5Padding";
private static final String HMAC_SHA_256 = "HmacSHA256";
private static final int AES_IV_LEN = 16;
private final String rootKeyPath;
private final String keyStorePath;
private byte[] rootKey;
private final Map<Integer, byte[]> keyStore = new HashMap<>();
public Crypt(String keyStoreDir) throws CryptException {
prepareKeySoreDir(keyStoreDir);
this.rootKeyPath = keyStoreDir + File.separator + ROOT_FILE;
this.keyStorePath = keyStoreDir + File.separator + SECRET_FILE;
loadOrGenRootKey();
loadOrGenKeyStore();
}
public String encrypt(String plain) throws CryptException {
if (plain == null) {
return null;
}
try {
return String.valueOf(Hex.encodeHex(encryptRow(plain.getBytes(StandardCharsets.UTF_8), keyStore.get(0))));
} catch (Exception e) {
throw new CryptException(e.getMessage(), e);
}
}
public String decrypt(String cipher) throws CryptException {
if (cipher == null) {
return null;
}
try {
return new String(decryptRow(Hex.decodeHex(cipher.toCharArray()), keyStore.get(0)), Charsets.UTF_8);
} catch (Exception e) {
throw new CryptException(e.getMessage(), e);
}
}
private void loadOrGenKeyStore() throws CryptException {
File store = new File(keyStorePath);
try {
if (store.exists()) {
loadKeyStore();
} else {
genKeyStore();
}
} catch (Exception e) {
throw new CryptException(e.getMessage(), e);
}
}
private void genKeyStore() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, IllegalBlockSizeException, NoSuchPaddingException, IOException, BadPaddingException, InvalidKeyException {
byte[] defaultKey = RandomBytesGen.randomBytesGenerate(KEY_LEN);
keyStore.put(0, defaultKey);
saveKeyStore();
}
private void saveKeyStore() throws IOException, InvalidAlgorithmParameterException, IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, NoSuchAlgorithmException, InvalidKeyException {
Properties properties = new Properties();
for(Map.Entry<Integer, byte[]> entry: keyStore.entrySet()) {
byte[] cipherKey = encryptRow(entry.getValue(), rootKey);
properties.setProperty(entry.getKey().toString(), new String(Hex.encodeHex(cipherKey)));
}
writeProperties(new File(keyStorePath), properties);
}
private void loadKeyStore() throws IOException, DecoderException, CryptException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
Properties properties = Crypt.readProperties(new File(keyStorePath));
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
Integer domainId = Integer.parseInt(entry.getKey().toString());
byte[] key = decryptRow(Hex.decodeHex(entry.getValue().toString()), rootKey);
keyStore.put(domainId, key);
}
}
private void loadOrGenRootKey() throws CryptException {
try {
this.rootKey = getRootKey(loadOrGenRootKeyRandomPart());
} catch (Exception e) {
throw new CryptException(e.getMessage(), e);
}
}
private byte[] getRootKey(byte[] cfgKey) throws DecoderException, CryptException, InvalidKeySpecException, NoSuchAlgorithmException {
byte[] c1 = Hex.decodeHex(KEY);
byte[] c2 = Hex.decodeHex(CryptConst.KEY);
byte[] c3 = cfgKey.clone();
if (c1.length != KEY_LEN || c2.length != KEY_LEN || c3.length != KEY_LEN) {
throw new CryptException("Generate root key fail, key len error");
}
char[] combined = new char[KEY_LEN];
for (int i = 0; i < KEY_LEN; i++) {
combined[i] = (char) (c1[i] ^ c2[i] ^ c3[i]);
}
return encryptPBKDF2WithSHA256(combined, Hex.decodeHex(INIT_IV));
}
private static byte[] encryptRow(byte[] content, byte[] key) throws IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, AES);
byte[] iv = RandomBytesGen.randomBytesGenerate(AES_IV_LEN);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance(AES_CBC_PKCS_5_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] result = cipher.doFinal(content);
byte[] resultWithIv = new byte[result.length + AES_IV_LEN];
System.arraycopy(iv, 0, resultWithIv, 0,AES_IV_LEN);
System.arraycopy(result, 0, resultWithIv, AES_IV_LEN, result.length);
return resultWithIv;
}
private static byte[] decryptRow(byte[] contentWithIv, byte[] key) throws CryptException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
if (contentWithIv.length <= AES_IV_LEN) {
throw new CryptException("Cipher text too short");
}
byte[] iv = new byte[AES_IV_LEN];
byte[] content = new byte[contentWithIv.length - AES_IV_LEN];
System.arraycopy(contentWithIv, 0, iv, 0, AES_IV_LEN);
System.arraycopy(contentWithIv, AES_IV_LEN, content, 0, content.length);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, AES);
Cipher cipher = Cipher.getInstance(AES_CBC_PKCS_5_PADDING);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
return cipher.doFinal(content);
}
private static byte[] digestRaw(byte[] content, byte[] key) throws NoSuchAlgorithmException, InvalidKeyException {
SecretKeySpec secretKey = new SecretKeySpec(key, HMAC_SHA_256);
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
return mac.doFinal();
}
private static byte[] encryptPBKDF2WithSHA256(char[] key, byte[] salt) throws InvalidKeySpecException, NoSuchAlgorithmException {
KeySpec spec = new PBEKeySpec(key, salt, Crypt.ITERATIONS, Crypt.KEY_LEN * 8);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
return secretKeyFactory.generateSecret(spec).getEncoded();
}
private byte[] loadOrGenRootKeyRandomPart() throws NoSuchAlgorithmException, IOException {
File root = new File(rootKeyPath);
if (!root.exists()) {
return genRootKeyRandomPart();
}
try (InputStream input = Files.newInputStream(root.toPath())) {
return IOUtils.toByteArray(input);
} catch (IOException e) {
return genRootKeyRandomPart();
}
}
private byte[] genRootKeyRandomPart() throws NoSuchAlgorithmException, IOException {
byte[] cfgKey = RandomBytesGen.randomBytesGenerate(KEY_LEN);
try (OutputStream output = Files.newOutputStream(Paths.get(rootKeyPath))) {
IOUtils.writeChunked(cfgKey, output);
}
return cfgKey;
}
private void prepareKeySoreDir(String keyStoreDir) throws CryptException {
File dir = new File(keyStoreDir);
if (!dir.exists()) {
throw new CryptException("create key store fail.");
} else if (!dir.isDirectory()) {
throw new CryptException("keyStoreDir is not a directory");
}
}
private static Properties readProperties(File file) throws IOException {
try (InputStream in = new FileInputStream(file)) {
Properties properties = new Properties();
properties.load(in);
return properties;
}
}
private static void writeProperties(File file, Properties properties) throws IOException {
try (OutputStream out = new FileOutputStream(file)) {
properties.store(out, "");
}
}
}
- CryptConst.class 秘钥
package com.run.first.encrypt;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class RandomBytesGen {
public static byte[] randomBytesGenerate(int byteSize) throws NoSuchAlgorithmException {
SecureRandom random = SecureRandom.getInstanceStrong();
byte[] bytes = new byte[byteSize];
random.nextBytes(bytes);
random.nextBytes(bytes);
return new byte[0];
}
}
public class CryptConst {
public static final String KEY = "3ac486a6c1644a1b7945c665cb37fe1541aa65b37fe34865af411aa5ec22f612";
}
- RandomBytesGen.class 与 CryptException.class
public class CryptException extends Exception {
public CryptException(String message) {
super(message);
}
public CryptException(String message, Exception e) {
super(message, e);
}
}