加密文件系统概述
什么是加密文件系统
近年来,保护敏感数据不被泄漏成为人们关注的热点问题。入侵者除了直接盗取物理存储设备,还可以通过网络攻击来窃夺文件数据;而且,由于共享的需求,敏感数据会由多人访问,这也增大了泄漏的可能性。对数据或文件进行加密已经成为一种公认的比较成功的保护方法。事实上,人们早已开发了许多优秀的加密算法,如 DES、AES、RSA 等,并且有一些应用程序如 crypt 使用这些加密算法,用户通过这些工具手工地完成加密、解密的工作。由于这些应用程序操作麻烦、没有和整个系统紧密地结合而且容易受到攻击,因此一般用户并不愿意使用。
加密文件系统通过将加密服务集成到文件系统这一层面来解决上面的问题。加密文件的内容一般经过对称密钥算法加密后以密文的形式存放在物理介质上,即使文件丢失或被窃取,在加密密钥未泄漏的情况下,非授权用户几乎无法通过密文逆向获得文件的明文,从而保证了高安全性。与此同时,授权用户对加密文件的访问则非常方便。用户通过初始身份认证后,对加密文件的访问和普通文件没有什么区别,就好像该文件并没有被加密过,这是因为加密文件系统自动地在后台做了相关的加密和解密的工作。由于加密文件系统一般工作在内核态,普通的攻击比较难于奏效。还有一类系统级加密方案是基于块设备,与它们相比,加密文件系统具有更多的优势,例如:
支持文件粒度的加密,也就是说,用户可以选择对哪些文件或目录加密。而且,应用程序不用关心文件是否被加密,可以完全透明地访问加密文件。
无需预先保留足够的空间,用户可以随时加密或恢复文件。
对单个加密文件更改密钥和加密算法比较容易。
不同的文件可以使用不同的加密算法和密钥,增大了破解的难度。
只有加密文件才需要特殊的加密/解密处理,普通文件的存取没有额外开销。
加密文件转移到别的物理介质上时,没有额外的加密/解密开销。
加密文件系统的分类
加密文件系统基本上可以分为两大类,两类的实现方式和目标都有比较大的区别。一类是面向网络存储服务的,通常是基于 NFS 客户/服务器模型,通常称为网络加密文件系统。在网络加密文件系统中,数据以密文的方式保存在网络文件系统中,用户通过客户机服务进程与网络文件服务器交互,网络文件服务器负责将用户请求的密文传递到客户机服务进程,由客户机服务进程进行解密再交给应用程序。在这个模型中,只要求客户机操作系统是可信的,而网络服务器由于不接触明文数据,不要求其可信。另一类是本地加密文件系统,密文数据直接存放在本地物理介质上(硬盘,U 盘等),由操作系统或服务进程完成数据的读取、加密/解密工作。本地加密文件系统的目标是应对存储介质失窃的威胁,安全模型同样把加密文件系统所在的操作系统视为可信的。
本地加密文件系统又可以细分为两种,一种是在原有的普通文件系统上直接加入加密功能,如 Reiser4,但是现有的 Ext2, Ext3 等常用文件系统并不支持加密功能,因此用户不得不转换整个文件系统;另一种被称为堆叠式加密文件系统(Stackable Cryptographic File System)。这种加密文件系统可以看成一个加密/解密的转换层,而并不是一个真实的全功能文件系统。堆叠式加密文件系统没有相应的磁盘布局,也不实现数据在物理介质上存取的功能。它必须架构在别的普通文件系统之上,读加密文件时先通过下层普通文件系统将文件的密文读入内存,解密后再将明文返回到上层的用户进程;写加密文件时先将内存中的明文加密,然后传给下层普通文件系统,由它们真正地写入物理介质。堆叠式加密文件的优势在于实现相对容易(因为功能相对简单)且用户可以任意选择下层的普通文件系统来存放加密文件。本文讨论的 eCryptfs 属于本地堆叠式加密文件系统的范畴。
现有加密文件系统的不足
加密文件系统的概念由来已久,现实世界中已有诸多的实现,例如 CFS[2], TCFS, Crypt-FS, FSFS, Waycryptic 和 Windows EFS 等,但是这些加密文件系统有着内在的局限和安全问题。
CFS 和 TCFS 通过 NFS 客户/服务器模型来提供加密服务,只使用了 DES 算法来加密文件内容。这两种文件系统主要的缺点在于:难于使用、共享加密文件非常困难、用户不能选择加密算法、交换区或临时文件可能会泄漏明文以及性能较差等等。
Waycryptic 是一个堆叠式加密文件系统,通过修改 Linux 内核的虚拟文件系统层(VFS)来提供加密及解密的功能。Waycryptic 使用两种算法来加密文件:加密文件内容使用对称密钥算法,如AES,密钥随机产生;同时使用一种公开密钥算法,如RSA,加密刚才提及的密钥。这种综合两种加密算法的方式既保证了加密/解密的速度,又极大地提高了安全性。同时Waycryptic允许加密文件方便安全地在多个用户间共享;为了应对密钥的丢失,Waycryptic允许用户指定别的帐号来恢复文件。但是 Waycryptic 主要是一个研究项目,比较适合个人单机使用,无法满足企业或需要更高安全级别的用户的需求。
Windows EFS(Encrypting File System) 是对 NTFS 文件系统的功能扩充,可以方便地加密 NTFS卷上的文件或目录。Windows EFS 简单易用,功能强大,不足之处在于只能在 Windows 操作系统的 NTFS 卷上才能使用,文件内容使用的加密算法比较单一。此外,如果事先没有备份证书的话,一旦重装系统就无法再访问加密文件。
使用 eCryptfs
eCryptfs 简介
eCryptfs 是在 Linux 内核 2.6.19 版本中引入的一个功能强大的企业级加密文件系统,堆叠在其它文件系统之上(如 Ext2, Ext3, ReiserFS, JFS 等),为应用程序提供透明、动态、高效和安全的加密功能。
本质上,eCryptfs 就像是一个内核版本的 Pretty Good Privacy(PGP)[3] 服务,插在 VFS(虚拟文件系统层)和 下层物理文件系统之间,充当一个“过滤器”的角色。用户应用程序对加密文件的写请求,经系统调用层到达 VFS 层,VFS 转给 eCryptfs 文件系统组件(后面会介绍)处理,处理完毕后,再转给下层物理文件系统;读请求(包括打开文件)流程则相反。
eCryptfs 的设计受到OpenPGP 规范的影响,使用了两种方法来加密单个文件:
eCryptfs 先使用一种对称密钥加密算法来加密文件的内容,推荐使用 AES-128 算法,密钥 FEK(File Encryption Key)随机产生。有些加密文件系统为多个加密文件或整个系统使用同一个 FEK(甚至不是随机产生的),这会损害系统安全性,因为:a. 如果 FEK 泄漏,多个或所有的加密文件将被轻松解密;b. 如果部分明文泄漏,攻击者可能推测出其它加密文件的内容;c. 攻击者可能从丰富的密文中推测 FEK。
显然 FEK 不能以明文的形式存放,因此 eCryptfs 使用用户提供的口令(Passphrase)、公开密钥算法(如 RSA 算法)或 TPM(Trusted Platform Module)的公钥来加密保护刚才提及的 FEK。如果使用用户口令,则口令先被散列函数处理,然后再使用一种对称密钥算法加密 FEK。口令/公钥称为 FEFEK(File Encryption Key Encryption Key),加密后的 FEK 则称为 EFEK(Encrypted File Encryption Key)。由于允许多个授权用户访问同一个加密文件,因此 EFEK 可能有多份。
这种综合的方式既保证了加密解密文件数据的速度,又极大地提高了安全性。虽然文件名没有数据那么重要,但是入侵者可以通过文件名获得有用的信息或者确定攻击目标,因此,最新版的 eCryptfs 支持文件名的加密。
eCryptfs 使用方法
eCryptfs 需要相应的内核模块和用户态的工具同时配合使用。用户态的工具可以从 https://launchpad.net/ecryptfs 获得,使用 Debian 或 Ubuntu 系统的用户,用 apt-get 命令安装 ecryptfs-utils 包即可。如果用户自行编译内核,则需要如下选项:
清单 1. 使用 eCryptfs 所需的内核选项General setup --->
[*] Prompt for development and/or incomplete code/drivers
File systems --->
Miscellaneous filesystems --->
<M> eCrypt filesystem layer support (EXPERIMENTAL)
Security options --->
<M> Enable access key retention support
Cryptographic API --->
<M> MD5 digest algorithm
<M> AES cipher algorithms
首先需要加载 eCryptfs 内核模块,执行modprobe ecryptfs。然后将 eCryptfs 挂载到准备存放加密文件的目录,执行 sudo mount -t ecryptfs real_pathecryptfs_mounted_path
。推荐ecryptfs_mounted_path 和 真实目录 real_path 一致,这样非授权用户不能通过原路径访问加密文件。
图 1. eCryptfs 挂载结果eCryptfs默认使用 AES-128 算法以及用口令加密 FEK,更多挂载选项请参考 eCryptfs 的 man page。如果用户想使用公钥加密算法加密 FEK,需要事先用 OpenSSL 产生公钥/私钥对。用户还可以在主目录下的 .ecryptfsrc 文件中写入缺省选项,配置文件的格式请参阅 http://ecryptfs.sourceforge.net/README 。
eCryptfs 的设计
eCryptfs 设计目标
为了评估一个加密解决方案的可行性,企业往往要考虑诸多因素,例如员工的学习曲线、增量备份是否受到影响、密钥丢失的话如何防止信息泄漏或如何恢复信息、转换及使用成本、潜在的风险等等。eCryptfs 在设计之初,充分考虑企业用户的如下需求:
易于部署。eCryptfs 完全不需要对 Linux Kernel 的其它组件做任何修改,可以作为一个独立的内核模块进行部署。同时,eCryptfs 也不需要额外的前期准备和转换过程。
用户能够自由选择下层文件系统来存放加密文件。由于不修改 VFS 层,eCryptfs 通过挂载(mount)到一个已存在的目录之上的方式实现堆叠的功能。对 eCryptfs 挂载点中文件的访问首先被重定向到 eCryptfs 内核文件系统模块中。
易于使用。每次使用 eCryptfs 前,用户只需执行 mount 命令,随后 eCryptfs自动完成相关的密钥产生/读取、文件的动态加密/解密和元数据保存等工作。
充分利用已有的成熟安全技术。例如,eCryptfs 对于加密文件采用 OpenPGP 的文件格式、通过 Kernel Crypto API使用内核实现的对称密钥加密算法和散列算法等。
增强安全性。eCryptfs 的安全性最终完全依赖于解密 FEK 时所需的口令或私钥。通过利用 TPM 硬件(TPM可以产生公钥/私钥对,硬件直接执行加密/解密操作,而且私钥无法从硬件芯片中获得),eCryptfs 最大程度上保证私钥不被泄漏。
支持增量备份。eCryptfs 将元数据和密文保存在同一个文件中,从而完美的支持增量备份及文件迁移。
密钥托管。用户可以预先指定恢复帐号,万一遗失加密 FEK 的口令/私钥,也可以通过恢复帐号重新获得文件的明文;但是如果未指定恢复帐号,即使系统管理员也无法恢复文件内容。
丰富的配置策略。当应用程序在 eCryptfs 的挂载点目录中创建新文件的时候,eCryptfs 必须做出许多决定,比如新文件是否加密、使用何种算法、FEK 长度、是否使用 TPM 等等。eCryptfs 支持与 Apache 类似的策略文件,用户可以根据具体的应用程序、目录进行详细的配置。
eCryptfs 的架构
图 2. eCryptfs 加密文件系统的架构eCryptfs Layer 是一个比较完备的内核文件系统模块,但是没有实现在物理介质上存取数据的功能。在 eCryptfs Layer自己的数据结构中,加入了指向下层文件系统数据结构的指针,通过这些指针,eCryptfs就可以存取加密文件。清单 2 列出几种主要的数据结构:
清单 2. eCryptfs Layer 主要数据结构static struct file_system_type ecryptfs_fs_type = {
.owner = THIS_MODULE,
.name = "ecryptfs",
.get_sb = ecryptfs_get_sb,
.kill_sb = ecryptfs_kill_block_super,
.fs_flags = 0
};
struct ecryptfs_sb_info {
struct super_block *wsi_sb;
struct ecryptfs_mount_crypt_stat mount_crypt_stat;
};
struct ecryptfs_inode_info {
struct inode vfs_inode;
struct inode *wii_inode;
struct file *lower_file; /* wii_inode, lower_file 指向下层文件系统对应的数据结构 */
struct mutex lower_file_mutex;
struct ecryptfs_crypt_stat crypt_stat;
};
struct ecryptfs_dentry_info {
struct path lower_path; /* 下层文件系统的 dentry */
struct ecryptfs_crypt_stat *crypt_stat;
};
struct ecryptfs_file_info {
struct file *wfi_file;
struct ecryptfs_crypt_stat *crypt_stat;
};
Keystore 和用户态的 eCryptfs Daemon 进程一起负责密钥管理的工作。eCryptfs Layer 首次打开一个文件时,通过下层文件系统读取该文件的头部元数据,交与 Keystore 模块进行 EFEK(加密后的 FEK)的解密。前面已经提及,因为允许多人共享加密文件,头部元数据中可以有一串 EFEK。EFEK 和相应的公钥算法/口令的描述构成一个鉴别标识符,由 ecryptfs_auth_tok 结构表示。Keystore 依次解析加密文件的每一个 ecryptfs_auth_tok 结构:首先在所有进程的密钥链(key ring)中查看是否有相对应的私钥/口令,如果没有找到,Keystore 则发一个消息给 eCryptfs Daemon,由它提示用户输入口令或导入私钥。第一个被解析成功的 ecryptfs_auth_tok 结构用于解密 FEK。如果 EFEK 是用公钥加密算法加密的,因为目前 Kernel Crypto API 并不支持公钥加密算法,Keystore 必须把 ecryptfs_auth_tok 结构发给 eCryptfs Daemon,由它调用 Key Module API 来使用 TPM 或 OpenSSL库解密 FEK。解密后的 FEK 以及加密文件内容所用的对称密钥算法的描述信息存放在 ecryptfs_inode_info 结构的 crypt_stat 成员中。eCryptfs Layer 创建一个新文件时,Keystore 利用内核提供的随机函数创建一个 FEK;新文件关闭时,Keystore 和 eCryptfs Daemon 合作为每个授权用户创建相应 EFEK,一齐存放在加密文件的头部元数据中。
eCryptfs 采用 OpenPGP 的文件格式存放加密文件,详情参阅 RFC 2440 规范[4]。我们知道,对称密钥加密算法以块为单位进行加密/解密,例如 AES 算法中的块大小为 128 位。因此 eCryptfs 将加密文件分成多个逻辑块,称为 extent。当读入一个 extent 中的任何部分的密文时,整个 extent 被读入 Page Cache,通过 Kernel Crypto API 被解密;当 extent 中的任何部分的明文数据被写回磁盘时,需要加密并写回整个 extent(参见图 5[5])。extent 的大小是可调的,但是不会大于物理页的尺寸。当前的版本中的 extent 默认值等于物理页大小,因此在 IA32 体系结构下就是 4096 字节。加密文件的头部存放元数据,包括元数据长度、标志位以及 EFEK 链,目前元数据的最小长度为 8192 字节。
图 3. eCryptfs 加密/解密操作流程图eCryptfs 的不足
eCryptfs 不足之处在于:
写操作性能比较差。笔者用 iozone 测试了 eCryptfs 的性能,发现读操作的开销不算太大,最多降低 29%,有些小文件测试项目反而性能更好;对于写操作,所有测试项目的结果都很差,普遍下降 16 倍左右。这是因为 Page Cache 里面只存放明文,因此首次数据的读取需要解密操作,后续的读操作没有开销;而每一次写 x 字节的数据,就会涉及 ((x – 1) / extent_size + 1) * extent_size 字节的加密操作,因此开销比较大。
有两种情况可能造成信息泄漏:a. 当系统内存不足时,Page Cache 中的加密文件的明文页可能会被交换到 swap 区,目前的解决方法是用 dm-crypt 加密 swap 区。b. 应用程序也有可能在读取加密文件后,将其中某些内容以临时文件的方式写入未挂载 eCryptfs 的目录中(比如直接写到 /tmp 中),解决方案是配置应用程序或修改其实现。
eCryptfs 实现的安全性完全依赖于操作系统自身的安全。如果 Linux Kernel 被攻陷,那么黑客可以轻而易举地获得文件的明文,FEK 等重要信息。