hpgrahsl / kryptonite-for-kafka

Kryptonite for Kafka is a client-side 🔒 field level 🔓 cryptography library for Apache Kafka® offering a Kafka Connect SMT, ksqlDB UDFs, and a standalone HTTP API service. It's an ! UNOFFICIAL ! community project

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Separate function for Decryption not working

baduy9x opened this issue · comments

Hi @hpgrahsl ,

I'm trying to write an function to decrypt the encrypted values from the output of this plugin.
But currently it's not able to decrypt. Here is the code.
key.json

`import com.google.crypto.tink.Aead;
import com.google.crypto.tink.CleartextKeysetHandle;
import com.google.crypto.tink.JsonKeysetReader;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.aead.AeadConfig;
import com.google.crypto.tink.aead.AeadFactory;
import com.google.crypto.tink.aead.AeadKeyTemplates;
import com.google.crypto.tink.config.TinkConfig;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Base64;

public class TinkEncryption {
public static byte[] asBytes() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.writeBytes("k1".getBytes(StandardCharsets.UTF_8));
baos.writeBytes("02".getBytes(StandardCharsets.UTF_8));
baos.writeBytes("584420223".getBytes(StandardCharsets.UTF_8));
return baos.toByteArray();
}

private static final byte[] EMPTY_ASSOCIATED_DATA = new byte[0];


public static void main(String[] args) throws Exception {
    AeadConfig.register();


    KeysetHandle handle = null;
    try (FileInputStream inputStream = new FileInputStream("key.json")) {
        handle = CleartextKeysetHandle.read(JsonKeysetReader.withInputStream(inputStream));
    } catch (GeneralSecurityException | IOException ex) {
        System.err.println("Error reading key: " + ex);
        System.exit(1);
    }

    Aead aead = null;
    try {
        aead = handle.getPrimitive(Aead.class);
    } catch (GeneralSecurityException ex) {
        System.err.println("Error creating primitive: %s " + ex);
        System.exit(1);
    }

    System.out.println("Read key file successfully");

    String plainText = "This is a plain text which needs to be encrypted!";
    String aad = "k102584420223";
    byte[] ciphertext = aead.encrypt(plainText.getBytes(), aad.getBytes());
    System.out.println(Base64.getEncoder().encodeToString(ciphertext));
    byte[] decrypted = aead.decrypt(ciphertext, aad.getBytes());
    String decrypted_text = new String(decrypted);
    System.out.println(decrypted_text);
    System.out.println("Done...");





    //String base64 = "ASLVi39Pz9uqvEa9WDTFTBfqMKZ8VhzlJ4iRxgmCXDcQMy3GPMaYzmFo1P6Q7qEOzzPPTaWLboJ60f00NuPmbkrj6mCxxbRogSy1eCW6+abxaQ==";
    //String base64 = "LgEi1Yt/HVcZLhbLctOOFDjVkmI079znSMFq8U33SIIlr6s9iIPDcTNd24ULswwwsm15LWRlbW8tc2VjcmV0LWtleS0xMrNrsQ==";
    String base64 = "OQEi1Yt/1e0OpZh4ru/ouSYU2WOErLlA56rxHGw3zryCxBviUxXzPduGpwZ/JJE0WwRwGUFR/sImDDCybXktZGVtby1zZWNyZXQta2V5LTEys2ux";
    ciphertext = Base64.getDecoder().decode(base64);
    decrypted = aead.decrypt(ciphertext, asBytes());
    decrypted_text = new String(decrypted);
    System.out.println(decrypted_text);



}

}
`

Here is the output

Exception in thread "main" java.security.GeneralSecurityException: decryption failed at com.google.crypto.tink.aead.AeadWrapper$WrappedAead.decrypt(AeadWrapper.java:112) at TinkEncryption.main(TinkEncryption.java:90)

Want to check with you if the asBytes() function here (for associatedData) is correct or not.
Really appreciate your help.

Thank you so much,
Vincent Trinh.

Hi! Can you add a little bit more context to what you are trying to achieve with this?

It looks like you want to write your own custom code that takes encrypted output of kryptonite-for-kafka and tries to decrypt it "manually" in an external application, right? What's the purpose in doing this, and would it work for you to rely on some of the existing "building blocks" my library provides instead of reimplementing decryption from scratch?

Hi @hpgrahsl,
Thanks for your response.

Yes, I have the use-case to decrypt to data outside of the kafka connect. That's why I need to write a function to manually decrypt it. I followed your code that add associated data to decrypt but the decryption failed. I thought there is some issue with the associate data (asBytes function). Could you help to check if the values I pass is correct or not, as in the key I attached above.

Thank you very much,
Vincent Trinh.

Hi again,

As I suggested, you should probably make use of the existing building blocks that are part of the kryptonite "core" library. If you do that there is no need to directly touch Tink-related crypto primitives which means it's easier in the end :)

Another reason is that kryptonite doesn't directly operate on raw byte sequences only. There is an object structure around it which stores meta data in addition to the raw ciphertext -> EncryptedField.class. Additionally, the data that needs to be encrypted is first serialized to bytes which this is done using the Kryo library (external dependency). That being said, code that you would want to write to perform what you have in mind would roughly look as follows:

            var kryptonite =  new Kryptonite(new TinkKeyVault(keyConfigs));        
            var serdeProcessor = new KryoSerdeProcessor();

            //base64 encoded ciphertext with metadata
            //e.g. resulting from kryptonite connect SMT when encrypting some string
            var cipheredB64 = "...";
            
            //deserialized object with metadata + ciphertext bytes (this uses kryo under the hood to get an EncryptedField object 
            var encryptedField = KryoInstance.get().readObject(
                    new Input(Base64.getDecoder().decode(cipheredB64)), EncryptedField.class);
            
            //deciphered bytes as a result from the decrypt operation handled by kryptonite
            var plainBytes = kryptonite.decipherField(encryptedField);
            
            //original string object after deserialization
            var plainString = (String)serdeProcessor.bytesToObject(plainBytes);
            System.out.println("RESULT: "+plainString);

Hope this gives you some ideas how to proceed.

@baduy9x I hope you were able to make some progress based on my comment above. Will close this for now. If necessary, feel free to ask further questions.

Hi @hpgrahsl we have the exact same use-case as this actually -- We will use Connect to encrypt some data but then in some cases will be using a vanilla Java Kafka Client to consume and process the data, and there we need to be able to decrypt the value.

I wonder if it would be useful or possible to add some information to your documentation how this can be done? (or maybe even on top of that provide a small Java library which can be just plugged in somewhere would be cool as well maybe? 😎 )

Edit: and now that I looked again at the readme I see that I completely missed this extra HTTP service that you have mentioned (https://github.com/hpgrahsl/kryptonite-for-kafka/tree/master/funqy-http-kryptonite) -- is the idea that "most" cases should just instead run this HTTP service instead of trying to use Kryptonite in Java like an API or would using it directly in Java also be acceptable?

@hpgrahsl one issue as we dive even deeper into this option is that it seems in order to run the code you pasted above, we need to pull down a copy of the entire Kryptonite project and build it along with our services. So it is a bit "clumsy" compared to most other things we are doing, where we usually just fetch dependencies directly from a public Maven repository somewhere and then import the right classes and use them in our code.

Is it possible to have an example where we won't need to make a copy of the entire Kryptonite project to run (just that we need to add dependencies for Tink and maybe these com.esotericsoftware.kryo classes), and/or that these parts of Kryptonite could be packaged as a library and published to some kind of Maven repository?

@joshuagrisham-karolinska

is the idea that "most" cases should just instead run this HTTP service instead of trying to use Kryptonite in Java like an API or would using it directly in Java also be acceptable?

the main idea behind providing the separate http service is to allow for easier integration with languages/runtimes other than java/jvm. also the http service is a REST API that talks JSON only so this is something to keep in mind as well. if you are building a custom client app on top of the jvm, I'd probably recommend to use kryptonite "natively" i.e. working with the kryptonite core code like shown in the code snippet I posted above.

@joshuagrisham-karolinska

Is it possible to have an example where we won't need to make a copy of the entire Kryptonite project to run (just that we need to add dependencies for Tink and maybe these com.esotericsoftware.kryo classes), and/or that these parts of Kryptonite could be packaged as a library and published to some kind of Maven repository?

what you suggest is definitely the way to go. there haven't been any requests for building custom code on top of kryptonite so far which is the only reason why the "core" library hasn't been published to an artefact repository yet. I might well consider doing that for one of the upcoming releases as soon as I have some time :-)

meanwhile you can build a your poc and experiment with your custom app by integrating a pre-built local kryptonite jar artefact via maven.