libes/
lib.rs

1//! # libes
2//! ![Crates.io](https://img.shields.io/crates/l/libes?style=flat)
3//! [![GitHub last commit](https://img.shields.io/github/last-commit/TJRoh01/libes?style=flat)](https://github.com/TJRoh01/libes)
4//! [![Crates.io](https://img.shields.io/crates/v/libes?style=flat)](https://crates.io/crates/libes)
5//! [![docs.rs](https://img.shields.io/docsrs/libes/latest?style=flat)](https://docs.rs/libes/latest/libes)
6//! [![Libraries.io](https://img.shields.io/librariesio/release/cargo/libes?style=flat)](https://libraries.io/cargo/libes)
7//!
8//! **lib**rary of **e**ncryption **s**chemes is a collection of ECIES variants.
9//!
10//! The goal of this is library is to become a one-stop shop for everything ECIES.
11//!
12//! For project details like ECIES variant flowcharts, explanations, license, and release tracks
13//! please see the README.md on [GitHub](https://github.com/TJRoh01/libes/blob/main/README.md).
14//!
15//! ## ⚠️ Beta Release Track - Not Production Ready ⚠️
16//! During beta development, versions 0.2+.Z, backwards compatibility for decryption is guaranteed.
17//!
18//! This means that data encrypted using library version X.Y.Z can be decrypted using any
19//! superseding library version as long as X is the same, even if the algorithm used for encryption
20//! was yanked it will still be available for decryption until X is incremented.
21//!
22//! The public API structure will not change, but algorithms that are potentially found to be broken
23//! for any reason will be immediately removed and the library will be released with an incremented
24//! Y in X.Y.Z, and versions implementing that algorithm will be yanked.
25//!
26//! The private API is still under development, so make sure that you always use the latest version
27//! 0.Y.Z to receive all patches that are released. An incremented Z in X.Y.Z will not require any
28//! modifications in your code, of course with the exception for an algorithm being yanked.
29//!
30//! ## The mechanics of libes
31//! Internally, libes is built up using generics. This allows the library to add support for
32//! algorithms with only a couple lines of code per algorithm to glue the dependency providing that
33//! algorithm with the private trait system. Then all the procedures are
34//! automatically & appropriately implemented with the help of generics & constraints.
35//! This significantly reduces the risk for human error,
36//! and ensures that the behavior is uniform between all supported algorithms.
37//!
38//! Externally, this is abstracted for the user with the struct [Ecies<K, E, A>] where:
39//! - `K` is an **Elliptic Curve** algorithm from [key]
40//! - `E` is an **Encryption** algorithm from [enc]
41//! - `A` is an **Authentication** algorithm from [auth]
42//!
43//! [Ecies<K, E, A>] can be instantiated using the associated function
44//! [new(recipient_public_key)][Ecies::new()],
45//! and then the method [encrypt(plaintext)][Ecies::encrypt()]
46//! will become available to use for **encryption**. The instantiated struct can be
47//! **safely reused** to encrypt multiple messages for the same recipient.
48//! The struct also has an associated function
49//! [decrypt(recipient_secret_key, ciphertext)][Ecies::decrypt()] for **decryption**.
50//!
51//! The library user is responsible for choosing `K` & `E` that are compatible with `A`,
52//! otherwise encryption and/or decryption functionality will not be available on the struct.
53//!
54//! Compatibility can be determined by checking whether `K` & `E` implement:
55//! - [EciesMacEncryptionSupport]/[EciesMacDecryptionSupport] if `A` is of variant `ECIES-MAC` e.g. [HmacSha256][auth::HmacSha256]
56//! - [EciesAeadEncryptionSupport]/[EciesAeadDecryptionSupport] if `A` is [Aead][auth::Aead]
57//! - [EciesSynEncryptionSupport]/[EciesSynDecryptionSupport] if `A` is [Syn][auth::Syn]
58//!
59//! ## Short usage guide
60//! 1. We decide that we want to use the `ECIES-AEAD` variant
61//! 2. We need to choose an **Elliptic Curve** algorithm from [key] and an **Encryption** algorithm from [enc] that are compatible with `ECIES-AEAD`
62//! 3. Both [key::X25519] and [enc::XChaCha20Poly1305] implement [EciesAeadEncryptionSupport] and [EciesAeadDecryptionSupport], so we can choose to use those algorithms
63//! 4. We will use [auth::Aead] to mark that it is our **Authentication** algorithm of choice
64//! 5. We enable the corresponding features for the libes dependency to compile our chosen functionality
65//!     - ```toml
66//!       [dependencies.libes]
67//!       version = "0" # For the Beta Release Track, always use the latest major version 0
68//!       features = ["ECIES-AEAD", "x25519", "XChaCha20-Poly1305"]
69//!       ```
70//!
71//! ## Code example
72//! ### Receiver
73//! ```rust
74//! # use libes::{KeyError, EciesError, auth, Ecies, enc, key};
75//! #
76//! # #[derive(Debug)]
77//! # struct DocError;
78//! #
79//! # impl From<EciesError> for DocError {
80//! #     fn from(_: EciesError) -> Self {
81//! #         Self
82//! #     }
83//! # }
84//! #
85//! # impl From<KeyError> for DocError {
86//! #     fn from(_: KeyError) -> Self {
87//! #         Self
88//! #     }
89//! # }
90//! #
91//! # fn main() -> Result<(), DocError> {
92//! // Create an alias for Ecies with our chosen algorithms
93//! type MyEcies = Ecies<key::X25519, enc::XChaCha20Poly1305, auth::Aead>;
94//!
95//! // Generate an appropriate elliptic key pair
96//! let secret_key = x25519_dalek::StaticSecret::new(rand_core::OsRng);
97//! let public_key = x25519_dalek::PublicKey::from(&secret_key);
98//!
99//! // Convert public_key to bytes
100//! let public_key_bytes = public_key.to_bytes().to_vec();
101//!
102//! // Send public_key_bytes to the message sender
103//! #
104//! # // Instantiate Ecies using the received public_key_bytes
105//! # let encryptor = MyEcies::try_new(public_key_bytes)?;
106//! #
107//! # // Encrypt the message
108//! # let message = b"Hello Alice, this is Bob.";
109//! # let encrypted_message = encryptor.encrypt(message)?;
110//! #
111//! # // Send encrypted_message to the message recipient
112//! #
113//! # // Decrypt the message
114//! # let decrypted_message = MyEcies::decrypt(&secret_key, &encrypted_message)?;
115//! #
116//! # assert_eq!(message.to_vec(), decrypted_message);
117//! #
118//! # Ok(())
119//! # }
120//! ```
121//!
122//! \~~~ _Network_ \~~~
123//!
124//! ### Sender
125//! ```rust
126//! # use libes::{KeyError, EciesError, auth, Ecies, enc, key};
127//! #
128//! # #[derive(Debug)]
129//! # struct DocError;
130//! #
131//! # impl From<EciesError> for DocError {
132//! #     fn from(_: EciesError) -> Self {
133//! #         Self
134//! #     }
135//! # }
136//! #
137//! # impl From<KeyError> for DocError {
138//! #     fn from(_: KeyError) -> Self {
139//! #         Self
140//! #     }
141//! # }
142//! #
143//! # fn main() -> Result<(), DocError> {
144//! // Create an alias for Ecies with our chosen algorithms
145//! type MyEcies = Ecies<key::X25519, enc::XChaCha20Poly1305, auth::Aead>;
146//!
147//! # // Generate a elliptic key pair
148//! # let secret_key = x25519_dalek::StaticSecret::new(rand_core::OsRng);
149//! # let public_key = x25519_dalek::PublicKey::from(&secret_key);
150//! #
151//! # // Convert public_key to bytes
152//! # let public_key_bytes = public_key.to_bytes().to_vec();
153//! #
154//! # // Send public_key_bytes to the message sender
155//! #
156//! // Instantiate Ecies using the received public_key_bytes
157//! let encryptor = MyEcies::try_new(public_key_bytes)?;
158//!
159//! // Encrypt the message
160//! let message = b"Hello Alice, this is Bob.";
161//! let encrypted_message = encryptor.encrypt(message)?;
162//!
163//! // Send encrypted_message to the message recipient
164//! #
165//! # // Decrypt the message
166//! # let decrypted_message = MyEcies::decrypt(&secret_key, &encrypted_message)?;
167//! #
168//! # assert_eq!(message.to_vec(), decrypted_message);
169//! #
170//! # Ok(())
171//! # }
172//! ```
173//!
174//! \~~~ _Network_ \~~~
175//!
176//! ### Receiver
177//! ```rust
178//! # use libes::{KeyError, EciesError, auth, Ecies, enc, key};
179//! #
180//! # #[derive(Debug)]
181//! # struct DocError;
182//! #
183//! # impl From<EciesError> for DocError {
184//! #     fn from(_: EciesError) -> Self {
185//! #         Self
186//! #     }
187//! # }
188//! #
189//! # impl From<KeyError> for DocError {
190//! #     fn from(_: KeyError) -> Self {
191//! #         Self
192//! #     }
193//! # }
194//! #
195//! # fn main() -> Result<(), DocError> {
196//! // Create an alias for Ecies with our chosen algorithms
197//! type MyEcies = Ecies<key::X25519, enc::XChaCha20Poly1305, auth::Aead>;
198//!
199//! # // Generate a elliptic key pair
200//! # let secret_key = x25519_dalek::StaticSecret::new(rand_core::OsRng);
201//! # let public_key = x25519_dalek::PublicKey::from(&secret_key);
202//! #
203//! # // Convert public_key to bytes
204//! # let public_key_bytes = public_key.to_bytes().to_vec();
205//! #
206//! # // Send public_key_bytes to the message sender
207//! #
208//! # // Instantiate Ecies using the received public_key_bytes
209//! # let encryptor = MyEcies::try_new(public_key_bytes)?;
210//! #
211//! # // Encrypt the message
212//! # let message = b"Hello Alice, this is Bob.";
213//! # let encrypted_message = encryptor.encrypt(message)?;
214//! #
215//! # // Send encrypted_message to the message recipient
216//! #
217//! // Decrypt the message
218//! let decrypted_message = MyEcies::decrypt(&secret_key, &encrypted_message)?;
219//! #
220//! # assert_eq!(message.to_vec(), decrypted_message);
221//! #
222//! # Ok(())
223//! # }
224//! ```
225//!
226//! ## Algorithm support
227//! Matrix entries are of form `Encryption & Decryption` or `Encryption`/`Decryption`
228//!
229//! ### Elliptic Curve Support Matrix
230//! | Algorithm/ECIES Variant | ECIES-MAC | ECIES-AEAD | ECIES-SYN |
231//! |:-----------------------:|:---------:|:----------:|:---------:|
232//! |         x25519          |    🚀     |     🚀     |    🚀     |
233//! |         ed25519         |    🚀     |    🚀     |    🚀    |
234//! |    K-256 / secp256k1    |    🚀     |     🚀     |    🚀     |
235//! |    P-256 / secp256r1    |    🚀     |     🚀     |    🚀     |
236//! |    P-384 / secp384r1    |    🚀     |     🚀     |    🚀     |
237//! |    P-521 / secp521r1    |    🤔     |     🤔     |    🤔     |
238//!
239//! ### Encryption Support Matrix
240//! | Algorithm/ECIES Variant | ECIES-MAC | ECIES-AEAD | ECIES-SYN |
241//! |:-----------------------:|:---------:|:----------:|:---------:|
242//! |    ChaCha20-Poly1305    |    🚀     |     🚀     |    🚀     |
243//! |   XChaCha20-Poly1305    |    🚀     |     🚀     |    🚀     |
244//! |       AES128-GCM        |  🚫[^1]   |   🚫[^1]   |  🚫[^1]   |
245//! |       AES256-GCM        |    🚀     |     🚀     |    🚀     |
246//!
247//! ### Authentication Support Matrix
248//! | Algorithm/ECIES Variant | ECIES-MAC |
249//! |:-----------------------:|:---------:|
250//! |       HMAC-SHA256       |    🚀     |
251//! |       HMAC-SHA512       |    🤔     |
252//!
253//! [^1]: AES128-GCM uses a 128-bit key and a 96-bit nonce, and when using a CSPRNG as the de-facto source to generate them,
254//! the collision risk in a 224-bit space is unsatisfactory. Due to this encryption is not implemented, along with decryption
255//! in order to not encourage using this variant in other libraries. **Note:** like AES128-GCM, AES256-GCM and some other
256//! encryption algorithms in this library also use a 96-bit nonce, but unlike AES256-GCM they have larger keys like 256 bits,
257//! which when combined with a 96-bit nonce makes the collision risk acceptable.
258
259pub mod auth;
260pub mod enc;
261pub mod key;
262pub mod markers;
263
264#[cfg(not(any(feature = "ECIES-MAC", feature = "ECIES-AEAD", feature = "ECIES-SYN")))]
265compile_error!(
266    "At least one variant feature must be activated: 'ECIES-MAC', 'ECIES_AEAD', or 'ECIES-SYN'"
267);
268
269#[cfg(feature = "ECIES-MAC")]
270use auth::generics::{Mac, TakeMac, TakeMacKey};
271#[cfg(feature = "ECIES-MAC")]
272use markers::{EciesMacDecryptionSupport, EciesMacEncryptionSupport};
273
274#[cfg(feature = "ECIES-AEAD")]
275use auth::Aead;
276#[cfg(feature = "ECIES-AEAD")]
277use markers::{EciesAeadDecryptionSupport, EciesAeadEncryptionSupport};
278
279#[cfg(feature = "ECIES-SYN")]
280use auth::Syn;
281#[cfg(feature = "ECIES-SYN")]
282use markers::{EciesSynDecryptionSupport, EciesSynEncryptionSupport};
283
284#[cfg(any(feature = "ECIES-MAC", feature = "ECIES-AEAD"))]
285use enc::generics::GenerateNonce;
286
287#[cfg(any(feature = "ECIES-MAC", feature = "ECIES-AEAD", feature = "ECIES-SYN"))]
288use enc::generics::{Encryption, TakeEncryptionKey, TakeNonce};
289#[cfg(any(feature = "ECIES-MAC", feature = "ECIES-AEAD", feature = "ECIES-SYN"))]
290use key::conversion::IntoSecretKeyRef;
291#[cfg(any(feature = "ECIES-MAC", feature = "ECIES-AEAD", feature = "ECIES-SYN"))]
292use key::generics::{DeriveKeyMaterial, GenerateEphemeralKey, KeyExchange, TakeEphemeralKey};
293
294use key::conversion::{IntoPublicKey, TryIntoPublicKey};
295use key::generics::Key;
296use std::marker::PhantomData;
297
298#[derive(Debug)]
299pub enum EciesError {
300    /// Failed to parse some data
301    ///
302    /// - for encryption: `internal error`
303    /// - for decryption: `bad ciphertext` or `internal error`
304    BadData,
305    /// Failed to verify attached Authentication Tag
306    ///
307    /// Only on ECIES-MAC
308    VerificationError,
309    /// Failed to encrypt data
310    EncryptionError,
311    /// Failed to decrypt data
312    DecryptionError,
313}
314
315#[derive(Debug)]
316pub enum KeyError {
317    /// Failed to convert raw key data into key for specified algorithm
318    BadData,
319}
320
321/// Generic `ECIES` instance
322pub struct Ecies<K, E, A> {
323    recipient_pk: K,
324    k: PhantomData<K>,
325    e: PhantomData<E>,
326    a: PhantomData<A>,
327}
328
329impl<K: Key, E, A> Ecies<K, E, A> {
330    /// Create a new `ECIES<K, E, A>` instance given a `recipient_public_key` compatible with `K`
331    pub fn new<T: IntoPublicKey<K>>(recipient_public_key: T) -> Self {
332        Self {
333            recipient_pk: recipient_public_key.into_pk(),
334            k: PhantomData,
335            e: PhantomData,
336            a: PhantomData,
337        }
338    }
339
340    pub fn try_new<T: TryIntoPublicKey<K>>(recipient_public_key: T) -> Result<Self, KeyError> {
341        Ok(Self::new(recipient_public_key.try_into_pk()?))
342    }
343}
344
345#[cfg(feature = "ECIES-MAC")]
346impl<K, E, A> Ecies<K, E, A>
347where
348    K: EciesMacEncryptionSupport + Key + GenerateEphemeralKey + KeyExchange + DeriveKeyMaterial,
349    E: EciesMacEncryptionSupport + Encryption + GenerateNonce + TakeEncryptionKey,
350    A: Mac + TakeMacKey,
351{
352    /// Encrypt `plaintext` using the `ECIES-MAC` variant
353    pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, EciesError> {
354        // Generate
355        let (ephemeral_pk, ephemeral_sk) = K::get_ephemeral_key();
356        let nonce = E::get_nonce();
357
358        // Derive
359        let shared_secret = K::key_exchange(&self.recipient_pk, &ephemeral_sk);
360        let mut derived_key = K::derive_key_material(
361            &ephemeral_pk,
362            shared_secret,
363            E::ENCRYPTION_KEY_LEN + A::MAC_KEY_LEN,
364        );
365        let enc_key = E::get_encryption_key(&mut derived_key)?;
366        let mac_key = A::get_mac_key(&mut derived_key)?;
367
368        // Process
369        let ciphertext = E::encrypt(&enc_key, &nonce, plaintext)?;
370        let mac = A::digest(&mac_key, &nonce, &ciphertext)?;
371
372        // Output
373        let mut out = Vec::new();
374        out.extend_from_slice(&ephemeral_pk.as_bytes());
375        out.extend_from_slice(nonce.as_slice());
376        out.extend_from_slice(mac.as_slice());
377        out.extend_from_slice(ciphertext.as_slice());
378
379        Ok(out)
380    }
381}
382
383#[cfg(feature = "ECIES-MAC")]
384impl<K, E, A> Ecies<K, E, A>
385where
386    K: EciesMacDecryptionSupport + Key + TakeEphemeralKey + KeyExchange + DeriveKeyMaterial,
387    E: EciesMacDecryptionSupport + Encryption + TakeNonce + TakeEncryptionKey,
388    A: Mac + TakeMac,
389{
390    /// Decrypt `ciphertext` using the `ECIES-MAC` variant, given the `recipient_secret_key` it was
391    /// encrypted for
392    pub fn decrypt<T: IntoSecretKeyRef<K>>(
393        recipient_secret_key: &T,
394        ciphertext: &[u8],
395    ) -> Result<Vec<u8>, EciesError> {
396        let mut ciphertext = ciphertext.to_vec();
397
398        let ephemeral_pk = K::get_ephemeral_key(&mut ciphertext)?;
399        let nonce = E::get_nonce(&mut ciphertext)?;
400        let mac = A::get_mac(&mut ciphertext)?;
401
402        let shared_secret = K::key_exchange(&ephemeral_pk, recipient_secret_key.into_sk_ref());
403        let mut derived_key = K::derive_key_material(
404            &ephemeral_pk,
405            shared_secret,
406            E::ENCRYPTION_KEY_LEN + A::MAC_KEY_LEN,
407        );
408        let enc_key = E::get_encryption_key(&mut derived_key)?;
409        let mac_key = A::get_mac_key(&mut derived_key)?;
410
411        A::verify(&mac_key, &nonce, &ciphertext, &mac)?;
412        E::decrypt(&enc_key, &nonce, &ciphertext)
413    }
414}
415
416#[cfg(feature = "ECIES-AEAD")]
417impl<K, E> Ecies<K, E, Aead>
418where
419    K: EciesAeadEncryptionSupport + Key + GenerateEphemeralKey + KeyExchange + DeriveKeyMaterial,
420    E: EciesAeadEncryptionSupport + Encryption + GenerateNonce + TakeEncryptionKey,
421{
422    /// Encrypt `plaintext` using the `ECIES-AEAD` variant
423    pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, EciesError> {
424        // Generate
425        let (ephemeral_pk, ephemeral_sk) = K::get_ephemeral_key();
426        let nonce = E::get_nonce();
427
428        // Derive
429        let shared_secret = K::key_exchange(&self.recipient_pk, &ephemeral_sk);
430        let mut derived_key =
431            K::derive_key_material(&ephemeral_pk, shared_secret, E::ENCRYPTION_KEY_LEN);
432        let enc_key = E::get_encryption_key(&mut derived_key)?;
433
434        // Process
435        let ciphertext = E::encrypt(&enc_key, &nonce, plaintext)?;
436
437        // Output
438        let mut out = Vec::new();
439        out.extend_from_slice(&ephemeral_pk.as_bytes());
440        out.extend_from_slice(nonce.as_slice());
441        out.extend_from_slice(ciphertext.as_slice());
442
443        Ok(out)
444    }
445}
446
447#[cfg(feature = "ECIES-AEAD")]
448impl<K, E> Ecies<K, E, Aead>
449where
450    K: EciesAeadDecryptionSupport + Key + TakeEphemeralKey + KeyExchange + DeriveKeyMaterial,
451    E: EciesAeadDecryptionSupport + Encryption + TakeNonce + TakeEncryptionKey,
452{
453    /// Decrypt `ciphertext` using the `ECIES-AEAD` variant, given the `recipient_secret_key` it was
454    /// encrypted for
455    pub fn decrypt<T: IntoSecretKeyRef<K>>(
456        recipient_secret_key: &T,
457        ciphertext: &[u8],
458    ) -> Result<Vec<u8>, EciesError> {
459        let mut ciphertext = ciphertext.to_vec();
460
461        let ephemeral_pk = K::get_ephemeral_key(&mut ciphertext)?;
462        let nonce = E::get_nonce(&mut ciphertext)?;
463
464        let shared_secret = K::key_exchange(&ephemeral_pk, recipient_secret_key.into_sk_ref());
465        let mut derived_key =
466            K::derive_key_material(&ephemeral_pk, shared_secret, E::ENCRYPTION_KEY_LEN);
467        let enc_key = E::get_encryption_key(&mut derived_key)?;
468
469        E::decrypt(&enc_key, &nonce, &ciphertext)
470    }
471}
472
473#[cfg(feature = "ECIES-SYN")]
474impl<K, E> Ecies<K, E, Syn>
475where
476    K: EciesSynEncryptionSupport + Key + GenerateEphemeralKey + KeyExchange + DeriveKeyMaterial,
477    E: EciesSynEncryptionSupport + Encryption + TakeNonce + TakeEncryptionKey,
478{
479    /// Encrypt `plaintext` using the `ECIES-SYN` variant
480    pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, EciesError> {
481        // Generate
482        let (ephemeral_pk, ephemeral_sk) = K::get_ephemeral_key();
483
484        // Derive
485        let shared_secret = K::key_exchange(&self.recipient_pk, &ephemeral_sk);
486        let mut derived_key = K::derive_key_material(
487            &ephemeral_pk,
488            shared_secret,
489            E::ENCRYPTION_KEY_LEN + E::ENCRYPTION_NONCE_LEN,
490        );
491        let enc_key = E::get_encryption_key(&mut derived_key)?;
492        let nonce = E::get_nonce(&mut derived_key)?;
493
494        // Process
495        let ciphertext = E::encrypt(&enc_key, &nonce, plaintext)?;
496
497        // Output
498        let mut out = Vec::new();
499        out.extend_from_slice(&ephemeral_pk.as_bytes());
500        out.extend_from_slice(ciphertext.as_slice());
501
502        Ok(out)
503    }
504}
505
506#[cfg(feature = "ECIES-SYN")]
507impl<K, E> Ecies<K, E, Syn>
508where
509    K: EciesSynDecryptionSupport + Key + TakeEphemeralKey + KeyExchange + DeriveKeyMaterial,
510    E: EciesSynDecryptionSupport + Encryption + TakeNonce + TakeEncryptionKey,
511{
512    /// Decrypt `ciphertext` using the `ECIES-SYN` variant, given the `recipient_secret_key` it was
513    /// encrypted for
514    pub fn decrypt<T: IntoSecretKeyRef<K>>(
515        recipient_secret_key: &T,
516        ciphertext: &[u8],
517    ) -> Result<Vec<u8>, EciesError> {
518        let mut ciphertext = ciphertext.to_vec();
519
520        let ephemeral_pk = K::get_ephemeral_key(&mut ciphertext)?;
521
522        let shared_secret = K::key_exchange(&ephemeral_pk, recipient_secret_key.into_sk_ref());
523        let mut derived_key = K::derive_key_material(
524            &ephemeral_pk,
525            shared_secret,
526            E::ENCRYPTION_KEY_LEN + E::ENCRYPTION_NONCE_LEN,
527        );
528        let enc_key = E::get_encryption_key(&mut derived_key)?;
529        let nonce = E::get_nonce(&mut derived_key)?;
530
531        E::decrypt(&enc_key, &nonce, &ciphertext)
532    }
533}