From 13b0003494611567b2f48ea5e7081f9ed3d568c3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 15 Oct 2015 07:57:04 +0200 Subject: [PATCH] [New] Added AESCryptoHelper with tests --- .../utils/helper/AesCryptoHelper.java | 351 ++++++++++++++ .../utils/helper/AesCryptoHelperTest.java | 127 +++++ src/test/resources/crypto_test_image.ico | Bin 0 -> 5430 bytes src/test/resources/crypto_test_long.txt | 447 ++++++++++++++++++ src/test/resources/crypto_test_middle.txt | 3 + src/test/resources/crypto_test_short.txt | 1 + 6 files changed, 929 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java create mode 100644 src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java create mode 100644 src/test/resources/crypto_test_image.ico create mode 100644 src/test/resources/crypto_test_long.txt create mode 100644 src/test/resources/crypto_test_middle.txt create mode 100644 src/test/resources/crypto_test_short.txt diff --git a/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java b/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java new file mode 100644 index 000000000..1f580405c --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java @@ -0,0 +1,351 @@ +package ch.eitchnet.utils.helper; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.security.AlgorithmParameters; +import java.security.spec.KeySpec; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.dbc.DBC; + +public class AesCryptoHelper { + + private static final String CIPHER = "AES/CBC/PKCS5Padding"; + + private static final Logger logger = LoggerFactory.getLogger(AesCryptoHelper.class); + + public static OutputStream wrapEncrypt(char[] password, byte[] salt, OutputStream outputStream) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + return wrapEncrypt(secret, outputStream); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static OutputStream wrapEncrypt(SecretKey secret, OutputStream outputStream) { + + try { + + // set up cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.ENCRYPT_MODE, secret); + + // set up the initialization vector + AlgorithmParameters params = cipher.getParameters(); + byte[] initVector = params.getParameterSpec(IvParameterSpec.class).getIV(); + DBC.INTERIM.assertEquals("IV must be 16 bytes long!", 16, initVector.length); + + // write the initialization vector, but not through the cipher output stream! + outputStream.write(initVector); + outputStream.flush(); + + CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher); + return cipherOutputStream; + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static InputStream wrapDecrypt(char[] password, byte[] salt, InputStream inputStream) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + return wrapDecrypt(secret, inputStream); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static InputStream wrapDecrypt(SecretKey secret, InputStream inputStream) { + + try { + + // read the initialization vector from the normal input stream + byte[] initVector = new byte[16]; + inputStream.read(initVector); + + // init cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(initVector)); + + CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher); + return cipherInputStream; + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void encrypt(char[] password, byte[] salt, String clearTextFileS, String encryptedFileS) { + + try (FileInputStream inFile = new FileInputStream(clearTextFileS); + FileOutputStream outFile = new FileOutputStream(encryptedFileS)) { + + encrypt(password, salt, inFile, outFile); + + } catch (Exception e) { + throw new RuntimeException("Failed to encrypt file " + clearTextFileS + " to " + encryptedFileS, e); + } + + logger.info("Encrypted file " + clearTextFileS + " to " + encryptedFileS); + } + + public static void encrypt(SecretKey secret, String clearTextFileS, String encryptedFileS) { + + try (FileInputStream inFile = new FileInputStream(clearTextFileS); + FileOutputStream outFile = new FileOutputStream(encryptedFileS)) { + + encrypt(secret, inFile, outFile); + + } catch (Exception e) { + throw new RuntimeException("Failed to encrypt file " + clearTextFileS + " to " + encryptedFileS, e); + } + + logger.info("Encrypted file " + clearTextFileS + " to " + encryptedFileS); + } + + public static void encrypt(char[] password, byte[] salt, InputStream inFile, OutputStream outFile) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + encrypt(secret, inFile, outFile); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void encrypt(SecretKey secret, InputStream inFile, OutputStream outFile) { + + try { + + // set up cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.ENCRYPT_MODE, secret); + + // set up the initialization vector + AlgorithmParameters params = cipher.getParameters(); + byte[] initVector = params.getParameterSpec(IvParameterSpec.class).getIV(); + DBC.INTERIM.assertEquals("IV must be 16 bytes long!", 16, initVector.length); + outFile.write(initVector); + + byte[] input = new byte[64]; + int bytesRead; + + while ((bytesRead = inFile.read(input)) != -1) { + byte[] output = cipher.update(input, 0, bytesRead); + if (output != null) + outFile.write(output); + } + + byte[] output = cipher.doFinal(); + if (output != null) + outFile.write(output); + + outFile.flush(); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void decrypt(char[] password, byte[] salt, String encryptedFileS, String decryptedFileS) { + + try (FileInputStream fis = new FileInputStream(encryptedFileS); + FileOutputStream fos = new FileOutputStream(decryptedFileS)) { + + decrypt(password, salt, fis, fos); + + } catch (Exception e) { + throw new RuntimeException("Failed to decrypt file " + decryptedFileS + " to " + decryptedFileS, e); + } + + logger.info("Decrypted file " + encryptedFileS + " to " + decryptedFileS); + + } + + public static void decrypt(SecretKey secret, String encryptedFileS, String decryptedFileS) { + + try (FileInputStream fis = new FileInputStream(encryptedFileS); + FileOutputStream fos = new FileOutputStream(decryptedFileS)) { + + decrypt(secret, fis, fos); + + } catch (Exception e) { + throw new RuntimeException("Failed to decrypt file " + decryptedFileS + " to " + decryptedFileS, e); + } + + logger.info("Decrypted file " + encryptedFileS + " to " + decryptedFileS); + + } + + public static void decrypt(char[] password, byte[] salt, InputStream fis, OutputStream fos) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + decrypt(secret, fis, fos); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void decrypt(SecretKey secret, InputStream fis, OutputStream fos) { + + try { + + // read the initialization vector + byte[] initVector = new byte[16]; + fis.read(initVector); + + // init cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(initVector)); + + byte[] in = new byte[64]; + int read; + while ((read = fis.read(in)) != -1) { + byte[] output = cipher.update(in, 0, read); + if (output != null) + fos.write(output); + } + + byte[] output = cipher.doFinal(); + if (output != null) + fos.write(output); + + fos.flush(); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static byte[] encrypt(char[] password, byte[] salt, String clearText) { + return encrypt(password, salt, clearText.getBytes()); + } + + public static byte[] encrypt(SecretKey secret, byte[] salt, String clearText) { + return encrypt(secret, clearText.getBytes()); + } + + public static byte[] encrypt(char[] password, byte[] salt, byte[] clearTextBytes) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + return encrypt(secret, clearTextBytes); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static byte[] encrypt(SecretKey secret, byte[] clearTextBytes) { + + try { + + // set up cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.ENCRYPT_MODE, secret); + + // set up the initialization vector + AlgorithmParameters params = cipher.getParameters(); + byte[] initVector = params.getParameterSpec(IvParameterSpec.class).getIV(); + DBC.INTERIM.assertEquals("IV must be 16 bytes long!", 16, initVector.length); + + // encrypt + byte[] encryptedBytes = cipher.doFinal(clearTextBytes); + + // create result bytes + ByteBuffer byteBuffer = ByteBuffer.allocate(initVector.length + encryptedBytes.length); + byteBuffer.put(initVector); + byteBuffer.put(encryptedBytes); + + return byteBuffer.array(); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static byte[] decrypt(char[] password, byte[] salt, byte[] encryptedBytes) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + return decrypt(secret, encryptedBytes); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static byte[] decrypt(SecretKey secret, byte[] encryptedBytes) { + + try { + + // read initialization vector + byte[] initVector = new byte[16]; + System.arraycopy(encryptedBytes, 0, initVector, 0, 16); + + // init cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(initVector)); + + byte[] decryptedBytes = cipher.doFinal(encryptedBytes, 16, encryptedBytes.length - 16); + return decryptedBytes; + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static SecretKey buildSecret(char[] password, byte[] salt) { + try { + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + KeySpec keySpec = new PBEKeySpec(password, salt, 65536, 256); + SecretKey secretKey = factory.generateSecret(keySpec); + SecretKey secret = new SecretKeySpec(secretKey.getEncoded(), "AES"); + + return secret; + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java b/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java new file mode 100644 index 000000000..797f79d86 --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java @@ -0,0 +1,127 @@ +package ch.eitchnet.utils.helper; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import org.junit.Test; + +public class AesCryptoHelperTest { + + private static final char[] password = "A2589309-17AE-4819-B9E4-E577CFA7778F".toCharArray(); + private static final byte[] salt; + + static { + try { + salt = "E68761B3-4E8E-4122-9B12-8B89E0AEB233".getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + @Test + public void shouldWrapStreams() throws Exception { + + byte[] clearTextBytes = "Some text".getBytes(); + + // encrypt data + ByteArrayOutputStream encryptedOut = new ByteArrayOutputStream(); + OutputStream outputStream = AesCryptoHelper.wrapEncrypt(password, salt, encryptedOut); + outputStream.write(clearTextBytes); + outputStream.flush(); + outputStream.close(); + + // decrypt data + byte[] encryptedBytes = encryptedOut.toByteArray(); + ByteArrayInputStream encryptedIn = new ByteArrayInputStream(encryptedBytes); + InputStream inputStream = AesCryptoHelper.wrapDecrypt(password, salt, encryptedIn); + + ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream(); + byte[] readBuffer = new byte[64]; + int read = 0; + while ((read = inputStream.read(readBuffer)) != -1) { + decryptedOut.write(readBuffer, 0, read); + } + + byte[] decryptedBytes = decryptedOut.toByteArray(); + + assertArrayEquals(clearTextBytes, decryptedBytes); + } + + @Test + public void shouldEncryptBytes() { + + byte[] clearTextBytes = "Some text".getBytes(); + + byte[] encryptedBytes = AesCryptoHelper.encrypt(password, salt, clearTextBytes); + byte[] decryptedBytes = AesCryptoHelper.decrypt(password, salt, encryptedBytes); + + assertArrayEquals(clearTextBytes, decryptedBytes); + } + + @Test + public void shouldEncryptShortFile() { + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_short.txt"; + // encrypted file + String encryptedFileS = "target/encrypted_short.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_short.txt"; + + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + } + + @Test + public void shouldEncryptMiddleFile() { + + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_middle.txt"; + // encrypted file + String encryptedFileS = "target/encrypted_middle.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_middle.txt"; + + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + } + + @Test + public void shouldEncryptLongFile() { + + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_long.txt"; + // encrypted file + String encryptedFileS = "target/encrypted_long.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_long.txt"; + + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + } + + @Test + public void shouldEncryptBinaryFile() { + + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_image.ico"; + // encrypted file + String encryptedFileS = "target/encrypted_image.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_image.ico"; + + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + } + + private static void testCrypto(String clearTextFileS, String encryptedFileS, String decryptedFileS) { + AesCryptoHelper.encrypt(password, salt, clearTextFileS, encryptedFileS); + AesCryptoHelper.decrypt(password, salt, encryptedFileS, decryptedFileS); + + String inputSha256 = StringHelper.getHexString(FileHelper.hashFileSha256(new File(clearTextFileS))); + String doutputSha256 = StringHelper.getHexString(FileHelper.hashFileSha256(new File(decryptedFileS))); + assertEquals(inputSha256, doutputSha256); + } +} diff --git a/src/test/resources/crypto_test_image.ico b/src/test/resources/crypto_test_image.ico new file mode 100644 index 0000000000000000000000000000000000000000..b48b3442ad90fd9b5bcbef8a9f703c1852de1561 GIT binary patch literal 5430 zcmchb`BM}}6vrn=RerU~e}JE;{1#6HHI^o#f*c}>As#Wtm_$)Ph+G1TXayBe6BCaj zBvFZYti~%*t5QJ(qGiBR5RfGTyDo=F)D_J}U0&Yn+3uO1JtUi$srpv;&d&7rGu>}y z-Wx@ENqJcb3R18QQYuF%O0c3RBe134AHS+74cLDj_u(PRR21!J8}2u50HBb1J8i-H z^GnJy-(Fx}HF~CN<)|6J^~tDkV0r`F0EJ_@GTutC@5?VP>+P@wb6;w&{Zbd38W!W= ze*Rj)LQDa&q=ZI0uKrvT%db0iVypwN8I1DBq5m%sCSss@IAAI*OG0mw(cjx|y|2Ii zaF}QV#&}i&4y?7>yht*>uTqszL3?#w9`!A4Z<>(o%HDl46i*XPO*DN?& zb`b9M^*>*|{m|8FfeVLE!M5e;>^k)8=WDIjjEw|M5!hG7nJyeUiDCNBwxOXRXuNnC zGN#S<8B?4mdE9JB9v2D8$U)Lr)P8jU{+za>sTGn#qWs2o3iBrhy)+)9{~fCxj&9xU z)-G&!!HFGTLyh?iIR3cLtwmc=xLu7YA90j2J_=IC&sK#D#G_}bK6FZqMIB51b;BHK zYnC^A2{PQ1j&H2K>^HVk(O)l>M`QBlFV@Vbk$#>yQ|#@a7WaH<^Ca#$WJ(K(L4T6Q!TZE>@$hMBJPY}= zHPhyZui3gO@i5TcD;-a9;XSrhp0o)uAW873Oh$iOYddUTmabD8?EEMb4i@eJEB>e7 zT+g1t*2Qal#x`-KPly$gINV${hU?dl@2~H2x!_poUca$TYxZgA9H#V8SHqH^P+ore`&~wWw7>02EzsdYeFz}e>EoaI=f0E$P z_mRh&zn)LOX2**62!HCd)YW^$w()+;m^c@N!m!VzF?sqod7UR>I=cBQ$^VYcF3pv< z_|8Osy#xz!AxrJiAEhzd8_?e{N3?E*(aXTzc1t>z)`Q29w>2Z4tV!|cZ?+Yr}0hH@s)ccGAh%fLfy1}1r~v~}ZarnOoeQH&d78-Kfs z=B(%;e{TwmN#l85Y;THLZa=;>VAT__liOn98CMPQolSFYlORrV7lW##0>eLEH9wJJq<#YCS8{-=$+ zs?f6&z3DpzeU=D9iSHEn4a2zMGiBu7wFTzY<073@yA0=hMAFy@=N>$Votw77Eb1}c zw+&tg`5TkQNC}B@vf9*L8;h~rb-R8X-sd$qaj?2hmxH%mxdwj@K9ckh-z`+P6}`KP z)opwnYFU1*tI;~KwxW%4pBuGTptVZ_u;qTt=VdsCFRV4C$s;9-fAMBM=kVy!~kovz!EF=d`RZ&qtT_5G>kr(HMHW9fZ7(A|gi`CXu#m#%$FVv>}5QhmUm z_dLh5y3en~yq)I;2Xc#<|L>S%sCkmRCc!#{J-c=J5kLP>;pZYB+LyhVoj-X0ff$ot zcb1f6%mse_gZ=^N>#)Iqy%(rnppEi2DPO=mTz*H#!&S^Jy4yP?*_tgZU)m74RL&_w z;~;NpqEme~xt~cM?=~b;aDKnO#LS+}hG(*IOzqw1eh=YwAIRQneste4 nvNEsKToAs