Skip to content

Commit

Permalink
fix: change algorithm to "AES/GCM/NoPadding";
Browse files Browse the repository at this point in the history
  • Loading branch information
credmond-git committed Jul 15, 2024
1 parent 40aec41 commit 7641394
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import org.github.gestalt.config.node.NodeType;
import org.github.gestalt.config.secret.rules.SecretConcealer;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
Expand All @@ -22,21 +24,25 @@
* @author <a href="mailto:colin.redmond@outlook.com"> Colin Redmond </a> (c) 2024.
*/
public class EncryptedLeafNode extends LeafNode {
private final Cipher decryptCipher;

public static final String ENCRYPTION_ALGORITHM = "AES/GCM/NoPadding";
public static final int GCM_TAG_LENGTH = 16;
public static final int GCM_IV_LENGTH = 12;
private final SecretKey skey;

private final byte[] encryptedData;

public EncryptedLeafNode(byte[] encryptedData, Cipher decryptCipher) throws IllegalBlockSizeException, BadPaddingException {
public EncryptedLeafNode(byte[] encryptedData, SecretKey skey) throws IllegalBlockSizeException, BadPaddingException {
super("");

this.decryptCipher = decryptCipher;
this.skey = skey;
this.encryptedData = encryptedData;
}

@Override
public Optional<String> getValue() {
try {
return Optional.of(new String(decryptCipher.doFinal(encryptedData)));
return Optional.of(decryptGcm(skey, encryptedData));
} catch (IllegalBlockSizeException | BadPaddingException e) {
return Optional.empty();
}
Expand Down Expand Up @@ -97,4 +103,25 @@ public String printer(String path, SecretConcealer secretConcealer, SentenceLexe
"value='" + nodeValue + '\'' +
"}";
}

public static String decryptGcm(SecretKey skey, byte[] ciphertext)
throws BadPaddingException, IllegalBlockSizeException /* these indicate corrupt or malicious ciphertext */
/* Note that AEADBadTagException may be thrown in GCM mode; this is a subclass of BadPaddingException */
{
/* Precond: skey is valid and GCM mode is available in the JRE;
* otherwise IllegalStateException will be thrown. */
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] initVector = Arrays.copyOfRange(ciphertext, 0, GCM_IV_LENGTH);
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * java.lang.Byte.SIZE, initVector);
cipher.init(Cipher.DECRYPT_MODE, skey, spec);
byte[] plaintext = cipher.doFinal(ciphertext, GCM_IV_LENGTH, ciphertext.length - GCM_IV_LENGTH);
return new String(plaintext);
} catch (NoSuchPaddingException | InvalidAlgorithmParameterException |
InvalidKeyException | NoSuchAlgorithmException e)
{
/* None of these exceptions should be possible if precond is met. */
throw new IllegalStateException(e.toString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import org.github.gestalt.config.utils.GResultOf;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.GCMParameterSpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
Expand All @@ -23,15 +23,40 @@
* Checks if the node is a leaf and a temporary secret. if it is, replaces the leaf node with a TemporaryLeafNode that can only be accessed
* a limited number of times. After the limited number of times, the value is released to be GC'ed.
*
* @author <a href="mailto:colin.redmond@outlook.com"> Colin Redmond </a> (c) 2024.
* @author <a href="mailto:colin.redmond@outlook.com"> Colin Redmond </a> (c) 2024.
*/
@ConfigPriority(400)
public class EncryptedSecretConfigNodeProcessor implements ConfigNodeProcessor {

public static final String AES_CBC_PKCS_5_PADDING = "AES/CBC/PKCS5Padding";
private SecretChecker encryptedSecret = new RegexSecretChecker(Set.of());
public static final String ENCRYPTION_ALGORITHM = "AES/GCM/NoPadding";
public static final int GCM_TAG_LENGTH = 16;
public static final int GCM_IV_LENGTH = 12;

private static final System.Logger logger = System.getLogger(EncryptedSecretConfigNodeProcessor.class.getName());
private SecretChecker encryptedSecret = new RegexSecretChecker(Set.of());

private static SecretKey generateKey(int n) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(n);
return keyGenerator.generateKey();
}

private byte[] encryptGcm(SecretKey skey, String plaintext) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, ShortBufferException, IllegalBlockSizeException, BadPaddingException {
/* Precond: skey is valid and GCM mode is available in the JRE;
* otherwise IllegalStateException will be thrown. */
byte[] ciphertext = null;
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] initVector = new byte[GCM_IV_LENGTH];
(new SecureRandom()).nextBytes(initVector);
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * java.lang.Byte.SIZE, initVector);
cipher.init(Cipher.ENCRYPT_MODE, skey, spec);
byte[] encoded = plaintext.getBytes(java.nio.charset.StandardCharsets.UTF_8);
ciphertext = new byte[initVector.length + cipher.getOutputSize(encoded.length)];
System.arraycopy(initVector, 0, ciphertext, 0, initVector.length);
// Perform encryption
cipher.doFinal(encoded, 0, encoded.length, ciphertext, initVector.length);
return ciphertext;
}

@Override
public void applyConfig(ConfigNodeProcessorConfig config) {
Expand Down Expand Up @@ -69,33 +94,15 @@ public GResultOf<ConfigNode> process(String path, ConfigNode currentNode) {
// We use the encryption cipher to encrypt the data and pass the encrypted data along with the
// decryption cipher to the leaf.
try {
SecretKey secretKey = generateKey(256);
IvParameterSpec iv = generateIv();

Cipher encryptCipher = Cipher.getInstance(AES_CBC_PKCS_5_PADDING);
encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
var secretKey = generateKey(128);
var encryptedData = encryptGcm(secretKey, optionalLeafNodeValue.orElse(""));

Cipher decryptCipher = Cipher.getInstance(AES_CBC_PKCS_5_PADDING);
decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
return GResultOf.result(new EncryptedLeafNode(encryptedData, secretKey));

return GResultOf.result(new EncryptedLeafNode(encryptCipher.doFinal(optionalLeafNodeValue.orElse("").getBytes()),
decryptCipher));

} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException |
InvalidAlgorithmParameterException ex) {
} catch (NoSuchAlgorithmException | IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException |
InvalidAlgorithmParameterException | InvalidKeyException | ShortBufferException ex) {
return GResultOf.errors(new ValidationError.EncryptedNodeFailure(path, ex));
}
}

private static IvParameterSpec generateIv() {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
return new IvParameterSpec(iv);
}

private static SecretKey generateKey(int n) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(n);
return keyGenerator.generateKey();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,78 +7,89 @@
import org.junit.jupiter.api.Test;

import javax.crypto.*;
import java.nio.charset.StandardCharsets;
import javax.crypto.spec.GCMParameterSpec;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;

class EncryptedLeafNodeTest {

private Cipher decryptCipher;
private byte[] encryptedData;
private SecretKey secretKey;

public static final String ENCRYPTION_ALGORITHM = "AES/GCM/NoPadding";
public static final int GCM_TAG_LENGTH = 16;
public static final int GCM_IV_LENGTH = 12;

@BeforeEach
void setUp() throws Exception {
// Initialize the encryption and decryption ciphers
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
SecretKey secretKey = keyGenerator.generateKey();

Cipher encryptCipher = Cipher.getInstance("AES");
encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey);

decryptCipher = Cipher.getInstance("AES");
decryptCipher.init(Cipher.DECRYPT_MODE, secretKey);
secretKey = keyGenerator.generateKey();

// Encrypt sample data
String originalText = "secretData";
encryptedData = encryptCipher.doFinal(originalText.getBytes(StandardCharsets.UTF_8));

byte[] ciphertext = null;
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] initVector = new byte[GCM_IV_LENGTH];
(new SecureRandom()).nextBytes(initVector);
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * java.lang.Byte.SIZE, initVector);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec);
byte[] encoded = originalText.getBytes(java.nio.charset.StandardCharsets.UTF_8);
ciphertext = new byte[initVector.length + cipher.getOutputSize(encoded.length)];
System.arraycopy(initVector, 0, ciphertext, 0, initVector.length);
// Perform encryption
cipher.doFinal(encoded, 0, encoded.length, ciphertext, initVector.length);
encryptedData = ciphertext;
}

@Test
void testGetValue() throws Exception {
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, decryptCipher);
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, secretKey);
assertEquals(Optional.of("secretData"), encryptedLeafNode.getValue());
}

@Test
void testGetValueWithBadPaddingException() throws Exception {
Cipher invalidDecryptCipher = Cipher.getInstance("AES");
invalidDecryptCipher.init(Cipher.DECRYPT_MODE, KeyGenerator.getInstance("AES").generateKey());
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
var newSecretKey = keyGenerator.generateKey();

EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, invalidDecryptCipher);
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, newSecretKey);
assertEquals(Optional.empty(), encryptedLeafNode.getValue());
}

@Test
void testGetNodeType() throws IllegalBlockSizeException, BadPaddingException {
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, decryptCipher);
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, secretKey);
assertEquals(NodeType.LEAF, encryptedLeafNode.getNodeType());
}

@Test
void testGetIndex() throws IllegalBlockSizeException, BadPaddingException {
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, decryptCipher);
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, secretKey);
assertEquals(Optional.empty(), encryptedLeafNode.getIndex(0));
}

@Test
void testGetKey() throws IllegalBlockSizeException, BadPaddingException {
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, decryptCipher);
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, secretKey);
assertEquals(Optional.empty(), encryptedLeafNode.getKey("key"));
}

@Test
void testSize() throws IllegalBlockSizeException, BadPaddingException {
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, decryptCipher);
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, secretKey);
assertEquals(1, encryptedLeafNode.size());
}

@Test
void testEquals() throws Exception {
EncryptedLeafNode encryptedLeafNode1 = new EncryptedLeafNode(encryptedData, decryptCipher);
EncryptedLeafNode encryptedLeafNode2 = new EncryptedLeafNode(encryptedData, decryptCipher);
EncryptedLeafNode encryptedLeafNode1 = new EncryptedLeafNode(encryptedData, secretKey);
EncryptedLeafNode encryptedLeafNode2 = new EncryptedLeafNode(encryptedData, secretKey);

assertEquals(encryptedLeafNode1, encryptedLeafNode1);
assertEquals(encryptedLeafNode1, encryptedLeafNode2);
Expand All @@ -87,21 +98,21 @@ void testEquals() throws Exception {

@Test
void testHashCode() throws Exception {
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, decryptCipher);
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, secretKey);
assertEquals(Arrays.hashCode(encryptedData), encryptedLeafNode.hashCode());
}

@Test
void testPrinter() throws Exception {
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, decryptCipher);
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, secretKey);
SecretConcealer secretConcealer = (path, value) -> "concealedSecret";

assertEquals("EncryptedLeafNode{value='concealedSecret'}", encryptedLeafNode.printer("", secretConcealer, new PathLexer()));
}

@Test
void testToString() throws Exception {
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, decryptCipher);
EncryptedLeafNode encryptedLeafNode = new EncryptedLeafNode(encryptedData, secretKey);
assertEquals("EncryptedLeafNode{value='secret'}", encryptedLeafNode.toString());
}
}

0 comments on commit 7641394

Please sign in to comment.