[PATCH] fscrypt: add support for ChaCha20 contents encryption
Ard Biesheuvel
ard.biesheuvel at linaro.org
Fri Dec 8 01:11:12 PST 2017
Hi Eric,
On 8 December 2017 at 01:38, Eric Biggers <ebiggers3 at gmail.com> wrote:
> From: Eric Biggers <ebiggers at google.com>
>
> fscrypt currently only supports AES encryption. However, many low-end
> mobile devices still use older CPUs such as ARMv7, which do not support
> the AES instructions (the ARMv8 Cryptography Extensions). This results
> in very poor AES performance, even if the NEON bit-sliced implementation
> is used. Roughly 20-40 MB/s is a typical number, in comparison to
> 300-800 MB/s on CPUs that support the AES instructions. Switching from
> AES-256 to AES-128 only helps by about 30%.
>
> The result is that vendors don't enable encryption on these devices,
> leaving users unprotected.
>
> A performance difference of similar magnitude can also be observed on
> x86, between CPUs with and without the AES-NI instruction set.
>
> This patch provides an alternative to AES by updating fscrypt to support
> the ChaCha20 stream cipher (RFC7539) for contents encryption. ChaCha20
> was designed to have a large security margin, to be efficient on
> general-purpose CPUs without dedicated instructions, and to be
> vectorizable. It is already supported by the Linux crypto API,
> including a vectorized implementation for ARM using NEON instructions,
> and vectorized implementations for x86 using SSSE3 or AVX2 instructions.
>
> On 32-bit ARM processors with NEON support, ChaCha20 is about 3.2 times
> faster than AES-128-XTS (chacha20-neon vs. xts-aes-neonbs). Without
> NEON support, ChaCha20 is about 1.5 times as fast (chacha20-generic vs.
> xts(aes-asm)). The improvement over AES-256-XTS is even greater.
>
> Note that stream ciphers are not an ideal choice for disk encryption,
> since each data block has to be encrypted with the same IV each time it
> is overwritten. Consequently, an adversary who observes the ciphertext
> both before and after a write can trivially recover the keystream if
> they can guess one of the plaintexts. Moreover, an adversary who can
> write to the ciphertext can flip arbitrary bits in the plaintext, merely
> by flipping the corresponding bits in the ciphertext. A block cipher
> operating in the XTS or CBC-ESSIV mode provides some protection against
> these types of attacks -- albeit not full protection, which would at
> minimum require the use an authenticated encryption mode with nonces.
>
> Unfortunately, we are unaware of any block cipher which performs as well
> as ChaCha20, has a similar or greater security margin, and has been
> subject to as much public security analysis. We do not consider Speck
> to be a viable alternative at this time.
>
> Still, a stream cipher is sufficient to protect data confidentiality in
> the event of a single point-in-time permanent offline compromise of the
> disk, which currently is the primary threat model for fscrypt. Thus,
> when the alternative is quite literally *no encryption*, we might as
> well use a stream cipher.
>
> We offer ChaCha20 rather than the reduced-round variants ChaCha8 or
> ChaCha12 because ChaCha20 has a much higher security margin, and we are
> primarily targeting CPUs where ChaCha20 is fast enough, in particular
> CPUs that have vector instructions such as NEON or SSSE3. Also, the
> crypto API currently only supports ChaCha20. Still, if ChaCha8 and/or
> ChaCha12 support were to be added to the crypto API, it would be
> straightforward to support them in fscrypt too.
>
> Currently, stream ciphers cannot be used for filenames encryption with
> fscrypt because all filenames in a directory have to be encrypted with
> the same IV. Therefore, we offer ChaCha20 for contents encryption only.
> Filenames encryption still must use AES-256-CTS-CBC. This is acceptable
> because filenames encryption is not as performance-critical as contents
> encryption.
>
> Reviewed-by: Michael Halcrow <mhalcrow at google.com>
> Signed-off-by: Eric Biggers <ebiggers at google.com>
> ---
> Documentation/filesystems/fscrypt.rst | 43 +++++++++++++++++++---
> fs/crypto/Kconfig | 1 +
> fs/crypto/crypto.c | 69 ++++++++++++++++++++++++++++-------
> fs/crypto/keyinfo.c | 2 +
> include/linux/fscrypt.h | 6 ++-
> include/uapi/linux/fs.h | 1 +
> 6 files changed, 102 insertions(+), 20 deletions(-)
>
> diff --git a/Documentation/filesystems/fscrypt.rst b/Documentation/filesystems/fscrypt.rst
> index 776ddc655f79..927d3c88816b 100644
> --- a/Documentation/filesystems/fscrypt.rst
> +++ b/Documentation/filesystems/fscrypt.rst
> @@ -184,6 +184,9 @@ replaced with HKDF or another more standard KDF in the future.
> Encryption modes and usage
> ==========================
>
> +Available modes
> +---------------
> +
> fscrypt allows one encryption mode to be specified for file contents
> and one encryption mode to be specified for filenames. Different
> directory trees are permitted to use different encryption modes.
> @@ -191,24 +194,52 @@ Currently, the following pairs of encryption modes are supported:
>
> - AES-256-XTS for contents and AES-256-CTS-CBC for filenames
> - AES-128-CBC for contents and AES-128-CTS-CBC for filenames
> +- ChaCha20 for contents and AES-256-CTS-CBC for filenames
>
> It is strongly recommended to use AES-256-XTS for contents encryption.
> AES-128-CBC was added only for low-powered embedded devices with
> crypto accelerators such as CAAM or CESA that do not support XTS.
>
> +Similarly, ChaCha20 was only added for low-end devices that have
> +neither a CPU with AES instructions, nor a hardware crypto
> +accelerator. Note that since ChaCha20 is a stream cipher, it is
> +easily broken if an attacker can view encrypted data both before and
> +after it is overwritten. Thus, even moreso than the other modes,
> +ChaCha20 can protect data confidentiality *only* in the event of a
> +single point-in-time, permanent offline compromise of the storage.
> +Also, ChaCha20 is supported only for contents encryption, not
> +filenames encryption, because all filenames in a directory have to be
> +encrypted with the same IV, which would be especially inappropriate
> +for a stream cipher.
> +
> New encryption modes can be added relatively easily, without changes
> to individual filesystems. However, authenticated encryption (AE)
> modes are not currently supported because of the difficulty of dealing
> with ciphertext expansion.
>
> +Contents encryption
> +-------------------
> +
> For file contents, each filesystem block is encrypted independently.
> Currently, only the case where the filesystem block size is equal to
> -the system's page size (usually 4096 bytes) is supported. With the
> -XTS mode of operation (recommended), the logical block number within
> -the file is used as the IV. With the CBC mode of operation (not
> -recommended), ESSIV is used; specifically, the IV for CBC is the
> -logical block number encrypted with AES-256, where the AES-256 key is
> -the SHA-256 hash of the inode's data encryption key.
> +the system's page size (usually 4096 bytes) is supported.
> +
> +With the XTS mode of operation, the logical block number within the
> +file is used as the IV.
> +
> +With the CBC mode of operation, ESSIV is used. Specifically, the IV
> +is the file logical block number encrypted with AES-256, where the
> +AES-256 key is the SHA-256 hash of the inode's data encryption key.
> +
> +With ChaCha20, the file logical block number is also used as the IV,
> +but it is formatted differently to ensure that it is copied into the
> +"nonce" portion of the ChaCha20 initial state (words 14-15) rather
> +than the "block counter" portion (words 12-13). This detail is
> +critical, since otherwise different portions of the file would be
> +encrypted with the same keystream.
> +
> +Filenames encryption
> +--------------------
>
> For filenames, the full filename is encrypted at once. Because of the
> requirements to retain support for efficient directory lookups and
> diff --git a/fs/crypto/Kconfig b/fs/crypto/Kconfig
> index 02b7d91c9231..44f052e9d842 100644
> --- a/fs/crypto/Kconfig
> +++ b/fs/crypto/Kconfig
> @@ -3,6 +3,7 @@ config FS_ENCRYPTION
> select CRYPTO
> select CRYPTO_AES
> select CRYPTO_CBC
> + select CRYPTO_CHACHA20
> select CRYPTO_ECB
> select CRYPTO_XTS
> select CRYPTO_CTS
> diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c
> index 732a786cce9d..d5c95a18db59 100644
> --- a/fs/crypto/crypto.c
> +++ b/fs/crypto/crypto.c
> @@ -126,15 +126,66 @@ struct fscrypt_ctx *fscrypt_get_ctx(const struct inode *inode, gfp_t gfp_flags)
> }
> EXPORT_SYMBOL(fscrypt_get_ctx);
>
> +struct fscrypt_iv {
> + __le64 first_half;
> + __le64 second_half;
> +};
> +
> +/*
> + * Generate the IV for encrypting/decrypting the block at the given logical
> + * block number within a file.
> + */
> +static void fscrypt_generate_iv(struct fscrypt_iv *iv, u64 lblk_num,
> + const struct fscrypt_info *ci)
> +{
> + BUILD_BUG_ON(sizeof(*iv) != FS_IV_SIZE);
> +
> + if (ci->ci_data_mode == FS_ENCRYPTION_MODE_CHACHA20) {
> + /*
> + * ChaCha20 interprets its IV as a block counter followed by a
> + * nonce. We *MUST NOT* use the file logical block number as
> + * the Chacha block counter because the ChaCha block counter
> + * counts 64-byte ChaCha blocks, which are much smaller than
> + * file blocks. If we did, then portions of the keystream would
> + * be repeated, which would be catastrophic.
> + *
OK, so you are saying that LBA n + 1 will share its keystream with LBA
n but shift by 64 blocks, right? Yeah, that's terrible.
But I severely dislike having to make the fscrypt code intimately
aware of this. Why can't we just encrypt the IV like we do for
AES-CBC? We already pull in that code for the filenames anyway.
Alternatively, we could have a IV mangling wrapper around chacha20
that moves this special handling out of fscrypt.
> + * We could initialize the block counter with the offset in the
> + * file in ChaCha blocks. But RFC7539 defines the ChaCha20
> + * block counter to be 32-bit, which is only enough for a 256GiB
> + * keystream. Confusingly, this differs from the original
> + * ChaCha paper which defines the block counter to be 64-bit.
> + *
> + * To be compatible with either convention, just put the file
> + * logical block number in the second half of the IV, so that it
> + * goes into the "nonce" portion of the ChaCha initial state
> + * (words 14-15). The ChaCha block counter then starts at 0 for
> + * each file block. In other words, we use one keystream per
> + * file block instead of one keystream per file.
> + */
> + iv->first_half = 0;
> + iv->second_half = cpu_to_le64(lblk_num);
> +
> + BUG_ON(ci->ci_essiv_tfm != NULL);
> + } else {
> + /* XTS or CBC */
> + iv->first_half = cpu_to_le64(lblk_num);
> + iv->second_half = 0;
> +
> + if (ci->ci_essiv_tfm != NULL) {
> + /* CBC */
> + BUILD_BUG_ON(AES_BLOCK_SIZE != FS_IV_SIZE);
> + crypto_cipher_encrypt_one(ci->ci_essiv_tfm,
> + (u8 *)iv, (u8 *)iv);
> + }
> + }
> +}
> +
> int fscrypt_do_page_crypto(const struct inode *inode, fscrypt_direction_t rw,
> u64 lblk_num, struct page *src_page,
> struct page *dest_page, unsigned int len,
> unsigned int offs, gfp_t gfp_flags)
> {
> - struct {
> - __le64 index;
> - u8 padding[FS_IV_SIZE - sizeof(__le64)];
> - } iv;
> + struct fscrypt_iv iv;
> struct skcipher_request *req = NULL;
> DECLARE_CRYPTO_WAIT(wait);
> struct scatterlist dst, src;
> @@ -144,15 +195,7 @@ int fscrypt_do_page_crypto(const struct inode *inode, fscrypt_direction_t rw,
>
> BUG_ON(len == 0);
>
> - BUILD_BUG_ON(sizeof(iv) != FS_IV_SIZE);
> - BUILD_BUG_ON(AES_BLOCK_SIZE != FS_IV_SIZE);
> - iv.index = cpu_to_le64(lblk_num);
> - memset(iv.padding, 0, sizeof(iv.padding));
> -
> - if (ci->ci_essiv_tfm != NULL) {
> - crypto_cipher_encrypt_one(ci->ci_essiv_tfm, (u8 *)&iv,
> - (u8 *)&iv);
> - }
> + fscrypt_generate_iv(&iv, lblk_num, ci);
>
> req = skcipher_request_alloc(tfm, gfp_flags);
> if (!req) {
> diff --git a/fs/crypto/keyinfo.c b/fs/crypto/keyinfo.c
> index 5e6e846f5a24..bae4cce2389a 100644
> --- a/fs/crypto/keyinfo.c
> +++ b/fs/crypto/keyinfo.c
> @@ -13,6 +13,7 @@
> #include <linux/scatterlist.h>
> #include <linux/ratelimit.h>
> #include <crypto/aes.h>
> +#include <crypto/chacha20.h>
> #include <crypto/sha.h>
> #include "fscrypt_private.h"
>
> @@ -134,6 +135,7 @@ static const struct {
> FS_AES_128_CBC_KEY_SIZE },
> [FS_ENCRYPTION_MODE_AES_128_CTS] = { "cts(cbc(aes))",
> FS_AES_128_CTS_KEY_SIZE },
> + [FS_ENCRYPTION_MODE_CHACHA20] = { "chacha20", CHACHA20_KEY_SIZE },
> };
>
> static int determine_cipher_type(struct fscrypt_info *ci, struct inode *inode,
> diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
> index 08b4b40c5aa8..1cfb7950f915 100644
> --- a/include/linux/fscrypt.h
> +++ b/include/linux/fscrypt.h
> @@ -100,11 +100,15 @@ static inline bool fscrypt_dummy_context_enabled(struct inode *inode)
> static inline bool fscrypt_valid_enc_modes(u32 contents_mode,
> u32 filenames_mode)
> {
> + if (contents_mode == FS_ENCRYPTION_MODE_AES_256_XTS &&
> + filenames_mode == FS_ENCRYPTION_MODE_AES_256_CTS)
> + return true;
> +
> if (contents_mode == FS_ENCRYPTION_MODE_AES_128_CBC &&
> filenames_mode == FS_ENCRYPTION_MODE_AES_128_CTS)
> return true;
>
> - if (contents_mode == FS_ENCRYPTION_MODE_AES_256_XTS &&
> + if (contents_mode == FS_ENCRYPTION_MODE_CHACHA20 &&
> filenames_mode == FS_ENCRYPTION_MODE_AES_256_CTS)
> return true;
>
> diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
> index 4199f8acbce5..5a25eb2994b1 100644
> --- a/include/uapi/linux/fs.h
> +++ b/include/uapi/linux/fs.h
> @@ -275,6 +275,7 @@ struct fsxattr {
> #define FS_ENCRYPTION_MODE_AES_256_CTS 4
> #define FS_ENCRYPTION_MODE_AES_128_CBC 5
> #define FS_ENCRYPTION_MODE_AES_128_CTS 6
> +#define FS_ENCRYPTION_MODE_CHACHA20 7
>
> struct fscrypt_policy {
> __u8 version;
> --
> 2.15.1.424.g9478a66081-goog
>
More information about the linux-mtd
mailing list