Skip to main content

shadow_crypt_core/v1/
mod.rs

1//! Version 1 of the encryption protocol.
2//!
3//! Uses XChaCha20-Poly1305 with Argon2id key derivation. Unlike later
4//! versions, the header fields are not authenticated. This format is
5//! legacy: new files are always written as v3, and v1 support exists only
6//! to decrypt and list existing files via [`file::EncryptedFile::decrypt`]
7//! and [`header::FileHeader::decrypt_filename`].
8//!
9//! This module is deliberately independent of [`crate::v2`]: the two formats
10//! share no code, so changes to one can never silently alter the other.
11
12use crate::algorithm::Algorithm;
13
14/// Encryption and decryption primitives.
15pub mod crypt;
16
17/// Encrypted file structures and whole-file decrypt operations.
18pub mod file;
19
20/// File header structures and serialization.
21pub mod header;
22
23/// Key derivation parameters and operations.
24pub mod key;
25
26/// The AEAD algorithm used by every v1 file.
27pub const ALGORITHM: Algorithm = Algorithm::XChaCha20Poly1305;
28
29#[cfg(test)]
30mod tests {
31    use crate::v1::{
32        crypt::encrypt_bytes, file::EncryptedFile, header::FileHeader, key::KeyDerivationParams,
33    };
34
35    /// Round trip through the decrypt façade against a manually assembled v1
36    /// file, mirroring how existing v1 files were produced.
37    #[test]
38    fn decrypt_round_trip_via_bytes() {
39        let salt = [1u8; 16];
40        let params = KeyDerivationParams::test_defaults();
41        let content_nonce = [2u8; 24];
42        let filename_nonce = [3u8; 24];
43        let (key, _) = params.derive_key(b"password", &salt).unwrap();
44
45        let (filename_ct, _) = encrypt_bytes(b"name.txt", key.as_bytes(), &filename_nonce).unwrap();
46        let (content_ct, _) = encrypt_bytes(b"hello", key.as_bytes(), &content_nonce).unwrap();
47
48        let header = FileHeader::new(salt, params, content_nonce, filename_nonce, filename_ct);
49        let file = EncryptedFile::new(header, content_ct);
50
51        let parsed = EncryptedFile::from_bytes(&file.to_bytes()).unwrap();
52        let decrypted = parsed.decrypt(&key).unwrap();
53
54        assert_eq!(decrypted.filename().as_str(), "name.txt");
55        assert_eq!(decrypted.content().as_slice(), b"hello");
56    }
57
58    /// Decrypting with a key derived from the wrong password must fail.
59    #[test]
60    fn decrypt_with_wrong_key_fails() {
61        let salt = [1u8; 16];
62        let params = KeyDerivationParams::test_defaults();
63        let (key, _) = params.derive_key(b"password", &salt).unwrap();
64        let (wrong_key, _) = params.derive_key(b"wrong", &salt).unwrap();
65
66        let (filename_ct, _) = encrypt_bytes(b"name.txt", key.as_bytes(), &[3u8; 24]).unwrap();
67        let (content_ct, _) = encrypt_bytes(b"hello", key.as_bytes(), &[2u8; 24]).unwrap();
68
69        let header = FileHeader::new(salt, params, [2u8; 24], [3u8; 24], filename_ct);
70        let file = EncryptedFile::new(header, content_ct);
71
72        assert!(file.decrypt(&wrong_key).is_err());
73    }
74}