Skip to content

Data Seal is a JVM-based framework that provides annotation based application level signing and encrypting of data structures, with support for major frameworks like JUnit, Hibernate and Spring.

License

Notifications You must be signed in to change notification settings

springdam/data-seal

Repository files navigation

Data Seal Framework

Data Seal is a JVM-based framework that allows for annotation based signing and encrypting of data structures fields intended for application level signing and encryption of data.

When data is secured by the application before being persisted, it ensures that only the intended business processes (e.g. the application) can read / modify the data, so that no administrator can modify data in a database without detection or read data if it is encrypted.

In particular the framework can be used for column level encryption and signing of data in SQL databases.

When to use this framework?

We recommend only using this framework is you have a (business/compliance) requirement to secure data "at rest" for instance data in a database, session data in memory, files on disk etc.

The framework provides 3 types of security mechanisms

  • Signed fields, the data remains in plain text, but cannot be modified without detection
  • Encrypted fields, the data is encrypted using AES-GCM, the data can optionally be gzipped
  • Searchable fields, the data is encrypted using AES-CBC, which allows for matching the encrypted values if the exact plain text is know. This is useful for data that needs to be secured, but still searchable, however the level of security is reduced.

Both encrypted and searchable fields are also signed.

Supported data types

The following data types are supported for signing

  • All primitives (boolean, byte, short, int, long, float, double, char) and their corresponding arrays
  • All boxed primitives (Bolean, Byte, Short, Integer, Long, Float, Double, Character) and their corresponding arrays
  • java.math.BigInteger
  • java.math.BigDecimal
  • java.lang.String, and arrays of java.lang.String
  • java.util.UUID
  • java.time.Instant
  • All enums

The framework allows for extension of the supported types via custom plugins.

The following data types are supported for encryption and searchable encryption:

  • java.lang.String
  • byte[]

Examples

Below is an example of a simple bean that utilizes all 3 security mechanisms.

It implements the Sealable interface which is required and provides the Sealable.seal() which creates the HMAC signature and encrypts the specified fields and the corresponding Sealable.unseal() method which validates the HMAC signature and decrypts the encrypted fields.

The @Metadata data annotated field is required as this is where the bean field layout information, bean encryption key and signature is kept.

public class Bean implements Sealable {

    @Signed
    String signed;
    
    @Searchable
    String searchable;
    
    @Encrypted
    String encrypted;
        
    @Metadata
    String metadata;
}

To secure the bean simply do the following

    Bean bean = new Bean();
    bean.signed = "signed";
    bean.searchable = "searchable";
    bean.encrypted = "encrypted";
    bean.seal();

The bean is now secured and modifications will be detected when .unseal() is called.

    bean.unseal();

If any of the fields are modifed before unseal is called a MacMismatchException will be thrown for instance,

    Bean bean = new Bean();
    bean.signed = "signed";
    bean.searchable = "searchable";
    bean.encrypted = "encrypted";
    bean.seal();
    
    // Modify data while object is sealed
    bean.signed = "different";
    
    // Will now throw MacMismatchException
    bean.unseal(); 

To allow for search for matching values of fields protected with @Searchable the search values can be generated by using the Seal.createSearchValues()

    Bean bean = new Bean();
    bean.signed = "signed";
    bean.searchable = "searchable";
    bean.encrypted = "encrypted";
    bean.seal();
    
    // Generates a list of search values to support rotation of encryption keys
    List<String> searchValues = Seal.createSearchValues("searchable", bean.getClass(), "signed");
    boolean match = false;
    for (var searchValue : searchValues) {
        if (searchValues.equals(bean.searchable)) {
            match = true;
            break;
        }
    }

Key management

Data Seal relies on 2 layers of encryption keys to secure the data that is sealed.

The KeyService provides functionality for managing these keys, such as loading keys and generating new ones, and the KeyService must be initialized before any invocations of Sealable.seal() and Sealable.unseal()

In the example below the KeyService is initialized with a SoftwareSecurityModule, which loads its keys from the provided input stream - here loaded from the classpath - as well as a single key set also loaded from the classpath.

    // Prepare the SecurityModule
    var keyAlias = "example-key";
    var jksInputStream = SealableTest.class.getClassLoader().getResourceAsStream("keystore.jks");
    var softwareSecurityModule = new SoftwareSecurityModule("jks", jksInputStream, "secret".toCharArray(), keyAlias, "secret".toCharArray(), "RSA");
    KeyService.INSTANCE.setSecurityModule(softwareSecurityModule)
    KeyService.INSTANCE.setSecurityModuleKeyAlias(keyAlias); 
          
    // Load encrypted AES keys - will be decrypted by the security module 
    ObjectMapper mapper = new ObjectMapper(); 
    var keySet = mapper.readValue(getClass().getClassLoader().getResourceAsStream("keyset-0.json"), EncryptedKeySet.class);
    KeyService.INSTANCE.loadEncryptedKeySet(keySet);
    KeyService.INSTANCE.setCurrentKeySetId(0);

The keyset being loaded looks like this

{
    "id": 0,
    "signatureKey": "VF9FRJx9Y2GOrAommEfkPK/cJ+FIzEa5ijxv3cc+yf31s33hUZG5zyC3v+K8EylzDxS/626GDO1aECZR7L+KPn4GZoMxbgT+XP3FeRrFWkgLqFtD9OQCekVmN1fNc2G6pwXxwer/M1HSKwkrD5faZf/N+T791lzz0whO2zSYpVJ9gW42bTwRc2PfRTShnjUHGErANYQ/IfbX536C8egVJUN0UNP5AXeapmVFGAntUq6JaRshMKFfnHUaRVjdJ306P5lczs5mNAQK6piuGOeq/Ts3+3K5e2s8/+nYs28pYFE3C3YDXq16C/ZgZEt2FF7uaP4waan/ug3/BdOEIZFL/Q==",
    "encryptionKey": "EeSEqO/3q3RmKX26NgsbhbM6wsh+wssEdUwlXbiWOeJSkWXjvGzEy33/qZJ9sWqUXLfyTQTQCnfw71iLyoOoHzJmmUbWcEKmw6Mgp1a0LJd6K59+UFnU6IYzoLYlOOngNpXVj4Jcfhnz449z55zbJKKMIjVTxH9nxtAy58Pv2IAD7e6wAR56hQ6TOHr+p/oj2WQ7OsYGJEYJ3rtWjQ1LLcbvC5Z06OpJqIXBdR1+eGSNNYp7hGtZPxrnD9xSzRlpxIp2r0qXssSFv2mcLa0ecVRJJgOkGXSV+y5g1WNuKhD5vTsno95yfho6+y+iU715a3SdxR9ZkaZu6XvLcJkDEw==",
    "searchableEncryptionKey": "Y+KaMPeS7bulN3rjZb0vfqyg+Wgf6QpH2KNwRUddSh41wIp3RSi19ts5Jbi4QOajlNhDt6iYK+FpneDmtFoXampzQL0uap4ljrcwsDs+PeFmLUdVv5mvUCAfrq8jeQQVfNPdetuLmBOOWxjTnLmVq4MC4QlIcALIYsRbnl3P/pAIRsshIiHjiC9qhRDahcK9dfa/mJb43WQpSmhBt9mjsP3FvkK8Bj116fy7v9ieAzs4Evv/9yYyj5Pjcd5ifNAxSVRCYniN5RJyjicAE7IlBjQ5VoAUPXOaBJnjheBYe9hodm1BaIHAZUbOggsbhzjVMXf7eGgNu8tEFiNiMoZ2GA=="
}

JUnit 4 and 5 Support

To support testing, both JUnit 4 and JUnit 5 plugins are provided that automatically wires up the necessary keys for Data Seal.

JUnit 4

For JUnit 4 for a @ClassRule DataSealRule is provided via the data-seal-junit module.

This can be used as follows

public class ExampleTest {
    
    @ClassRule
    public static DataSealRule beforeAll = new DataSealRule();
    
    @Test
    public void test() {
        var bean = new Bean();
        bean.setSigned("signed");
        bean.seal();
        bean.unseal();
    }
}

JUnit 5

For JUnit 5 for an extension DataSealExtension is provided via the data-seal-junit-jupiter module.

This can be used as follows

@ExtendWith(DataSealExtension.class)
public class ExampleTest {
    
    @Test
    public void test() {
        var bean = new Bean();
        bean.setSigned("signed");
        bean.seal();
        bean.unseal();
    }
}

Hibernate Support

The seal framework supports Hibernate (v6.x.x) providing transparent signing and encryption of Hibernate entities as they are loaded and persisted, making it possible for developers to focus on what data needs to be secured, rather than keeping track of object states.

To use with hibernate entities, simply annotate the entity wit the @Signed, @Encrypted and @Searchable annotations as required.

@Data
@Entity
public class BeanEntity implements Sealable {

    @Signed
    @Id
    private int id;
        
    @Signed
    @Column
    private String signed;
    
    @Searchable
    @Column
    private String searchable;

    @Encrypted
    @Column
    private String encrypted;
    
    // Relations can also be signed
    @Signed
    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "child_entity_id")
    private ChildEntity child;
    
    @Metadata
    @Column
    private String metadata;
    
}

Spring Boot Support

Data Seal comes with support for the Spring Boot (v3.x.x) via the data-seal-spring-booter-starter module and for use with spring-boot-starter-data-jpa the data-seal-hibernate-spring-boot-starter provides for plug and play integration.

To configure Data Seal with Spring Boot a DataSealConfigurer must be provided to the Spring Context, which implements methods for loading the encrypted key sets, a security module that supports encrypting and decrypting the encrypted key sets and a key alias to allow for rotation of security module keys.

Below is an example of how to implement the DataSealConfigurer, that leverages Lombok and Jackson.

@Configuration
public class AppConfig implements DataSealConfigurer {
    
    ObjectMapper mapper = new ObjectMapper();

    @Override
    @SneakyThrows
    public List<EncryptedKeySet> encryptedKeySets() {
        // Read encrypted key set serialized as json and located on the classpath
        var keySet = mapper.readValue(getClass().getClassLoader().getResourceAsStream("keyset-0.json"), EncryptedKeySet.class);
        return List.of(keySet);
    }

    @Override
    public SecurityModule loadSecurityModule() {
        // Read JKS keystore located on the classpath
        var jksInputStream = AppConfig.class.getClassLoader().getResourceAsStream("keystore.jks");
        return new SoftwareSecurityModule("jks", jksInputStream, "secret".toCharArray(), "key-alias", "secret".toCharArray(), "RSA");
    }

    @Override
    public String securityModuleKeyAlias() {
        return "key-alias";
    }
    
}

License

The Spring Framework is released under version 2.0 of the Apache License.

About

Data Seal is a JVM-based framework that provides annotation based application level signing and encrypting of data structures, with support for major frameworks like JUnit, Hibernate and Spring.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages