From a82d761b9e8f91d068b59fce124b66ab4ddd0bf0 Mon Sep 17 00:00:00 2001 From: koco Date: Wed, 27 May 2020 16:23:39 +0300 Subject: [PATCH 01/11] First implementation of TripleDesEncryptionImpl --- pom.xml | 35 +++++++++- .../verygood/security/coding/Encryption.java | 6 -- .../security/coding/api/Encryption.java | 17 +++++ .../coding/api/ISecretKeyService.java | 8 +++ .../coding/service/SecretKeyServiceImpl.java | 46 +++++++++++++ .../service/TripleDesEncryptionImpl.java | 58 ++++++++++++++++ .../service/SecretKeyServiceImplTest.java | 31 +++++++++ .../service/TripleDesEncryptionImplTest.java | 67 +++++++++++++++++++ 8 files changed, 261 insertions(+), 7 deletions(-) delete mode 100644 src/main/java/com/verygood/security/coding/Encryption.java create mode 100644 src/main/java/com/verygood/security/coding/api/Encryption.java create mode 100644 src/main/java/com/verygood/security/coding/api/ISecretKeyService.java create mode 100644 src/main/java/com/verygood/security/coding/service/SecretKeyServiceImpl.java create mode 100644 src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java create mode 100644 src/test/java/com/verygood/security/coding/service/SecretKeyServiceImplTest.java create mode 100644 src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java diff --git a/pom.xml b/pom.xml index 9f4bf02..1ff9fec 100644 --- a/pom.xml +++ b/pom.xml @@ -10,15 +10,48 @@ 1.8 + 2.2 + 5.6.2 org.junit.jupiter junit-jupiter-api - 5.6.2 + ${junit-jupiter-api.version} + test + + + org.hamcrest + hamcrest + ${hamcrest.version} + provided + + + org.projectlombok + lombok + ${lombok.version} + provided + + + org.mockito + mockito-junit-jupiter + 2.23.0 test + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + diff --git a/src/main/java/com/verygood/security/coding/Encryption.java b/src/main/java/com/verygood/security/coding/Encryption.java deleted file mode 100644 index 3f1a28c..0000000 --- a/src/main/java/com/verygood/security/coding/Encryption.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.verygood.security.coding; - -public interface Encryption { - String encrypt(String text); - String decrypt(String encryptedData); -} diff --git a/src/main/java/com/verygood/security/coding/api/Encryption.java b/src/main/java/com/verygood/security/coding/api/Encryption.java new file mode 100644 index 0000000..70bedd2 --- /dev/null +++ b/src/main/java/com/verygood/security/coding/api/Encryption.java @@ -0,0 +1,17 @@ +package com.verygood.security.coding.api; + +import java.io.UnsupportedEncodingException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + +public interface Encryption { + String encrypt(String text) + throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeySpecException; + String decrypt(String encryptedData) + throws NoSuchAlgorithmException, UnsupportedEncodingException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException; +} diff --git a/src/main/java/com/verygood/security/coding/api/ISecretKeyService.java b/src/main/java/com/verygood/security/coding/api/ISecretKeyService.java new file mode 100644 index 0000000..c283333 --- /dev/null +++ b/src/main/java/com/verygood/security/coding/api/ISecretKeyService.java @@ -0,0 +1,8 @@ +package com.verygood.security.coding.api; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +public interface ISecretKeyService { + byte[] getKey() throws NoSuchAlgorithmException, InvalidKeySpecException; +} diff --git a/src/main/java/com/verygood/security/coding/service/SecretKeyServiceImpl.java b/src/main/java/com/verygood/security/coding/service/SecretKeyServiceImpl.java new file mode 100644 index 0000000..689e58c --- /dev/null +++ b/src/main/java/com/verygood/security/coding/service/SecretKeyServiceImpl.java @@ -0,0 +1,46 @@ +package com.verygood.security.coding.service; + +import com.verygood.security.coding.api.ISecretKeyService; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + +public class SecretKeyServiceImpl implements ISecretKeyService { + + private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1"; + private static final String SHA1PRNG_ALGORITHM = "SHA1PRNG"; + static final int KEY_SIZE = 24; + private byte[] encryptionKeyInBytes; + private String encryptionKey; + + SecretKeyServiceImpl(String encryptionKey) { + this.encryptionKey = encryptionKey; + } + + @Override + public byte[] getKey() throws NoSuchAlgorithmException, InvalidKeySpecException { + if (encryptionKeyInBytes == null) { + encryptionKeyInBytes = generateKey(); + } + return encryptionKeyInBytes; + } + + private byte[] generateKey() throws NoSuchAlgorithmException, InvalidKeySpecException { + int iterations = 1000; + char[] chars = encryptionKey.toCharArray(); + byte[] salt = getSalt(); + + PBEKeySpec spec = new PBEKeySpec(chars, salt, iterations, KEY_SIZE * 8); + SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM); + return skf.generateSecret(spec).getEncoded(); + } + + private static byte[] getSalt() throws NoSuchAlgorithmException { + SecureRandom sr = SecureRandom.getInstance(SHA1PRNG_ALGORITHM); + byte[] salt = new byte[16]; + sr.nextBytes(salt); + return salt; + } +} diff --git a/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java b/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java new file mode 100644 index 0000000..ff77c9a --- /dev/null +++ b/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java @@ -0,0 +1,58 @@ +package com.verygood.security.coding.service; + +import com.verygood.security.coding.api.Encryption; +import com.verygood.security.coding.api.ISecretKeyService; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Base64; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class TripleDesEncryptionImpl implements Encryption { + + private static final Charset ENCODING = StandardCharsets.UTF_8; + private final ISecretKeyService secretKeyService; + + public TripleDesEncryptionImpl(ISecretKeyService secretKeyService) { + this.secretKeyService = secretKeyService; + } + + @Override + public String encrypt(String text) + throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException, + NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeySpecException { + byte[] bytes = encryptDecryptInternal(text.getBytes(ENCODING), Cipher.ENCRYPT_MODE); + return Base64.getEncoder().encodeToString(bytes); + } + + @Override + public String decrypt(String text) throws NoSuchAlgorithmException, + NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException { + byte[] textDecoded = Base64.getDecoder().decode(text); + byte[] bytes = encryptDecryptInternal(textDecoded, Cipher.DECRYPT_MODE); + return new String(bytes, ENCODING); + } + + private byte[] encryptDecryptInternal(byte[] text, int mode) throws NoSuchAlgorithmException, + InvalidAlgorithmParameterException, InvalidKeyException, + NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException { + + final SecretKey key = new SecretKeySpec(secretKeyService.getKey(), "DESede"); + // TODO: make random each time + final IvParameterSpec iv = new IvParameterSpec(new byte[8]); + final Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding"); + cipher.init(mode, key, iv); + + return cipher.doFinal(text); + } + +} diff --git a/src/test/java/com/verygood/security/coding/service/SecretKeyServiceImplTest.java b/src/test/java/com/verygood/security/coding/service/SecretKeyServiceImplTest.java new file mode 100644 index 0000000..4f12974 --- /dev/null +++ b/src/test/java/com/verygood/security/coding/service/SecretKeyServiceImplTest.java @@ -0,0 +1,31 @@ +package com.verygood.security.coding.service; + +import static com.verygood.security.coding.service.SecretKeyServiceImpl.KEY_SIZE; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import org.junit.jupiter.api.Test; + +class SecretKeyServiceImplTest { + + private SecretKeyServiceImpl secretKeyService; + + @Test + void getKey_retuns24BytesKey() throws InvalidKeySpecException, NoSuchAlgorithmException { + // given + String keyInString = "test"; + secretKeyService = new SecretKeyServiceImpl(keyInString); + + // when + byte[] actualEncryptionKey = secretKeyService.getKey(); + + // then + assertThat(actualEncryptionKey.length, is(KEY_SIZE)); + String keyAfterGeneration = new String(actualEncryptionKey, StandardCharsets.UTF_8); + assertThat(keyInString, is(not(keyAfterGeneration))); + } +} \ No newline at end of file diff --git a/src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java b/src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java new file mode 100644 index 0000000..081987a --- /dev/null +++ b/src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java @@ -0,0 +1,67 @@ +package com.verygood.security.coding.service; + +import static com.verygood.security.coding.service.SecretKeyServiceImpl.KEY_SIZE; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.text.IsEmptyString.emptyString; +import static org.mockito.BDDMockito.given; + +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class TripleDesEncryptionImplTest { + + private static final String STRING_TO_ENCRYPT = "test"; + @Mock + SecretKeyServiceImpl secretKeyService; + @InjectMocks + private TripleDesEncryptionImpl tripleDesEncryption; + + @BeforeEach + void setUp() throws InvalidKeySpecException, NoSuchAlgorithmException { + given(secretKeyService.getKey()).willReturn(new byte[KEY_SIZE]); + } + + @Test + void encrypt_encryptsData() + throws BadPaddingException, InvalidKeyException, IllegalBlockSizeException, + InvalidKeySpecException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, + NoSuchPaddingException { + // when + String encryptedText = tripleDesEncryption.encrypt(STRING_TO_ENCRYPT); + + // then + assertThat(encryptedText, is(not(emptyString()))); + assertThat(encryptedText, is(not(STRING_TO_ENCRYPT))); + + } + + @Test + void encrypt_decryptsData() + throws BadPaddingException, InvalidKeyException, IllegalBlockSizeException, + InvalidKeySpecException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, + NoSuchPaddingException { + // when + String encryptedText = tripleDesEncryption.encrypt(STRING_TO_ENCRYPT); + String decryptedText = tripleDesEncryption.decrypt(encryptedText); + + // then + assertThat(decryptedText, is(not(emptyString()))); + assertThat(decryptedText, is(STRING_TO_ENCRYPT)); + + } +} \ No newline at end of file From 00e0b1a1af8ccc8ef6e549e18c6c516cac73b7d6 Mon Sep 17 00:00:00 2001 From: koco Date: Wed, 27 May 2020 16:49:26 +0300 Subject: [PATCH 02/11] Make initialization vector random --- pom.xml | 6 ------ .../coding/service/TripleDesEncryptionImpl.java | 15 +++++++++++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 1ff9fec..b8b672b 100644 --- a/pom.xml +++ b/pom.xml @@ -27,12 +27,6 @@ ${hamcrest.version} provided - - org.projectlombok - lombok - ${lombok.version} - provided - org.mockito mockito-junit-jupiter diff --git a/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java b/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java index ff77c9a..b8563b0 100644 --- a/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java +++ b/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java @@ -7,6 +7,7 @@ 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; import javax.crypto.BadPaddingException; @@ -21,6 +22,7 @@ public class TripleDesEncryptionImpl implements Encryption { private static final Charset ENCODING = StandardCharsets.UTF_8; private final ISecretKeyService secretKeyService; + private IvParameterSpec ivParameterSpec; public TripleDesEncryptionImpl(ISecretKeyService secretKeyService) { this.secretKeyService = secretKeyService; @@ -47,12 +49,21 @@ private byte[] encryptDecryptInternal(byte[] text, int mode) throws NoSuchAlgori NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException { final SecretKey key = new SecretKeySpec(secretKeyService.getKey(), "DESede"); - // TODO: make random each time - final IvParameterSpec iv = new IvParameterSpec(new byte[8]); + final IvParameterSpec iv = getIvParameterSpec(); final Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding"); cipher.init(mode, key, iv); return cipher.doFinal(text); } + private IvParameterSpec getIvParameterSpec() throws NoSuchAlgorithmException { + if (ivParameterSpec == null) { + SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG"); + byte[] bytes = new byte[8]; + randomSecureRandom.nextBytes(bytes); + ivParameterSpec = new IvParameterSpec(bytes); + } + return ivParameterSpec; + } + } From 3f428cc335ae0e9738c74b05ff2a5d86dec47466 Mon Sep 17 00:00:00 2001 From: koco Date: Wed, 27 May 2020 16:54:38 +0300 Subject: [PATCH 03/11] Extract algorithms to constants --- .../security/coding/service/TripleDesEncryptionImpl.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java b/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java index b8563b0..753f281 100644 --- a/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java +++ b/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java @@ -21,6 +21,9 @@ public class TripleDesEncryptionImpl implements Encryption { private static final Charset ENCODING = StandardCharsets.UTF_8; + private static final String ALGORITHM = "DESede"; + private static final String TRANSFORMATION = ALGORITHM + "/CBC/PKCS5Padding"; + private static final String SECURE_RANDOM_ALGORITHM = "SHA1PRNG"; private final ISecretKeyService secretKeyService; private IvParameterSpec ivParameterSpec; @@ -48,9 +51,9 @@ private byte[] encryptDecryptInternal(byte[] text, int mode) throws NoSuchAlgori InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException { - final SecretKey key = new SecretKeySpec(secretKeyService.getKey(), "DESede"); + final SecretKey key = new SecretKeySpec(secretKeyService.getKey(), ALGORITHM); final IvParameterSpec iv = getIvParameterSpec(); - final Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding"); + final Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(mode, key, iv); return cipher.doFinal(text); @@ -58,7 +61,7 @@ private byte[] encryptDecryptInternal(byte[] text, int mode) throws NoSuchAlgori private IvParameterSpec getIvParameterSpec() throws NoSuchAlgorithmException { if (ivParameterSpec == null) { - SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG"); + SecureRandom randomSecureRandom = SecureRandom.getInstance(SECURE_RANDOM_ALGORITHM); byte[] bytes = new byte[8]; randomSecureRandom.nextBytes(bytes); ivParameterSpec = new IvParameterSpec(bytes); From 7574fa5e84c37eb5c1c771aad14cbf4568ec3232 Mon Sep 17 00:00:00 2001 From: koco Date: Wed, 27 May 2020 17:21:25 +0300 Subject: [PATCH 04/11] Add more tests for TripleDesEncryptionImpl.java --- .../service/TripleDesEncryptionImpl.java | 12 +++- .../service/TripleDesEncryptionImplTest.java | 67 +++++++++++++++++-- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java b/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java index 753f281..91490c4 100644 --- a/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java +++ b/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java @@ -35,6 +35,9 @@ public TripleDesEncryptionImpl(ISecretKeyService secretKeyService) { public String encrypt(String text) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeySpecException { + if (text == null) { + return null; + } byte[] bytes = encryptDecryptInternal(text.getBytes(ENCODING), Cipher.ENCRYPT_MODE); return Base64.getEncoder().encodeToString(bytes); } @@ -42,6 +45,9 @@ public String encrypt(String text) @Override public String decrypt(String text) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException { + if (text == null) { + return null; + } byte[] textDecoded = Base64.getDecoder().decode(text); byte[] bytes = encryptDecryptInternal(textDecoded, Cipher.DECRYPT_MODE); return new String(bytes, ENCODING); @@ -51,9 +57,9 @@ private byte[] encryptDecryptInternal(byte[] text, int mode) throws NoSuchAlgori InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException { - final SecretKey key = new SecretKeySpec(secretKeyService.getKey(), ALGORITHM); - final IvParameterSpec iv = getIvParameterSpec(); - final Cipher cipher = Cipher.getInstance(TRANSFORMATION); + SecretKey key = new SecretKeySpec(secretKeyService.getKey(), ALGORITHM); + IvParameterSpec iv = getIvParameterSpec(); + Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(mode, key, iv); return cipher.doFinal(text); diff --git a/src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java b/src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java index 081987a..7a96359 100644 --- a/src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java +++ b/src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java @@ -31,16 +31,14 @@ class TripleDesEncryptionImplTest { @InjectMocks private TripleDesEncryptionImpl tripleDesEncryption; - @BeforeEach - void setUp() throws InvalidKeySpecException, NoSuchAlgorithmException { - given(secretKeyService.getKey()).willReturn(new byte[KEY_SIZE]); - } - @Test void encrypt_encryptsData() throws BadPaddingException, InvalidKeyException, IllegalBlockSizeException, InvalidKeySpecException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException { + // given + given(secretKeyService.getKey()).willReturn(new byte[KEY_SIZE]); + // when String encryptedText = tripleDesEncryption.encrypt(STRING_TO_ENCRYPT); @@ -51,10 +49,32 @@ void encrypt_encryptsData() } @Test - void encrypt_decryptsData() + void encrypt_returnsTheSameValueOnMultipleInvocations() + throws BadPaddingException, InvalidKeyException, IllegalBlockSizeException, + InvalidKeySpecException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, + NoSuchPaddingException { + // given + given(secretKeyService.getKey()).willReturn(new byte[KEY_SIZE]); + + // when + String firstEncryption = tripleDesEncryption.encrypt(STRING_TO_ENCRYPT); + String secondEncryption = tripleDesEncryption.encrypt(STRING_TO_ENCRYPT); + + // then + assertThat(firstEncryption, is(not(emptyString()))); + assertThat(secondEncryption, is(not(emptyString()))); + assertThat(firstEncryption, is((secondEncryption))); + + } + + @Test + void decrypt_decryptsData() throws BadPaddingException, InvalidKeyException, IllegalBlockSizeException, InvalidKeySpecException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException { + // given + given(secretKeyService.getKey()).willReturn(new byte[KEY_SIZE]); + // when String encryptedText = tripleDesEncryption.encrypt(STRING_TO_ENCRYPT); String decryptedText = tripleDesEncryption.decrypt(encryptedText); @@ -64,4 +84,39 @@ void encrypt_decryptsData() assertThat(decryptedText, is(STRING_TO_ENCRYPT)); } + + @Test + void encrypt_properlyEncryptsEmptyString() + throws BadPaddingException, InvalidKeyException, IllegalBlockSizeException, + InvalidKeySpecException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, + NoSuchPaddingException { + // given + given(secretKeyService.getKey()).willReturn(new byte[KEY_SIZE]); + String expectedString = ""; + + // when + String encryptedText = tripleDesEncryption.encrypt(expectedString); + String decryptedText = tripleDesEncryption.decrypt(encryptedText); + + // then + assertThat(decryptedText, is(expectedString)); + + } + + @Test + void encrypt_returnsNullOnNullString() + throws BadPaddingException, InvalidKeyException, IllegalBlockSizeException, + InvalidKeySpecException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, + NoSuchPaddingException { + // given + String expectedString = null; + + // when + String encryptedText = tripleDesEncryption.encrypt(expectedString); + String decryptedText = tripleDesEncryption.decrypt(encryptedText); + + // then + assertThat(decryptedText, is(expectedString)); + + } } \ No newline at end of file From ebfd6d56ea2c466408e92db674e92a1d46f76c0d Mon Sep 17 00:00:00 2001 From: Oleksandr Kochniev Date: Wed, 27 May 2020 20:33:15 +0300 Subject: [PATCH 05/11] Rename SecretKeyService to EncryptionKeyService to avoid confusion with the SecretKey class. Avoid allocating new SecretKeySpec object on each encryption/decryption. Increase test coverage on EncryptionKeyServiceImpl.java --- ...ervice.java => IEncryptionKeyService.java} | 2 +- ...mpl.java => EncryptionKeyServiceImpl.java} | 11 +++- .../service/TripleDesEncryptionImpl.java | 16 +++-- .../service/EncryptionKeyServiceImplTest.java | 61 +++++++++++++++++++ .../service/SecretKeyServiceImplTest.java | 31 ---------- .../service/TripleDesEncryptionImplTest.java | 6 +- 6 files changed, 84 insertions(+), 43 deletions(-) rename src/main/java/com/verygood/security/coding/api/{ISecretKeyService.java => IEncryptionKeyService.java} (84%) rename src/main/java/com/verygood/security/coding/service/{SecretKeyServiceImpl.java => EncryptionKeyServiceImpl.java} (84%) create mode 100644 src/test/java/com/verygood/security/coding/service/EncryptionKeyServiceImplTest.java delete mode 100644 src/test/java/com/verygood/security/coding/service/SecretKeyServiceImplTest.java diff --git a/src/main/java/com/verygood/security/coding/api/ISecretKeyService.java b/src/main/java/com/verygood/security/coding/api/IEncryptionKeyService.java similarity index 84% rename from src/main/java/com/verygood/security/coding/api/ISecretKeyService.java rename to src/main/java/com/verygood/security/coding/api/IEncryptionKeyService.java index c283333..2eecff2 100644 --- a/src/main/java/com/verygood/security/coding/api/ISecretKeyService.java +++ b/src/main/java/com/verygood/security/coding/api/IEncryptionKeyService.java @@ -3,6 +3,6 @@ import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; -public interface ISecretKeyService { +public interface IEncryptionKeyService { byte[] getKey() throws NoSuchAlgorithmException, InvalidKeySpecException; } diff --git a/src/main/java/com/verygood/security/coding/service/SecretKeyServiceImpl.java b/src/main/java/com/verygood/security/coding/service/EncryptionKeyServiceImpl.java similarity index 84% rename from src/main/java/com/verygood/security/coding/service/SecretKeyServiceImpl.java rename to src/main/java/com/verygood/security/coding/service/EncryptionKeyServiceImpl.java index 689e58c..c4b10b8 100644 --- a/src/main/java/com/verygood/security/coding/service/SecretKeyServiceImpl.java +++ b/src/main/java/com/verygood/security/coding/service/EncryptionKeyServiceImpl.java @@ -1,13 +1,13 @@ package com.verygood.security.coding.service; -import com.verygood.security.coding.api.ISecretKeyService; +import com.verygood.security.coding.api.IEncryptionKeyService; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; -public class SecretKeyServiceImpl implements ISecretKeyService { +public class EncryptionKeyServiceImpl implements IEncryptionKeyService { private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1"; private static final String SHA1PRNG_ALGORITHM = "SHA1PRNG"; @@ -15,7 +15,7 @@ public class SecretKeyServiceImpl implements ISecretKeyService { private byte[] encryptionKeyInBytes; private String encryptionKey; - SecretKeyServiceImpl(String encryptionKey) { + EncryptionKeyServiceImpl(String encryptionKey) { this.encryptionKey = encryptionKey; } @@ -28,6 +28,11 @@ public byte[] getKey() throws NoSuchAlgorithmException, InvalidKeySpecException } private byte[] generateKey() throws NoSuchAlgorithmException, InvalidKeySpecException { + + if (encryptionKey == null) { + encryptionKey = ""; + } + int iterations = 1000; char[] chars = encryptionKey.toCharArray(); byte[] salt = getSalt(); diff --git a/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java b/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java index 91490c4..fe24f74 100644 --- a/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java +++ b/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java @@ -1,7 +1,7 @@ package com.verygood.security.coding.service; import com.verygood.security.coding.api.Encryption; -import com.verygood.security.coding.api.ISecretKeyService; +import com.verygood.security.coding.api.IEncryptionKeyService; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; @@ -24,10 +24,11 @@ public class TripleDesEncryptionImpl implements Encryption { private static final String ALGORITHM = "DESede"; private static final String TRANSFORMATION = ALGORITHM + "/CBC/PKCS5Padding"; private static final String SECURE_RANDOM_ALGORITHM = "SHA1PRNG"; - private final ISecretKeyService secretKeyService; + private final IEncryptionKeyService secretKeyService; private IvParameterSpec ivParameterSpec; + private SecretKeySpec secretKeySpecs; - public TripleDesEncryptionImpl(ISecretKeyService secretKeyService) { + public TripleDesEncryptionImpl(IEncryptionKeyService secretKeyService) { this.secretKeyService = secretKeyService; } @@ -57,7 +58,7 @@ private byte[] encryptDecryptInternal(byte[] text, int mode) throws NoSuchAlgori InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException { - SecretKey key = new SecretKeySpec(secretKeyService.getKey(), ALGORITHM); + SecretKey key = getSecretKey(); IvParameterSpec iv = getIvParameterSpec(); Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(mode, key, iv); @@ -65,6 +66,13 @@ private byte[] encryptDecryptInternal(byte[] text, int mode) throws NoSuchAlgori return cipher.doFinal(text); } + private SecretKeySpec getSecretKey() throws NoSuchAlgorithmException, InvalidKeySpecException { + if (secretKeySpecs == null) { + secretKeySpecs = new SecretKeySpec(secretKeyService.getKey(), ALGORITHM); + } + return secretKeySpecs; + } + private IvParameterSpec getIvParameterSpec() throws NoSuchAlgorithmException { if (ivParameterSpec == null) { SecureRandom randomSecureRandom = SecureRandom.getInstance(SECURE_RANDOM_ALGORITHM); diff --git a/src/test/java/com/verygood/security/coding/service/EncryptionKeyServiceImplTest.java b/src/test/java/com/verygood/security/coding/service/EncryptionKeyServiceImplTest.java new file mode 100644 index 0000000..2003d31 --- /dev/null +++ b/src/test/java/com/verygood/security/coding/service/EncryptionKeyServiceImplTest.java @@ -0,0 +1,61 @@ +package com.verygood.security.coding.service; + +import static com.verygood.security.coding.service.EncryptionKeyServiceImpl.KEY_SIZE; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import org.junit.jupiter.api.Test; + +class EncryptionKeyServiceImplTest { + + private EncryptionKeyServiceImpl encryptionKeyService; + + @Test + void getKey_returns24BytesKey() throws InvalidKeySpecException, NoSuchAlgorithmException { + // given + String keyInString = "test"; + encryptionKeyService = new EncryptionKeyServiceImpl(keyInString); + + // when + byte[] actualEncryptionKey = encryptionKeyService.getKey(); + + // then + assertThat(actualEncryptionKey.length, is(KEY_SIZE)); + String keyAfterGeneration = new String(actualEncryptionKey, StandardCharsets.UTF_8); + assertThat(keyInString, is(not(keyAfterGeneration))); + } + + @Test + void getKey_returns24BytesKeyForNull() throws InvalidKeySpecException, NoSuchAlgorithmException { + // given + String keyInString = null; + encryptionKeyService = new EncryptionKeyServiceImpl(keyInString); + + // when + byte[] actualEncryptionKey = encryptionKeyService.getKey(); + + // then + assertThat(actualEncryptionKey.length, is(KEY_SIZE)); + String keyAfterGeneration = new String(actualEncryptionKey, StandardCharsets.UTF_8); + assertThat(keyInString, is(not(keyAfterGeneration))); + } + + @Test + void getKey_returns24BytesKeyForEmptyString() throws InvalidKeySpecException, NoSuchAlgorithmException { + // given + String keyInString = ""; + encryptionKeyService = new EncryptionKeyServiceImpl(keyInString); + + // when + byte[] actualEncryptionKey = encryptionKeyService.getKey(); + + // then + assertThat(actualEncryptionKey.length, is(KEY_SIZE)); + String keyAfterGeneration = new String(actualEncryptionKey, StandardCharsets.UTF_8); + assertThat(keyInString, is(not(keyAfterGeneration))); + } +} \ No newline at end of file diff --git a/src/test/java/com/verygood/security/coding/service/SecretKeyServiceImplTest.java b/src/test/java/com/verygood/security/coding/service/SecretKeyServiceImplTest.java deleted file mode 100644 index 4f12974..0000000 --- a/src/test/java/com/verygood/security/coding/service/SecretKeyServiceImplTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.verygood.security.coding.service; - -import static com.verygood.security.coding.service.SecretKeyServiceImpl.KEY_SIZE; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; - -import java.nio.charset.StandardCharsets; -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; -import org.junit.jupiter.api.Test; - -class SecretKeyServiceImplTest { - - private SecretKeyServiceImpl secretKeyService; - - @Test - void getKey_retuns24BytesKey() throws InvalidKeySpecException, NoSuchAlgorithmException { - // given - String keyInString = "test"; - secretKeyService = new SecretKeyServiceImpl(keyInString); - - // when - byte[] actualEncryptionKey = secretKeyService.getKey(); - - // then - assertThat(actualEncryptionKey.length, is(KEY_SIZE)); - String keyAfterGeneration = new String(actualEncryptionKey, StandardCharsets.UTF_8); - assertThat(keyInString, is(not(keyAfterGeneration))); - } -} \ No newline at end of file diff --git a/src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java b/src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java index 7a96359..04863e8 100644 --- a/src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java +++ b/src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java @@ -1,13 +1,12 @@ package com.verygood.security.coding.service; -import static com.verygood.security.coding.service.SecretKeyServiceImpl.KEY_SIZE; +import static com.verygood.security.coding.service.EncryptionKeyServiceImpl.KEY_SIZE; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.text.IsEmptyString.emptyString; import static org.mockito.BDDMockito.given; -import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -15,7 +14,6 @@ import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -27,7 +25,7 @@ class TripleDesEncryptionImplTest { private static final String STRING_TO_ENCRYPT = "test"; @Mock - SecretKeyServiceImpl secretKeyService; + EncryptionKeyServiceImpl secretKeyService; @InjectMocks private TripleDesEncryptionImpl tripleDesEncryption; From a46cf6f711ad5317daaf772c1ad979d4070e1846 Mon Sep 17 00:00:00 2001 From: Oleksandr Kochniev Date: Wed, 27 May 2020 21:20:54 +0300 Subject: [PATCH 06/11] Add a logic to the main method. Add test for the main method. Avoid re-throwing checked exceptions in TripleDesEncryptionImpl.java --- pom.xml | 24 ++++++++- .../security/coding/CodingApplication.java | 26 ++++++++-- .../security/coding/api/Encryption.java | 15 +----- .../WrongInputArgumentsException.java | 8 +++ .../service/EncryptionKeyServiceImpl.java | 3 +- .../service/TripleDesEncryptionImpl.java | 23 ++++++--- .../coding/CodingApplicationTests.java | 50 ++++++++++++++++++- 7 files changed, 122 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/verygood/security/coding/api/exception/WrongInputArgumentsException.java diff --git a/pom.xml b/pom.xml index b8b672b..ee31a4c 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,7 @@ 1.8 2.2 5.6.2 + 2.23.0 @@ -30,7 +31,7 @@ org.mockito mockito-junit-jupiter - 2.23.0 + ${mockito-junit-jupiter.version} test @@ -45,6 +46,27 @@ ${java.version} + + maven-assembly-plugin + + + package + + single + + + + + + + com.verygood.security.coding.CodingApplication + + + + jar-with-dependencies + + + diff --git a/src/main/java/com/verygood/security/coding/CodingApplication.java b/src/main/java/com/verygood/security/coding/CodingApplication.java index 3854440..255bc63 100644 --- a/src/main/java/com/verygood/security/coding/CodingApplication.java +++ b/src/main/java/com/verygood/security/coding/CodingApplication.java @@ -1,8 +1,28 @@ package com.verygood.security.coding; +import com.verygood.security.coding.api.Encryption; +import com.verygood.security.coding.api.IEncryptionKeyService; +import com.verygood.security.coding.api.exception.WrongInputArgumentsException; +import com.verygood.security.coding.service.EncryptionKeyServiceImpl; +import com.verygood.security.coding.service.TripleDesEncryptionImpl; + public class CodingApplication { - public static void main(String[] args) { - // TODO encrypt passed data with algorithm of your choice - } + public static void main(String[] args) { + if (args.length <= 1) { + throw new WrongInputArgumentsException("Arguments are empty or half empty. Please input an encryption key and a text, " + + "e.g. ./application.jar FHDGYR 'text to encrypt'"); + } + + String encryptionKey = args[0]; + String textToEncrypt = args[1]; + IEncryptionKeyService encryptionKeyService = new EncryptionKeyServiceImpl(encryptionKey); + Encryption encryptionService = new TripleDesEncryptionImpl(encryptionKeyService); + String encryptedText = encryptionService.encrypt(textToEncrypt); + String decryptedText = encryptionService.decrypt(encryptedText); + + System.out.println("Encrypted text: " + encryptedText); + System.out.println("Decrypted text: " + decryptedText); + System.exit(0); + } } diff --git a/src/main/java/com/verygood/security/coding/api/Encryption.java b/src/main/java/com/verygood/security/coding/api/Encryption.java index 70bedd2..3e04acb 100644 --- a/src/main/java/com/verygood/security/coding/api/Encryption.java +++ b/src/main/java/com/verygood/security/coding/api/Encryption.java @@ -1,17 +1,6 @@ package com.verygood.security.coding.api; -import java.io.UnsupportedEncodingException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; - public interface Encryption { - String encrypt(String text) - throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeySpecException; - String decrypt(String encryptedData) - throws NoSuchAlgorithmException, UnsupportedEncodingException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException; + String encrypt(String text); + String decrypt(String encryptedData); } diff --git a/src/main/java/com/verygood/security/coding/api/exception/WrongInputArgumentsException.java b/src/main/java/com/verygood/security/coding/api/exception/WrongInputArgumentsException.java new file mode 100644 index 0000000..3fb1220 --- /dev/null +++ b/src/main/java/com/verygood/security/coding/api/exception/WrongInputArgumentsException.java @@ -0,0 +1,8 @@ +package com.verygood.security.coding.api.exception; + +public class WrongInputArgumentsException extends RuntimeException { + + public WrongInputArgumentsException(String message) { + super(message); + } +} diff --git a/src/main/java/com/verygood/security/coding/service/EncryptionKeyServiceImpl.java b/src/main/java/com/verygood/security/coding/service/EncryptionKeyServiceImpl.java index c4b10b8..cad84bf 100644 --- a/src/main/java/com/verygood/security/coding/service/EncryptionKeyServiceImpl.java +++ b/src/main/java/com/verygood/security/coding/service/EncryptionKeyServiceImpl.java @@ -15,7 +15,7 @@ public class EncryptionKeyServiceImpl implements IEncryptionKeyService { private byte[] encryptionKeyInBytes; private String encryptionKey; - EncryptionKeyServiceImpl(String encryptionKey) { + public EncryptionKeyServiceImpl(String encryptionKey) { this.encryptionKey = encryptionKey; } @@ -30,6 +30,7 @@ public byte[] getKey() throws NoSuchAlgorithmException, InvalidKeySpecException private byte[] generateKey() throws NoSuchAlgorithmException, InvalidKeySpecException { if (encryptionKey == null) { + System.out.println("Encryption key is blank, using an autogenerated key"); encryptionKey = ""; } diff --git a/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java b/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java index fe24f74..62ddebc 100644 --- a/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java +++ b/src/main/java/com/verygood/security/coding/service/TripleDesEncryptionImpl.java @@ -33,24 +33,33 @@ public TripleDesEncryptionImpl(IEncryptionKeyService secretKeyService) { } @Override - public String encrypt(String text) - throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException, - NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeySpecException { + public String encrypt(String text) { if (text == null) { return null; } - byte[] bytes = encryptDecryptInternal(text.getBytes(ENCODING), Cipher.ENCRYPT_MODE); + byte[] bytes = new byte[0]; + try { + bytes = encryptDecryptInternal(text.getBytes(ENCODING), Cipher.ENCRYPT_MODE); + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchPaddingException + | BadPaddingException | IllegalBlockSizeException | InvalidKeySpecException e) { + e.printStackTrace(); + } return Base64.getEncoder().encodeToString(bytes); } @Override - public String decrypt(String text) throws NoSuchAlgorithmException, - NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException { + public String decrypt(String text) { if (text == null) { return null; } byte[] textDecoded = Base64.getDecoder().decode(text); - byte[] bytes = encryptDecryptInternal(textDecoded, Cipher.DECRYPT_MODE); + byte[] bytes = new byte[0]; + try { + bytes = encryptDecryptInternal(textDecoded, Cipher.DECRYPT_MODE); + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchPaddingException + | BadPaddingException | IllegalBlockSizeException | InvalidKeySpecException e) { + e.printStackTrace(); + } return new String(bytes, ENCODING); } diff --git a/src/test/java/com/verygood/security/coding/CodingApplicationTests.java b/src/test/java/com/verygood/security/coding/CodingApplicationTests.java index bec028b..4b687a7 100644 --- a/src/test/java/com/verygood/security/coding/CodingApplicationTests.java +++ b/src/test/java/com/verygood/security/coding/CodingApplicationTests.java @@ -1,12 +1,58 @@ package com.verygood.security.coding; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.verygood.security.coding.api.exception.WrongInputArgumentsException; import org.junit.jupiter.api.Test; class CodingApplicationTests { - // TODO add test cases + @Test + void applicationStartsWithoutErrors() { + String[] args = new String[]{"encryptionKey", "textToEncrypt"}; + CodingApplication.main(args); + } + + @Test + void applicationStartsWithoutErrors_ifKeyAndTextAreEmpty() { + String[] args = new String[]{"", ""}; + CodingApplication.main(args); + } + + @Test + void applicationStartsWithoutErrors_ifKeyAndTextAreBlank() { + String[] args = new String[]{" ", " "}; + CodingApplication.main(args); + } + + @Test + void applicationStartsWithoutErrors_ifKeyIsBlank_whileTextHasSomeValue() { + String[] args = new String[]{" ", "test"}; + CodingApplication.main(args); + } + + @Test + void applicationStartsWithoutErrors_ifKeyNotBlank_whileTextIsBlank() { + String[] args = new String[]{"test", " "}; + CodingApplication.main(args); + } + + + @Test + void applicationThrowsWrongInputArgumentsException_ifOnlyOneArgPassed() { + assertThrows(WrongInputArgumentsException.class, + ()->{ + String[] args = new String[]{"encryptionKey"}; + CodingApplication.main(args); + }); + } @Test - void testSomething() { + void applicationThrowsWrongInputArgumentsException_ifNoArgPassed() { + assertThrows(WrongInputArgumentsException.class, + ()->{ + String[] args = new String[]{}; + CodingApplication.main(args); + }); } } From 639109f5428852b94cf8fefa789401b78e11a8b9 Mon Sep 17 00:00:00 2001 From: Oleksandr Kochniev Date: Wed, 27 May 2020 21:59:06 +0300 Subject: [PATCH 07/11] Add docker-compose.yml and Dockerfile. Resort to reading encryption key and encryption text from environment variables if they weren't passed as the program arguments --- docker/Dockerfile | 4 ++++ docker/docker-compose.yml | 5 +++++ .../security/coding/CodingApplication.java | 17 +++++++++++++++-- 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 docker/Dockerfile create mode 100644 docker/docker-compose.yml diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..740ab69 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,4 @@ +FROM java:8-jdk-alpine +COPY ../target/coding-0.0.1-SNAPSHOT-jar-with-dependencies.jar /usr/app/ +WORKDIR /usr/app +ENTRYPOINT ["java", "-jar", "coding-0.0.1-SNAPSHOT-jar-with-dependencies.jar"] \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..07a8670 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,5 @@ +encryption: + build: . + environment: + - ENCRYPTION_KEY=KDKDKDKD + - TEXT_TO_ENCRYPT='some t3xt' \ No newline at end of file diff --git a/src/main/java/com/verygood/security/coding/CodingApplication.java b/src/main/java/com/verygood/security/coding/CodingApplication.java index 255bc63..2302143 100644 --- a/src/main/java/com/verygood/security/coding/CodingApplication.java +++ b/src/main/java/com/verygood/security/coding/CodingApplication.java @@ -8,10 +8,16 @@ public class CodingApplication { + static final String ENCRYPTION_KEY_ENV_VARIABLE = "ENCRYPTION_KEY"; + static final String TEXT_TO_ENCRYPT_ENV_VARIABLE = "TEXT_TO_ENCRYPT"; + public static void main(String[] args) { if (args.length <= 1) { - throw new WrongInputArgumentsException("Arguments are empty or half empty. Please input an encryption key and a text, " - + "e.g. ./application.jar FHDGYR 'text to encrypt'"); + readArgsFromEnvironmentVariables(args); + if (args.length <= 1) { + throw new WrongInputArgumentsException("Arguments are empty or half empty. Please input an encryption key and a text, " + + "e.g. ./application.jar FHDGYR 'text to encrypt'"); + } } String encryptionKey = args[0]; @@ -25,4 +31,11 @@ public static void main(String[] args) { System.out.println("Decrypted text: " + decryptedText); System.exit(0); } + + private static void readArgsFromEnvironmentVariables(String[] args) { + String encryptionKey = System.getenv(ENCRYPTION_KEY_ENV_VARIABLE); + String textToEncrypt = System.getenv(TEXT_TO_ENCRYPT_ENV_VARIABLE); + args[0] = encryptionKey; + args[1] = textToEncrypt; + } } From 8ce77a69e9c1cbdff29a9bed735e9a2774fae1ee Mon Sep 17 00:00:00 2001 From: koco Date: Thu, 28 May 2020 10:12:58 +0300 Subject: [PATCH 08/11] Add README.md --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a84fba4..bba0ab6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,22 @@ -# coding -Project 4fun +# 3DES Encryption +3DES Encryption algorithm implementation in Java +- The application takes arguments from command line, the first argument is an encryption key, the second is text to be encrypted +- If encryption key and encryption text weren't passed from the command line, the application will try reading them from environment variables + `ENCRYPTION_KEY` `TEXT_TO_ENCRYPT` +- The application uses a 24 bit encryption key and 8 bit initialization vector +- Encryption key and initialization vector use SHA1PRNG for key generation +- DESede/CBC/PKCS5Padding is used as transformation +- Null values are not encrypted, if null used as an input for the encrypt method, the method will immediately return the null reference. The same +applies to the decrypt method + +## To run the application +- Checkout the project +- `mvn package` +- `java -jar coding-0.0.1-SNAPSHOT-jar-with-dependencies.jar KEY 'text to encrypt'` + +## To run the application via docker +- Checkout the project +- `mvn package` +- `cd docker` +- Open the `docker-compose.yml` and set `ENCRYPTION_KEY` `TEXT_TO_ENCRYPT` environment variables +- `docker-compose up` \ No newline at end of file From 98e41204c3ce83e6fba4b073b78108f08ce5f12d Mon Sep 17 00:00:00 2001 From: koco Date: Thu, 28 May 2020 10:41:43 +0300 Subject: [PATCH 09/11] Decrease the number of iterations when encrypting encryption key. Add tests more tests with Russian --- .../service/EncryptionKeyServiceImpl.java | 2 +- .../coding/CodingApplicationTests.java | 12 +++++ .../service/TripleDesEncryptionImplTest.java | 46 +++++++++---------- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/verygood/security/coding/service/EncryptionKeyServiceImpl.java b/src/main/java/com/verygood/security/coding/service/EncryptionKeyServiceImpl.java index cad84bf..abde18d 100644 --- a/src/main/java/com/verygood/security/coding/service/EncryptionKeyServiceImpl.java +++ b/src/main/java/com/verygood/security/coding/service/EncryptionKeyServiceImpl.java @@ -34,7 +34,7 @@ private byte[] generateKey() throws NoSuchAlgorithmException, InvalidKeySpecExce encryptionKey = ""; } - int iterations = 1000; + int iterations = 10; char[] chars = encryptionKey.toCharArray(); byte[] salt = getSalt(); diff --git a/src/test/java/com/verygood/security/coding/CodingApplicationTests.java b/src/test/java/com/verygood/security/coding/CodingApplicationTests.java index 4b687a7..9a37691 100644 --- a/src/test/java/com/verygood/security/coding/CodingApplicationTests.java +++ b/src/test/java/com/verygood/security/coding/CodingApplicationTests.java @@ -13,6 +13,18 @@ void applicationStartsWithoutErrors() { CodingApplication.main(args); } + @Test + void applicationEncryptsDecryptsRussian() { + String[] args = new String[]{"encryptionKey", "русский"}; + CodingApplication.main(args); + } + + @Test + void applicationEncryptsDecryptsRussian_whenKeyInRussian() { + String[] args = new String[]{"русскийКлюч", "русский"}; + CodingApplication.main(args); + } + @Test void applicationStartsWithoutErrors_ifKeyAndTextAreEmpty() { String[] args = new String[]{"", ""}; diff --git a/src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java b/src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java index 04863e8..829da2e 100644 --- a/src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java +++ b/src/test/java/com/verygood/security/coding/service/TripleDesEncryptionImplTest.java @@ -7,13 +7,8 @@ import static org.hamcrest.text.IsEmptyString.emptyString; import static org.mockito.BDDMockito.given; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -30,10 +25,7 @@ class TripleDesEncryptionImplTest { private TripleDesEncryptionImpl tripleDesEncryption; @Test - void encrypt_encryptsData() - throws BadPaddingException, InvalidKeyException, IllegalBlockSizeException, - InvalidKeySpecException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, - NoSuchPaddingException { + void encrypt_encryptsData() throws InvalidKeySpecException, NoSuchAlgorithmException { // given given(secretKeyService.getKey()).willReturn(new byte[KEY_SIZE]); @@ -47,10 +39,7 @@ void encrypt_encryptsData() } @Test - void encrypt_returnsTheSameValueOnMultipleInvocations() - throws BadPaddingException, InvalidKeyException, IllegalBlockSizeException, - InvalidKeySpecException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, - NoSuchPaddingException { + void encrypt_returnsTheSameValueOnMultipleInvocations() throws InvalidKeySpecException, NoSuchAlgorithmException { // given given(secretKeyService.getKey()).willReturn(new byte[KEY_SIZE]); @@ -66,10 +55,7 @@ void encrypt_returnsTheSameValueOnMultipleInvocations() } @Test - void decrypt_decryptsData() - throws BadPaddingException, InvalidKeyException, IllegalBlockSizeException, - InvalidKeySpecException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, - NoSuchPaddingException { + void decrypt_decryptsData() throws InvalidKeySpecException, NoSuchAlgorithmException { // given given(secretKeyService.getKey()).willReturn(new byte[KEY_SIZE]); @@ -84,10 +70,23 @@ void decrypt_decryptsData() } @Test - void encrypt_properlyEncryptsEmptyString() - throws BadPaddingException, InvalidKeyException, IllegalBlockSizeException, - InvalidKeySpecException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, - NoSuchPaddingException { + void decrypt_ProperlyDecryptsRussian() throws InvalidKeySpecException, NoSuchAlgorithmException { + // given + given(secretKeyService.getKey()).willReturn(new byte[KEY_SIZE]); + String expectedText = "русский"; + + // when + String encryptedText = tripleDesEncryption.encrypt(expectedText); + String decryptedText = tripleDesEncryption.decrypt(encryptedText); + + // then + assertThat(decryptedText, is(not(emptyString()))); + assertThat(decryptedText, is(expectedText)); + + } + + @Test + void encrypt_properlyEncryptsEmptyString() throws InvalidKeySpecException, NoSuchAlgorithmException { // given given(secretKeyService.getKey()).willReturn(new byte[KEY_SIZE]); String expectedString = ""; @@ -102,10 +101,7 @@ void encrypt_properlyEncryptsEmptyString() } @Test - void encrypt_returnsNullOnNullString() - throws BadPaddingException, InvalidKeyException, IllegalBlockSizeException, - InvalidKeySpecException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, - NoSuchPaddingException { + void encrypt_returnsNullOnNullString() { // given String expectedString = null; From 1fbf9ef5afcdaca5252dc4964678b25ac80ab0b1 Mon Sep 17 00:00:00 2001 From: koco Date: Thu, 28 May 2020 12:39:41 +0300 Subject: [PATCH 10/11] Move Dockerfile and docker-compose.yml to the root of the project. Add missing surefire maven plugin to run tests. Avoid System.exit in the main method in order to facilitate testing --- docker/Dockerfile => Dockerfile | 2 +- docker-compose.yml | 5 +++ docker/docker-compose.yml | 5 --- pom.xml | 28 ++++++++++++-- .../security/coding/CodingApplication.java | 38 +++++++++++-------- 5 files changed, 54 insertions(+), 24 deletions(-) rename docker/Dockerfile => Dockerfile (61%) create mode 100644 docker-compose.yml delete mode 100644 docker/docker-compose.yml diff --git a/docker/Dockerfile b/Dockerfile similarity index 61% rename from docker/Dockerfile rename to Dockerfile index 740ab69..0e08d29 100644 --- a/docker/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ FROM java:8-jdk-alpine -COPY ../target/coding-0.0.1-SNAPSHOT-jar-with-dependencies.jar /usr/app/ +COPY target/coding-0.0.1-SNAPSHOT-jar-with-dependencies.jar /usr/app/ WORKDIR /usr/app ENTRYPOINT ["java", "-jar", "coding-0.0.1-SNAPSHOT-jar-with-dependencies.jar"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c7ef0ee --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,5 @@ +encryption: + build: . + environment: + - ENCRYPTION_KEY="KDKDKDKD" + - TEXT_TO_ENCRYPT="some t3xt" \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index 07a8670..0000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,5 +0,0 @@ -encryption: - build: . - environment: - - ENCRYPTION_KEY=KDKDKDKD - - TEXT_TO_ENCRYPT='some t3xt' \ No newline at end of file diff --git a/pom.xml b/pom.xml index ee31a4c..a09b27b 100644 --- a/pom.xml +++ b/pom.xml @@ -11,15 +11,24 @@ 1.8 2.2 - 5.6.2 + 5.6.2 2.23.0 + 3.10 + UTF-8 + UTF-8 org.junit.jupiter junit-jupiter-api - ${junit-jupiter-api.version} + ${junit-jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} test @@ -34,6 +43,11 @@ ${mockito-junit-jupiter.version} test + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + @@ -67,7 +81,15 @@ - + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + -Dfile.encoding=UTF-8 + + + diff --git a/src/main/java/com/verygood/security/coding/CodingApplication.java b/src/main/java/com/verygood/security/coding/CodingApplication.java index 2302143..5d48a80 100644 --- a/src/main/java/com/verygood/security/coding/CodingApplication.java +++ b/src/main/java/com/verygood/security/coding/CodingApplication.java @@ -5,23 +5,18 @@ import com.verygood.security.coding.api.exception.WrongInputArgumentsException; import com.verygood.security.coding.service.EncryptionKeyServiceImpl; import com.verygood.security.coding.service.TripleDesEncryptionImpl; +import org.apache.commons.lang3.StringUtils; public class CodingApplication { static final String ENCRYPTION_KEY_ENV_VARIABLE = "ENCRYPTION_KEY"; static final String TEXT_TO_ENCRYPT_ENV_VARIABLE = "TEXT_TO_ENCRYPT"; + private static String encryptionKey; + private static String textToEncrypt; public static void main(String[] args) { - if (args.length <= 1) { - readArgsFromEnvironmentVariables(args); - if (args.length <= 1) { - throw new WrongInputArgumentsException("Arguments are empty or half empty. Please input an encryption key and a text, " - + "e.g. ./application.jar FHDGYR 'text to encrypt'"); - } - } + setArguments(args); - String encryptionKey = args[0]; - String textToEncrypt = args[1]; IEncryptionKeyService encryptionKeyService = new EncryptionKeyServiceImpl(encryptionKey); Encryption encryptionService = new TripleDesEncryptionImpl(encryptionKeyService); String encryptedText = encryptionService.encrypt(textToEncrypt); @@ -29,13 +24,26 @@ public static void main(String[] args) { System.out.println("Encrypted text: " + encryptedText); System.out.println("Decrypted text: " + decryptedText); - System.exit(0); } - private static void readArgsFromEnvironmentVariables(String[] args) { - String encryptionKey = System.getenv(ENCRYPTION_KEY_ENV_VARIABLE); - String textToEncrypt = System.getenv(TEXT_TO_ENCRYPT_ENV_VARIABLE); - args[0] = encryptionKey; - args[1] = textToEncrypt; + private static void setArguments(String[] args) { + if (args.length <= 1) { + readArgsFromEnvironmentVariables(); + if (StringUtils.isAllBlank(encryptionKey, textToEncrypt)) { + throw new WrongInputArgumentsException("Arguments are empty or half empty. Please input an encryption key and a text, " + + "e.g. ./application.jar FHDGYR 'text to encrypt'"); + } + } + else { + encryptionKey = args[0]; + textToEncrypt = args[1]; + } + } + + private static void readArgsFromEnvironmentVariables() { + String encryptionKeyFromEnv = System.getenv(ENCRYPTION_KEY_ENV_VARIABLE); + String textToEncryptFromEnv = System.getenv(TEXT_TO_ENCRYPT_ENV_VARIABLE); + encryptionKey = encryptionKeyFromEnv; + textToEncrypt = textToEncryptFromEnv; } } From af13a9b23094abd61fdc00b8e5113a46138c51d2 Mon Sep 17 00:00:00 2001 From: koco Date: Thu, 28 May 2020 12:47:05 +0300 Subject: [PATCH 11/11] Define a static name for the image built via docker-compose. Describe how to run the project via docker alone. --- README.md | 11 ++++++++--- docker-compose.yml | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bba0ab6..b27fe38 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,14 @@ applies to the decrypt method - `mvn package` - `java -jar coding-0.0.1-SNAPSHOT-jar-with-dependencies.jar KEY 'text to encrypt'` -## To run the application via docker +## To run the application via docker-compose - Checkout the project - `mvn package` -- `cd docker` - Open the `docker-compose.yml` and set `ENCRYPTION_KEY` `TEXT_TO_ENCRYPT` environment variables -- `docker-compose up` \ No newline at end of file +- `docker-compose up` + +## To run the application via docker +- Checkout the project +- `mvn package` +- `docker build -t 3des-algorithm_encryption . ` +- `docker run -it 3des-algorithm_encryption encryptionKey text` diff --git a/docker-compose.yml b/docker-compose.yml index c7ef0ee..20ad27c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -encryption: +3des-algorithm_encryption: build: . environment: - ENCRYPTION_KEY="KDKDKDKD"