Skip to main content

rar_stream/crypto/
mod.rs

1//! Cryptographic support for encrypted RAR archives.
2//!
3//! This module provides decryption support for both RAR4 and RAR5 encrypted archives.
4//! RAR uses AES in CBC mode with format-specific key derivation functions.
5//!
6//! ## Security Overview
7//!
8//! | Format | Cipher | Key Size | KDF | Iterations |
9//! |--------|--------|----------|-----|------------|
10//! | RAR4 | AES-128-CBC | 128-bit | SHA-1 based | 262,144 (2^18) |
11//! | RAR5 | AES-256-CBC | 256-bit | PBKDF2-HMAC-SHA256 | Configurable (default 2^15) |
12//!
13//! RAR5 is significantly more secure due to the larger key size and standard KDF.
14//!
15//! ## RAR5 Encryption
16//!
17//! RAR5 uses AES-256-CBC encryption with PBKDF2-HMAC-SHA256 for key derivation.
18//! The iteration count is stored as log2 (e.g., 15 means 32,768 iterations).
19//!
20//! ```rust,ignore
21//! use rar_stream::crypto::{Rar5Crypto, Rar5EncryptionInfo};
22//!
23//! // Parse encryption info from file header's extra area
24//! let info = Rar5EncryptionInfo::parse(&encryption_data)?;
25//!
26//! // Derive key from password (slow due to PBKDF2)
27//! let crypto = Rar5Crypto::derive_key("password", &info.salt, info.lg2_count);
28//!
29//! // Verify password (optional, if check value present)
30//! if let Some(ref check) = info.psw_check {
31//!     if !crypto.verify_password(check) {
32//!         return Err("Wrong password");
33//!     }
34//! }
35//!
36//! // Decrypt data in-place
37//! crypto.decrypt(&info.init_v, &mut encrypted_data)?;
38//! ```
39//!
40//! ## RAR4 Encryption
41//!
42//! RAR4 uses AES-128-CBC encryption with a custom SHA-1 based key derivation.
43//! The IV is derived alongside the key from the password and salt.
44//!
45//! ```rust,ignore
46//! use rar_stream::crypto::Rar4Crypto;
47//!
48//! // Derive key and IV from password and 8-byte salt
49//! let crypto = Rar4Crypto::derive_key("password", &salt);
50//!
51//! // Decrypt data in-place (uses derived IV)
52//! crypto.decrypt(&mut encrypted_data)?;
53//! ```
54//!
55//! ## Encrypted Headers (RAR5 only)
56//!
57//! RAR5 supports encrypting file headers with `rar -hp`. When headers are encrypted:
58//!
59//! 1. An encryption header appears right after the archive signature
60//! 2. All subsequent headers are encrypted with AES-256-CBC
61//! 3. File names and metadata cannot be read without the password
62//!
63//! ## Security Notes
64//!
65//! - **Password strength**: RAR's KDFs are computationally expensive, but strong
66//!   passwords are still essential for security.
67//! - **No authentication**: RAR encryption provides confidentiality but not integrity.
68//!   Corrupt or tampered data may decrypt without error but produce garbage output.
69//! - **Salt uniqueness**: Each file uses a unique random salt, preventing rainbow
70//!   table attacks and ensuring identical files encrypt differently.
71
72mod rar4;
73mod rar5;
74
75pub use rar4::{Rar4Crypto, Rar4EncryptionInfo};
76pub use rar5::{Rar5Crypto, Rar5EncryptionInfo};
77
78/// Encryption method used by the archive.
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum EncryptionMethod {
81    /// RAR 5.0 encryption (AES-256-CBC, PBKDF2-HMAC-SHA256)
82    Rar50,
83    /// RAR 3.0/4.0 encryption (AES-128-CBC, custom SHA-1 KDF)
84    Rar30,
85    /// Unknown encryption method
86    Unknown,
87}
88
89/// Error type for cryptographic operations.
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub enum CryptoError {
92    /// Wrong password provided
93    WrongPassword,
94    /// Invalid encryption header
95    InvalidHeader,
96    /// Decryption failed
97    DecryptionFailed,
98    /// Unsupported encryption version
99    UnsupportedVersion(u8),
100}
101
102impl std::fmt::Display for CryptoError {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        match self {
105            CryptoError::WrongPassword => write!(f, "Wrong password"),
106            CryptoError::InvalidHeader => write!(f, "Invalid encryption header"),
107            CryptoError::DecryptionFailed => write!(f, "Decryption failed"),
108            CryptoError::UnsupportedVersion(v) => {
109                write!(f, "Unsupported encryption version: {}", v)
110            }
111        }
112    }
113}
114
115impl std::error::Error for CryptoError {}