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}