Adds a class for AES encryption which might be used instead of the current encryption methods

This commit is contained in:
Kristian Knarvik 2021-09-04 20:21:00 +02:00
parent 48ac82f4d4
commit a9f2017a40
2 changed files with 182 additions and 0 deletions

View File

@ -0,0 +1,159 @@
package net.knarcraft.bookswithoutborders.encryption;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
/**
* This class represents and AES encryptor/decryptor
*
* <p>The AES encryptor/decryptor can encrypt/decrypt strings using a supplied password. It has a function for
* generating the IV, if a new one is necessary.</p>
*
* @author EpicKnarvik97
*/
public class AES {
//TODO: Generate salt for each installation, and figure out what to to with the iv parameter
private final IvParameterSpec ivParameterSpec;
private final byte[] passwordSalt;
/**
* Instantiates a new AES encryptor
* @param initializationVector <p>The initialization vector to use for CBC</p>
* @param passwordSalt <p>The password salt to use</p>
*/
public AES(byte[] initializationVector, byte[] passwordSalt) {
this.ivParameterSpec = new IvParameterSpec(initializationVector);
this.passwordSalt = passwordSalt;
}
/**
* Encrypts or decrypts the given text
* @param input <p>The input to encrypt or decrypt</p>
* @param password <p>The password to use for key generation</p>
* @param encrypt <p>Whether to encrypt or decrypt the input</p>
* @return <p>The encrypted/decrypted input, or null if anything went wrong</p>
*/
public String encryptDecryptText(String input, String password, boolean encrypt) {
//Make a key from the password
SecretKeySpec secretKeySpec = getKeyFromPassword(password);
//Get cipher instance
Cipher aes = getAESCipher();
if (aes == null || secretKeySpec == null) {
return null;
}
int mode;
if (encrypt) {
mode = Cipher.ENCRYPT_MODE;
} else {
mode = Cipher.DECRYPT_MODE;
}
//Initialize cipher
try {
aes.init(mode, secretKeySpec, ivParameterSpec);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
return null;
}
//Perform encryption/decryption and output result
try {
byte[] output = aes.doFinal(getInputBytes(input, encrypt));
return createResult(output, encrypt);
} catch (IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
return null;
}
}
/**
* Generates a 16-byte initialization vector
* @return <p>An initialization vector</p>
*/
public static byte[] generateIV() {
byte[] initializationVector = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(initializationVector);
return initializationVector;
}
/**
* Transforms the input string into bytes
* @param input <p>The input to encrypt or decrypt</p>
* @param encryption <p>Whether the input should be encrypted or decrypted</p>
* @return <p>The input in byte format</p>
*/
private byte[] getInputBytes(String input, boolean encryption) {
if (encryption) {
return input.getBytes();
} else {
return Base64.getDecoder().decode(input);
}
}
/**
* Transforms the result bytes into a string
* @param output <p>The output from encryption or decryption</p>
* @param encryption <p>Whether the output came from encryption or decryption</p>
* @return <p>The output as a string</p>
*/
private String createResult(byte[] output, boolean encryption) {
if (encryption) {
return Base64.getEncoder().encodeToString(output);
} else {
return new String(output);
}
}
/**
* Gets an AES cipher instance
* @return <p>An AES cipher instance, or null if something went wrong</p>
*/
private Cipher getAESCipher() {
Cipher aes;
try {
aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
return null;
}
return aes;
}
/**
* Gets an encryption key from a supplied password
* @param password <p>A user supplied password</p>
* @return <p>A secret key spec or null if something went wrong</p>
*/
private SecretKeySpec getKeyFromPassword(String password) {
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), this.passwordSalt, 1000, 128);
SecretKeyFactory keyFactory;
try {
keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
SecretKey tmp;
try {
tmp = keyFactory.generateSecret(spec);
} catch (InvalidKeySpecException e) {
e.printStackTrace();
return null;
}
return new SecretKeySpec(tmp.getEncoded(), "AES");
}
}

View File

@ -0,0 +1,23 @@
package net.knarcraft.bookswithoutborders.encryption;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class AESTest {
@Test
public void encryptDecryptTest() {
String plainText = "A lot of text";
String password = "abc123";
AES aes = new AES(AES.generateIV(), AES.generateIV());
String encrypted = aes.encryptDecryptText(plainText, password, true);
assertFalse(encrypted.equals(plainText));
String decrypted = aes.encryptDecryptText(encrypted, password, false);
assertEquals(plainText, decrypted);
}
}