ovunto_security/crypto/
message.rs

1//! Module for working with cipher messages.
2//!
3//! A cipher message consists of encrypted data along with a nonce used for encryption.
4//! This module provides a struct `CipherMessage` to represent cipher messages and
5//! functions for encryption and decryption using the AES-GCM encryption algorithm.
6//!
7//! # Examples
8//!
9//! ```
10//! use ovunto_security::crypto::{CipherMessage, Key, Nonce};
11//!
12//! // Generate a random encryption key
13//! let key = Key::random();
14//!
15//! // Generate a random nonce
16//! let nonce = Nonce::random();
17//!
18//! // Encrypt a message using the key and nonce
19//! let message = b"Hello, world!".to_vec();
20//! let cipher = key.encrypt(&message).unwrap();
21//!
22//! // Decrypt the cipher message using the key
23//! let decrypted_message = key.decrypt(cipher).unwrap();
24//! assert_eq!(message, decrypted_message);
25//! ```
26
27use std::fmt::{Debug, Formatter};
28
29use base64::Engine;
30use serde::{Deserialize, Deserializer, Serialize, Serializer};
31
32use crate::Error;
33
34use super::Nonce;
35
36/// Represents a cipher message containing encrypted data and a nonce.
37#[derive(Clone)]
38pub struct CipherMessage {
39    /// Encrypted data
40    pub cipher_text: Vec<u8>,
41    /// Nonce used for encryption
42    pub nonce: Nonce,
43}
44
45impl CipherMessage {
46    pub fn to_base64(&self) -> crate::Result<String> {
47        let nonce = self.nonce.to_base64();
48        let cipher_text =
49            base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&self.cipher_text);
50
51        Ok(format!("{nonce}.{cipher_text}"))
52    }
53
54    pub fn from_base64(base64: String) -> crate::Result<Self> {
55        let mut split = base64.split('.');
56        let base64_nonce = split
57            .next()
58            .ok_or(Error::from_string("Invalid CipherMessage format"))?;
59        let base64_cipher = split
60            .next()
61            .ok_or(Error::from_string("Invalid CipherMessage format"))?;
62
63        let nonce = Nonce::from_base64(base64_nonce.to_string())?;
64        let cipher_text = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(base64_cipher)?;
65
66        Ok(Self { nonce, cipher_text })
67    }
68}
69
70impl Debug for CipherMessage {
71    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
72        f.debug_struct("CipherMessage")
73            .field("cipher_text", &hex::encode(&self.cipher_text))
74            .field("nonce", &self.nonce)
75            .finish()
76    }
77}
78
79impl Serialize for CipherMessage {
80    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
81    where
82        S: Serializer,
83    {
84        let base64 = self.to_base64().map_err(|err| {
85            serde::ser::Error::custom(format!("Error serializing CipherMessage: {:?}", err))
86        })?;
87
88        serializer.serialize_str(&base64)
89    }
90}
91
92impl<'de> Deserialize<'de> for CipherMessage {
93    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
94    where
95        D: Deserializer<'de>,
96    {
97        let base64 = String::deserialize(deserializer)?;
98
99        Self::from_base64(base64).map_err(|err| {
100            serde::de::Error::custom(format!("Error deserializing CipherMessage: {:?}", err))
101        })
102    }
103}