Hi,
We are deploying Camunda on google cloud. We are using postgresql. Google offers encryption at rest, but this is not enough for us.
We would like to be able to encrypt all process variables, as some of them contain some personal data, before they get stored in the Database and of course decrypt them when they get loaded in a transparent manner. The key to encrypt the variables should be customizable (we’d like to rotate the key at will). Is there any plan for Camunda to support such integration point or extension ?
I presume we are alone facing this kind of security concern.
Thanks
Nicolas.
1 Like
Hi,
You may be interested in this thread
In addition, Ive done a POC where I did the encryption/decryption client side in javascript - it worked fine, but I can’t say it really added any security as the client must have access to the keys…
regards
Rob
Have you guys explored the SealedObject class in crypto?
https://docs.oracle.com/javase/8/docs/api/javax/crypto/SealedObject.html
So you seal up your object and then pass that sealed object into the variable system.
You can generate your sealedobject from any serializable object and the cipher class is pretty flexible: https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html
1 Like
@thorben @Webcyberrob @nicolas
Here is a example I just cleaned up and pulled from some previous work:
The example if simplified for demonstration purposes. In practice you would add things like external key files, and more configuration options.
I have also shown two different styles of Java object access with the Java.type()
vs the direct class names.
Seal Test task is execution some javascript as follows:
//------------------------------------------------------------
var SealedObject = Java.type('javax.crypto.SealedObject')
var Cipher = Java.type('javax.crypto.Cipher')
var keyGenerator = javax.crypto.KeyGenerator.getInstance("DES");
var desKey = keyGenerator.generateKey();
var desCipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
desCipher.init(Cipher.ENCRYPT_MODE, desKey);
// Gets the Base64/String representation of the Key that was generated
function getKeyAsString(){
var encodedKey = java.util.Base64.getEncoder().encodeToString(desKey.getEncoded());
return encodedKey
}
// Generates a new Key (equiv to the desCipher object)
function buildKey(encodedKey){
var decodedKey = java.util.Base64.getDecoder().decode(encodedKey);
var originalKey = new javax.crypto.spec.SecretKeySpec(decodedKey, 0, decodedKey.length, "DES");
return originalKey
}
// Generates a SealedObject with the default desCipher
function seal(serialObject){
var so = new SealedObject(serialObject, desCipher);
return so
}
//------------------------------------------------------------
/*
Reference Docs:
1. https://docs.oracle.com/javase/8/docs/api/javax/crypto/SealedObject.html
2. https://docs.oracle.com/javase/8/docs/api/javax/crypto/spec/SecretKeySpec.html
3. https://docs.oracle.com/javase/8/docs/api/javax/crypto/SecretKey.html
4. https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html
*/
// Generate a generic SPIN object for encryption demonstration
var myJsonToEncrypt = {
"mykey":"my secret text"
}
var mySpinToEncrypt = S(JSON.stringify(myJsonToEncrypt))
var mySpinStringToEncrypt = mySpinToEncrypt.toString()
execution.setVariable('original_object', mySpinStringToEncrypt)
// Generate SealedObject from SPIN String
var sealed = seal(mySpinStringToEncrypt)
execution.setVariable('sealed_object',sealed)
// Get the Encoded Key
// In practice you can swap this out for uses of Certificates and Key files
var encodedkey = getKeyAsString()
execution.setVariable('secret_key', encodedkey)
// Simulate Generating a new SecretKey object from the encodedKey
// Simulates generating a key based on the String/Base64 of the secret key
var newKey = buildKey(encodedkey)
var unsealed = sealed.getObject(newKey)
execution.setVariable('unsealed_object', unsealed)
In Practice you setup a js file as a series of functions to Seal and UnSeal objects, and you set the script to auto load the cipher/key from a external file/volume. So when you need the encryption abilities you just do something like:
load('classpath:sealObject.js')
var myData = ...
execution.setVariable('myEncryptedData', seal(myData))
and when you need the data:
load('classpath:sealObject.js')
var myData_encrypted = execution.getVariable('myEncryptedData')
var myData = unseal(myData_encrypted, getCipher())
Further detailed here: Process Variable Encryption (scripting)
Hi Stephen,
Thanks for the info - interesting. What I have found is the encryption technology is the easy part, its the key management which I find the harder part to manage…
My ideal solution would be an encrypted type within the Camunda storage types with a ‘Service Provider Interface’ to realise the key managament etc. Then I could use say an AWS KMS and the engine would perform the encrypt/decrypt at approriate moments…
regards
Rob
1 Like
@Webcyberrob Key management is relatively easy as long as you version id the key names. When you first encrypt something you are say using two possible scenarios: Encrypt using a specific key, or encrypt using the “latest” key. When you encrypt in either of these scenarios you need to log which key “version” was used at the time of the encryption.
When you go to rotate your key, you are either storing the old key, or re-encrypting the old data (assuming you want to access the historical encrypted data). So if your process instance has a string variable with the name of the encryption, then you are just calling that specific public or private key version for historical access.
Even if you had a “custom storage” type, your service provider would be doing the exact same logic at some level. You could easily transform the logic back into a Java class (as per source ref links in the source code and readme) to preform this logic using expressions and delegates.
Hi Stephen,
This is where I see the challenges. The motivation for performing application level encryption is to prevent access to data by say database administrator, otherwise storage level encryption would suffice. So the challenge is how can we give the engine and only the engine access to the keys? Ideally this needs to be a commoditised pattern…
regards
Rob
If your keys are in a third-party service, then you are preforming some sort of login to the service provider for the key and then preform the decryption. So IMO thats a token authentication issue and not so much a encryption issue. You need a Hasicorp Vault or equiv which issues you tokens for access to the encryption keys.
If you are using your service provider to do the actual encryption such as: Encrypt - AWS Key Management Service, then again we are back to the same Vault usage: a process instance requests a one time use token to preform a operation. You manage your admin access with access policies and audit logs.
Thanks for all your inputs, it helps. I’ll give it a try.
I need to check if the data gets encrypted in the history stream that we capture.
I’ll let you know.
Nicolas.
@nicolas when you say history steam do you mean Camunda History provider? If so, then yes it would be a sealed object in there as well. In the code i provided the data is being encrypted before it is being saved into the Camunda database.
Hi, we are registering an HistoryEventHandler to capture the history events and process them in an external system
should be fine given the encryption occurs before variable creation