Decrypt a DEK from a file header using the master key. Expects the encrypted DEK format: `[nonce:12B][ciphertext+tag]`.
(&self, encrypted_dek: &[u8])
| 218 | /// |
| 219 | /// Expects the encrypted DEK format: `[nonce:12B][ciphertext+tag]`. |
| 220 | pub fn decrypt_dek(&self, encrypted_dek: &[u8]) -> crate::Result<[u8; 32]> { |
| 221 | let master = self |
| 222 | .master_key |
| 223 | .as_ref() |
| 224 | .ok_or_else(|| crate::Error::Encryption { |
| 225 | detail: "master key not loaded".into(), |
| 226 | })?; |
| 227 | |
| 228 | if encrypted_dek.len() < 12 + 32 + 16 { |
| 229 | // 12B nonce + 32B DEK + 16B GCM tag |
| 230 | return Err(crate::Error::Encryption { |
| 231 | detail: format!( |
| 232 | "encrypted DEK too short: {} bytes (need at least 60)", |
| 233 | encrypted_dek.len() |
| 234 | ), |
| 235 | }); |
| 236 | } |
| 237 | |
| 238 | let (nonce_bytes, ciphertext) = encrypted_dek.split_at(12); |
| 239 | let cipher = Aes256Gcm::new_from_slice(master).map_err(|e| crate::Error::Encryption { |
| 240 | detail: format!("AES-GCM key init failed: {e}"), |
| 241 | })?; |
| 242 | let nonce_arr: [u8; 12] = nonce_bytes |
| 243 | .try_into() |
| 244 | .map_err(|_| crate::Error::Encryption { |
| 245 | detail: "nonce slice is not 12 bytes".into(), |
| 246 | })?; |
| 247 | let nonce = Nonce::from(nonce_arr); |
| 248 | |
| 249 | let plaintext = |
| 250 | cipher |
| 251 | .decrypt(&nonce, ciphertext) |
| 252 | .map_err(|_| crate::Error::Encryption { |
| 253 | detail: "DEK decryption failed: authentication tag mismatch".into(), |
| 254 | })?; |
| 255 | |
| 256 | if plaintext.len() != 32 { |
| 257 | return Err(crate::Error::Encryption { |
| 258 | detail: format!( |
| 259 | "decrypted DEK wrong size: {} bytes (expected 32)", |
| 260 | plaintext.len() |
| 261 | ), |
| 262 | }); |
| 263 | } |
| 264 | |
| 265 | let mut dek = [0u8; 32]; |
| 266 | dek.copy_from_slice(&plaintext); |
| 267 | Ok(dek) |
| 268 | } |
| 269 | |
| 270 | /// Rotate the master key: re-encrypt all DEKs with a new master key. |
| 271 | /// |