1

我目前正在为需要 AES 加密的文件系统目的进行一些内核开发,并且遇到以下限制:

  • 我必须能够加密任何长度的明文
  • 填充(如果有)不明显
  • 填充字节可能导致的开销是不可接受的

在纸上,有一个简单的方法可以解决这个问题:使用 CTR 加密模式!

有了这个绝妙的想法(嗯……),我很高兴地深入研究 Linux 内核的加密 API 源来学习如何开始。

在这一点上,我注意到加密功能涉及杂项功能的使用:

  • crypt_inplace函数。其目的是处理用户想要将密文存储在与给定明文相同的内存区域中的情况。(与crypt_segment相同,但有内存限制)
  • crypt_segment函数。它是标准的加密函数。它对整个数据块(AES 为 16 个字节)进行加密。
  • crypt_final函数。当给定明文的长度L不是底层分组密码 blocksize 的倍数时,此函数对剩余字节执行加密。

因此,使用我们的L字节长明文和 AES,前L/16块根据请求使用crypt_segmentcrypt_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;
}

如您所见,计数器以块大小为基础递增。在大多数用例中,这不是问题,但请考虑这种情况:

  1. 在给定位置p写入请求(例如,这可以是硬盘驱动器上的(扇区、偏移)特定值):11 字节长的明文
  2. 在p+3处读取 8 个字节的请求:这里,我们希望从写入的密文中取回初始明文的最后 8 个字节。

第一步将通过在加密序列中调用crypt_final来完成。然后,将 11 个加密字节写入正确的位置。但是当我们想要检索这块数据的最后 8 个字节时,由于“块范围”加密,我们需要存储在pp+1p+2中的前 3 个字节来执行解密操作。

显然,从文件系统的角度来看,当请求读取时,如果内核不做出一些与硬件相关的假设,就没有办法知道这种事情。

因此,这是我的问题:有没有办法将 CTR 模式设置为始终以字节为基础执行(解密)操作,或者我应该创建自己的 CTR 模式实现来强制执行此操作?(我在 ctr 源代码中没有找到任何入口点来做这个配置操作,我可能漏掉了什么)

在此先感谢,我希望我没有用这个大帖子惊呆你!

PS:这篇文章中的代码片段可以在 Linux Kernel 源代码树的 crypto 目录下的 ctr.c 文件中找到。显示的版本来自 3.8-rc3 内核版本。


编辑 :

实际上,CTR 模式旨在处理任意长度的数据。我会回忆起 ISO/IEC 10116 规范中的描述。

假设我们的明文P被分成等长的块( P i ) 0 < i < n ( j位)。
K为加密提供的密钥,IV为计数器的初始化向量。
密文C将像明文P一样被分成块( C i ) 0 < i < n。 CTR 模式引入了一个计数器,该计数器在每个已处理的块处走动。也就是说,我们将调用用于加密(或解密)块P i(或C i
) CTR i
最后,让CTR 1 = IV
使用这些符号,计算可能如下所示:

FOR i 从 1 到 n DO

  1. Y = AES_encrypt( CTR i , K ) <- Y 是 16 字节长
  2. E = Truncate(Y, j) <- 只保留 Y 的最左边的j
  3. C i = P i XOR E
  4. CTR i+1 = ComputeNextCTR( CTR i ) <- 通常ComputeNextCTR是一个简单的增量。

完毕

在 Linux 内核中的 CTR 版本中,此行为是通过j采用底层块密码块大小(128 位,AES)的值来强制执行的,除了最后一步,如果给定的明文没有合适的长度,则会发生截断.

我的问题是:有没有办法告诉加密 API 应用我想要的j参数?对我来说,答案似乎是“不”,所以我必须“重新发明”轮子并重新实现 CTR 模式才能获得这个额外的功能。由于我可能遗漏了一些东西,我将感谢能清楚说明这一点的人。

奖励一:如果答案是“否”,我们将非常欢迎快速了解 Crypto API的algapi如何工作(我目前正在研究这个)。

再次提前感谢。

4

1 回答 1

0

在挖掘了加密资源之后,我可以说......不,我无法设置我在问题中为 CTR 模式引入的j参数。

尽管如此,我将提供有关内部加密 API 的一些细节,以使这个答案具有相关性。此处公开的细节描述了内部低级 API,以在加密 API 中添加对新算法/模式的支持。


加密对象

首先,加密 API 管理各种对象的集合。我使用对象这个词是因为即使它是 C 编程,代码显然有点面向对象。有三个主要对象:

  • 算法:struct crypto_alg
  • 模板:struct crypto_template
  • 实例:struct crypto_instancestruct crypto_spawn
  • 变换 :struct crypto_tfm和朋友 ( struct crypto_blkcipher, ...)。

第一类,就是这个内兽的里程碑。当一个人想要创建一个新算法时,他只需要创建一个struct crypto_alg,填写适当的字段并注册它。
模板有点不同,因为它们依赖于给定的算法结构。在运行时需要时传递算法对象。因此,模板是创建自定义执行模式的合适对象(就像我最初的问题中暗示的那样)。
然后,实例和生成物在内部处理这个小世界如何与外部和自身交互。
最后,转换结构是暴露于外部接口的“一次性”特定结构(这些将用于需要加密/解密某些东西的其他模块)。


创建新模板

基本上,该过程相当简单,可以从现有模板开始,例如在 ctr.c 文件下找到的模板。不过,我将展示一些细节。


加密API如何处理上述内容

这部分将介绍内部加密部分处理模板或算法创建的方式以及它向用户提供加密功能的方式(按用户,了解其他模块)。

注意:这个答案正在建设中,我会定期编辑它以添加额外的信息。

于 2013-01-28T14:59:04.127 回答