Skip to content

Commit

Permalink
Merge pull request #3105 from mercedes-benz/feature-837-encrypt-sechu…
Browse files Browse the repository at this point in the history
…b-configuration-in-db

Feature 837 subframework for encrypting data at rest.
  • Loading branch information
Jeeppler authored May 7, 2024
2 parents 3f60c7c + e489658 commit 14862ef
Show file tree
Hide file tree
Showing 24 changed files with 2,447 additions and 4 deletions.
2 changes: 2 additions & 0 deletions sechub-commons-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ dependencies {

api spring_boot_dependency.slf4j_api

implementation 'org.bouncycastle:bcprov-jdk18on:1.73'

testImplementation spring_boot_dependency.junit_jupiter
testImplementation spring_boot_dependency.mockito_core

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
import javax.crypto.SecretKey;

/**
* Represents a common possiblity to encrypt data. The secret key of an instance
* can unseal and seal objects for example. Be aware: Every crypto access object
* has its own secret key inside! So you need to use the same crypto access
* object for you operations...
* Represents a common possibility to encrypt data. The secret key of an
* instance can unseal and seal objects for example. Be aware: Every crypto
* access object has its own secret key inside! So you need to use the same
* crypto access object for you operations...
*
* @author Albert Tregnaghi
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.mercedesbenz.sechub.commons.core.security.persistence;

import java.util.Arrays;

/**
* @author Jeremias Eppler
*/
public abstract class AbstractBinaryString implements BinaryString {
byte[] bytes;

protected AbstractBinaryString(byte[] bytes) {
if (bytes == null) {
throw new IllegalArgumentException("Byte array cannot be null.");
}

this.bytes = bytes;
}

protected AbstractBinaryString(String string) {
if (string == null) {
throw new IllegalArgumentException("String cannot be null.");
}

this.bytes = string.getBytes();
}

public abstract String toString();

public byte[] getBytes() {
// deep copy
return Arrays.copyOf(bytes, bytes.length);
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(bytes);
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AbstractBinaryString other = (AbstractBinaryString) obj;
return Arrays.equals(bytes, other.bytes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package com.mercedesbenz.sechub.commons.core.security.persistence;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

/**
* Providing access to AES-GCM-SIV
*
* AES-GCM-SIV is a nonce misuse-resistant authenticated encryption algorithm.
*
* For more information refer to
* <a href="https://datatracker.ietf.org/doc/html/rfc8452">RFC 8452</a>
*
* @author Jeremias Eppler
*/
public class AesGcmSiv implements PersistenceCipher {

private SecretKey secret;
private Provider cryptoProvider;
private PersistenceCipherType cipherType;

private static final String ALGORITHM = "AES/GCM-SIV/NoPadding";

/**
* The recommended initialization vector (iv) for AES-GCM-SIV is 12 bytes or 96
* bits.
*
* For an explanation have a look at: -
* https://datatracker.ietf.org/doc/html/rfc8452#section-4 -
* https://crypto.stackexchange.com/questions/41601/aes-gcm-recommended-iv-size-why-12-bytes
*/
public static final int IV_LENGTH_IN_BYTES = 12;

public static final int AUTHENTICATION_TAG_LENGTH_IN_BITS = 16 * 8; // 16 bytes (128 bits)

private AesGcmSiv(SecretKey secret, PersistenceCipherType cipherType) {
this.secret = secret;
this.cipherType = cipherType;
cryptoProvider = new BouncyCastleProvider();
Security.addProvider(cryptoProvider);
}

public static AesGcmSiv create(BinaryString secret) throws InvalidKeyException {
AesGcmSiv instance = null;

byte[] rawSecret = secret.getBytes();

if (rawSecret.length == 32 || rawSecret.length == 16) {
PersistenceCipherType cipherType = (rawSecret.length == 32) ? PersistenceCipherType.AES_GCM_SIV_256 : PersistenceCipherType.AES_GCM_SIV_128;
SecretKey secretKey = new SecretKeySpec(rawSecret, 0, rawSecret.length, "AES");

instance = new AesGcmSiv(secretKey, cipherType);
} else {
throw new InvalidKeyException("The secret has to be 128 or 256 bits long, but was " + (rawSecret.length * 8) + " bits long.");
}

return instance;
}

public BinaryString generateNewInitializationVector() {
return generateNewInitializationVector(BinaryStringEncodingType.BASE64);
}

public BinaryString generateNewInitializationVector(BinaryStringEncodingType encodingType) {
byte[] initializationVector = new byte[IV_LENGTH_IN_BYTES];

SecureRandom random = new SecureRandom();
random.nextBytes(initializationVector);

return BinaryStringFactory.createFromBytes(initializationVector, encodingType);
}

public BinaryString encrypt(String plaintext, BinaryString initializationVector) throws InvalidAlgorithmParameterException, InvalidKeyException {
return encrypt(plaintext, initializationVector, BinaryStringEncodingType.BASE64);
}

public String decrypt(BinaryString ciphertext, BinaryString initializationVector)
throws InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher;
try {
cipher = Cipher.getInstance(ALGORITHM, cryptoProvider);

SecretKeySpec keySpec = new SecretKeySpec(secret.getEncoded(), "AES");

GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(AUTHENTICATION_TAG_LENGTH_IN_BITS, initializationVector.getBytes());

cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);

byte[] ciphertextBytes = ciphertext.getBytes();

byte[] plaintextBytes = cipher.doFinal(ciphertextBytes);

String plaintext = new String(plaintextBytes);

return plaintext;
} catch (NoSuchAlgorithmException | NoSuchPaddingException providerException) {
throw new IllegalStateException("Decryption not possible, please check the provider", providerException);
}
}

@Override
public PersistenceCipherType getCipherType() {
return cipherType;
}

@Override
public BinaryString encrypt(String plaintext, BinaryString initializationVector, BinaryStringEncodingType encodingType)
throws InvalidAlgorithmParameterException, InvalidKeyException {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM, cryptoProvider);

SecretKeySpec keySpec = new SecretKeySpec(secret.getEncoded(), "AES");

GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(AUTHENTICATION_TAG_LENGTH_IN_BITS, initializationVector.getBytes());

cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);

byte[] ciphertext = cipher.doFinal(plaintext.getBytes());

return BinaryStringFactory.createFromBytes(ciphertext, encodingType);
} catch (NoSuchAlgorithmException | NoSuchPaddingException providerException) {
throw new IllegalStateException("Encryption not possible, please check the provider", providerException);
} catch (BadPaddingException | IllegalBlockSizeException paddingBlockException) {
throw new IllegalStateException("Should not occure. AES in GCM-SIV mode does not require padding.", paddingBlockException);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.mercedesbenz.sechub.commons.core.security.persistence;

import java.util.Base64;

/**
* A base64 encoded string.
*
* @author Jeremias Eppler
*
*/
public class Base64String extends AbstractBinaryString {

Base64String(byte[] bytes) {
super(bytes);
}

Base64String(String string) {
super(string);
}

@Override
public String toString() {
return Base64.getEncoder().encodeToString(bytes);
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + java.util.Arrays.hashCode(bytes);
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Base64String other = (Base64String) obj;
return java.util.Arrays.equals(bytes, other.bytes);
}

@Override
public BinaryStringEncodingType getType() {
return BinaryStringEncodingType.BASE64;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.mercedesbenz.sechub.commons.core.security.persistence;

/**
* A binary string type which knows it's encoding.
*
* The binary string keeps it's internal representation in binary format. In
* addition, the binary string knows it's encoding (e.g. base64 etc.).
*
* @see BinaryStringEncodingType
*
* @author Jeremias Eppler
*/
public interface BinaryString {
public byte[] getBytes();

public String toString();

public BinaryStringEncodingType getType();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.mercedesbenz.sechub.commons.core.security.persistence;

/**
* Encoding type of a binary string.
*
* @see BinaryString
*
* @author Jeremias Eppler
*/
public enum BinaryStringEncodingType {
/**
* A string with no encoding.
*/
PLAIN,

/**
* A string encoded in hexadecimal format
*/
HEX,

/**
* A string encoded in base64 format
*/
BASE64,
}
Loading

0 comments on commit 14862ef

Please sign in to comment.