Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decapsulate a encapped key from a ciphertext #29

Closed
securitymonster opened this issue Mar 2, 2023 · 4 comments
Closed

Decapsulate a encapped key from a ciphertext #29

securitymonster opened this issue Mar 2, 2023 · 4 comments

Comments

@securitymonster
Copy link

Hi,

I have cypher texts with an encapsulated symmetric key (send by third party). Is it possible to decapsulate these keys using the private key? I think rust-hpke can.

I asked the question on chat gpt which came up with the code below, but the HypridPKE class it is talking about, does not exist in 1.0.1 of hybrid-pke.

from hybrid_pke import HybridPKE, HybridError

# Initialize the HybridPKE object
hybrid_pke = HybridPKE()

# Generate a key pair
private_key, public_key = hybrid_pke.generate_key_pair()

# Encrypt a message
message = b"Hello, world!"
ciphertext, encapped_key = hybrid_pke.encrypt(public_key, message)

# Decapsulate the encapped key
decapsulated_key = hybrid_pke.decapsulate(private_key, encapped_key)

# Decrypt the ciphertext using the decapsulated key
try:
    decrypted_message = hybrid_pke.decrypt(private_key, ciphertext, decapsulated_key)
    print(decrypted_message)
except HybridError as e:
    print(e)
@jvmncs
Copy link
Member

jvmncs commented Mar 3, 2023

Hi, thanks for the interesting issue.

I'm not sure where you see a decapsulation method in rust-hpke, can you point to an example? There is an internal trait method Kem::decap, but this is not available in the public api. Similarly, hpke-rs has a kem decap function that is internal-only. The only similar thing I can think of in the user-facing hpke API is the secret export functionality, which is provided in this library via one of [hybrid_pke.Hpke.send_export, hybrid_pke.Hpke.receive_export, hybrid_pke.Context.export] depending on your situation. I'm not sure why you'd want to use the decapsulated shared secret, but it's likely that you can do whatever you want with either the existing open/seal, or by using the secret exporting functions in combination with hybrid_pke.Hpke.key_schedule.

In general, the HPKE spec sections 5 & 6 are the intended user-facing API. KEM (and decapsulation) is in section 4, which is just for declaring the protocol's primitives/dependencies. I haven't yet seen an hpke library that also exposes the KEM functionality, since this would likely just pollute the API.

Also fyi, this library is based on hpke-rs, not rust-hpke. Anything not supported by hpke-rs would be out-of-scope for us.

Aside from the method calls, the ChatGPT snippet seems to be hallucinating a few other details about this library. The code snippet uses a hybrid_pke.HybridPKE object -- I think that should be hybrid_pke.Hpke. There's also no such thing as HybridError in this library, we just expose the more granular error set here.

@securitymonster
Copy link
Author

securitymonster commented Mar 4, 2023

Hi,

First, thank you for your clear answer :)

So Cloudflare uses HPKE to encrypt certain JSON fields in their WAF (web application firewall) logs, this is a specific part of the logs they consider sensitive data, they call it payload logging. You have to specifically turn on the logging of this payload data. Cloudflare will encrypt this field by default, you can't turn this off. When you configure the logging you can either enter you own public/private key pair, or have the web-interface generate it for you after you which you can save the key-pair. You can then use the private key to decrypt the log fields in their web GUI. You can also use a tool called matched-data-cli, to generate a key pair and also decrypt a cipher text using a private key. In neither scenario a symmetric key/shared secret is used.

I'm working an app to receive the logs, decrypt the relevant JSON field and then forward into a SIEM. I had to read up on HPKE, as I never worked with it before. They way I understand it is that in basic mode you need to have a private key + shared secret to decrypt a cypher text: pt <- Open(enc, skR, info, aad, ct).

I went through the source code of matched-data-cli, which you can find here. main.rs defines two commands: generate-key-pair and decrypt, there is no encryption option. Looking at the decrypt function, it only takes the cipher text + private key as input. decrypt calls decrypt_data from matched_data.rs, in which I found decapsulate code, which led to my question:

// Decrypts data with provided private key
pub fn decrypt_data(
    encrypted_data: &EncryptedData,
    private_key: &PrivateKey,
) -> Result<Vec<u8>, HpkeError> {
    // Decapsulate and derive the shared secret. Create a shared AEAD context.
    let mut aead_ctx = setup_receiver::<Aead, Kdf, Kem>(
        &OpModeR::Base,
        private_key,
        &encrypted_data.encapped_key,
        &[],
    )?;

    // Decrypt ciphertext in place
    let mut ciphertext_copy = encrypted_data.ciphertext.clone();
    aead_ctx.open_in_place_detached(&mut ciphertext_copy, &[], &encrypted_data.tag)?;

    // Rename for clarity
    let plaintext = ciphertext_copy;

    Ok(plaintext)
}

Edit: forgot to respond to your Chat Gpt comment, it is indeed funny that it just makes stuff up sometimes. I had it happen a couple of times when it kept insisting a library or module existed that didn't. I'm curious why this happens. It is fun to play around with though :)

@securitymonster
Copy link
Author

I dig a bit more in the Rust code and it turns out that Cloudflare serialises the share-key, cipher-text and meta-data into a Rust struct:

#[derive(Serialize, Deserialize)]
pub struct EncryptedData {
    encapped_key: EncappedKey,
    ciphertext: Vec<u8>,
    tag: AeadTag<Aead>,
}

The types in the struct are:

type EncappedKey = <Kem as KemTrait>::EncappedKey;

So now I need to figure out how to go from Rust struct to Python class.

@jvmncs
Copy link
Member

jvmncs commented Mar 6, 2023

ah I see! ya, in this case, I think you will need to figure out how to parse the members of EncryptedData from bytes. in that cli tool, it looks like the bytes are first base64-decoded, and then parsed with bincode. one option would be to use pyo3 to export some of that functionality to a python library; another option would be to try to reverse engineer that bincode parsing in python (depends on how complicated the bincode serialization is, i've never used it). maybe your simplest bet would be to just do everything in rust (including the hpke decryption). if you really need things to be in python to work with your SIEM, you can then have a much smaller binding to python that receives the private key/json, and returns the decrypted logs

anyway, this no longer looks like an issue for hybrid-pke, so I will close the issue. good luck with your project!

@jvmncs jvmncs closed this as completed Mar 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants