use alloc::vec::Vec;
use core::sync::atomic::{fence, Ordering};
use hmac::{Hmac, Mac};
use sha2::Sha256;
use subtle::ConstantTimeEq;
use crate::algorithms::aes_cbc::Aes256Cbc;
use crate::algorithms::aes_ctr::Aes128CtrCipher;
use crate::algorithms::ed25519::{X25519PrivateKey, X25519PublicKey};
use crate::algorithms::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey};
use crate::algorithms::secp256r1::{Secp256r1PrivateKey, Secp256r1PublicKey};
use crate::algorithms::{PrivateKey, PublicKey};
use crate::error::{CryptoError, OrCryptoError};
use crate::hash::HashAlgorithm;
use crate::operations::encryption::{KeyExchange, KeyGeneration, SymmetricEncryption};
use crate::utils::generate_random_bytes;
pub const ECIES_SECP256K1_ALGORITHM: &str = "ECIES-secp256k1-AES128CTR";
pub const ECIES_X25519_ALGORITHM: &str = "ECIES-X25519-AES-CBC";
pub const ECIES_SECP256R1_ALGORITHM: &str = "ECIES-secp256r1-AES256CBC";
pub trait Ecies {
type PublicKey;
type PrivateKey;
fn encrypt<T: AsRef<[u8]>>(recipient_public_key: &Self::PublicKey, plaintext: T) -> Result<Vec<u8>, CryptoError>;
fn decrypt<T: AsRef<[u8]>>(recipient_private_key: &Self::PrivateKey, ciphertext: T)
-> Result<Vec<u8>, CryptoError>;
}
pub struct EciesSecp256k1;
impl EciesSecp256k1 {
fn derive_keys(shared_secret: impl AsRef<[u8]>) -> Result<([u8; 16], [u8; 32]), CryptoError> {
fence(Ordering::SeqCst);
let kdf_output = Self::kdf(shared_secret.as_ref(), 32)?;
let mut encryption_key = [0u8; 16];
encryption_key.copy_from_slice(&kdf_output[0..16]);
let mac_key_hash = HashAlgorithm::Sha2_256.hash_array::<32>(&kdf_output[16..32])?;
fence(Ordering::SeqCst);
Ok((encryption_key, mac_key_hash))
}
fn kdf(secret: impl AsRef<[u8]>, output_length: usize) -> Result<Vec<u8>, CryptoError> {
let mut ctr = 1u32;
let mut written = 0;
let mut result = Vec::new();
while written < output_length {
let ctr_bytes = [(ctr >> 24) as u8, (ctr >> 16) as u8, (ctr >> 8) as u8, ctr as u8];
let mut combined = Vec::with_capacity(4 + secret.as_ref().len());
combined.extend_from_slice(&ctr_bytes);
combined.extend_from_slice(secret.as_ref());
let hash_result = HashAlgorithm::Sha2_256.hash(&combined);
result.extend_from_slice(&hash_result);
written += 32;
ctr += 1;
}
Ok(result)
}
}
impl Ecies for EciesSecp256k1 {
type PublicKey = Secp256k1PublicKey;
type PrivateKey = Secp256k1PrivateKey;
fn encrypt<T: AsRef<[u8]>>(
recipient_public_key: &Secp256k1PublicKey,
plaintext: T,
) -> Result<Vec<u8>, CryptoError> {
let ephemeral_private = Secp256k1PrivateKey::generate_random()?;
let ephemeral_public = ephemeral_private.as_public_key();
let shared_secret = ephemeral_private.ecdh(recipient_public_key)?;
let ephemeral_public_uncompressed = ephemeral_public.to_uncompressed_bytes();
let (encryption_key, mac_key) = Self::derive_keys(&shared_secret)?;
let iv = Aes128CtrCipher::generate_iv()?;
let cipher = Aes128CtrCipher::new();
let ciphertext_only = cipher.encrypt_with_iv(encryption_key, iv, plaintext.as_ref())?;
let mut cipher_with_iv = Vec::with_capacity(16 + ciphertext_only.len());
cipher_with_iv.extend_from_slice(&iv);
cipher_with_iv.extend_from_slice(&ciphertext_only);
let mut mac = <Hmac<Sha256> as Mac>::new_from_slice(&mac_key).or_encryption_failed()?;
mac.update(&cipher_with_iv);
let hmac_result = mac.finalize().into_bytes();
let mut result = Vec::with_capacity(65 + cipher_with_iv.len() + 32);
result.extend_from_slice(&ephemeral_public_uncompressed);
result.extend_from_slice(&cipher_with_iv);
result.extend_from_slice(&hmac_result);
Ok(result)
}
fn decrypt<T: AsRef<[u8]>>(
recipient_private_key: &Secp256k1PrivateKey,
ciphertext: T,
) -> Result<Vec<u8>, CryptoError> {
if ciphertext.as_ref().len() < 113 {
return Err(CryptoError::DecryptionFailed);
}
let ephemeral_public_bytes = &ciphertext.as_ref()[0..65];
let hmac_start = ciphertext.as_ref().len() - 32;
let cipher_with_iv = &ciphertext.as_ref()[65..hmac_start]; let received_hmac = &ciphertext.as_ref()[hmac_start..];
let ephemeral_public = Secp256k1PublicKey::try_from(ephemeral_public_bytes)?;
let shared_secret = recipient_private_key.ecdh(&ephemeral_public)?;
let (encryption_key, mac_key) = Self::derive_keys(&shared_secret)?;
let mut mac = <Hmac<Sha256> as Mac>::new_from_slice(&mac_key).or_decryption_failed()?;
mac.update(cipher_with_iv);
let computed_hmac = mac.finalize().into_bytes();
fence(Ordering::SeqCst);
let hmac_matches = computed_hmac.ct_eq(received_hmac);
if hmac_matches.unwrap_u8() == 0 {
return Err(CryptoError::DecryptionFailed);
}
fence(Ordering::SeqCst);
if cipher_with_iv.len() < 16 {
return Err(CryptoError::DecryptionFailed);
}
let iv = &cipher_with_iv[0..16];
let encrypted_data = &cipher_with_iv[16..];
let cipher = Aes128CtrCipher::new();
let plaintext = cipher.decrypt_with_iv(encryption_key, iv, encrypted_data)?;
Ok(plaintext)
}
}
pub struct EciesX25519;
impl Ecies for EciesX25519 {
type PublicKey = X25519PublicKey;
type PrivateKey = X25519PrivateKey;
fn encrypt<T: AsRef<[u8]>>(recipient_public_key: &X25519PublicKey, plaintext: T) -> Result<Vec<u8>, CryptoError> {
let ephemeral_private_bytes = generate_random_bytes::<32>()?;
let ephemeral_private = X25519PrivateKey::try_from(ephemeral_private_bytes.as_slice())?;
let ephemeral_public = ephemeral_private.derive_public_key();
let shared_secret = ephemeral_private.diffie_hellman(recipient_public_key);
let sha512_hash = HashAlgorithm::Sha2_512.hash(shared_secret);
let encryption_key = &sha512_hash[0..32]; let mac_key = &sha512_hash[32..];
let iv = generate_random_bytes::<16>()?;
let cipher = Aes256Cbc;
let iv_and_ciphertext = SymmetricEncryption::encrypt(&cipher, encryption_key, Some(&iv), plaintext.as_ref())?;
let ciphertext = &iv_and_ciphertext[16..];
let ephemeral_public_bytes: Vec<u8> = ephemeral_public.into();
let mut mac = <Hmac<Sha256> as Mac>::new_from_slice(mac_key).or_encryption_failed()?;
mac.update(&iv);
mac.update(&ephemeral_public_bytes);
mac.update(ciphertext);
let hmac_result = mac.finalize().into_bytes();
let mut result = Vec::with_capacity(16 + 32 + 32 + ciphertext.len());
result.extend_from_slice(&iv);
result.extend_from_slice(&ephemeral_public_bytes);
result.extend_from_slice(&hmac_result);
result.extend_from_slice(ciphertext);
Ok(result)
}
fn decrypt<T: AsRef<[u8]>>(
recipient_private_key: &X25519PrivateKey,
ciphertext: T,
) -> Result<Vec<u8>, CryptoError> {
if ciphertext.as_ref().len() < 80 {
return Err(CryptoError::DecryptionFailed);
}
let iv = &ciphertext.as_ref()[0..16];
let ephemeral_public_bytes = &ciphertext.as_ref()[16..48];
let received_mac = &ciphertext.as_ref()[48..80];
let encrypted_data = &ciphertext.as_ref()[80..];
let ephemeral_public = X25519PublicKey::try_from(ephemeral_public_bytes)?;
let shared_secret = recipient_private_key.diffie_hellman(&ephemeral_public);
let sha512_hash = HashAlgorithm::Sha2_512.hash(shared_secret);
let encryption_key = &sha512_hash[0..32]; let mac_key = &sha512_hash[32..];
let mut mac = <Hmac<Sha256> as Mac>::new_from_slice(mac_key).or_decryption_failed()?;
mac.update(iv);
mac.update(ephemeral_public_bytes);
mac.update(encrypted_data);
fence(Ordering::SeqCst);
let computed_mac = mac.finalize().into_bytes();
let mac_matches = computed_mac.ct_eq(received_mac);
if mac_matches.unwrap_u8() == 0 {
return Err(CryptoError::DecryptionFailed);
}
fence(Ordering::SeqCst);
let cipher = Aes256Cbc;
let mut iv_and_ciphertext = Vec::with_capacity(16 + encrypted_data.len());
iv_and_ciphertext.extend_from_slice(iv);
iv_and_ciphertext.extend_from_slice(encrypted_data);
let plaintext = SymmetricEncryption::decrypt(&cipher, encryption_key, &iv_and_ciphertext)?;
Ok(plaintext)
}
}
pub struct EciesSecp256r1;
impl Ecies for EciesSecp256r1 {
type PublicKey = Secp256r1PublicKey;
type PrivateKey = Secp256r1PrivateKey;
fn encrypt<T: AsRef<[u8]>>(
recipient_public_key: &Secp256r1PublicKey,
plaintext: T,
) -> Result<Vec<u8>, CryptoError> {
let ephemeral_private = Secp256r1PrivateKey::generate_random()?;
let ephemeral_public = ephemeral_private.as_public_key();
let shared_secret = ephemeral_private.ecdh(recipient_public_key)?;
let ephemeral_public_uncompressed = ephemeral_public.to_uncompressed_bytes();
let shared_secret_x = &shared_secret;
let (encryption_key, mac_key) = Self::derive_keys(&ephemeral_public_uncompressed, shared_secret_x)?;
let iv = generate_random_bytes::<16>()?;
let cipher = Aes256Cbc;
let iv_and_ciphertext = SymmetricEncryption::encrypt(&cipher, encryption_key, Some(&iv), plaintext.as_ref())?;
let ciphertext_only = &iv_and_ciphertext[16..];
let mut mac = <Hmac<sha2::Sha512> as Mac>::new_from_slice(&mac_key).or_encryption_failed()?;
mac.update(ciphertext_only);
mac.update(&[0u8; 8]); let hmac_result = mac.finalize().into_bytes();
let mut result = Vec::with_capacity(65 + ciphertext_only.len() + 64 + 16);
result.extend_from_slice(&ephemeral_public_uncompressed);
result.extend_from_slice(ciphertext_only);
result.extend_from_slice(&hmac_result);
result.extend_from_slice(&iv);
Ok(result)
}
fn decrypt<T: AsRef<[u8]>>(
recipient_private_key: &Secp256r1PrivateKey,
ciphertext: T,
) -> Result<Vec<u8>, CryptoError> {
if ciphertext.as_ref().len() < 161 {
return Err(CryptoError::DecryptionFailed);
}
let ephemeral_public_bytes = &ciphertext.as_ref()[0..65];
let iv_start = ciphertext.as_ref().len() - 16;
let hmac_start = ciphertext.as_ref().len() - 80; let encrypted_data = &ciphertext.as_ref()[65..hmac_start];
let received_hmac = &ciphertext.as_ref()[hmac_start..iv_start];
let iv = &ciphertext.as_ref()[iv_start..];
let ephemeral_public = Secp256r1PublicKey::try_from(ephemeral_public_bytes)?;
let shared_secret = recipient_private_key.ecdh(&ephemeral_public)?;
let shared_secret_x = &shared_secret;
let (encryption_key, mac_key) = Self::derive_keys(ephemeral_public_bytes, shared_secret_x)?;
let mut mac = <Hmac<sha2::Sha512> as Mac>::new_from_slice(&mac_key).or_decryption_failed()?;
mac.update(encrypted_data);
mac.update(&[0u8; 8]); let computed_hmac = mac.finalize().into_bytes();
fence(Ordering::SeqCst);
let hmac_matches = computed_hmac.ct_eq(received_hmac);
if hmac_matches.unwrap_u8() == 0 {
return Err(CryptoError::DecryptionFailed);
}
fence(Ordering::SeqCst);
let cipher = Aes256Cbc;
let mut iv_and_ciphertext = Vec::with_capacity(16 + encrypted_data.len());
iv_and_ciphertext.extend_from_slice(iv);
iv_and_ciphertext.extend_from_slice(encrypted_data);
let plaintext = SymmetricEncryption::decrypt(&cipher, encryption_key, &iv_and_ciphertext)?;
Ok(plaintext)
}
}
impl EciesSecp256r1 {
fn derive_keys(
ephemeral_public_key: impl AsRef<[u8]>,
shared_secret_x: impl AsRef<[u8]>,
) -> Result<([u8; 32], [u8; 128]), CryptoError> {
use sha2::{Digest, Sha512};
let mut seed = Vec::with_capacity(65 + 32);
seed.extend_from_slice(ephemeral_public_key.as_ref());
seed.extend_from_slice(shared_secret_x.as_ref());
let symmetric_key_bytes = 256 / 8; let mac_key_bytes = 1024 / 8; let digest_bytes = 512 / 8; let total_bytes: usize = symmetric_key_bytes + mac_key_bytes;
let mut derivation_key = Vec::new();
let iterations = total_bytes.div_ceil(digest_bytes);
for i in 1..=iterations {
let mut hasher = Sha512::new();
hasher.update(&seed);
hasher.update((i as u32).to_be_bytes()); let digest = hasher.finalize();
derivation_key.extend_from_slice(&digest);
}
derivation_key.truncate(total_bytes);
let mut symmetric_key = [0u8; 32];
let mut mac_key = [0u8; 128];
symmetric_key.copy_from_slice(&derivation_key[0..32]);
mac_key.copy_from_slice(&derivation_key[32..160]);
Ok((symmetric_key, mac_key))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{create_secp256k1_keypair, create_secp256r1_keypair, create_x25519_keypair};
crate::test_utils::test_ecies!(secp256k1_tests, EciesSecp256k1, create_secp256k1_keypair);
crate::test_utils::test_ecies!(secp256r1_tests, EciesSecp256r1, create_secp256r1_keypair);
crate::test_utils::test_ecies!(x25519_tests, EciesX25519, create_x25519_keypair);
}