ps_cypher/
lib.rs

1mod error;
2
3pub use error::{DecryptionError, EncryptionError, PsCypherError};
4pub use ps_buffer::Buffer;
5
6use chacha20poly1305::aead::{Aead, KeyInit};
7use chacha20poly1305::ChaCha20Poly1305;
8use ps_deflate::{compress, decompress};
9use ps_ecc::{decode, encode, Codeword, DecodeError};
10use ps_hash::{Hash, PARITY_SIZE};
11use ps_util::subarray;
12use std::ops::Deref;
13use std::sync::Arc;
14
15#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
16pub struct Encrypted {
17    pub bytes: Buffer,
18    pub hash: Arc<Hash>,
19    pub key: Arc<Hash>,
20}
21
22const KSIZE: usize = 32;
23const NSIZE: usize = 12;
24
25const PARITY: u8 = 12;
26
27#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
28pub struct ParsedKey {
29    key: [u8; KSIZE],
30    nonce: [u8; NSIZE],
31    length: usize,
32}
33
34impl std::fmt::Debug for ParsedKey {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        f.debug_struct("ParsedKey")
37            .field("key", &"<REDACTED>")
38            .field("nonce", &self.nonce)
39            .field("length", &self.length)
40            .finish()
41    }
42}
43
44impl From<&Hash> for ParsedKey {
45    fn from(value: &Hash) -> Self {
46        Self {
47            key: *value.digest(),
48            length: value.data_max_len().to_usize(),
49            nonce: *subarray(value.parity(), PARITY_SIZE - NSIZE),
50        }
51    }
52}
53
54/// Encrypts a message.
55/// # Errors
56/// - [`PsCypherError::PsDeflateError`] is returned if compression fails.
57/// - [`PsCypherError::ChaChaError`] is returned if encryption fails.
58/// - [`PsCypherError::HashError`] is returned if hashing fails.
59pub fn encrypt(data: &[u8]) -> Result<Encrypted, EncryptionError> {
60    let compressed_data = compress(data)?;
61    let hash_of_raw_data = ps_hash::hash(data)?;
62
63    let ParsedKey {
64        key: encryption_key,
65        length: _,
66        nonce,
67    } = (&hash_of_raw_data).into();
68
69    let chacha = ChaCha20Poly1305::new(&encryption_key.into());
70    let encrypted_data = chacha
71        .encrypt(&nonce.into(), compressed_data.as_ref())
72        .map_err(|_| EncryptionError::ChaChaError)?;
73
74    let bytes = encode(&encrypted_data, PARITY)?;
75    let hash = Hash::hash(&bytes)?.into();
76
77    let encrypted = Encrypted {
78        bytes,
79        hash,
80        key: hash_of_raw_data.into(),
81    };
82
83    Ok(encrypted)
84}
85
86/// Attempts the decryption of encrypted data.
87/// # Errors
88/// [`PsCypherError::ChaChaError`] is returned if decryption fails.
89/// [`PsCypherError::PsDeflateError`] is returned if decompression fails.
90pub fn decrypt(data: &[u8], key: &Hash) -> Result<Buffer, DecryptionError> {
91    let ParsedKey {
92        key: encryption_key,
93        length: out_size,
94        nonce,
95    } = key.into();
96
97    let ecc_decoded = extract_encrypted(data)?;
98    let chacha = ChaCha20Poly1305::new(&encryption_key.into());
99    let compressed_data = chacha
100        .decrypt(&nonce.into(), &ecc_decoded[..])
101        .map_err(|_| DecryptionError::ChaChaError)?;
102
103    Ok(decompress(&compressed_data, out_size)?)
104}
105
106#[inline]
107/// Extracts the raw ChaCha-encrypted content from the provided slice.
108/// # Errors
109/// Returns [`DecodeError`] if `data` is invalid or irrecoverably corrupted.
110pub fn extract_encrypted(data: &[u8]) -> Result<Codeword<'_>, DecodeError> {
111    decode(data, PARITY)
112}
113
114#[inline]
115#[must_use]
116/// Checks whether `data` has been corrupted or tampered with.
117///
118/// Returns `true` if the data's checksum is intact and no errors are detected.
119///
120/// # Parameters
121/// * `data` - The encrypted data buffer to validate
122///
123/// # Examples
124/// ```
125/// # use ps_cypher::{encrypt, validate_ecc};
126/// let data = b"important data";
127/// let encrypted = encrypt(data).expect("encryption failed");
128/// assert!(validate_ecc(&encrypted));
129/// ```
130pub fn validate_ecc(data: &[u8]) -> bool {
131    ps_ecc::validate(data, PARITY)
132}
133
134impl AsRef<[u8]> for Encrypted {
135    fn as_ref(&self) -> &[u8] {
136        self
137    }
138}
139
140impl Deref for Encrypted {
141    type Target = [u8];
142
143    fn deref(&self) -> &Self::Target {
144        &self.bytes
145    }
146}
147
148#[cfg(test)]
149#[allow(clippy::unwrap_used)]
150mod tests {
151    use ps_buffer::ToBuffer;
152    use ps_hash::hash;
153
154    use super::*;
155
156    #[test]
157    fn test_encrypt_and_decrypt() -> Result<(), PsCypherError> {
158        let original_data = b"Hello, World!";
159
160        let encrypted_data = encrypt(original_data)?;
161
162        let decrypted_data = decrypt(&encrypted_data.bytes, &encrypted_data.key)?;
163
164        assert_ne!(
165            original_data.to_buffer().unwrap(),
166            encrypted_data.bytes,
167            "Encryption should modify the data"
168        );
169
170        assert_eq!(
171            encrypted_data.bytes.len(),
172            31 + 2 * usize::from(PARITY),
173            "Encrypted data should be 31 bytes long"
174        );
175
176        assert_eq!(
177            original_data,
178            &decrypted_data[..],
179            "Decryption should reverse encryption"
180        );
181
182        Ok(())
183    }
184
185    // Helper function to create a sample key (for testing purposes)
186    fn create_test_key() -> Hash {
187        hash("Hello, world!").unwrap()
188    }
189
190    #[test]
191    fn test_parse_key() {
192        let key = &create_test_key();
193
194        let ParsedKey {
195            key: encryption_key,
196            length: _,
197            nonce,
198        } = key.into();
199
200        assert_eq!(encryption_key.len(), 32);
201        assert_eq!(nonce.len(), 12);
202        // Basic check of the key and nonce values.
203        assert_eq!(&encryption_key[0..4], &[220, 186, 155, 106]); // First 4 bytes of key
204        assert_eq!(&nonce[0..4], &[46, 215, 220, 44]); // First 4 bytes of nonce
205    }
206
207    #[test]
208    fn test_encrypt_decrypt() {
209        let data = b"This is some data to encrypt";
210        let encrypted = encrypt(data).unwrap();
211        let decrypted = decrypt(&encrypted, &encrypted.key).unwrap();
212        assert_eq!(&*decrypted, data);
213    }
214
215    #[test]
216    fn test_encrypt_decrypt_empty_data() {
217        let data = b"";
218        let encrypted = encrypt(data).unwrap();
219        let decrypted = decrypt(&encrypted, &encrypted.key).unwrap();
220        assert_eq!(&*decrypted, data);
221    }
222
223    #[test]
224    fn test_encrypt_decrypt_long_data() {
225        let data = "This is a very long string to test the encryption and decryption with a large amount of data.  We want to make sure that the compression and decompression work correctly, and that the encryption and decryption can handle a significant amount of data without any issues.  This should be longer than any reasonable message.  Let's add some more to be absolutely sure. And even more, just to be safe.".as_bytes();
226        let encrypted = encrypt(data).unwrap();
227        let decrypted = decrypt(&encrypted, &encrypted.key).unwrap();
228        assert_eq!(&*decrypted, data);
229    }
230
231    #[test]
232    fn test_encrypt_decrypt_different_key() {
233        let data = b"This is some data";
234        let encrypted = encrypt(data).unwrap();
235        let different_key = create_test_key(); // Use a different key.
236
237        let result = decrypt(&encrypted, &different_key);
238        assert!(result.is_err());
239        match result.unwrap_err() {
240            DecryptionError::ChaChaError => {} // Expected error type.
241            _ => panic!("Unexpected error type"),
242        }
243    }
244
245    #[test]
246    fn test_encrypt_decrypt_tampered_data() -> Result<(), PsCypherError> {
247        let data = b"This is some data";
248        let mut encrypted = encrypt(data).unwrap();
249        // Tamper with the encrypted data
250        encrypted.bytes[0] ^= 0x01; // Flip a bit
251
252        let decrypted = decrypt(&encrypted, &encrypted.key)?;
253
254        assert_eq!(decrypted.slice(..), data);
255
256        Ok(())
257    }
258
259    #[test]
260    fn test_as_ref_encrypted() {
261        let data = b"Test data";
262        let encrypted = encrypt(data).unwrap();
263        let as_ref_data: &[u8] = encrypted.as_ref();
264        assert_eq!(as_ref_data, &*encrypted);
265        assert_eq!(as_ref_data, &encrypted.bytes[..]);
266    }
267
268    #[test]
269    fn test_deref_encrypted() {
270        let data = b"More test data";
271        let encrypted = encrypt(data).unwrap();
272        let deref_data: &[u8] = &encrypted; // Use the Deref trait
273        assert_eq!(deref_data, &encrypted.bytes[..]);
274    }
275
276    #[test]
277    fn test_key_from_hash() {
278        let data = b"Test data for key derivation";
279        let h = hash(data).unwrap();
280
281        let ParsedKey {
282            key,
283            length: _,
284            nonce: _,
285        } = (&h).into();
286
287        assert_eq!(key.len(), 32);
288    }
289
290    #[test]
291    fn test_encrypt_large_data() {
292        // Create a large amount of data (1MB)
293        let data = vec![b'A'; 1024 * 1024];
294        let encrypted = encrypt(&data).unwrap();
295        let decrypted = decrypt(&encrypted, &encrypted.key).unwrap();
296        assert_eq!(&*decrypted, &data[..]);
297    }
298
299    #[test]
300    fn test_ps_cypher_error_display() {
301        let data = b"test";
302        let encrypted = encrypt(data).unwrap();
303        let bad_key = hash(b"invalid_key").unwrap();
304        let result = decrypt(&encrypted, &bad_key);
305
306        if let Err(e) = result {
307            let error_message = format!("{e}");
308            assert_eq!(
309                error_message,
310                "Encryption/Decryption failure (from chacha20poly1305)"
311            ); // Check for a substring.
312        } else {
313            panic!("Expected an error, but got success");
314        }
315    }
316
317    #[test]
318    fn test_ps_cypher_error_source() {
319        let data = b"test";
320        let encrypted = encrypt(data).unwrap();
321        let bad_key = hash(b"invalid_key").unwrap();
322        let result = decrypt(&encrypted, &bad_key);
323
324        if let Err(e) = result {
325            let source = std::error::Error::source(&e);
326            if let Some(err) = source {
327                let _ = format!("{err}"); //check it does not panic
328            }
329        } else {
330            panic!("Expected an error, but got success");
331        }
332    }
333}