windows_dpapi/
lib.rs

1//! Windows Data Protection API (DPAPI) wrapper for Rust
2//!
3//! This library provides a safe wrapper around Windows' built-in encryption system (DPAPI).
4//! It allows you to encrypt and decrypt data that is tied to either the current user
5//! or the local machine.
6//!
7//! # Security Considerations
8//!
9//! - Data encrypted with `Scope::User` can only be decrypted by the same user on the same machine
10//! - Data encrypted with `Scope::Machine` can be decrypted by any user on the same machine
11//! - Encrypted data cannot be decrypted on a different machine
12//! - The encryption is tied to the Windows user/machine credentials
13//!
14//! # Examples
15//!
16//! ```rust
17//! use windows_dpapi::{encrypt_data, decrypt_data, Scope};
18//!
19//! fn main() -> anyhow::Result<()> {
20//!     // Encrypt data for current user only
21//!     let secret = b"my secret data";
22//!     let encrypted = encrypt_data(secret, Scope::User)?;
23//!
24//!     // Decrypt the data
25//!     let decrypted = decrypt_data(&encrypted, Scope::User)?;
26//!     assert_eq!(secret, decrypted.as_slice());
27//!     Ok(())
28//! }
29//! ```
30//!
31//! # Common Use Cases
32//!
33//! - Storing application secrets
34//! - Securing user credentials
35//! - Protecting sensitive configuration data
36//!
37//! # Limitations
38//!
39//! - Windows-only (this crate will not compile on other platforms)
40//! - Data cannot be decrypted on a different machine
41//! - Machine scope is less secure than user scope
42
43#[cfg(windows)]
44use anyhow::{Context, Result};
45#[cfg(windows)]
46use std::{ptr, slice};
47#[cfg(windows)]
48use winapi::shared::minwindef::DWORD;
49#[cfg(windows)]
50use winapi::um::dpapi::{CryptProtectData, CryptUnprotectData};
51#[cfg(windows)]
52use winapi::um::wincrypt::DATA_BLOB;
53
54/// Defines the encryption scope: user or machine
55#[cfg(windows)]
56#[derive(Clone, Copy, Debug)]
57pub enum Scope {
58    /// Tied to current user account. Data can only be decrypted by the same user
59    /// on the same machine. This is the most secure option for user-specific data.
60    ///
61    /// # Security
62    ///
63    /// - Data is encrypted using the current user's credentials
64    /// - Only the same user on the same machine can decrypt the data
65    /// - If the user's password changes, the data can still be decrypted
66    /// - If the user is deleted, the data cannot be decrypted
67    /// - Note: The decryption will only succeed if the data was encrypted with
68    ///   Scope::User by the same user. The scope flag is used during encryption
69    ///   to determine which key to use.
70    User,
71
72    /// Tied to local machine. Data can be decrypted by any user on the same machine.
73    ///
74    /// # Security
75    ///
76    /// - Data is encrypted using the machine's credentials
77    /// - Any user on the same machine can decrypt the data
78    /// - Useful for shared secrets that need to be accessible to all users
79    /// - Less secure than user scope as it's accessible to all local users
80    /// - Note: The decryption will only succeed if the data was encrypted with
81    ///   Scope::Machine. The scope flag is used during encryption to determine
82    ///   which key to use.
83    Machine,
84}
85
86#[cfg(windows)]
87fn to_blob(data: &[u8]) -> DATA_BLOB {
88    DATA_BLOB {
89        cbData: data.len() as DWORD,
90        pbData: data.as_ptr() as *mut u8,
91    }
92}
93
94/// Encrypts data using Windows DPAPI
95///
96/// # Arguments
97///
98/// * `data` - The data to encrypt
99/// * `scope` - The encryption scope (User or Machine)
100///
101/// # Returns
102///
103/// Returns a `Result` containing the encrypted data as a `Vec<u8>` if successful.
104///
105/// # Errors
106///
107/// Returns an error if:
108/// - The Windows API call fails
109/// - Memory allocation fails
110/// - The current user doesn't have permission to encrypt data
111///
112/// # Examples
113///
114/// ```rust
115/// use windows_dpapi::{encrypt_data, Scope};
116///
117/// fn main() -> anyhow::Result<()> {
118///     let secret = b"my secret data";
119///     let encrypted = encrypt_data(secret, Scope::User)?;
120///     Ok(())
121/// }
122/// ```
123#[cfg(windows)]
124pub fn encrypt_data(data: &[u8], scope: Scope) -> Result<Vec<u8>> {
125    log::debug!("Encrypting with DPAPI ({:?} scope)", scope);
126
127    let flags = match scope {
128        Scope::User => 0,      // default = user + UI prompt (but no entropy = silent)
129        Scope::Machine => 0x4, // CRYPTPROTECT_LOCAL_MACHINE
130    };
131
132    unsafe {
133        let mut input = to_blob(data);
134        let mut output = DATA_BLOB {
135            cbData: 0,
136            pbData: ptr::null_mut(),
137        };
138
139        let success = CryptProtectData(
140            &mut input,
141            ptr::null(),
142            ptr::null_mut(),
143            ptr::null_mut(),
144            ptr::null_mut(),
145            flags,
146            &mut output,
147        );
148
149        if success == 0 {
150            return Err(std::io::Error::last_os_error()).context("CryptProtectData failed");
151        }
152
153        let encrypted = slice::from_raw_parts(output.pbData, output.cbData as usize).to_vec();
154        winapi::um::winbase::LocalFree(output.pbData as *mut _);
155        Ok(encrypted)
156    }
157}
158
159/// Decrypts data that was encrypted using Windows DPAPI
160///
161/// # Arguments
162///
163/// * `data` - The encrypted data to decrypt
164/// * `scope` - The encryption scope that was used to encrypt the data
165///
166/// # Returns
167///
168/// Returns a `Result` containing the decrypted data as a `Vec<u8>` if successful.
169///
170/// # Errors
171///
172/// Returns an error if:
173/// - The Windows API call fails
174/// - The data is corrupted
175/// - The current context does not match the encryption scope (e.g., wrong user or machine)
176/// - The current user doesn't have permission to decrypt the data
177/// - The data was encrypted on a different machine
178///
179/// # Examples
180///
181/// ```rust
182/// use windows_dpapi::{encrypt_data, decrypt_data, Scope};
183///
184/// fn main() -> anyhow::Result<()> {
185///     // First encrypt some data
186///     let secret = b"my secret data";
187///     let encrypted = encrypt_data(secret, Scope::User)?;
188///     
189///     // Then decrypt it
190///     let decrypted = decrypt_data(&encrypted, Scope::User)?;
191///     assert_eq!(secret, decrypted.as_slice());
192///     Ok(())
193/// }
194/// ```
195#[cfg(windows)]
196pub fn decrypt_data(data: &[u8], scope: Scope) -> Result<Vec<u8>> {
197    log::debug!("Decrypting with DPAPI ({:?} scope)", scope);
198
199    let flags = match scope {
200        Scope::User => 0,
201        Scope::Machine => 0x4,
202    };
203
204    unsafe {
205        let mut input = to_blob(data);
206        let mut output = DATA_BLOB {
207            cbData: 0,
208            pbData: ptr::null_mut(),
209        };
210
211        let success = CryptUnprotectData(
212            &mut input,
213            ptr::null_mut(),
214            ptr::null_mut(),
215            ptr::null_mut(),
216            ptr::null_mut(),
217            flags,
218            &mut output,
219        );
220
221        if success == 0 {
222            return Err(std::io::Error::last_os_error()).context("CryptUnprotectData failed");
223        }
224
225        let decrypted = slice::from_raw_parts(output.pbData, output.cbData as usize).to_vec();
226        winapi::um::winbase::LocalFree(output.pbData as *mut _);
227        Ok(decrypted)
228    }
229}
230
231#[cfg(test)]
232#[cfg(windows)]
233mod tests {
234    use super::*;
235
236    #[test]
237    fn round_trip_user_scope() {
238        let original = b"user secret";
239        let encrypted = encrypt_data(original, Scope::User).expect("User encryption failed");
240        assert_ne!(original.to_vec(), encrypted);
241        let decrypted = decrypt_data(&encrypted, Scope::User).expect("User decryption failed");
242        assert_eq!(original.to_vec(), decrypted);
243    }
244
245    #[test]
246    fn round_trip_machine_scope() {
247        let original = b"machine secret";
248        let encrypted = encrypt_data(original, Scope::Machine).expect("Machine encryption failed");
249        assert_ne!(original.to_vec(), encrypted);
250        let decrypted =
251            decrypt_data(&encrypted, Scope::Machine).expect("Machine decryption failed");
252        assert_eq!(original.to_vec(), decrypted);
253    }
254
255    #[test]
256    fn handles_empty_input() {
257        let data = b"";
258        let encrypted = encrypt_data(data, Scope::Machine).expect("Encrypt empty");
259        let decrypted = decrypt_data(&encrypted, Scope::Machine).expect("Decrypt empty");
260        assert_eq!(data.to_vec(), decrypted);
261    }
262
263    #[test]
264    fn handles_large_input() {
265        let data = vec![0xAAu8; 5 * 1024 * 1024];
266        let encrypted = encrypt_data(&data, Scope::Machine).expect("Encrypt large");
267        let decrypted = decrypt_data(&encrypted, Scope::Machine).expect("Decrypt large");
268        assert_eq!(data, decrypted);
269    }
270
271    #[test]
272    fn fails_on_corrupted_data() {
273        let original = b"important";
274        let mut encrypted = encrypt_data(original, Scope::Machine).expect("Encrypt failed");
275        encrypted[0] ^= 0xFF;
276        let result = decrypt_data(&encrypted, Scope::Machine);
277        assert!(result.is_err(), "Corrupted data should fail");
278    }
279}