Closed jplock closed 4 months ago
I think I figured out what I need to do.
I guess I was too slow. Let me know when there are more questions.
Thanks! I’m now storing the encap value alongside the aad tag and cipher text and using the single shot “open”, but I’m still getting OpenError. Anything else I should be looking at?
Do you have a minimal example of what you try to do? That would help me to understand better what you're trying to achieve. Generally, reusing key materials is not safe. But maybe I misunderstand what you're trying to achieve.
I'm building a reference implementation of encrypting data but only being able to decrypt the data from with an AWS Nitro Enclave using the AWS Key Management Service (KMS).
I have a symmetric key defined in KMS. I generate a new ECC_NIST_P256
keypair from KMS then using that to encrypt the data using this library (that part completes without error).
I'm using hybrid-pke which is a Python wrapper around this library and the code looks like:
from dataclasses import dataclass
import pickle
from typing import Dict, Any
import hybrid_pke
__all__ = ["encrypt_values"]
@dataclass(frozen=True, kw_only=True, slots=True)
class EncryptedData:
encapped_key: bytes
ciphertext: bytes
tag: bytes
def encode(self) -> bytes:
return pickle.dumps(self)
def encrypt_value(encap: bytes, sender_context: hybrid_pke.Context, plaintext: bytes) -> EncryptedData:
aad = b""
ciphertext: bytes = sender_context.seal(aad, plaintext)
return EncryptedData(encapped_key=encap, ciphertext=ciphertext, tag=aad)
def encrypt_values(public_key: bytes, plaintext_values: Dict[str, Any]) -> Dict[str, bytes]:
if not public_key:
return plaintext_values
if not plaintext_values:
return {}
hpke = hybrid_pke.default(
kem=hybrid_pke.Kem.DHKEM_P256,
kdf=hybrid_pke.Kdf.HKDF_SHA256,
aead=hybrid_pke.Aead.AES_256_GCM,
)
info = b""
encap, sender_context = hpke.setup_sender(public_key, info)
encrypted_values: Dict[str, bytes] = {}
for key, value in plaintext_values.items():
data = encrypt_value(encap, sender_context, str(value).encode())
encrypted_values[key] = data.encode()
del plaintext_values
return encrypted_values
I'm then storing the Python pickled EncryptedData
objects.
To decrypt the data, I'm sending the pickled EncryptedData
objects into a Rust application, along with the encrypted secret key I got from KMS. I use KMS to decrypt the secret key (that part works fine). I'm then using the following methods to try and decrypt the values, but it's failing on the open
call with OpenError
and I'm not sure why.
use std::collections::BTreeMap;
use anyhow::{anyhow, Result};
use base64::{prelude::BASE64_STANDARD, Engine as _};
use hpke_rs::{Hpke, HpkePrivateKey, Mode};
use hpke_rs_crypto::types::{AeadAlgorithm, KdfAlgorithm, KemAlgorithm};
use hpke_rs_rust_crypto::HpkeRustCrypto as ImplHpkeCrypto;
use serde_json::Value;
use serde_pickle::from_slice;
use crate::models::EncryptedData;
fn decrypt_value(
hpke: &Hpke<ImplHpkeCrypto>,
secret_key: &HpkePrivateKey,
b64_encrypted_value: &str,
) -> Result<String> {
let encrypted_value = BASE64_STANDARD
.decode(b64_encrypted_value)
.map_err(|err| anyhow!("unable to decode b64_encrypted_value: {:?}", err))?;
println!("[enclave] encrypted_value: {:?}", encrypted_value);
let encrypted_data: EncryptedData = from_slice(encrypted_value.as_slice(), Default::default())
.map_err(|err| anyhow!("unable to unpickle encrypted_value: {:?}", err))?;
println!("[enclave] encrypted_data: {:?}", encrypted_data);
let info = "".as_bytes();
let plaintext_value = hpke
.open(
&encrypted_data.encapped_key,
secret_key,
info,
&encrypted_data.tag,
&encrypted_data.ciphertext,
None,
None,
None,
)
.map_err(|err| anyhow!("unable to decrypt data: {:?}", err))?;
println!("[enclave] plaintext_value: {:?}", plaintext_value);
let string_value = String::from_utf8(plaintext_value)
.map_err(|err| anyhow!("unable to convert plaintext_value to string: {:?}", err))?;
println!("[enclave] string_value: {:?}", string_value);
Ok(string_value)
}
pub fn decrypt_values(
secret_key: &HpkePrivateKey,
fields: &BTreeMap<String, String>,
) -> Result<BTreeMap<String, Value>> {
let hpke = Hpke::<ImplHpkeCrypto>::new(
Mode::Base,
KemAlgorithm::DhKemP256,
KdfAlgorithm::HkdfSha256,
AeadAlgorithm::Aes256Gcm,
);
let decrypted_fields: BTreeMap<String, Value> = {
let mut decrypted_fields = BTreeMap::new();
for (field, b64_encrypted_value) in fields {
let string_value = decrypt_value(&hpke, secret_key, b64_encrypted_value)
.map_err(|err| anyhow!("unable to decrypt value: {:?}", err))?;
decrypted_fields.insert(field.to_string(), string_value.into());
}
decrypted_fields
};
Ok(decrypted_fields)
}
Loading the HpkePrivateKey
looks like:
use hpke_rs::HpkePrivateKey;
use p256::{pkcs8::DecodePrivateKey, SecretKey};
...
// Decode the DER PKCS#8 secret key
let sk: SecretKey = SecretKey::from_pkcs8_der(&plaintext_sk).map_err(|err| anyhow!("unable to decode PKCS#8 private key: {:?}", err))?;
Ok(HpkePrivateKey::new(sk.to_bytes().to_vec()))
which doesn't error out, so I'm assuming it's working as intended. There wasn't a direct way to decode the DER PKCS#8 encoded secret key I get back from KMS without using the p256
library first before sending the data into HpkePrivateKey
.
thank you for taking a look, I really appreciate it.
Thanks for the description!
I don't see anything wrong with what you're doing here. But I can see that maybe some encodings don't line up. Do you have a sample secret key that you get out after decoding the pkcs8 (not a real one please 😉)? That's the most likely culprit.
We can close this issue as I don't think its related to this library. I've tried three different Python HPKE libraries and I can't seem to get this to work when using a KMS generated data key pair. What is super strange, during my decryption test (which is iterating over a Dict[str,str]
with field names and encrypted values), I am able to decrypt the first value from the first iteration, but then all other iterations fail to decrypt.
hm, that's weird. That sounds like the subsequent encapsulations/encryptions are done differently for some reason. But looking at your code I don't see how that could happen.
I figured this out - I was using a sender context when encrypting and was trying to use one-shot decryption. That's not supported.
On the sender, I'm use the Python hybrid-pke library (Python wrapper around this library) and calling
setup_sender
with a public key andinfo
. That returns a sender_context andencap
in bytes.Is it possible to store the
encap
bytes, along with the public key and secret key, and later use secret key andencap
to callopen
on a receiver context (assuming I used the same KEM, KDF, and AEAD options on both the sender and receiver)?With this setup, I'm able to successfully encrypt values, but I've been unable to decrypt anything.
I'm not getting any errors creating the
HpkePrivateKey
, andsetup_receiver
is also completing successfully.Whenever I try and call
ctx.open(&aad, &cipher_text)
, I getOpenError
without any information as to what may be the issue.A related question, I'm assuming I can reuse the same
encap
value after encrypting multiple individual attributes, is that correct?