如何在linux内核中使用sync skcipher?

问题描述 投票:0回答:2

我想在linux内核中使用同步加密(因为代码是在中断上下文中运行的,不能休眠)。在 /proc/crypto 下,有几个标记为同步的候选,例如 __gcm(aes)__ctr(aes)__xts(aes)。我尝试在内核加密 API 文档中使用此代码示例,但在尝试使用 crypto_alloc_req 分配 tfm 时出现错误。代码如下:

static int test_skcipher(void)
{
    struct crypto_skcipher *tfm = NULL;
    struct skcipher_request *req = NULL;
    u8 *data = NULL;
    const size_t datasize = 512; /* data size in bytes */
    struct scatterlist sg;
    DECLARE_CRYPTO_WAIT(wait);
    u8 iv[16];  /* AES-256-XTS takes a 16-byte IV */
    u8 key[64]; /* AES-256-XTS takes a 64-byte key */
    int err;

    /*
     * Allocate a tfm (a transformation object) and set the key.
     *
     * In real-world use, a tfm and key are typically used for many
     * encryption/decryption operations.  But in this example, we'll just do a
     * single encryption operation with it (which is not very efficient).
     */

    tfm = crypto_alloc_skcipher("__xts(aes)", 0, 0);
    if (IS_ERR(tfm)) {
            pr_err("Error allocating xts(aes) handle: %ld\n", PTR_ERR(tfm));
            return PTR_ERR(tfm);
    }

    get_random_bytes(key, sizeof(key));
    err = crypto_skcipher_setkey(tfm, key, sizeof(key));
    if (err) {
            pr_err("Error setting key: %d\n", err);
            goto out;
    }

    /* Allocate a request object */
    req = skcipher_request_alloc(tfm, GFP_KERNEL);
    if (!req) {
            err = -ENOMEM;
            goto out;
    }

    /* Prepare the input data */
    data = kmalloc(datasize, GFP_KERNEL);
    if (!data) {
            err = -ENOMEM;
            goto out;
    }
    get_random_bytes(data, datasize);

    /* Initialize the IV */
    get_random_bytes(iv, sizeof(iv));

    /*
     * Encrypt the data in-place.
     *
     * For simplicity, in this example we wait for the request to complete
     * before proceeding, even if the underlying implementation is asynchronous.
     *
     * To decrypt instead of encrypt, just change crypto_skcipher_encrypt() to
     * crypto_skcipher_decrypt().
     */
    sg_init_one(&sg, data, datasize);
    skcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
                                       CRYPTO_TFM_REQ_MAY_SLEEP,
                                  crypto_req_done, &wait);
    skcipher_request_set_crypt(req, &sg, &sg, datasize, iv);
    err = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);
    if (err) {
            pr_err("Error encrypting data: %d\n", err);
            goto out;
    }

    pr_debug("Encryption was successful\n");
out:
    crypto_free_skcipher(tfm);
    skcipher_request_free(req);
    kfree(data);
    return err;
}

这段代码与文档基本相同,只是将xts(aes)替换为__xts(aes)。我已经检查过 __xts(aes) 出现在 /proc/crypto 中,但似乎内核找不到相应的算法。还有其他关于如何在 Linux 内核中使用sync skcipher 的文档吗?一个工作代码示例将不胜感激。

encryption linux-kernel aes
2个回答
1
投票

今天我在同一问题上和我的“No-Straightforward-Documentation-Blues”中花了很多时间。我在这里找到了你几乎相同的故事,但没有任何回复(天哪......)。因此,如果您仍然面临这个问题,我希望它会有所帮助。

GCM是关于AEAD密码操作模式,这种操作模式也反映到linux/crypto中。使用 skcipher 完成的转换不适合 GCMAEAD 的作用不仅仅是加密/解密数据。它能够进行身份验证。因此 skcipher 无法“看到”在 GCM 模式下工作的密码,因为它不知道如何处理它们。因此,如果您尝试使用 skcipher 获取 GCM 的转换句柄,它会显示“gc 什么?!gc 谁?!!”。现在更清楚了,因为我也考虑过 skcipher 是关于元数据转换的东西,但事实并非如此。还有另一个“tfm”(记录很少,顺便说一句)可以参加我们。

Linux 加密 API 提供 struct crypto_aeadcrypto_aead 具有 skcipher 所具有的所有相应便利功能。总的来说,几乎是“相同”。然而,有必要考虑GCM的特殊性以及它如何组织成crypto_aead的东西。不管怎样,它并不是那么神秘(如果你已经知道 AEAD/GCM 背后的概念的某些方面,并且我正在考虑你知道的)。看看:

int do_aes_gcm_min_sample(void) {
    struct crypto_aead *tfm = NULL;
    struct aead_request *req = NULL;
    u8 *buffer = NULL;
    size_t buffer_size = TEST_DATA_SIZE;
    u8 *bp = NULL, *bp_end = NULL;
    struct scatterlist sg = { 0 };
    DECLARE_CRYPTO_WAIT(wait);
    // INFO(Rafael): The majority of AES/GCM implementation uses 12 bytes iv (crypto_aead_ivsize()
    //               returned this, so for this reason I am using this "magic" value here.)
    u8 iv[12] = { 0 };
    u8 key[32] = { 0 }; // INFO(Rafael): The version of AES is defined by the size (in bytes) of the
                        //               informed key. So, here we are using AES-256.
    int err = -1;

    tfm = crypto_alloc_aead("gcm(aes)", 0, 0);

    if (IS_ERR(tfm)) {
        err = PTR_ERR(tfm);
        pr_err("AES/GCM min sample: crypto_alloc_aead() has failed: %d.\n", err);
        goto do_aes_gcm_min_sample_epilogue;
    }

    // INFO(Rafael): Telling to api how many bytes will compound our MAC (a.k.a tag etc [etc no!...]).
    err = crypto_aead_setauthsize(tfm, AES_GCM_TAG_SIZE);
    if (err != 0) {
        pr_err("AES/GCM min sample: crypto_aead_setauthsize() has failed: %d.\n", err);
        goto do_aes_gcm_min_sample_epilogue;
    }

    // WARN(Rafael): In practice it could come from a `KDF(weak_usr_password)` stuff.
    //               Never ever use directly the "key" informed by the user.
    //               It demolishes the good will of any crypto algorithm on
    //               doing good encryption. Let's value hard work of cryptographers on
    //               seeking to create state of the art ciphers ;), please!
    //               So, I am only getting the key from a csprng that is a thing
    //               near to what an alleged good KDF is able to do with a weak password
    //               or at least must do.
    get_random_bytes(key, sizeof(key));
    err = crypto_aead_setkey(tfm, key, sizeof(key));
    if (err != 0) {
        pr_err("AES/GCM min sample: crypto_aead_setkey() has failed: %d.\n", err);
        goto do_aes_gcm_min_sample_epilogue;
    }

    req = aead_request_alloc(tfm, GFP_KERNEL);
    if (req == NULL) {
        err = -ENOMEM;
        pr_err("AES/GCM min sample: aead_request_alloc() has failed.\n");
        goto do_aes_gcm_min_sample_epilogue;
    }
    req->assoclen = 0; // INFO(Rafael): No associated data, just reinforcing it.
                       //               Anyway, when you want to also authenticated
                       //               plain data (a.k.a AAD, associated data) you
                       //               must indicate the size in bytes of the
                       //               aad here and prepend your plaintext with
                       //               aad.

    get_random_bytes(iv, sizeof(iv));

    // INFO(Rafael): The AES/GCM encryption primitive will also spit at the end of
    //               the encrypted buffer the n bytes asked tags generated by GHASH.
    //               Since we are using the same buffer for output stuff, this buffer
    //               must be able to fit the input and ***also*** the result.
    buffer_size = TEST_DATA_SIZE + AES_GCM_TAG_SIZE;
    buffer = kmalloc(buffer_size, GFP_KERNEL);
    if (buffer == NULL) {
        err = -ENOMEM;
        pr_err("AES/GCM min sample: kmalloc() has failed.\n");
        goto do_aes_gcm_min_sample_epilogue;
    }

    // INFO(Rafael): Copying the one block input...
    memcpy(buffer, TEST_DATA, TEST_DATA_SIZE);
    bp = buffer;
    bp_end = bp + TEST_DATA_SIZE;

    // INFO(Rafael): Currently buffer contains only the one dummy test block. Right?...
    pr_info("Original data: ");
    while (bp != bp_end) {
        pr_info("%c\n", isprint(*bp) ? *bp : '.');
        bp++;
    }

    // INFO(Rafael): ...however our scattterlist must be initialised
    //               by indicating the whole allocated buffer segment (including room
    //               for the tag). Because it will also output data, got it?
    sg_init_one(&sg, buffer, buffer_size);
    aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
                                   CRYPTO_TFM_REQ_MAY_SLEEP, crypto_req_done, &wait);

    // INFO(Rafael): Thus, for ***encrypting*** our input buffer is
    //                      `TEST_DATA_SIZE == buffer_size - AES_GCM_TAG_SIZE`,
    //               since
    //                      `buffer_size == TEST_DATA_SIZE + AES_GCM_TAG_SIZE`.
    aead_request_set_crypt(req, &sg, &sg, buffer_size - AES_GCM_TAG_SIZE, iv);
    err = crypto_wait_req(crypto_aead_encrypt(req), &wait);
    if (err != 0) {
        pr_err("AES/GCM min sample: error when encrypting data: %d.\n", err);
        goto do_aes_gcm_min_sample_epilogue;
    }

    // INFO(Rafael): If aad would be also passed it would prepend the cryptogram.
    //               req-assoclen give you the clue of traverse or even skip it.

    pr_info("Cryptogram: ");
    // INFO(Rafael): Now buffer contains the authenticated cryptogram. I meant <cryptogram><MAC>.
    //               Here the intention is only print the cryptogram.
    bp = buffer;
    bp_end = bp + buffer_size - AES_GCM_TAG_SIZE;
    while (bp != bp_end) {
        pr_info("%c\n", isprint(*bp) ? *bp : '.');
        bp++;
    }

    pr_info("Authentication tag: ");
    // INFO(Rafael): Since bp is already pointing to the first byte of what should be the tag, let's only moving
    //               AES_GCM_TAG_SIZE bytes ahead the end marker of the output buffer.
    bp_end += AES_GCM_TAG_SIZE;
    while (bp != bp_end) {
        pr_info("%c\n", isprint(*bp) ? *bp : '.');
        bp++;
    }

    // INFO(Rafael): I hate incomplete samples, so let's decrypt, too.
    //               Decrypting with GCM involves checking if the tag informed at the end of cryptogram,
    //               is really the same of the on-the-fly calculated by GHASH. Thus, when decrypting the
    //               is necesary to indicate the cryptogram and ***also*** the tag, so here its size is
    //               expressed by buffer_size.
    aead_request_set_crypt(req, &sg, &sg, buffer_size, iv);


    // INFO(Rafael): What about testing if GCM is really detecting tampered data?
    //               Give it a try by uncomment all or even one of the following three lines.
    //key[sizeof(key) >> 1] += 1;
    //buffer[buffer_size >> 1] += 1;
    //buffer[buffer_size - AES_GCM_TAG_SIZE + 1] += 1; // INFO(Rafael): Bit flipping MAC.

    // INFO(Rafael): For the context of this sample, it would not be necessary. Anyway, we want to test
    //               corrupted key cases.
    err = crypto_aead_setkey(tfm, key, sizeof(key));
    if (err != 0) {
        pr_err("AES/GCM min sample: crypto_aead_setkey() has failed: %d.\n", err);
        goto do_aes_gcm_min_sample_epilogue;
    }

    err = crypto_wait_req(crypto_aead_decrypt(req), &wait);
    if (err != 0) {
        pr_err("AES/GCM min sample: Error when decrypting data, it seems tampered. "
               "Ask for a retransmission or verify your key.\n");
        goto do_aes_gcm_min_sample_epilogue;
    }

    // INFO(Rafael): If aad would be also passed it would prepend the plaintext.
    //               req->assoclen give you the clues of how to traverse or even
    //               skipping it. But even skipped it must be passed by the
    //               decryption routine. Because it also authenticates the whole
    //               buffer, got it?

    pr_info("Authenticated plaintext: ");
    bp = buffer;
    bp_end = bp + buffer_size - AES_GCM_TAG_SIZE; // INFO(Rafael): It will not reallocate the buffer so, let's exclude the MAC.
                                                  //               Due to it maybe should be good to ensure a buffer_size multiple of four.
                                                  //               It would keep this simpler. Anyway you can apply a more sophisticated
                                                  //               padding technique, but for this sample I think it express the main idea.
    while (bp != bp_end) {
        pr_info("%c\n", isprint(*bp) ? *bp : '.');
        bp++;
    }


do_aes_gcm_min_sample_epilogue:

    if (req != NULL) {
        aead_request_free(req);
    }

    if (tfm != NULL) {
        crypto_free_aead(tfm);
    }

    if (buffer != NULL) {
        kfree(buffer);
    }

    return err;
}

如果您想快速测试,您可以克隆此示例存储库此处

我也尝试在内核4.4.14上运行此代码(它需要一些小的解决方法,看起来不错,但我没有对其进行很多测试)。为了简洁起见,我不会评论我所做的事情,但如果您有兴趣,请随意克隆存储库并掌握这些“黑客”。但代码只是一个示例,还需要做更多工作才能提供良好的加密,请避免复制和粘贴。


0
投票

我在中断上下文中遇到了同样的问题。

也许crypto_alloc_sync_skcipher()是解决方案

© www.soinside.com 2019 - 2024. All rights reserved.