Skip to main content

fxfs_crypto/cipher/
fscrypt_ino_lblk32.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use super::{
6    BLOCK_SIZE, Cipher, MAX_FILENAME_LEN, MAX_SYMLINK_LEN, Tweak, UnwrappedKey,
7    XtsInPlaceProcessor, decrypt_filename_cts, encrypt_filename_cts,
8};
9use aes::Aes256;
10use aes::cipher::{BlockCipherDecrypt, BlockCipherEncrypt, KeyInit};
11use anyhow::{Context, Error};
12use siphasher::sip::SipHasher;
13use std::hash::Hasher;
14use storage_ptr_slice::MutPtrByteSlice;
15use zerocopy::IntoBytes;
16
17#[derive(Debug)]
18pub(crate) struct FscryptInoLblk32DirCipher {
19    cts_key: [u8; 32],
20    ino_hash_key: [u8; 16],
21    dir_hash_key: [u8; 16],
22}
23impl FscryptInoLblk32DirCipher {
24    pub fn new(key: &UnwrappedKey) -> Self {
25        Self {
26            cts_key: key[..32].try_into().unwrap(),
27            ino_hash_key: key[32..48].try_into().unwrap(),
28            dir_hash_key: key[48..64].try_into().unwrap(),
29        }
30    }
31}
32impl Cipher for FscryptInoLblk32DirCipher {
33    fn encrypt(
34        &self,
35        _ino: u64,
36        _attribute_id: u64,
37        _device_offset: u64,
38        _file_offset: u64,
39        _buffer: MutPtrByteSlice<'_>,
40    ) -> Result<(), Error> {
41        Err(zx_status::Status::NOT_SUPPORTED).context("encrypt not supported for InoLblk32Dir")
42    }
43
44    fn decrypt(
45        &self,
46        _ino: u64,
47        _attribute_id: u64,
48        _device_offset: u64,
49        _file_offset: u64,
50        _buffer: MutPtrByteSlice<'_>,
51    ) -> Result<(), Error> {
52        Err(zx_status::Status::NOT_SUPPORTED).context("decrypt not supported for InoLblk32Dir")
53    }
54
55    fn encrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
56        self.encrypt_filename_with_max_len(object_id, buffer, MAX_FILENAME_LEN)
57    }
58
59    fn decrypt_filename(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
60        self.decrypt_filename_with_max_len(object_id, buffer, MAX_FILENAME_LEN)
61    }
62
63    fn encrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
64        self.encrypt_filename_with_max_len(object_id, buffer, MAX_SYMLINK_LEN)
65    }
66
67    fn decrypt_symlink(&self, object_id: u64, buffer: &mut Vec<u8>) -> Result<(), Error> {
68        self.decrypt_filename_with_max_len(object_id, buffer, MAX_SYMLINK_LEN)
69    }
70
71    fn hash_code(&self, _raw_filename: &[u8], _filename: &str) -> Option<u32> {
72        None
73    }
74
75    fn hash_code_casefold(&self, filename: &str) -> u32 {
76        fscrypt::direntry::casefold_encrypt_hash_filename(filename.into(), &self.dir_hash_key)
77    }
78
79    fn supports_inline_encryption(&self) -> bool {
80        false
81    }
82
83    fn crypt_ctx(&self, _ino: u64, _attribute_id: u64, _file_offset: u64) -> Option<(u64, u8)> {
84        None
85    }
86}
87
88impl FscryptInoLblk32DirCipher {
89    fn iv(&self, object_id: u64) -> [u32; 4] {
90        let mut hasher = SipHasher::new_with_key(&self.ino_hash_key);
91        hasher.write(object_id.as_bytes());
92        [hasher.finish() as u32, 0, 0, 0]
93    }
94
95    fn encrypt_filename_with_max_len(
96        &self,
97        object_id: u64,
98        buffer: &mut Vec<u8>,
99        max_len: usize,
100    ) -> Result<(), Error> {
101        let iv = self.iv(object_id);
102        encrypt_filename_cts(&self.cts_key, &iv, buffer, max_len)
103    }
104
105    fn decrypt_filename_with_max_len(
106        &self,
107        object_id: u64,
108        buffer: &mut Vec<u8>,
109        max_len: usize,
110    ) -> Result<(), Error> {
111        let iv = self.iv(object_id);
112        decrypt_filename_cts(&self.cts_key, &iv, buffer, max_len)
113    }
114}
115
116#[derive(Debug)]
117pub struct FscryptInoLblk32FileCipher {
118    slot: u8,
119    ino_hash_key: [u8; 16],
120}
121
122impl FscryptInoLblk32FileCipher {
123    pub fn new(key: &UnwrappedKey) -> Self {
124        Self { slot: key.slot().unwrap(), ino_hash_key: key[..16].try_into().unwrap() }
125    }
126
127    #[inline(always)]
128    fn tweak(&self, ino: u64, block_num: u64) -> u32 {
129        let mut hasher = SipHasher::new_with_key(&self.ino_hash_key);
130        hasher.write(ino.as_bytes());
131        (hasher.finish().wrapping_add(block_num)) as u32
132    }
133}
134
135// TODO(https://fxbug.dev/436902004): Remove encrypt/decrypt support once this cipher supports
136// inline encryption.
137impl Cipher for FscryptInoLblk32FileCipher {
138    fn encrypt(
139        &self,
140        _ino: u64,
141        _attribute_id: u64,
142        _device_offset: u64,
143        _file_offset: u64,
144        _buffer: MutPtrByteSlice<'_>,
145    ) -> Result<(), Error> {
146        let e: Error = zx_status::Status::NOT_SUPPORTED.into();
147        Err(e.context("encrypt not supported for InoLblk32File"))
148    }
149
150    fn decrypt(
151        &self,
152        _ino: u64,
153        _attribute_id: u64,
154        _device_offset: u64,
155        _file_offset: u64,
156        _buffer: MutPtrByteSlice<'_>,
157    ) -> Result<(), Error> {
158        let e: Error = zx_status::Status::NOT_SUPPORTED.into();
159        Err(e.context("decrypt not supported for InoLblk32File"))
160    }
161
162    fn encrypt_filename(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
163        let e: Error = zx_status::Status::NOT_SUPPORTED.into();
164        Err(e.context("encrypt_filename not supported for InoLblk32File"))
165    }
166
167    fn decrypt_filename(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
168        let e: Error = zx_status::Status::NOT_SUPPORTED.into();
169        Err(e.context("decrypt_filename not supported for InoLblk32File"))
170    }
171
172    fn encrypt_symlink(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
173        let e: Error = zx_status::Status::NOT_SUPPORTED.into();
174        Err(e.context("encrypt_symlink not supported for InoLblk32File"))
175    }
176
177    fn decrypt_symlink(&self, _object_id: u64, _buffer: &mut Vec<u8>) -> Result<(), Error> {
178        let e: Error = zx_status::Status::NOT_SUPPORTED.into();
179        Err(e.context("decrypt_symlink not supported for InoLblk32File"))
180    }
181
182    fn hash_code(&self, _raw_filename: &[u8], _filename: &str) -> Option<u32> {
183        debug_assert!(false, "hash_code called on file cipher");
184        None
185    }
186
187    fn hash_code_casefold(&self, _filename: &str) -> u32 {
188        debug_assert!(false, "hash_code_casefold called on file cipher");
189        0
190    }
191
192    fn supports_inline_encryption(&self) -> bool {
193        true
194    }
195
196    fn crypt_ctx(&self, ino: u64, _attribute_id: u64, file_offset: u64) -> Option<(u64, u8)> {
197        assert_eq!(file_offset % BLOCK_SIZE as u64, 0);
198        let block_num = file_offset / BLOCK_SIZE as u64;
199        let tweak = self.tweak(ino, block_num);
200        Some((tweak as u64, self.slot))
201    }
202}
203
204// Software-fallback for the lblk32 file cipher.
205#[derive(Debug)]
206pub struct FscryptSoftwareInoLblk32FileCipher {
207    xts_key1: Aes256,
208    xts_key2: Aes256,
209}
210
211impl FscryptSoftwareInoLblk32FileCipher {
212    pub fn new(key: &UnwrappedKey) -> Self {
213        Self {
214            xts_key1: Aes256::new((&key[..32]).try_into().unwrap()),
215            xts_key2: Aes256::new((&key[32..64]).try_into().unwrap()),
216        }
217    }
218
219    pub fn encrypt(&self, buffer: &mut [u8], mut tweak: u128) -> Result<(), Error> {
220        fxfs_trace::duration!("encrypt", "len" => buffer.len());
221        assert_eq!(buffer.len() % BLOCK_SIZE, 0);
222
223        for block in buffer.chunks_exact_mut(BLOCK_SIZE) {
224            let mut block_tweak = tweak;
225            self.xts_key2.encrypt_block(block_tweak.as_mut_bytes().try_into().unwrap());
226            self.xts_key1.encrypt_with_backend(XtsInPlaceProcessor::new(
227                Tweak(block_tweak),
228                MutPtrByteSlice::from(&mut block[..]),
229            ));
230            tweak += 1;
231        }
232        Ok(())
233    }
234
235    pub fn decrypt(&self, buffer: &mut [u8], mut tweak: u128) -> Result<(), Error> {
236        fxfs_trace::duration!("decrypt", "len" => buffer.len());
237        assert_eq!(buffer.len() % BLOCK_SIZE, 0);
238        for block in buffer.chunks_exact_mut(BLOCK_SIZE) {
239            let mut block_tweak = tweak;
240            self.xts_key2.encrypt_block(block_tweak.as_mut_bytes().try_into().unwrap());
241            self.xts_key1.decrypt_with_backend(XtsInPlaceProcessor::new(
242                Tweak(block_tweak),
243                MutPtrByteSlice::from(&mut block[..]),
244            ));
245            tweak += 1;
246        }
247        Ok(())
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::{
254        BLOCK_SIZE, FscryptInoLblk32DirCipher, FscryptInoLblk32FileCipher,
255        FscryptSoftwareInoLblk32FileCipher, UnwrappedKey,
256    };
257    use crate::Cipher;
258    use crate::cipher::fscrypt_test_data;
259    use fscrypt::proxy_filename::ProxyFilename;
260    use std::sync::Arc;
261
262    #[test]
263    fn test_encrypt_filename() {
264        let mut unwrapped_key = UnwrappedKey::new([0; 64].to_vec());
265        unwrapped_key[0] = 0x10;
266        let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&unwrapped_key));
267        let object_id = 2;
268
269        // One block case.
270        // ```shell
271        // echo -n filename > in.txt ; truncate -s 16 in.txt
272        // openssl aes-256-cbc -e -iv 014ae2cc000000000000000000000000 -nosalt -K 1000000000000000000000000000000000000000000000000000000000000000  -in in.txt -out out.txt -nopad
273        // hexdump out.txt -e "16/1 \"%02x\" \"\n\"" -v
274        // ```
275        let mut text = b"filename".to_vec();
276        cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
277        assert_eq!(text, hex::decode("2b7c885165f090393fcbb15f5018f18a").expect("decode failed"));
278
279        // Two block case.
280        // ```shell
281        // echo -n "0123456789abcdef_filename" > in.txt ; truncate -s 16 in.txt
282        // openssl aes-256-cbc -e -iv 014ae2cc000000000000000000000000 -nosalt -K 1000000000000000000000000000000000000000000000000000000000000000  -in in.txt -out out.txt -nopad
283        // hexdump out.txt -e "16/1 \"%02x\" \"\n\"" -v
284        // 3da06c8fc2e54065f391531affeae1fb
285        // d6bad68cc11eb87719735fc50b7efbb3
286        // <Swap the last two blocks and concatenate>
287        // ``````
288        let mut text = b"0123456789abcdef_filename".to_vec();
289        cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
290        assert_eq!(
291            text,
292            hex::decode("d6bad68cc11eb87719735fc50b7efbb33da06c8fc2e54065f391531affeae1fb")
293                .expect("decode failed")
294        );
295
296        // Test a 192 byte filename -- same as in test image (known to decrypt successfully).
297        // ```shell
298        // export LONG_NAME_16=xxxxxxxxyyyyyyyy
299        // export LONG_NAME_32=${LONG_NAME_16}${LONG_NAME_16}
300        // export LONG_NAME_64=${LONG_NAME_32}${LONG_NAME_32}
301        // export LONG_NAME_128=${LONG_NAME_64}${LONG_NAME_64}
302        // export LONG_NAME_192=${LONG_NAME_128}${LONG_NAME_64}
303        // echo -n "${LONG_NAME_192}" > in.txt
304        // openssl aes-256-cbc -e -iv 014ae2cc000000000000000000000000 -nosalt -K 1000000000000000000000000000000000000000000000000000000000000000  -in in.txt -out out.txt -nopad
305        // hexdump out.txt -e "16/1 \"%02x\" \"\n\"" -v
306        // f59d083c16915d5d3479b9dbf7b7f053
307        // 1905bde71624f4ba1ab416b15831ca87
308        // c2d99e43f97bd2fc18f2ad03da252715
309        // abf9d0cd9bde4215bfeeec7d07dbcf89
310        // 0bcc4a230faaaf73cabdfc3ca8b20a06
311        // 84847f7f3991d55b6b30859dfc662c1a
312        // ef03c7d16830ef7df367a3392a82e588
313        // 629b89feffe49036e420686598545b20
314        // 119c346af4f80fdbd225a625aa0f45ce
315        // 393cfff0bd9971b6782d8768dbd13587
316        // 38e3a65f8ef14612881e6cbd38cf4bcf
317        // 08a75c38d9fb681304fdaa1e85a091ce
318        // <Swap the last two blocks and concatenate>
319        // ``````
320        let long_name_64 = b"xxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyy";
321        let mut text = vec![];
322        for _ in 0..3 {
323            text.extend_from_slice(long_name_64);
324        }
325
326        let raw = hex::decode(concat!(
327            "f59d083c16915d5d3479b9dbf7b7f0531905bde71624f4ba1ab416b15831ca87",
328            "c2d99e43f97bd2fc18f2ad03da252715abf9d0cd9bde4215bfeeec7d07dbcf89",
329            "0bcc4a230faaaf73cabdfc3ca8b20a0684847f7f3991d55b6b30859dfc662c1a",
330            "ef03c7d16830ef7df367a3392a82e588629b89feffe49036e420686598545b20",
331            "119c346af4f80fdbd225a625aa0f45ce393cfff0bd9971b6782d8768dbd13587",
332            "08a75c38d9fb681304fdaa1e85a091ce38e3a65f8ef14612881e6cbd38cf4bcf"
333        ))
334        .expect("decode failed");
335        cipher.encrypt_filename(object_id, &mut text).expect("encrypt filename failed");
336        assert_eq!(text, raw);
337    }
338
339    #[test]
340    fn test_decrypt_filename() {
341        // Should be equivalent to:
342        // ```shell
343        // openssl aes-256-cbc -d -iv 014ae2cc000000000000000000000000 -nosalt -K 1000000000000000000000000000000000000000000000000000000000000000  -in in.txt -out out.txt -nopad
344        // cat in.txt
345        // ```
346        let mut unwrapped_key = UnwrappedKey::new([0; 64].to_vec());
347        unwrapped_key[0] = 0x10;
348        let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&unwrapped_key));
349        let object_id = 2;
350
351        // One block case.
352        let mut text = hex::decode("2b7c885165f090393fcbb15f5018f18a").expect("decode failed");
353        cipher.decrypt_filename(object_id, &mut text).expect("encrypt filename failed");
354        assert_eq!(text, b"filename".to_vec());
355
356        // Two block case.
357        let mut text =
358            hex::decode("d6bad68cc11eb87719735fc50b7efbb33da06c8fc2e54065f391531affeae1fb")
359                .expect("decode failed");
360        cipher.decrypt_filename(object_id, &mut text).expect("encrypt filename failed");
361        assert_eq!(text, b"0123456789abcdef_filename".to_vec());
362    }
363
364    #[test]
365    fn test_generated_filenames() {
366        let cipher: Arc<dyn Cipher> = Arc::new(FscryptInoLblk32DirCipher::new(&UnwrappedKey::new(
367            fscrypt::to_directory_keys(
368                fscrypt_test_data::KEY,
369                fscrypt_test_data::UUID,
370                fscrypt_test_data::DIR_NONCE,
371            )
372            .to_unwrapped_key(),
373        )));
374
375        for file in fscrypt_test_data::FILES {
376            let mut buffer = file.unencrypted_name.as_bytes().to_vec();
377            cipher.encrypt_filename(fscrypt_test_data::DIR_INODE, &mut buffer).unwrap();
378            let proxy_name = ProxyFilename::new(&buffer);
379            let proxy_name_str: String = proxy_name.into();
380            assert_eq!(
381                proxy_name_str,
382                file.proxy_name,
383                "Proxy name mismatch for (len {}) {}",
384                file.unencrypted_name.len(),
385                file.unencrypted_name
386            );
387            cipher.decrypt_filename(fscrypt_test_data::DIR_INODE, &mut buffer).unwrap();
388            assert_eq!(String::from_utf8(buffer).unwrap(), file.unencrypted_name);
389        }
390    }
391
392    #[test]
393    fn test_generated_casefold_filenames() {
394        let unwrapped = UnwrappedKey::new(
395            fscrypt::to_directory_keys(
396                fscrypt_test_data::KEY,
397                fscrypt_test_data::UUID,
398                fscrypt_test_data::CASEFOLD_DIR_NONCE,
399            )
400            .to_unwrapped_key(),
401        );
402        let cipher_struct = FscryptInoLblk32DirCipher::new(&unwrapped);
403        let cipher: Arc<dyn Cipher> = Arc::new(cipher_struct);
404
405        for file in fscrypt_test_data::CASEFOLD_FILES {
406            let mut buffer = file.unencrypted_name.as_bytes().to_vec();
407            cipher.encrypt_filename(fscrypt_test_data::CASEFOLD_DIR_INODE, &mut buffer).unwrap();
408
409            let expected_proxy: ProxyFilename = file.proxy_name.try_into().unwrap();
410            let mut hash_code = cipher.hash_code_casefold(file.unencrypted_name);
411            if file.unencrypted_name.len() == 255 {
412                // There's an f2fs bug for filenames that are 255 bytes long.  The bug means that
413                // the name isn't case folded before the hash is computed.  For now, we just copy
414                // f2fs's hash code computation.
415                hash_code = expected_proxy.hash_code as u32;
416            }
417            let actual_proxy = ProxyFilename::new_with_hash_code(hash_code as u64, &buffer);
418
419            assert_eq!(
420                actual_proxy,
421                expected_proxy,
422                "Proxy name mismatch for (len {}) {}",
423                file.unencrypted_name.len(),
424                file.unencrypted_name
425            );
426            cipher.decrypt_filename(fscrypt_test_data::CASEFOLD_DIR_INODE, &mut buffer).unwrap();
427            assert_eq!(String::from_utf8(buffer).unwrap(), file.unencrypted_name);
428        }
429    }
430
431    #[test]
432    fn test_generated_casefold_symlinks() {
433        let unwrapped = UnwrappedKey::new(
434            fscrypt::to_directory_keys(
435                fscrypt_test_data::KEY,
436                fscrypt_test_data::UUID,
437                fscrypt_test_data::CASEFOLD_DIR_NONCE,
438            )
439            .to_unwrapped_key(),
440        );
441        let cipher_struct = FscryptInoLblk32DirCipher::new(&unwrapped);
442        let cipher: Arc<dyn Cipher> = Arc::new(cipher_struct);
443
444        for file in fscrypt_test_data::SYMLINKS {
445            // Verify symlink target encryption/decryption
446            // Symlink targets are encrypted using the same mechanism as filenames,
447            // using the symlink's own inode as the IV.
448            let mut target_buffer = file.target.as_bytes().to_vec();
449            cipher.encrypt_symlink(file.inode, &mut target_buffer).unwrap();
450
451            let expected_proxy: ProxyFilename =
452                file.encrypted_target_proxy_name.try_into().unwrap();
453            // Symlinks don't have a hash code, so we use 0.
454            let actual_proxy = ProxyFilename::new_with_hash_code(0, &target_buffer);
455
456            assert_eq!(
457                actual_proxy,
458                expected_proxy,
459                "Proxy name mismatch for symlink length {}",
460                file.target.len()
461            );
462
463            cipher.decrypt_symlink(file.inode, &mut target_buffer).unwrap();
464            assert_eq!(
465                String::from_utf8(target_buffer).unwrap(),
466                file.target,
467                "Decrypted target mismatch for symlink {}",
468                file.target
469            );
470        }
471    }
472
473    #[test]
474    fn test_software_file_cipher_multi_block() {
475        let key = UnwrappedKey::new((0..64).collect());
476        let cipher = FscryptSoftwareInoLblk32FileCipher::new(&key);
477        let base_tweak: u128 = 0x1234_5678;
478
479        // Create a 3-block buffer with distinct data per block.
480        let mut multi_block_buf = Vec::with_capacity(3 * BLOCK_SIZE);
481        for i in 0..3u8 {
482            multi_block_buf.extend(std::iter::repeat_n(i + 1, BLOCK_SIZE));
483        }
484        let original_plaintext = multi_block_buf.clone();
485
486        // Encrypt all 3 blocks in a single call.
487        cipher.encrypt(&mut multi_block_buf, base_tweak).expect("multi-block encrypt failed");
488
489        // Encrypting each block individually with its respective tweak (base_tweak + i) must
490        // produce identical ciphertext for every block. Note that testing encrypt followed by
491        // decrypt on the same multi-block buffer would NOT catch a bug where both encrypt and
492        // decrypt compute the wrong tweak sequence across loop iterations.
493        for i in 0..3 {
494            let mut single_block =
495                original_plaintext[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE].to_vec();
496            cipher
497                .encrypt(&mut single_block, base_tweak + i as u128)
498                .expect("single-block encrypt failed");
499            assert_eq!(
500                &multi_block_buf[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE],
501                &single_block[..],
502                "Ciphertext mismatch at block {i}"
503            );
504        }
505
506        // Verify that decrypting each block individually from the multi-block ciphertext restores
507        // the original plaintext.
508        for i in 0..3 {
509            let mut single_block = multi_block_buf[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE].to_vec();
510            cipher
511                .decrypt(&mut single_block, base_tweak + i as u128)
512                .expect("single-block decrypt failed");
513            assert_eq!(
514                &single_block[..],
515                &original_plaintext[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE],
516                "Single-block decrypt mismatch at block {i}"
517            );
518        }
519
520        // Verify multi-block decrypt restores all blocks at once.
521        cipher.decrypt(&mut multi_block_buf, base_tweak).expect("multi-block decrypt failed");
522        assert_eq!(multi_block_buf, original_plaintext);
523    }
524
525    #[test]
526    fn test_file_cipher_uses_registered_slot() {
527        let ino_hash_key = [0xab; 16];
528        let unwrapped_with_slot = UnwrappedKey::new_with_slot(ino_hash_key.to_vec(), Some(42));
529        let cipher = FscryptInoLblk32FileCipher::new(&unwrapped_with_slot);
530
531        let (dun, slot) = cipher.crypt_ctx(7, 0, 3 * BLOCK_SIZE as u64).unwrap();
532        assert_eq!(slot, 42);
533        assert_eq!(dun, cipher.tweak(7, 3).into());
534    }
535
536    #[test]
537    fn test_fscrypt_ino_lblk32_file_cipher_dun() {
538        let ino = 0x1234_5678u64;
539        let file_offset = 10 * 4096;
540
541        let cipher_lblk32 = FscryptInoLblk32FileCipher::new(&UnwrappedKey::new_with_slot(
542            vec![0x42u8; 16],
543            Some(5),
544        ));
545        let (dun32, slot32) = cipher_lblk32.crypt_ctx(ino, 0, file_offset).unwrap();
546        assert_eq!(slot32, 5);
547        assert!(dun32 <= u32::MAX as u64);
548    }
549}