f2fs_reader/
crypto.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.
4use crate::xattr;
5use aes::cipher::inout::InOutBuf;
6use aes::cipher::{BlockDecrypt, BlockDecryptMut, BlockEncrypt, KeyInit, KeyIvInit};
7use aes::Block;
8use anyhow::Error;
9use fscrypt::{hkdf, Context};
10use siphasher::sip::SipHasher;
11use std::hash::Hasher;
12use zerocopy::{FromBytes, IntoBytes};
13
14const NAME_XATTR_CRYPTO_CONTEXT: &[u8] = b"c";
15
16pub fn try_read_context_from_xattr(
17    xattr: &Vec<xattr::XattrEntry>,
18) -> Result<Option<Context>, Error> {
19    let raw_context = if let Some(content) = xattr.iter().find(|entry| {
20        entry.index == xattr::Index::Encryption && *entry.name == *NAME_XATTR_CRYPTO_CONTEXT
21    }) {
22        &content.value
23    } else {
24        // No crypto context present.
25        return Ok(None);
26    };
27    Context::try_from_bytes(raw_context.as_ref())
28}
29
30pub struct PerFileDecryptor {
31    // For file contents (XTS)
32    xts_key1: aes::Aes256,
33    xts_key2: aes::Aes256,
34    // For file names (CTS)
35    cts_key: [u8; 32],
36    // For seeding SipHasher for directory entry hashes
37    dirhash_key: [u8; 16],
38
39    // For INO_LBLK_32 policy
40    ino_hash_key: Option<[u8; 16]>,
41}
42
43impl PerFileDecryptor {
44    pub(super) fn new(main_key: &[u8; 64], context: Context, uuid: &[u8; 16]) -> Self {
45        if context.flags & fscrypt::POLICY_FLAGS_INO_LBLK_32 != 0 {
46            // To support eMMC inline crypto hardware (and hardware wrapped keys), the lblk_32
47            // policy creates a shared key from the main key and filesystem UUID. In this mode the
48            // inode is hashed and mixed into the tweak. The nonce is still used for dirhash.
49            let mut hdkf_info = [0; 17];
50            hdkf_info[1..17].copy_from_slice(uuid);
51            hdkf_info[0] = fscrypt::ENCRYPTION_MODE_AES_256_XTS;
52            let xts_key = hkdf::fscrypt_hkdf::<64>(
53                main_key,
54                &hdkf_info,
55                hkdf::HKDF_CONTEXT_IV_INO_LBLK_32_KEY,
56            );
57            hdkf_info[0] = fscrypt::ENCRYPTION_MODE_AES_256_CTS;
58            let cts_key = hkdf::fscrypt_hkdf::<32>(
59                main_key,
60                &hdkf_info,
61                hkdf::HKDF_CONTEXT_IV_INO_LBLK_32_KEY,
62            );
63            let dirhash_key =
64                hkdf::fscrypt_hkdf::<16>(main_key, &context.nonce, hkdf::HKDF_CONTEXT_DIRHASH_KEY);
65            let ino_hash_key =
66                Some(hkdf::fscrypt_hkdf::<16>(main_key, &[], hkdf::HKDF_CONTEXT_INODE_HASH_KEY));
67            Self {
68                xts_key1: aes::Aes256::new((&xts_key[..32]).into()),
69                xts_key2: aes::Aes256::new((&xts_key[32..]).into()),
70                cts_key,
71                dirhash_key,
72                ino_hash_key,
73            }
74        } else {
75            // The default policy creates a unique key for each file using the main key and a
76            // 16-byte nonce.
77            let key = hkdf::fscrypt_hkdf::<64>(
78                main_key,
79                &context.nonce,
80                hkdf::HKDF_CONTEXT_PER_FILE_ENC_KEY,
81            );
82            let dirhash_key =
83                hkdf::fscrypt_hkdf::<16>(main_key, &context.nonce, hkdf::HKDF_CONTEXT_DIRHASH_KEY);
84            let cts_key: [u8; 32] = key[..32].try_into().unwrap();
85            Self {
86                xts_key1: aes::Aes256::new((&key[..32]).into()),
87                xts_key2: aes::Aes256::new((&key[32..]).into()),
88                cts_key,
89                dirhash_key,
90                ino_hash_key: None,
91            }
92        }
93    }
94
95    pub fn decrypt_data(&self, ino: u32, block_num: u32, buffer: &mut [u8]) {
96        assert_eq!((buffer.as_ptr() as usize) % 16, 0, "Require 16-byte aligned buffers");
97        assert_eq!(buffer.len() % 16, 0, "Require buffters be multiple of 16-bytes");
98        // TODO(b/406351838): Migrate to share implementation with fxfs-crypto?
99        let key1 = self.xts_key1.clone();
100        let key2 = self.xts_key2.clone();
101        let mut tweak: u128 = if let Some(ino_hash_key) = self.ino_hash_key {
102            let mut hasher = SipHasher::new_with_key(&ino_hash_key);
103            let ino64 = ino as u64;
104            hasher.write(ino64.as_bytes());
105            hasher.finish() as u32 + block_num
106        } else {
107            block_num
108        } as u128;
109        key2.encrypt_block(tweak.as_mut_bytes().into());
110        for chunk in buffer.chunks_exact_mut(16) {
111            *u128::mut_from_bytes(chunk).unwrap() ^= tweak;
112            key1.decrypt_block(chunk.into());
113            *u128::mut_from_bytes(chunk).unwrap() ^= tweak;
114            tweak = (tweak << 1) ^ (if tweak >> 127 != 0 { 0x87 } else { 0 });
115        }
116    }
117
118    /// Decrypt a filename (from a dentry or symlink).
119    pub fn decrypt_filename_data(&self, ino: u32, data: &mut [u8]) {
120        let mut iv = [0u8; 16];
121        if let Some(ino_hash_key) = self.ino_hash_key {
122            let mut hasher = SipHasher::new_with_key(&ino_hash_key);
123            hasher.write((ino as u64).as_bytes());
124            iv[..4].copy_from_slice(&hasher.finish().as_bytes()[..4]);
125        }
126        // AES-256-CTS is used for filename encryption and symlinks but because we
127        // require POLICY_FLAGS_PAD_16, we never actually steal any ciphertext and
128        // so CTS is equivalent to swapping the last two blocks and using CBC instead.
129        let mut cbc = cbc::Decryptor::<aes::Aes256>::new((&self.cts_key).into(), (&iv).into());
130        let inout: InOutBuf<'_, '_, u8> = data.into();
131        let (mut blocks, tail): (InOutBuf<'_, '_, Block>, _) = inout.into_chunks();
132        debug_assert_eq!(tail.len(), 0);
133        let mut chunks = blocks.get_out();
134        if chunks.len() >= 2 {
135            chunks.swap(chunks.len() - 1, chunks.len() - 2);
136        }
137        cbc.decrypt_blocks_mut(&mut chunks);
138    }
139
140    pub fn dirhash_key(&self) -> &[u8; 16] {
141        &self.dirhash_key
142    }
143}