From a9f2017a409a5e88ed95c3f00d6c0fd2ddf3ab68 Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Sat, 4 Sep 2021 20:21:00 +0200 Subject: [PATCH] Adds a class for AES encryption which might be used instead of the current encryption methods --- .../bookswithoutborders/encryption/AES.java | 159 ++++++++++++++++++ .../encryption/AESTest.java | 23 +++ 2 files changed, 182 insertions(+) create mode 100644 src/main/java/net/knarcraft/bookswithoutborders/encryption/AES.java create mode 100644 src/test/java/net/knarcraft/bookswithoutborders/encryption/AESTest.java diff --git a/src/main/java/net/knarcraft/bookswithoutborders/encryption/AES.java b/src/main/java/net/knarcraft/bookswithoutborders/encryption/AES.java new file mode 100644 index 0000000..6f3f6f5 --- /dev/null +++ b/src/main/java/net/knarcraft/bookswithoutborders/encryption/AES.java @@ -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 + * + *

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.

+ * + * @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

The initialization vector to use for CBC

+ * @param passwordSalt

The password salt to use

+ */ + public AES(byte[] initializationVector, byte[] passwordSalt) { + this.ivParameterSpec = new IvParameterSpec(initializationVector); + this.passwordSalt = passwordSalt; + } + + /** + * Encrypts or decrypts the given text + * @param input

The input to encrypt or decrypt

+ * @param password

The password to use for key generation

+ * @param encrypt

Whether to encrypt or decrypt the input

+ * @return

The encrypted/decrypted input, or null if anything went wrong

+ */ + 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

An initialization vector

+ */ + 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

The input to encrypt or decrypt

+ * @param encryption

Whether the input should be encrypted or decrypted

+ * @return

The input in byte format

+ */ + 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

The output from encryption or decryption

+ * @param encryption

Whether the output came from encryption or decryption

+ * @return

The output as a string

+ */ + 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

An AES cipher instance, or null if something went wrong

+ */ + 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

A user supplied password

+ * @return

A secret key spec or null if something went wrong

+ */ + 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"); + } + +} diff --git a/src/test/java/net/knarcraft/bookswithoutborders/encryption/AESTest.java b/src/test/java/net/knarcraft/bookswithoutborders/encryption/AESTest.java new file mode 100644 index 0000000..d44b73d --- /dev/null +++ b/src/test/java/net/knarcraft/bookswithoutborders/encryption/AESTest.java @@ -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); + } + +}