Progress in modernizing kernel cryptography

lwn.net · 2026-08-19 · by Long Nguyen ·generic
At the 2026 Linux Security Summit North America, Eric Biggers spoke about some of the problems [...]

Progress in modernizing kernel cryptography

By Joe Brockmeier July 8, 2026

---

LSS NA

At the 2026 Linux Security Summit North America, Eric Biggers spoke about some of the problems with the kernel's cryptography framework, as well as the recent progress in adding library APIs to allow developers to use cryptographic functions without using the traditional crypto API. He walked through a couple of examples to demonstrate the frailty of the original API and showed how the new library API made life easier for developers and kernel maintainers.

Biggers began by introducing himself. He is a maintainer of the crypto and cyclic redundancy check (CRC) library code in the Linux kernel, as well as of the fscryptlibrary and fs-veritysupport layer. He said that he was grouping CRCs in with crypto because "they are very similar from an implementation perspective" and the code was for the kernel itself to use—user space has its own code. "It's for all the kernel features that use these algorithms and need to execute them in kernel mode." The use cases, he said, range from storage or network encryption to generating random numbers, checking firmware integrity, protecting against denial-of-service attacks, and more.

#### Traditional API

The Linux kernel has had what Biggers called the traditional crypto API, found in the top-level cryptodirectory in the kernel tree, since 2002. It includes cryptographic algorithms as well as some non-cryptographic algorithms such as CRC. Unfortunately, he said, "this traditional crypto API is not working well. It's complex, it's hard to use, and it's often quite slow". That has always been true, he said, but it has gotten worse over time.

The traditional API does not match what most kernel developers want, he said, and has not kept up with hardware: "Specifically, it isn't well optimized for the CPU-based acceleration that modern systems use." He also said that the framework was outdated and that the API does not work well for newer algorithms and implementation strategies.

To prove the point, he said he would show a couple of examples of using a crypto algorithm in the kernel and "why the traditional crypto API isn't all that great". He began with computing a message authentication code (MAC) with a key and some data using HMAC-SHA256, "one of the most common MAC algorithms". The code is from on slides five and six in his presentation.

static int calc_hmac(const u8 *key, size_t keylen, const u8 *data, size_t datalen, u8 out[32]) { struct crypto_shash *tfm; int err; tfm = crypto_alloc_shash("hmac(sha256)", 0, 0); if (IS_ERR(tfm)) { pr_err("Failed to allocate hmac(sha256): %ld\n", PTR_ERR(tfm)); return PTR_ERR(tfm); }

To use the traditional crypto API to get the MAC value, he explained, the first thing to do is to dynamically allocate a crypto\_shash object for HMAC-SHA256 by passing the name of the algorithm as a string. Then, one has to check for errors, "since this step can and often does fail".

err = crypto_shash_setkey(tfm, key, keylen); if (!err) { SHASH_DESC_ON_STACK(desc, tfm);

desc->tfm = tfm; err = crypto_shash_digest(desc, data, datalen, out); shash_desc_zero(desc); } if (err) pr_err("Failed to calculate HMAC-SHA256 value: %d\n", err); crypto_free_shash(tfm); return err;

[ ![[Eric Biggers speaking]](https://static.lwn.net/images/2026/Eric_Biggers-sm.png "Eric Biggers speaking") ](https://lwn.net/Articles/1081206/#eric)

The next step is to set the key on the crypto\_shash object. That step can also fail, so more error-checking code is required. Then it is necessary to allocate a synchronous hash descriptor; he said that the easiest way to do that is to use the SHASH\_DESC\_ON\_STACK() macro. After that, initialize the hash descriptor by setting the pointer to the crypto\_shash. After all that, then it's time to call the function that actually computes the MAC value. Once that is done, clear sensitive data from the stack by zeroizing the hash descriptor, check for errors, and free the crypto\_shash.

Biggers said that this code "sort of works" but it is common for bugs to be found after such code is merged because allocating the crypto\_shash fails on some systems. The fix for that is to make sure that CRYPTO\_HMAC and CRYPTO\_SHA256 are enabled in the Kconfig options. "This part is often overlooked because the algorithms are loaded by name, so there's no link-time dependency on them." The code builds fine, but fails at run time and only on some systems. And if a developer wants the code to run in an early init call? "They're still out of luck; there's no way to do that with the traditional API because the crypto API has to initialize itself first."

Performance suffers as well, he said. He had benchmarked the operation on an x86\_64 system running the 6.12 kernel, which predates some recent optimizations. "It turns out only 38% of the time was spent actually doing the core SHA-256 computation. Most of the time was actually spent on the overhead of the traditional crypto API".

The better way, he said, was to just add a library function that directly implements HMAC-SHA256 without going through the traditional API. "It's much easier to use and much more efficient too." The example (from slide 11) is shown below:

hmac_sha256_usingrawkey(key, key_len, data, data_len, out);

He said that he had added this function in the 6.17 kernel, and that the new APIs were already being used in quite a few places in the kernel. The functions always succeed, he said, and callers don't need to handle errors. "They also work in all contexts in the kernel, and they just use standard link-time dependencies so that they don't fail at run time." In addition, it's also faster, particularly with shorter inputs. About 2.5 times as fast as the same operation he had mentioned previously. "I also added a FIPS-140 cryptographic algorithm self-test, so it should work fine with FIPS certifications".

This is not a new approach, he said; for example, crypto libraries were added in 2019 for the algorithms used by WireGuard. Libraries have also been the norm outside the crypto subsystem; he used memcpy()as an example. "It's just a function that does the thing you want, you don't have to dynamically allocate a memory copier object by name and then call several additional functions".

He briefly showed another example of encrypting data using ChaCha20-Poly1305, an authenticated encryption with associated data (AEAD) algorithm. It takes a key, a nonce, plain text, and associated data as input, and produces a ciphertext.

Using the traditional crypto API, the caller has to start by allocating an AEAD transformation object and setting a key: "as usual, both can fail, so you have to write error-handling code, and I hope you remember to test your error-handling code, too. I'm sure everyone does that". He noted that it was also necessary to put data in scatter-gather lists instead of standard buffers; he displayed the example code (slide 15) of setting up the scatter list:

struct scatterlist src_sg[2], dst_sg[2]; sg_init_table(src_sg, ARRAY_SIZE(src_sg)); sg_init_table(dst_sg, ARRAY_SIZE(dst_sg)); // The below assumes that none of ad, src, and dst points to vmalloc memory sg_set_buf(&src_sg[0], ad, ad_len); sg_set_buf(&src_sg[1], src, data_len); sg_set_buf(&dst_sg[0], ad, ad_len); sg_set_buf(&dst_sg[1], dst, data_len + crypto_aead_authsize(tfm));

That was the simplest case where the input and output addresses were already in the kernel's direct mapping. "However, if any of your data is in the stack or elsewhere in the vmalloc region, good luck: because in those cases you'd actually need much more complex code to set up the scatter lists correctly." Biggers said that he had yet to see a single case where someone actually submitted the correct code on the first try without needing fixes.

He added that the request may complete asynchronously, so callers needed to ensure they waited to free their data after submitting the request to complete, lest they create a use-after-free vulnerability.

The library example, once again, was a much simpler endeavor:

chacha20poly1305_encrypt(dst, src, src_len, ad, ad_len, nonce, key);

All that is required is to call a function that directly implements the algorithm the caller wanted. "As before, it's synchronous, returns void, and it works in any context." He noted that this function was added in 2019 to support WireGuard, so it was not new. His recent work has been focused on hashing and MACs instead, but this function provided a great example because it demonstrated that the traditional API suffered from a lot of complexity that was no longer needed.

#### Where we are today

As of the 7.1 kernel, the crypto and CRC libraries support a lot of algorithms, he said, and displayed a impressive list (slide 19) that are now supported. Roughly half were added in the last year and a half. But there are still some important ones that are missing, such as the AES encryption modes. "But I'm working on that."

The libraries provide a separate set of functions for each algorithm, which allows them to provide a good API for each one: "just whatever is simplest, easiest to use, and the most efficient". That means that the library works best for in-kernel users who need a single algorithm, he said, which is the most common case. If a kernel feature needs support for multiple algorithms, "you just dispatch to the different functions". That might sound inconvenient, but it is still significantly better than the traditional API. But that API remains available, and has been reimplemented using the libraries where possible.

In the last year and a half, Biggers said, he (along with co-maintainers Ard Biesheuvel and Jason Donenfeld) had adopted KUnit testing, added documentation, simplified how the architecture-optimized code is integrated, and enabled optimizations by default. He had migrated many algorithms into the libraries, and had re-implemented algorithms used by the traditional crypto API to use the libraries. "All this has resulted in lots of negative diffs, both in the crypto subsystem and in code that uses the crypto code, as well as performance improvements and even some bugs fixed too."

He displayed a list of kernel code that is now using the libraries instead of the crypto API (slide 22) that included apparmor, btrfs, dm-verity, fscrypt, bluetooth, and many others.

In addition, he said that some new features had been added to the crypto library that had not existed in the traditional API. For instance, he addedsupport for ML-DSAverification, along with a function for doing so. Support for SHAKE128 and SHAKE256 extendable-output functions (XOF) was addedin 6.19. These did not fit into the traditional API, he said, because it assumes fixed-length values. "In fact, I think the traditional crypto API actually predates cryptographers coming up with the concept of XOFs."

Biggers said that he had added support for SHA-256 interleaved hashing to accelerate dm-verity and fs-verity. "Again, that's something that didn't really fit into the traditional crypto API". He mentioned a few other improvements, such as optimizing CRC for arm64, RISC-V, and x86\_64; in one case, he said, "I actually improved performance by 12,400%". The performance increase was for CRC64 throughput with 16K messages on one of the latest AMD processors.

With regard to testing and code quality, his priority is correctness. That should be pretty self explanatory, he said. "Crypto code has to be correct, but even non-cryptographic algorithms like CRCs have to be correct too, since people rely on those for data integrity." He welcomed optimizations, but they had to be worthwhile and testable, and added that he had introduced 18 KUnit test suites for the crypto library and one for the CRC library in the 7.1 kernel.

One of the advantages of KUnit is that it's easier to run the tests now; the tests are already enabled in several continuous-integration (CI) systems, including KernelCI. "This wasn't easily possible before because the traditional crypto API uses a custom testing system." He would like to reach 100% code coverage; the library is already closer to reaching that goal than the traditional API.

There is still work to do, however; architecture-optimized code poses a problem. He said he had been testing about 50 combinations in QEMU, and was requiring QEMU support for new code as well to ensure that it was actually tested. "I know that Linux doesn't have this policy from most device drivers, but for crypto code, I do think it's the right policy."

#### When?

So, when should those working on kernel code that uses crypto or CRC algorithms use the library functions? "The answer is basically whenever you can. In most cases, I think you'll find the library functions are clearly better." There were two exceptions, Biggers said: the first was when the crypto library was missing a function that the traditional API has. He cited AES-GCM as an example. "I'm working on that."

The other exception was when a kernel feature allowed user space to specify an arbitrary algorithm from the traditional crypto API by name. "These features generally have to keep using the traditional API for backwards compatibility."

He advised that anyone adding new kernel features that use cryptography should choose a specific algorithm or, at most, support a small set of algorithms. Historically, he said, there has been a tendency to "just support every algorithm by accepting an arbitrary string from user space and passing it to the traditional crypto API". That was a problem, because it allowed use of algorithms that were insecure, obsolete, or make no sense for the feature in question. "For example, MD5. And also MD4, just in case you thought MD5 was a bit too cutting edge."

He called supporting every algorithm in every kernel feature "a huge footgun for users and a maintenance burden for kernel developers" that increases the attack surface and causes vulnerabilities. He strongly recommended being thoughtful and opinionated about choosing just one good algorithm for new features. "Keep in mind that you can always add more later if ever needed. Also, if you need help choosing algorithms, you can reach out to the Linux crypto mailing list."

#### AF\_ALG

He said that he wanted to call attention to "yet another exciting problem" with the traditional API: the algorithm address family, or AF\_ALG. It was added 16 years ago, he said, "and it was a mistake". It exposes almost all of the traditional crypto API to user-space programs, in a bug-prone way, which has resulted in "a continuous stream of security vulnerabilities". What is worse, those vulnerabilities were becoming easier to find and exploit. He did not elaborate on why that was, but the obvious answer is the use of LLM tools to scan for security vulnerabilities.

He said that the audience had probably heard of Copy Fail, but there were an additional four AF\_ALG bugs in the past year with working privilege-escalation exploits. The good news, he said, was that AF\_ALG had not been commonly used "because it does not provide much that can't be done in user space". That meant that it could be disabled by Linux distributions once a few programs were fixed to use user-space code.

However, Biggers acknowledged that breaking a user-space API in Linux "is kind of a big deal, and it needs to be a community effort". He said he was issuing a call for action to help harden and deprecate AF\_ALG; for example, people could help by migrating user-space programs away from AF\_ALG or by turning it off on their systems and helping to fix anything that breaks when doing so. On that positive note, he said a bit wryly, it was the end of the talk.

There was one question from the audience about how Biggers actually went about improving performance; did he do it on the assembly language level? He answered that most of the architecture-optimized code and CRC code was written in assembly language, but there were some cases where compiler intrinsics were used instead. With that, the session was out of time.

\[I would like to thank the Linux Foundation, LWN's travel sponsor, for supporting my trip to Minneapolis for the Linux Security Summit.\]

| Index entries for this article | | | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | Kernel | Cryptography | | Conference | Linux Security Summit North America/2026 |

---

to post comments

Unfortunately this isn't FIPS friendly :-(.

Posted Jul 9, 2026 0:14 UTC (Thu) by jra (subscriber, #55261) \[Link\] (8 responses)

I know upstream \really\ doesn't care much about FIPS, but here's an example of the changes not being at all FIPS friendly. The function: hmac\_sha256\_usingrawkey(key, key\_len, data, data\_len, out) is defined as 'void' - meaning it can't return an error.

Unfortunately in FIPS mode, if key\_len < 112 then FIPS has to have some way of signaling this is not approved. The operation can still complete, but in FIPS mode there has to be an indication this was a non-approved operation.

If hmac\_sha256\_usingrawkey() returned an error code, then callers could still ignore it, but the fact it returns an indication this is not approved in FIPS mode satisfies the certification. (NB. I never said FIPS had to make sense :-).

Without a return value, \all\ calls have to be changed to say:

if (fips\_enabled && key\_len < 112) // some FIPS indication.

\before\ calling hmac\_sha256\_usingrawkey().

This is a massive pain in the ass for vendors wanting to ship FIPS, which would have been easily avoided if Eric had allowed a status code return for these void() functions.

Unfortunately this isn't FIPS friendly :-(.

Posted Jul 9, 2026 1:30 UTC (Thu) by neggles (subscriber, #153254) \[Link\] (1 responses)

I imagine they'd take a patch adding a \`hmac\_sha256\_usingrawkey\_fips\` wrapper function or similar that can return an error

Unfortunately this isn't FIPS friendly :-(.

Posted Jul 9, 2026 2:55 UTC (Thu) by jra (subscriber, #55261) \[Link\]

\> I imagine they'd take a patch adding a \`hmac\_sha256\_usingrawkey\_fips\` wrapper function or similar that can return an error

But then you have to change the callers to call that if FIPS is enabled, which is a much larger change (and similar to the change I already mentioned). Not sure if that would be acceptable.

It's not just this function of course, there are many such that need a FIPS indicator.

Unfortunately this isn't FIPS friendly :-(.

Posted Jul 9, 2026 1:37 UTC (Thu) by ebiggers (subscriber, #130760) \[Link\] (4 responses)

As far as I can tell the library is the wrong place for the keylen < 112 check, as only the caller knows where the entropy is (in the key, or in the data as it is during entropy extraction for example) and whether the keylen >= 112 requirement applies to it (often it doesn't, for example because what it's doing might not be considered a FIPS security function in the first place). See also my response at https://lore.kernel.org/linux-crypto/20250918163347.GB142...

I'm also aware of FIPS 140-3 certified modules that implement HMAC without the key length check at all. So it's hard to see how it's a required thing in practice. Perhaps it depends on which lab is doing the certification?

I think I've actually been going pretty far out of my way to keep things FIPS 140-3 friendly, with adding self-tests according to the FIPS 140-3 Implementation Guidance, and fixing various subsystems to do the right thing when fips\_enabled=1. The HMAC thing on the other hand is just not needed, as far as I can tell, or at the very least what is actually needed is not at all what is being requested in your comment.

Anyone who's able to show otherwise is welcome to send a patch, which hasn't happened yet.

Unfortunately this isn't FIPS friendly :-(.

Posted Jul 9, 2026 2:46 UTC (Thu) by jra (subscriber, #55261) \[Link\] (3 responses)

Yeah I already followed your response (and Ted's additional comments). You're "technically correct, the best kind of correct" :-).

\> The HMAC thing on the other hand is just not needed, as far as I can tell, or at the very least what is actually needed is not at all what is being \> requested in your comment.

My lab says different I'm afraid, and I have to do what they say.

Unfortunately this isn't FIPS friendly :-(.

Posted Jul 9, 2026 13:23 UTC (Thu) by DemiMarie (subscriber, #164188) \[Link\] (2 responses)

Would it be sufficient for the library to log (once) that the nonconforming use happened?

Returning an error probably won’t fly. One of the crypto library’s advantages is that its functions never fail.

Unfortunately this isn't FIPS friendly :-(.

Posted Jul 9, 2026 14:12 UTC (Thu) by epa (subscriber, #39769) \[Link\] (1 responses)

Never fail? What if the raw\_key\_len is 2\*\*50?

I guess you mean that any failures which occur are outside the scope of the API, so they can't be reported back to the caller or recovered from.

Unfortunately this isn't FIPS friendly :-(.

Posted Jul 9, 2026 14:34 UTC (Thu) by ebiggers (subscriber, #130760) \[Link\]

HMAC supports keys up to the maximum message length of the underlying unkeyed hash function. For SHA-256 for example, that is 2\*\*61 - 1 bytes, i.e. unlimited for all practical purposes. The kernel's HMAC code doesn't enforce a limit. Of course, using such huge keys doesn't make sense, but there's no need for an artificial limit in the HMAC implementation itself.

Now, perhaps you're considering the case where the caller passes in a huge length without a valid buffer, so the code crashes. That is outside the scope of the API, is it not? It's no different from e.g. calling memcpy(dst, src, 2\\*50) without valid buffers of length 2\\*50.

Unfortunately this isn't FIPS friendly :-(.

Posted Jul 11, 2026 7:26 UTC (Sat) by riking (subscriber, #95706) \[Link\]

If the key is too short for the algorithm, that's a programming error and the kernel should WARN() and kill the calling process.

Why wasn’t the old code done this way?

Posted Jul 9, 2026 1:18 UTC (Thu) by rfunk (subscriber, #4054) \[Link\] (6 responses)

Most of this sounds great, although I tend to get suspicious about claims that code \can’t\ fail. But I’d be a lot more comfortable if there were some discussion of \why\ the old code was implemented the way it was, rather than this simpler way, and what has changed in the intervening years (besides XOFs) to make this way more of an option.

Why wasn’t the old code done this way?

Posted Jul 9, 2026 3:47 UTC (Thu) by willy (subscriber, #9762) \[Link\] (4 responses)

In 2002, CPUs did not ship with cryptography instructions. We had MMX and maybe SSE2 (Pentium 4 had it, but AMD didn't add it until 2003). PCLMULQDQ wasn't available until 2010.

But that didn't mean that crypto acceleration hardware didn't exist! There were PCI devices which could do this for you! If you think about how such a beast would work, the crypto API makes perfect sense. But that's not the world we live in now; CPUs are capable of doing synchronous crypto at much lower overhead than asking a device to do it.

So the world has changed, and there's no prospect that we'll go back to a world where some device is going to be faster at crypto than the CPU is. So we need to let go of the old API.

Why wasn’t the old code done this way?

Posted Jul 9, 2026 8:07 UTC (Thu) by amw (subscriber, #29081) \[Link\]

Thanks for the helpful explanation.

Why wasn’t the old code done this way?

Posted Jul 9, 2026 12:14 UTC (Thu) by muase (subscriber, #178466) \[Link\]

I’m not entirely sure this is universally true, because even today there are a devices that are special purpose or constrained, and would like to use dedicated hardware or special purpose instructions to run their crypto (be it for speed, battery life, or security reasons like dedicated HSMs and compliance).

Nonetheless, I agree that in 2026, this should not be the default API to use; and if we want to dispatch to such devices, this could be done in a much more modern way and probably should also be abstracted behind similar API surfaces. Honestly, from what was outlined here, even PKCS#12 feels more suitable as an abstraction layer for hardware (and that one has its own fair share of problems).

In an ideal world, the API would probably automatically dispatch to the appropriate implementation – hardware or software – without the caller even knowing; but I’m not sure how applicable that is IRL.

Why wasn’t the old code done this way?

Posted Jul 10, 2026 9:45 UTC (Fri) by linusw (subscriber, #40300) \[Link\] (1 responses)

In computer graphics there is a phenomenon called "the wheel of incarnation" which is the tendency for graphics acceleration to oscillate back and forth between specialized dedicated hardware (GPU, blitter etc) and just CPU rendering (when the CPU gets faster than accelerators).

A similar wheel of incarnation seems to be happening with cryptography. The same seems to happen with machine learning. Etc.

It'd be great if we could just engineer any heavy datapath to take this phenomenon into account.

Why wasn’t the old code done this way?

Posted Jul 11, 2026 12:46 UTC (Sat) by willy (subscriber, #9762) \[Link\]

Yes, I know about that wheel ;-) I even gave a talk on it spinning in storage at LCA 2018: <https://youtu.be/GjJUgmn-9Do> (I can't find an LWN writeup of that talk, unfortunately). I cover ST506 -> IDE -> SATA -> NVMe and then take a leap into the future by talking about pmem.

But I don't see how the wheel spins with cryptography. Yes, special purpose hardware can always outperform general purpose hardware, but there's a setup latency. And these days there's reluctance to allow data to leave the CPU unencrypted.

There's a trend to integrate more and more things onto the CPU. I can't think of too many examples where something got integrated into the CPU and then moved off again. Once there were separate FPUs, MMUs, Northbridges, etc.

The trouble with "engineer any heavy datapath to take this phenomenon into account" is that it increases complexity and reduces performance on today's machines.

Why wasn’t the old code done this way?

Posted Jul 15, 2026 6:50 UTC (Wed) by anton (subscriber, #25547) \[Link\]

I was wondering about that myself, and from the article (towards the bottom), it seems to me that the idea in the old code was that you don't want to hard-code a specific crypto algorithm for a specific purpose in the kernel, but want to leave it to user space, providing a way to upgrade the crypto independently of the in-kernel client.

So what has changed is mainly that the new API has a completely different outlook on that, letting the in-kernel client choose and hard-code the crypto algorithm (and making the in-kernel client responsible for crypto upgrades).

Yes, the overhead of the old API now consumes relatively more CPU time (because the speedup from CPU improvements has been greater for the actual cryptography part than for the overheadthan the overhead). And as a result, the speedup of the new API over the old API is bigger on recent CPUs. But that is just an additional benefit that would not be relevant without the new outlook.

Migration is not that hard

Posted Jul 9, 2026 2:22 UTC (Thu) by alip (subscriber, #170176) \[Link\]

As of version 3.57.0, Syd is going to use user-space crypto provided by libraries of the RustCrypto project, rather than relying on AF\_ALG for binary verification and transparent encryption. API was relatively similar so most migration was changing boilerplate code. This also allowed us to take another critical look at transparent encryption and how it is designed in Syd. After various improvements, end result is one-less monitor thread with emulator threads spawning aes threads on their own, allowing CLONE\_FILES to be unshared even in the presence of transparent encryption and ensuring EAGAIN return from open(2) family in case of aes thread spawn errors improving data safety. I learned a lot from this article and have a lot more ideas. I cannot express my appreciation with words. Kudos! Keep shining.

XOF

Posted Jul 9, 2026 3:22 UTC (Thu) by willy (subscriber, #9762) \[Link\]

Apparently the new currency of the crypto layer is the West African Franc.

Or maybe it's referring to <https://en.wikipedia.org/wiki/Extendable-output%5Ffunction>

lwn.net · 3 images · 61 links

Comments (0)

No comments yet