我想在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 的文档吗?一个工作代码示例将不胜感激。
今天我在同一问题上和我的“No-Straightforward-Documentation-Blues”中花了很多时间。我在这里找到了你几乎相同的故事,但没有任何回复(天哪......)。因此,如果您仍然面临这个问题,我希望它会有所帮助。
GCM是关于AEAD密码操作模式,这种操作模式也反映到linux/crypto中。使用 skcipher 完成的转换不适合 GCM。 AEAD 的作用不仅仅是加密/解密数据。它能够进行身份验证。因此 skcipher 无法“看到”在 GCM 模式下工作的密码,因为它不知道如何处理它们。因此,如果您尝试使用 skcipher 获取 GCM 的转换句柄,它会显示“gc 什么?!gc 谁?!!”。现在更清楚了,因为我也考虑过 skcipher 是关于元数据转换的东西,但事实并非如此。还有另一个“tfm”(记录很少,顺便说一句)可以参加我们。
Linux 加密 API 提供 struct crypto_aead。 crypto_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上运行此代码(它需要一些小的解决方法,看起来不错,但我没有对其进行很多测试)。为了简洁起见,我不会评论我所做的事情,但如果您有兴趣,请随意克隆存储库并掌握这些“黑客”。但代码只是一个示例,还需要做更多工作才能提供良好的加密,请避免复制和粘贴。
我在中断上下文中遇到了同样的问题。
也许crypto_alloc_sync_skcipher()是解决方案