Way to encrypt/decrypt data of a particular activity/workflow

Hi Team,

I am trying to figure out if there is any way to encrypt/decrypt only a specific portion of the payload being passed to activity/workflow? Also, can we have encryption logic only for a particular activity?
I read about DataConverter and PayloadConverter, but unable to understand if the same can be done for just a specific activity/workflow??
It would be really helpful if I can refer to any sample implementing this kind of behavior.

Check out the recently added crypto example. In the sample we use the jackson-json-crypto lib which allows you via annotations in your model classes to define which parts of the payload to encrypt or not, see for example here.

@tihomir This should help, will go across this example. Thank you!

@tihomir Mentioned sample works fine. But I tried to modify the sample a bit by creating Account class with 2 variables (@Encrypt) and then embedded the same as an array inside MyCustomer.java
When I try to run the workflow, initially I get an exception as below but eventually the workflow succeeds. Not able to figure out the reason. Can you please suggest on this.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
  @Encrypt private String accountName;

  @Encrypt private String accountType;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MyCustomer {
  @Encrypt private String name;
  private int age;
  private boolean approved;
  private Account[] accounts;

  public MyCustomer(String name, int age, Account[] accounts) {
    this.name = name;
    this.age = age;
    this.accounts = accounts;
  }
}

io.temporal.internal.replay.InternalWorkflowTaskException: Failure handling event 3 of type ‘EVENT_TYPE_WORKFLOW_TASK_STARTED’ during execution. {PreviousStartedEventId=3, workflowTaskStartedEventId=3, Currently Processing StartedEventId=3} io.temporal.internal.statemachines.WorkflowStateMachines.createEventProcessingException(WorkflowStateMachines.java:222) io.temporal.internal.statemachines.WorkflowStateMachines.handleEventsBatch(WorkflowStateMachines.java:201) io.temporal.internal.statemachines.WorkflowStateMachines.handleEvent(WorkflowStateMachines.java:175) io.temporal.internal.replay.ReplayWorkflowRunTaskHandler.handleWorkflowTaskImpl(ReplayWorkflowRunTaskHandler.java:176) io.temporal.internal.replay.ReplayWorkflowRunTaskHandler.handleWorkflowTask(ReplayWorkflowRunTaskHandler.java:145) io.temporal.internal.replay.ReplayWorkflowTaskHandler.handleWorkflowTaskWithQuery(ReplayWorkflowTaskHandler.java:122) io.temporal.internal.replay.ReplayWorkflowTaskHandler.handleWorkflowTask(ReplayWorkflowTaskHandler.java:97) io.temporal.internal.worker.WorkflowWorker$TaskHandlerImpl.handle(WorkflowWorker.java:241) io.temporal.internal.worker.WorkflowWorker$TaskHandlerImpl.handle(WorkflowWorker.java:199) io.temporal.internal.worker.PollTaskExecutor.lambda$process$0(PollTaskExecutor.java:93) java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) java.base/java.lang.Thread.run(Thread.java:834) Caused By: java.lang.RuntimeException: WorkflowTask: failure executing SCHEDULED->WORKFLOW_TASK_STARTED, transition history is [CREATED->WORKFLOW_TASK_SCHEDULED] io.temporal.internal.statemachines.StateMachine.executeTransition(StateMachine.java:151) io.temporal.internal.statemachines.StateMachine.handleHistoryEvent(StateMachine.java:101) io.temporal.internal.statemachines.EntityStateMachineBase.handleEvent(EntityStateMachineBase.java:67) io.temporal.internal.statemachines.WorkflowStateMachines.handleSingleEvent(WorkflowStateMachines.java:235) io.temporal.internal.statemachines.WorkflowStateMachines.handleEventsBatch(WorkflowStateMachines.java:199) io.temporal.internal.statemachines.WorkflowStateMachines.handleEvent(WorkflowStateMachines.java:175) io.temporal.internal.replay.ReplayWorkflowRunTaskHandler.handleWorkflowTaskImpl(ReplayWorkflowRunTaskHandler.java:176) io.temporal.internal.replay.ReplayWorkflowRunTaskHandler.handleWorkflowTask(ReplayWorkflowRunTaskHandler.java:145) io.temporal.internal.replay.ReplayWorkflowTaskHandler.handleWorkflowTaskWithQuery(ReplayWorkflowTaskHandler.java:122) io.temporal.internal.replay.ReplayWorkflowTaskHandler.handleWorkflowTask(ReplayWorkflowTaskHandler.java:97) io.temporal.internal.worker.WorkflowWorker$TaskHandlerImpl.handle(WorkflowWorker.java:241) io.temporal.internal.worker.WorkflowWorker$TaskHandlerImpl.handle(WorkflowWorker.java:199) io.temporal.internal.worker.PollTaskExecutor.lambda$process$0(PollTaskExecutor.java:93) java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) java.base/java.lang.Thread.run(Thread.java:834) Caused By: io.temporal.internal.sync.PotentialDeadlockException: Potential deadlock detected: workflow thread “workflow-method-cryptoWorkflo-e907120c-b0c9-4cc1-9e5e-8eaa283329f6” didn’t yield control for over a second. Other workflow threads: java.base@11.0.2/sun.security.provider.ByteArrayAccess.l2bBig(ByteArrayAccess.java:449) java.base@11.0.2/sun.security.provider.SHA5.implDigest(SHA5.java:131) java.base@11.0.2/sun.security.provider.DigestBase.engineDigest(DigestBase.java:210) java.base@11.0.2/sun.security.provider.DigestBase.engineDigest(DigestBase.java:189) java.base@11.0.2/java.security.MessageDigest$Delegate.engineDigest(MessageDigest.java:629) java.base@11.0.2/java.security.MessageDigest.digest(MessageDigest.java:385) java.base@11.0.2/com.sun.crypto.provider.HmacCore.engineDoFinal(HmacCore.java:220) java.base@11.0.2/javax.crypto.Mac.doFinal(Mac.java:581) java.base@11.0.2/javax.crypto.Mac.doFinal(Mac.java:624) java.base@11.0.2/com.sun.crypto.provider.PBKDF2KeyImpl.deriveKey(PBKDF2KeyImpl.java:197) java.base@11.0.2/com.sun.crypto.provider.PBKDF2KeyImpl.(PBKDF2KeyImpl.java:122) java.base@11.0.2/com.sun.crypto.provider.PBKDF2Core.engineGenerateSecret(PBKDF2Core.java:69) java.base@11.0.2/javax.crypto.SecretKeyFactory.generateSecret(SecretKeyFactory.java:344) app//com.codingrodent.jackson.crypto.BaseCryptoContext.createSecretKeySpec(BaseCryptoContext.java:189) app//com.codingrodent.jackson.crypto.BaseCryptoContext.getDecryptCipher(BaseCryptoContext.java:139) app//com.codingrodent.jackson.crypto.BaseCryptoContext.decrypt(BaseCryptoContext.java:85) app//com.codingrodent.jackson.crypto.EncryptionService.decrypt(EncryptionService.java:127) app//com.codingrodent.jackson.crypto.EncryptionService.decrypt(EncryptionService.java:141) app//com.codingrodent.jackson.crypto.EncryptedJsonDeserializer.deserialize(EncryptedJsonDeserializer.java:64) app//com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129) app//com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:313) app//com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:176) app//com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:214) app//com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:24) app//com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129) app//com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:313) app//com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:176) app//com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322) app//com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4674) app//com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3723) app//io.temporal.common.converter.JacksonJsonPayloadConverter.fromData(JacksonJsonPayloadConverter.java:82) app//io.temporal.common.converter.DefaultDataConverter.fromPayload(DefaultDataConverter.java:135) app//io.temporal.common.converter.DataConverter.arrayFromPayloads(DataConverter.java:104) app//io.temporal.internal.sync.POJOWorkflowImplementationFactory$POJOWorkflowImplementation.execute(POJOWorkflowImplementationFactory.java:285) app//io.temporal.internal.sync.WorkflowExecuteRunnable.run(WorkflowExecuteRunnable.java:72) app//io.temporal.internal.sync.SyncWorkflow.lambda$start$0(SyncWorkflow.java:137) app//io.temporal.internal.sync.SyncWorkflow$$Lambda$135/0x0000000800413840.run(Unknown Source) app//io.temporal.internal.sync.CancellationScopeImpl.run(CancellationScopeImpl.java:101) app//io.temporal.internal.sync.WorkflowThreadImpl$RunnableWrapper.run(WorkflowThreadImpl.java:111) java.base@11.0.2/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) java.base@11.0.2/java.util.concurrent.FutureTask.run(FutureTask.java:264) java.base@11.0.2/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) java.base@11.0.2/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) java.base@11.0.2/java.lang.Thread.run(Thread.java:834)

Also, one point to note here. It works fine if I have any one of the variables inside Account.java with @Encrypt annotation. Issue occurs when both the variables are annotated with @Encrypt.

I am not able to reproduce this issue, could you maybe share your sample with changes in a github repo so can try to run?

One thing maybe is that:

  1. Always provide a public no-args constructor, this is needed regardless of using crypto or not
  2. Annotate the public getter methods rather the the fields themselves.

Here is what i get when passing MyCustomer with two Account items in the array:

@tihomir I will try both the things that you talked about.
But any specific reason for point 1 - public no-args constructor? I had defined the class with annotation @NoArgsConstructor. Should it not suffice ?
Also, as mentioned in GitHub - codesqueak/jackson-json-crypto: Jackson Crypto Extension Module - States “Any field that is required to be encrypted has to be marked as such. This can be done by either annotating the getter() or by annotating the field definition.”
Can you explain on above points please.

@tihomir Also, as I understand, this is not being supported out of the box by Temporal since it is using another open source project “jackson-json-crypto”. Are there any plans to add this feature as part of Temporal Java SDK in future?

I don’t use Lombok so just wanted to mention these points. In the past iirc I ran into some issues with Lombok so wasn’t sure if your annotations are really applied or not, but generally public no-arg constructor is needed for any inputs to be converted to Payload via default Temporal data converter.

Another thing you could try is a newer version of jackson-json-crypto (latest should be 2.2.0) than what its used in the sample.
We had to use an older version because their 2.x require Java 11 and our samples have to run on 1.8 as well.
For the second point, I found that with the 1.1.0 version used I had better luck with annotating the getter methods rather than the fields directly, but that might be just a side-effect of not using their latest as well.

I think this would be a good idea. We trying to find similar open source solutions and trying to evaluate them as well (let us know if you know of any we could look at).
jackson-json-crypto was suggested by community, see here. One thing that’s a little alarming is this particular user comment regarding performance issues. It would be nice if you could test this out as well and let us know if you run into it and how you solved it.

Ok, Will try to use newer version of jackson-json-crypto and verify the same.

@tihomir Thanks for that feedback. Will check this for performance issues and confirm on the same once we are through with that.