【发布时间】:2013-01-22 15:48:43
【问题描述】:
我目前正在为需要 AES 加密的文件系统目的进行一些内核开发,但遇到以下限制:
- 我必须能够加密任意长度的明文
- 填充(如果有)不明显
- 填充字节可能导致的开销是不可接受的
在纸上,有一个简单的方法可以解决这个问题:使用 CTR 加密模式!
有了这个绝妙的想法(嗯...),我很高兴地深入研究 Linux 内核的加密 API 源来学习如何开始。
此时我注意到加密功能涉及到杂项功能的使用:
- crypt_inplace 函数。其目的是处理用户想要将密文存储在与给定明文相同的内存区域中的情况。 (与 crypt_segment 相同,但有内存限制)
- crypt_segment 函数。它是标准的加密函数。它对整个数据块(AES 为 16 个字节)进行加密。
- crypt_final 函数。当给定明文的长度 L 不是底层分组密码 blocksize 的倍数时,此函数对剩余字节执行加密。
因此,对于我们的 L 字节长的明文和 AES,前 L/16 个块使用 crypt_segment 或 crypt_inplace 处理 取决于请求的内容。然后使用 crypt_final 对剩余的 L mod 16 个字节进行加密。
内部crypt_segment函数定义如下:(crypt_inplace很相似)
static int crypto_ctr_crypt_segment(struct blkcipher_walk *walk,
struct crypto_cipher *tfm)
{
void (*fn)(struct crypto_tfm *, u8 *, const u8 *) =
crypto_cipher_alg(tfm)->cia_encrypt;
unsigned int bsize = crypto_cipher_blocksize(tfm);
u8 *ctrblk = walk->iv;
u8 *src = walk->src.virt.addr;
u8 *dst = walk->dst.virt.addr;
unsigned int nbytes = walk->nbytes;
do {
/* create keystream */
fn(crypto_cipher_tfm(tfm), dst, ctrblk);
crypto_xor(dst, src, bsize);
/* increment counter in counterblock */
crypto_inc(ctrblk, bsize);
src += bsize;
dst += bsize;
} while ((nbytes -= bsize) >= bsize);
return nbytes;
}
如您所见,计数器按块大小递增。在大多数用例中,这不是问题,但请考虑这种情况:
- 在给定位置写入请求p(例如,这可以是硬盘驱动器上的(扇区,偏移)特定值):11 字节长的明文
- p+3 处的 8 个字节的读取请求:这里,我们希望从写入的密文中取回初始明文的最后 8 个字节。
第一步将通过在加密序列中对 crypt_final 的一次调用来完成。然后,将 11 个加密字节写入正确的位置。但是当我们要检索这块数据的最后 8 个字节时,由于“块范围”加密,我们需要前 3 个字节,存储在 p, p+1 和 p+2 执行解密操作。
显然,从文件系统的角度来看,当请求读取时,如果内核不做出一些与硬件相关的假设,没有办法知道这种事情。
因此,这是我的问题:有没有办法设置 CTR 模式以始终以字节为基础执行(解密)操作,或者我应该创建自己的 CTR 模式实现以强制执行? (我在ctr源码中没有找到做这个配置操作的入口点,可能漏掉了什么)
先谢谢了,希望我的大帖子没有让你震惊!
PS:本文中的代码 sn-p 可以在 Linux 内核源代码树的 crypto 目录下的 ctr.c 文件中找到。显示的版本来自 3.8-rc3 Kernel 版本。
编辑:
实际上,CTR 模式旨在处理任意长度的数据。我会记得 ISO/IEC 10116 规范中的描述。
假设我们的明文P被分成(Pi)0的块等长(j 位)。
令 K 为加密提供的密钥,IV 为计数器的初始化向量。
密文 C 会像明文 一样被分成块 ( Ci )0 >P。
CTR 模式引入了一个计数器,该计数器在每个已处理的块处走动。也就是说,我们将调用用于加密(或解密)块 Pi 的计数器块(分别为 Ci) 点击率i
最后,让CTR1 = IV
使用这些符号,计算可能如下所示:
FOR i 从 1 到 n DO
- Y = AES_encrypt(CTRi, K)
- E = Truncate(Y, j) j 最左边的位
- Ci = Pi XOR E
- CTRi+1 = ComputeNextCTR(CTRi) ComputeNextCTR 是一个简单的增量。
完成
在 Linux 内核中的 CTR 版本中,此行为是通过 j 采用底层块密码块大小(AES 为 128 位)的值来强制执行的,除了最后一步截断如果给定的明文没有合适的长度,则会发生。
我的问题是:有没有办法告诉加密 API 应用我想要的 j 参数? 对我来说,答案似乎是“不”,所以我必须“重新发明”轮子并重新实现 CTR 模式才能获得这个额外的功能。由于我可能遗漏了什么,我会很感激能清楚说明这一点的人。
奖励一:如果答案是“否”,我们将非常欢迎您快速了解 Crypto API 的 algapi 的工作原理(我目前正在深入研究)。
再次提前致谢。
【问题讨论】:
-
这些函数是静态的。它们是实现细节。您不应该(也不能!)直接使用它们。你有
crypto_cipher_setkey、crypto_cipher_set_iv、crypto_cipher_encrypt和 crypto_cipher_encrypt`。就是这样。我对 CTR 不熟悉,但我认为这里的 IV 只是计数器。所以(我认为)要加密/解密从文件中随机位置获取的任意长度的块,您(1)将块 从两端填充到块边界,(2)执行 en /decryption,以及 (3) 删除填充。 -
是的,它们是静态的,但没关系,当在 CTR 密码实例上调用包装器“crypto_cipher_encrypt”时会调用它们。我只是暴露了代码以表明我挖掘了看看发生了什么!此外,可能需要在非对齐区域上进行写入,因此,加密层必须能够知道如何根据请求的区域(在写入/读取情况下)计算良好的 IV。我希望我足够清楚,如果我不明白,请告诉我。
-
如果你可以加密块对齐的区域,你也可以做未对齐的。只需将它们填充对齐,加密,然后丢弃对齐字节。为对齐的块计算 IV。这是我的理解。
-
问题是在加密后丢弃填充字节并写下剩余字节时,我们可能会遇到以下情况: - 在“丢弃”填充字节后写入 8 个字节 - 16 个字节“就在”前一个序列的最后一个字节之后 如果稍后完成整个读取请求,要求检索 24 个字节,解密过程会将前 16 个字节作为同一块的一部分(因此 8 个字节从“不完整”块和完整块的前 8 个字节)。我猜这会导致明文错误
-
我的上帝.. 不要使用 CTR 进行文件系统加密。使用宽块模式设计进行全盘加密,请参阅 XTS 模式(谷歌磁盘加密理论)。这个问题已经解决,并且发现 CTR 不足以进行磁盘加密,这就是为什么之前已经发明了轮子的原因。请不要重新发明它(除非这是为了学习,当然,如果是这样,请忽略我所说的一切)。
标签: c linux linux-kernel cryptography