ecies_25519 0.2.3

Cross-platform Elliptic Curve Integrated Encryption Scheme (ECIES) using X25519, AES-256-GCM, and HKDF-SHA256.
Documentation
# ECIES X25519

[![crates.io](https://img.shields.io/crates/v/ecies_25519.svg)](https://crates.io/crates/ecies_25519)
[![docs.rs](https://docs.rs/ecies_25519/badge.svg)](https://docs.rs/ecies_25519)
[![License: MPL 2.0](https://img.shields.io/badge/License-MPL%202.0-brightgreen.svg)](https://opensource.org/licenses/MPL-2.0)
<!-- [![Build Status](https://github.com/normano/ecies_25519/actions/workflows/rust.yml/badge.svg)](https://github.com/normano/ecies_25519/actions/workflows/rust.yml) --> <!-- Add your CI badge -->

Elliptic Curve Integrated Encryption Scheme (ECIES) implemented in Rust using X25519, AES-256-GCM, and HKDF-SHA256.

## Overview

This crate provides a straightforward implementation of ECIES, a hybrid encryption scheme allowing encryption of data using a recipient's X25519 public key. The data can only be decrypted by the corresponding private key holder.

It uses the following cryptographic primitives:

*   **Key Agreement:** X25519 (via `x25519-dalek`)
*   **Key Derivation Function (KDF):** HKDF-SHA256 (via `hkdf` and `sha2`)
*   **Data Encryption:** AES-256-GCM (Authenticated Encryption via `aes-gcm`)

The implementation aims for simplicity and security, using well-vetted underlying cryptographic libraries and providing efficient key parsing for standard formats.

## Features

*   ECIES encryption and decryption using X25519 / AES-256-GCM / HKDF-SHA256.
*   Generation of X25519 key pairs in OpenSSL-compatible DER formats (PKCS#8 v1, SubjectPublicKeyInfo).
*   Parsing of X25519 and Ed25519 keys from PEM and DER formats.
    *   Ed25519 keys are automatically converted to their corresponding X25519 representation.
*   Requires `std` (due to dependencies like `pem`).

## Installation

Add the crate to your `Cargo.toml`:

```toml
[dependencies]
ecies_25519 = "0.2.0" # Use the latest version from crates.io
rand_core = "0.9"   # Required for RNG traits (ensure compatibility)
# Add an RNG implementation like rand, OsRng, or rand_chacha
rand = "0.9"        # Example using the rand crate
```

Or run:

```bash
cargo add ecies_25519 rand_core rand # Add your preferred RNG crate if not rand
```

## Usage Example

```rust
use ecies_25519::{
    generate_keypair, // For generating new X25519 key pairs in DER format
    EciesX25519,      // The main struct for encryption/decryption
    // Functions to parse keys from PEM or DER format
    parse_public_key,
    parse_private_key,
    PublicKey,        // Re-exported key types
    StaticSecret,
};
use rand::rngs::OsRng; // Cryptographically secure random number generator
use rand_core::{RngCore, CryptoRng}; // Required traits for the RNG

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Initialize a secure random number generator
    let mut rng = OsRng;

    // 2. Generate the recipient's keypair (DER encoded)
    let recipient_keypair_der = generate_keypair(&mut rng);

    // 3. Parse the DER keys into usable crypto types
    //    (Alternatively, load your existing PEM/DER keys from files/strings)
    let recipient_public_key = parse_public_key(&recipient_keypair_der.public_der)
        .expect("Failed to parse generated public key");
    let recipient_private_key = parse_private_key(&recipient_keypair_der.private_der)
        .expect("Failed to parse generated private key");

    // 4. The message to encrypt
    let message = b"This is a super secret message! \xf0\x9f\x92\x96"; // Example with UTF-8 bytes

    // 5. Create an ECIES instance
    let ecies = EciesX25519::new();

    // 6. Encrypt the message using the recipient's public key
    println!("Encrypting message...");
    let encrypted_data = ecies.encrypt(
        &recipient_public_key,
        message,
        &mut rng, // RNG is needed for ephemeral key and AES nonce
    )?; // Propagate encryption errors
    println!("Encrypted data length: {}", encrypted_data.len());
    // Note: Ciphertext format is [ephemeral_public_key | aes_gcm_output]

    // 7. Decrypt the message using the recipient's private key
    println!("Decrypting message...");
    let decrypted_data = ecies.decrypt(
        &recipient_private_key,
        &encrypted_data,
    )?; // Propagate decryption errors

    // 8. Verify the result
    assert_eq!(message, decrypted_data.as_slice());
    println!("Decryption successful! Message: \"{}\"", String::from_utf8_lossy(&decrypted_data));

    Ok(())
}
```

## Key Concepts

*   **Hybrid Encryption:** Combines asymmetric (X25519) and symmetric (AES-256-GCM) cryptography.
*   **Ephemeral Key:** A temporary X25519 keypair is generated by the sender for *each encryption*. Only the public part is sent with the ciphertext. This provides forward secrecy.
*   **ECDH:** Elliptic Curve Diffie-Hellman is used between the sender's ephemeral private key and the recipient's static public key (for encryption) or the sender's ephemeral public key and the recipient's static private key (for decryption) to establish a shared secret.
*   **HKDF:** The ECDH shared secret, concatenated with the involved public keys (`[ephemeral_pk | shared_secret]` for encryption, `[recipient_pk | shared_secret]` for decryption), and a fixed context string (`"ecies_x25519"`), is fed into HKDF-SHA256 to derive a robust 32-byte AES key.
*   **AES-GCM:** The derived key is used with AES-256-GCM to encrypt the plaintext. AES-GCM provides both confidentiality (encryption) and integrity/authenticity (via a 16-byte MAC tag). It requires a unique 12-byte nonce (IV) for each encryption with the same key, which is generated randomly by this library.
*   **Ciphertext Format:** The output of `encrypt` is the concatenation: `[ephemeral_public_key (32 bytes) || nonce (12 bytes) || AES ciphertext || auth_tag (16 bytes)]`.

## API Reference

The public API is primarily exposed from the crate root (`ecies_25519::`).

**ECIES Operations:**

*   **`EciesX25519::new() -> Self`**: Creates a new ECIES instance.
*   **`EciesX25519::encrypt(...) -> Result<Vec<u8>, Error>`**: Encrypts data for `receiver_pub` using `rng`.
*   **`EciesX25519::decrypt(...) -> Result<Vec<u8>, Error>`**: Decrypts data using `receiver_sk`.

**Key Handling:**

*   **`generate_keypair<T>(csprng: &mut T) -> KeyPairDer`**: Generates a new X25519 key pair (DER encoded). Requires `RngCore + CryptoRng`.
*   **`parse_public_key(pem_or_der_bytes: &[u8]) -> Result<PublicKey, KeyParsingError>`**: Parses PEM/DER public key (X25519/Ed25519).
*   **`parse_private_key(pem_or_der_bytes: &[u8]) -> Result<StaticSecret, KeyParsingError>`**: Parses PEM/DER private key (X25519/Ed25519).

**Core Types:**

*   **`PublicKey`**: (`x25519_dalek::PublicKey`) X25519 public key.
*   **`StaticSecret`**: (`x25519_dalek::StaticSecret`) X25519 private key.
*   **`KeyPairDer`**: Struct holding `.public_der: Vec<u8>` (SPKI) and `.private_der: Vec<u8>` (PKCS#8). Has `.public_to_pem()` and `.private_to_pem()` methods.
*   **`Error`**: Enum for ECIES `encrypt`/`decrypt` errors.
*   **`KeyParsingError`**: Enum for key parsing errors (re-exported from `parser`).

## Security Considerations

*   **RNG:** Always use a cryptographically secure random number generator (CSPRNG) like `rand::rngs::OsRng`. The security of the ephemeral keys and AES nonces depends critically on the RNG quality.
*   **Private Key Security:** The recipient's X25519 private key (`StaticSecret`) must be kept confidential.
*   **HKDF Info:** This implementation uses a fixed info string (`"ecies_x25519"`). For better domain separation in complex applications using the same keys for multiple purposes, consider modifying the library or using distinct keys.
*   **Authenticated Encryption:** AES-GCM provides authenticity. The `decrypt` function **must** be checked for errors. An `Err(Error::DecryptionFailed)` indicates potential tampering or use of the wrong key.
*   **Internal `unwrap()`:** Note that the current implementation of `EciesX25519::encrypt` uses `unwrap()` internally when parsing the generated ephemeral keys. While key generation *should* produce valid keys, this could theoretically panic if the underlying `parser` module had a bug related to generation output. Robust applications might prefer generating/parsing keys separately and handling potential parsing errors explicitly before calling encryption/decryption methods.

## Contributing

Contributions (bug reports, pull requests, feature suggestions) are welcome! Please open an issue to discuss significant changes before submitting a PR.

## License

Licensed under the Mozilla Public License Version 2.0 ([LICENSE](LICENSE) or <https://opensource.org/licenses/MPL-2.0>).