Skip to main content

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, None)?;
23//!
24//!     // Decrypt the data
25//!     let decrypted = decrypt_data(&encrypted, Scope::User, None)?;
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/// * `entropy` - Optional additional entropy data to strengthen encryption
101///
102/// # Returns
103///
104/// Returns a `Result` containing the encrypted data as a `Vec<u8>` if successful.
105///
106/// # Errors
107///
108/// Returns an error if:
109/// - The Windows API call fails
110/// - Memory allocation fails
111/// - The current user doesn't have permission to encrypt data
112///
113/// # Examples
114///
115/// ```rust
116/// use windows_dpapi::{encrypt_data, Scope};
117///
118/// fn main() -> anyhow::Result<()> {
119///     let secret = b"my secret data";
120///     let entropy = b"my entropy";
121///     let encrypted = encrypt_data(secret, Scope::User, Some(entropy))?;
122///     Ok(())
123/// }
124/// ```
125#[cfg(windows)]
126pub fn encrypt_data(data: &[u8], scope: Scope, entropy: Option<&[u8]>) -> Result<Vec<u8>> {
127    log::debug!("Encrypting with DPAPI ({:?} scope)", scope);
128
129    let flags = match scope {
130        Scope::User => 0,      // default = user + UI prompt (but no entropy = silent)
131        Scope::Machine => 0x4, // CRYPTPROTECT_LOCAL_MACHINE
132    };
133
134    unsafe {
135        let mut input = to_blob(data);
136        let mut entropy_blob = if let Some(ent) = entropy {
137            to_blob(ent)
138        } else {
139            DATA_BLOB {
140                cbData: 0,
141                pbData: ptr::null_mut(),
142            }
143        };
144
145        let mut output = DATA_BLOB {
146            cbData: 0,
147            pbData: ptr::null_mut(),
148        };
149
150        let success = CryptProtectData(
151            &mut input,
152            ptr::null(),
153            if entropy.is_some() {
154                &mut entropy_blob
155            } else {
156                ptr::null_mut()
157            },
158            ptr::null_mut(),
159            ptr::null_mut(),
160            flags,
161            &mut output,
162        );
163
164        if success == 0 {
165            return Err(std::io::Error::last_os_error()).context("CryptProtectData failed");
166        }
167
168        let encrypted = slice::from_raw_parts(output.pbData, output.cbData as usize).to_vec();
169        winapi::um::winbase::LocalFree(output.pbData as *mut _);
170        Ok(encrypted)
171    }
172}
173
174/// Decrypts data that was encrypted using Windows DPAPI
175///
176/// # Arguments
177///
178/// * `data` - The encrypted data to decrypt
179/// * `scope` - The encryption scope that was used to encrypt the data
180/// * `entropy` - The optional entropy that was used to encrypt the data
181///
182/// # Returns
183///
184/// Returns a `Result` containing the decrypted data as a `Vec<u8>` if successful.
185///
186/// # Errors
187///
188/// Returns an error if:
189/// - The Windows API call fails
190/// - The data is corrupted
191/// - The current context does not match the encryption scope (e.g., wrong user or machine)
192/// - The current user doesn't have permission to decrypt the data
193/// - The data was encrypted on a different machine
194///
195/// # Examples
196///
197/// ```rust
198/// use windows_dpapi::{encrypt_data, decrypt_data, Scope};
199///
200/// fn main() -> anyhow::Result<()> {
201///     // First encrypt some data
202///     let secret = b"my secret data";
203///     let entropy = b"my entropy";
204///     let encrypted = encrypt_data(secret, Scope::User, Some(entropy))?;
205///     
206///     // Then decrypt it
207///     let decrypted = decrypt_data(&encrypted, Scope::User, Some(entropy))?;
208///     assert_eq!(secret, decrypted.as_slice());
209///     Ok(())
210/// }
211/// ```
212#[cfg(windows)]
213pub fn decrypt_data(data: &[u8], scope: Scope, entropy: Option<&[u8]>) -> Result<Vec<u8>> {
214    log::debug!("Decrypting with DPAPI ({:?} scope)", scope);
215
216    let flags = match scope {
217        Scope::User => 0,
218        Scope::Machine => 0x4,
219    };
220
221    unsafe {
222        let mut input = to_blob(data);
223        let mut entropy_blob = if let Some(ent) = entropy {
224            to_blob(ent)
225        } else {
226            DATA_BLOB {
227                cbData: 0,
228                pbData: ptr::null_mut(),
229            }
230        };
231        let mut output = DATA_BLOB {
232            cbData: 0,
233            pbData: ptr::null_mut(),
234        };
235
236        let success = CryptUnprotectData(
237            &mut input,
238            ptr::null_mut(),
239            if entropy.is_some() {
240                &mut entropy_blob
241            } else {
242                ptr::null_mut()
243            },
244            ptr::null_mut(),
245            ptr::null_mut(),
246            flags,
247            &mut output,
248        );
249
250        if success == 0 {
251            return Err(std::io::Error::last_os_error()).context("CryptUnprotectData failed");
252        }
253
254        let decrypted = slice::from_raw_parts(output.pbData, output.cbData as usize).to_vec();
255        winapi::um::winbase::LocalFree(output.pbData as *mut _);
256        Ok(decrypted)
257    }
258}
259
260#[cfg(test)]
261#[cfg(windows)]
262mod tests {
263    use super::*;
264
265    #[test]
266    fn round_trip_user_scope() {
267        let original = b"user secret";
268        let encrypted = encrypt_data(original, Scope::User, None).expect("User encryption failed");
269        assert_ne!(original.to_vec(), encrypted);
270        let decrypted =
271            decrypt_data(&encrypted, Scope::User, None).expect("User decryption failed");
272        assert_eq!(original.to_vec(), decrypted);
273    }
274
275    #[test]
276    fn round_trip_user_scope_entropy() {
277        let original = b"user secret";
278        let entropy = b"user entropy";
279        let encrypted =
280            encrypt_data(original, Scope::User, Some(entropy)).expect("User encryption failed");
281        assert_ne!(original.to_vec(), encrypted);
282        let decrypted =
283            decrypt_data(&encrypted, Scope::User, Some(entropy)).expect("User decryption failed");
284        assert_eq!(original.to_vec(), decrypted);
285    }
286
287    #[test]
288    fn round_trip_machine_scope() {
289        let original = b"machine secret";
290        let encrypted =
291            encrypt_data(original, Scope::Machine, None).expect("Machine encryption failed");
292        assert_ne!(original.to_vec(), encrypted);
293        let decrypted =
294            decrypt_data(&encrypted, Scope::Machine, None).expect("Machine decryption failed");
295        assert_eq!(original.to_vec(), decrypted);
296    }
297
298    #[test]
299    fn round_trip_machine_scope_entropy() {
300        let original = b"machine secret";
301        let entropy = b"user entropy";
302        let encrypted = encrypt_data(original, Scope::Machine, Some(entropy))
303            .expect("Machine encryption failed");
304        assert_ne!(original.to_vec(), encrypted);
305        let decrypted = decrypt_data(&encrypted, Scope::Machine, Some(entropy))
306            .expect("Machine decryption failed");
307        assert_eq!(original.to_vec(), decrypted);
308    }
309
310    #[test]
311    fn handles_empty_input() {
312        let data = b"";
313        let encrypted = encrypt_data(data, Scope::Machine, None).expect("Encrypt empty");
314        let decrypted = decrypt_data(&encrypted, Scope::Machine, None).expect("Decrypt empty");
315        assert_eq!(data.to_vec(), decrypted);
316    }
317
318    #[test]
319    fn handles_empty_input_entropy() {
320        let data = b"";
321        let entropy = b"random entropy";
322        let encrypted = encrypt_data(data, Scope::Machine, Some(entropy)).expect("Encrypt empty");
323        let decrypted =
324            decrypt_data(&encrypted, Scope::Machine, Some(entropy)).expect("Decrypt empty");
325        assert_eq!(data.to_vec(), decrypted);
326    }
327
328    #[test]
329    fn handles_empty_entropy() {
330        let data = b"random value";
331        let entropy = b"";
332        let encrypted = encrypt_data(data, Scope::Machine, Some(entropy)).expect("Encrypt empty");
333        let decrypted =
334            decrypt_data(&encrypted, Scope::Machine, Some(entropy)).expect("Decrypt empty");
335        assert_eq!(data.to_vec(), decrypted);
336    }
337
338    #[test]
339    fn handles_large_input() {
340        let data = vec![0xAAu8; 5 * 1024 * 1024];
341        let encrypted = encrypt_data(&data, Scope::Machine, None).expect("Encrypt large");
342        let decrypted = decrypt_data(&encrypted, Scope::Machine, None).expect("Decrypt large");
343        assert_eq!(data, decrypted);
344    }
345
346    #[test]
347    fn handles_large_input_entropy() {
348        let data = vec![0xAAu8; 5 * 1024 * 1024];
349        let entropy = b"random entropy";
350        let encrypted = encrypt_data(&data, Scope::Machine, Some(entropy)).expect("Encrypt large");
351        let decrypted =
352            decrypt_data(&encrypted, Scope::Machine, Some(entropy)).expect("Decrypt large");
353        assert_eq!(data, decrypted);
354    }
355
356    #[test]
357    fn handles_large_entropy() {
358        let data = b"Random input";
359        let entropy = &vec![0xAAu8; 5 * 1024 * 1024];
360        let encrypted = encrypt_data(data, Scope::Machine, Some(entropy)).expect("Encrypt large");
361        let decrypted =
362            decrypt_data(&encrypted, Scope::Machine, Some(entropy)).expect("Decrypt large");
363        assert_eq!(data.to_vec(), decrypted);
364    }
365
366    #[test]
367    fn fails_on_corrupted_data() {
368        let original = b"important";
369        let mut encrypted = encrypt_data(original, Scope::Machine, None).expect("Encrypt failed");
370        encrypted[0] ^= 0xFF;
371        let result = decrypt_data(&encrypted, Scope::Machine, None);
372        assert!(result.is_err(), "Corrupted data should fail");
373    }
374    #[test]
375    fn fails_on_corrupted_data_entropy() {
376        let original = b"important";
377        let entropy = b"entropy";
378        let mut encrypted =
379            encrypt_data(original, Scope::Machine, Some(entropy)).expect("Encrypt failed");
380        encrypted[0] ^= 0xFF;
381        let result = decrypt_data(&encrypted, Scope::Machine, Some(entropy));
382        assert!(result.is_err(), "Corrupted data should fail");
383    }
384
385    #[test]
386    fn fails_on_wrong_entropy() {
387        let original = b"user secret";
388        let entropy = b"user entropy";
389        let bad_entropy = b"bad entropy";
390        let encrypted =
391            encrypt_data(original, Scope::User, Some(entropy)).expect("User encryption failed");
392        assert_ne!(original.to_vec(), encrypted);
393        let result = decrypt_data(&encrypted, Scope::User, Some(bad_entropy));
394        assert!(result.is_err(), "Wrong entropy should fail");
395    }
396
397    #[test]
398    fn entropy_encrypts_differently() {
399        let original = b"user secret";
400        let entropy = b"user entropy";
401        let bad_entropy = b"bad entropy";
402        let encrypted =
403            encrypt_data(original, Scope::User, Some(entropy)).expect("User encryption failed");
404        assert_ne!(original.to_vec(), encrypted);
405        let other_encrypted =
406            encrypt_data(original, Scope::User, Some(bad_entropy)).expect("User encryption failed");
407        assert_ne!(encrypted, other_encrypted);
408    }
409}