dcrypt_symmetric/aead/gcm/
types.rs

1//! Types specific to GCM mode of operation
2
3use crate::error::{validate, validate_format, Result};
4use base64;
5use rand::{rngs::OsRng, RngCore};
6use std::fmt;
7
8/// GCM nonce type (96 bits/12 bytes is the recommended size for GCM)
9#[derive(Clone, Debug)]
10pub struct GcmNonce([u8; 12]);
11
12impl GcmNonce {
13    /// Creates a new nonce from raw bytes
14    pub fn new(bytes: [u8; 12]) -> Self {
15        Self(bytes)
16    }
17
18    /// Creates a new random nonce
19    pub fn generate() -> Self {
20        let mut nonce = [0u8; 12];
21        OsRng.fill_bytes(&mut nonce);
22        Self(nonce)
23    }
24
25    /// Returns a reference to the raw nonce bytes
26    pub fn as_bytes(&self) -> &[u8; 12] {
27        &self.0
28    }
29
30    /// Creates a nonce from a base64 string
31    pub fn from_string(s: &str) -> Result<Self> {
32        let bytes =
33            base64::decode(s).map_err(|_| dcrypt_api::error::Error::SerializationError {
34                context: "nonce base64",
35                #[cfg(feature = "std")]
36                message: "invalid base64 encoding".to_string(),
37            })?;
38
39        validate::length("GCM nonce", bytes.len(), 12)?;
40
41        let mut nonce = [0u8; 12];
42        nonce.copy_from_slice(&bytes);
43
44        Ok(Self(nonce))
45    }
46}
47
48impl fmt::Display for GcmNonce {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        write!(f, "{}", base64::encode(self.0))
51    }
52}
53
54/// Format for storing both ciphertext and nonce together
55#[derive(Clone, Debug)]
56pub struct AesCiphertextPackage {
57    /// The nonce used for encryption
58    pub nonce: GcmNonce,
59    /// The encrypted data
60    pub ciphertext: Vec<u8>,
61}
62
63impl AesCiphertextPackage {
64    /// Creates a new package containing nonce and ciphertext
65    pub fn new(nonce: GcmNonce, ciphertext: Vec<u8>) -> Self {
66        Self { nonce, ciphertext }
67    }
68
69    /// Parses a serialized package
70    pub fn from_string(s: &str) -> Result<Self> {
71        validate_format(
72            s.starts_with("dcrypt-AES-GCM:"),
73            "package deserialization",
74            "invalid package format",
75        )?;
76
77        let parts: Vec<&str> = s["dcrypt-AES-GCM:".len()..].split(':').collect();
78        validate_format(
79            parts.len() == 2,
80            "package deserialization",
81            "expected format: dcrypt-AES-GCM:<nonce>:<ciphertext>",
82        )?;
83
84        let nonce_bytes =
85            base64::decode(parts[0]).map_err(|_| dcrypt_api::error::Error::SerializationError {
86                context: "nonce base64",
87                #[cfg(feature = "std")]
88                message: "invalid base64 encoding".to_string(),
89            })?;
90
91        validate::length("GCM nonce", nonce_bytes.len(), 12)?;
92
93        let mut nonce = [0u8; 12];
94        nonce.copy_from_slice(&nonce_bytes);
95
96        let ciphertext =
97            base64::decode(parts[1]).map_err(|_| dcrypt_api::error::Error::SerializationError {
98                context: "ciphertext base64",
99                #[cfg(feature = "std")]
100                message: "invalid base64 encoding".to_string(),
101            })?;
102
103        Ok(Self {
104            nonce: GcmNonce(nonce),
105            ciphertext,
106        })
107    }
108}
109
110impl fmt::Display for AesCiphertextPackage {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        let nonce_b64 = base64::encode(self.nonce.as_bytes());
113        let ciphertext_b64 = base64::encode(&self.ciphertext);
114        write!(f, "dcrypt-AES-GCM:{}:{}", nonce_b64, ciphertext_b64)
115    }
116}