Encrypting data
As described in How to use the akka-gdpr module, you can take advantage of the provided encryption implementation or plug in your own. The akka-gdpr
module also provides options for key management and for testing during development. The sections on this page describe how to add a dependency on akka-gdpr
and provide more detail on working with GdprEncryption
, AbstractGdprEncryption
, JavaKeyStoreGdprEncryption
, and TestGdprEncryption
.
Note: Implementations of GdprEncryption
, AbstractGdprEncryption
and KeyManagement
must be thread-safe, as they may be accessed by multiple threads concurrently.
GdprEncryption
The GDPR for Akka Persistence module defines a GdprEncryption
extension that you need to implement to plug in encryption and key management. The akka.persistence.gdpr.scaladsl.GdprEncryption
akka.persistence.gdpr.javadsl.GdprEncryption
extension has three methods to implement:
encrypt
- Encrypt the given payload with the key identified bydataSubjectId
.decrypt
- Decrypt the given payload with the key identified bydataSubjectId
, unless the key has been removed.shred
- Remove the givendataSubjectId
.
If you need full control over encryption algorithms you can implement this interface. However, it is expected most implementations will extend akka.persistence.gdpr.scaladsl.AbstractGdprEncryption
akka.persistence.gdpr.javadsl.AbstractGdprEncryption
.
AbstractGdprEncryption
The AbstractGdprEncryption
implements all of the methods from GdprEncryption
but adds a KeyManagement
interface, which enables you to plug in logic for creating new keys and retrieving keys.
The KeyManagment
interface has three methods to implement:
shred
- Remove the key identified by the givendataSubjectId
permanently.getOrCreateKey
- Create aSecretKey
if it doesn’t exist already.getKey
- Retrieve the key identified by the givendataSubjectId
, returning None if the key has been shredded.
An example without actual implementation of the methods may look like this:
- Scala
-
import scala.concurrent.Future import akka.Done import akka.actor.ExtendedActorSystem import akka.persistence.gdpr.scaladsl.AbstractGdprEncryption import akka.persistence.gdpr.scaladsl.KeyManagement import javax.crypto.SecretKey class ExampleGdprEncryption(system: ExtendedActorSystem, configPath: String) extends AbstractGdprEncryption(system) { override protected val keyManagement: KeyManagement = { val keySize = system.settings.config.getInt("akka.persistence.gdpr.key-size") new ExampleKeyManagement(keySize) } } class ExampleKeyManagement(keySize: Int) extends KeyManagement { /** * Remove the key identified by dataSubjectId permanently. */ override def shred(dataSubjectId: String): Future[Done] = { ??? // FIXME implement this } /** * Create a `SecretKey` if it doesn't exist already. Length of the key should be taken * from `akka.persistence.gdpr.key-size` * * If using `AbstractGdprEncryption` this must be an AES key. */ override def getOrCreateKey(dataSubjectId: String): Future[SecretKey] = { ??? // FIXME implement this } /** * Retrieve the key identified by dataSubjectId, returning None if the key has been shredded. */ override def getKey(dataSubjectId: String): Future[Option[SecretKey]] = { ??? // FIXME implement this } }
- Java
-
import akka.Done; import akka.actor.ExtendedActorSystem; import akka.persistence.gdpr.javadsl.AbstractGdprEncryption; import akka.persistence.gdpr.javadsl.KeyManagement; import javax.crypto.SecretKey; import java.util.Optional; import java.util.concurrent.CompletionStage; public class ExampleGdprEncryption extends AbstractGdprEncryption { private final KeyManagement keyManagement; static class ExampleKeyManagement implements KeyManagement { private final int keySize; ExampleKeyManagement(int keySize) { this.keySize = keySize; } /** * Remove the key identified by dataSubjectId permanently. */ @Override public CompletionStage<Done> shred(String dataSubjectId) { // FIXME implement this throw new RuntimeException("To be implemented"); } /** * Create a `SecretKey` if it doesn't exist already. Length of the key should be taken * from `akka.persistence.gdpr.key-size` * * If using `AbstractGdprEncryption` this must be an AES key. */ @Override public CompletionStage<SecretKey> getOrCreateKey(String dataSubjectId) { // FIXME implement this throw new RuntimeException("To be implemented"); } /** * Retrieve the key identified by dataSubjectId, returning None if the key has been shredded. */ @Override public CompletionStage<Optional<SecretKey>> getKey(String dataSubjectId) { // FIXME implement this throw new RuntimeException("To be implemented"); } } public ExampleGdprEncryption(ExtendedActorSystem system, String configPath) { super(system); int keySize = system.settings().config().getInt("akka.persistence.gdpr.key-size"); keyManagement = new ExampleKeyManagement(keySize); } @Override public KeyManagement keyManagement() { return keyManagement; } }
JavaKeyStoreGdprEncryption
An existing implementation of GdprEncryption
is included in JavaKeyStoreGdprEncryption
. It has support for PKCS12 and JCEKS keystores. It can only be used for single node applications since it saves to a local file so won’t be available for other nodes in the cluster. If your application is distributed you’ll need to create a KeyManagement
implementation for your distributed secret store e.g. Vault. JavaKeyStoreGdprEncryption
can be enabled with configuration:
akka.persistence.gdpr.encryption-provider = "akka.persistence.gdpr.jca-provider"
It’s also important to be aware of that the the JavaKeyStoreGdprEncryption
doesn’t have any redundancy, so if the storage is corrupted for some reason all encrypted data is “lost”.
To configure JavaKeyStoreGdprEncryption
to use a local PKCS12 or JCEKS key store you need to specify:
- Location of your keystore file
- Password for your keystore
- Key size (keys will be generated for new data subjects)
- Keystore type
akka.persistence.gdpr.jca-provider {
class = "akka.persistence.gdpr.JavaKeyStoreGdprEncryption"
# Possible values: jceks, pkscs12
keystore-type = "jceks"
# Set this from env variable. Do not store passwords in configuration.
keystore-password = "password"
# Path to the keystore
keystore-location = ""
# Java key store API has blocking calls.
use-dispatcher = "akka.actor.default-blocking-io-dispatcher"
}
It’s recommended to configure the key-size
to 256, which may require installation of Java Cryptography Extension (JCE).
akka.persistence.gdpr.key-size = 256
TestGdprEncryption
For unit testing purposes TestGdprEncryption
is provided. Don’t use this test module in production, since it is only holding the generated keys in memory. TestGdprEncryption
can be can be enabled with the following configuration:
akka.persistence.gdpr.encryption-provider = "akka.persistence.gdpr.test-provider"
Pointing to your encryption implementation
Define the akka.persistence.gdpr.encryption-provider
setting to point to a configuration block for your encryption implementation. The only mandatory field in that block is class
, which is a fully qualified class name of your implementation of GdprEncryption
. The class must have a constructor that takes in an ExtendedActorSystem
and the config path as a String
.
akka.persistence.gdpr {
# It is mandatory to define this property. It's path to the configuration
# section for your encryption mechanism. E.g. "akka.persistence.gdpr.jca-provider".
# That section must contain a "class" property with a FQCN of an implementation of the
# GdprEncryption Extension with a constructor that takes in an ExtendedActorSystem
# and the config path as a String.
encryption-provider = ""
# 128, 192 or 256
key-size = 128
# Valid values are 128, 120, 112, 104, 96
# See https://docs.oracle.com/javase/8/docs/api/javax/crypto/spec/GCMParameterSpec.html
gcm-tlen = 96
}
We recommend configuring the key-size
to 256, which may require installation of Java Cryptography Extension (JCE).
akka.persistence.gdpr.key-size = 256