Skip to content

Replace built-in hashlib with verified implementations from HACL* #99108

Description

@msprotz

Feature or enhancement

We propose to replace the non-OpenSSL cryptographic primitives in hashlib with high-assurance, verified versions from the HACL* project.

Pitch

As evidenced by the recent SHA3 buffer overflow, cryptographic primitives are tricky to implement correctly. There might be issues with memory management, exceeding lengths, incorrect buffer management, or worse, incorrect implementations in corner cases.

The HACL* project https://github.com/hacl-star/hacl-star provides verified implementations of cryptographic primitives. These implementations are mathematically shown to be:

  • memory safe (no buffer overflows, no use-after-free)
  • functionally correct (they always compute the right result)
  • side-channel resistant (the most egregious variants of side-channels, such as memory and timing leaks, are ruled out by construction).

See https://hacl-star.github.io/Overview.html#what-is-verified-software for a longer description of how formal methods can help write high-assurance software and rule out entire classes of bugs.

The performance of HACL* is competitive with, and sometimes exceeds, that of OpenSSL. HACL* is distributed as pure C, and therefore is portable. Parts of HACL* have been adopted in Mozilla, Linux, the Tezos blockchain, and many more, thereby demonstrating that formally verified code is ready for production-time.

Previous discussion

Tagging @alex with whom I've informally discussed this.

Activity

  1. gpshead commented on Nov 7, 2022

    @gpshead
    Member

    (missing context: there was a private email thread cc'ing ~7 of us including myself, @tiran, @alex, @msprotz & others that led to this issue and PR) in response to #98517 CVE happening with our old XKCP sha3 vendored code.

    One reason I might support this work across all of our required always supported hashes is that if these perform well, I'd be happy to see the _hashlib module wrapping OpenSSL deprecated entirely. (Others probably disagree with that, so we may never get there). One less thing depending on OpenSSL sounds nice - but the OpenSSL EVP interface and these algorithms are far less likely to be where OpenSSL CVE bugs lurk (famous last words). BUT... if the vendored third party code we use as a fallback for hash algorithms can be replaced with something nearly guaranteed not to be the next CVE as the hack-star project appears to aim to be (I cannot actually be the judge of that), and be high performance... Why should we bother involving OpenSSL for these at all?

    If this sounds like the opposite of my statements over buried in the blake3 rejection thread - it's because I did not seek an alternative to the variety of vendored fallback hash algorithm implementations we carry there. So the goal in those statements was to lean towards simplicity instead of complexity. (which has already paid off via our XKCP removal in favor of tiny_sha3 by not having Python 3.11+ suffer from the sha3 XKCP CVE that the larger complex third party code in 3.6-3.10 did)

    We provide and ship native implementations for the hash algorithms anyways as we support builds without OpenSSL present, and support building on systems with libssl versions or variants that do not provide everything.

    Q: Would adopting this code increase our maintenance burden?

    A: Probably, yes. We'd presumably want to update it with more recent versions from hacl-star from time to time. Though the theory is that there should never be a security need to do so? In the past we've been asked to update vendored hash algorithm code from time to time but have hesitated due to the complexity and perceived risk, and assumed lack of reward given people who care about performance would presumably have a modern OpenSSL to get that from. Though if we did get rid of the OpenSSL wrapper code that'd also be a reduced burden.

    Q: What about performance?

    A: Good question. That needs continual measuring on our Tiered supported platforms. Don't focus solely on thruput. OpenSSL's EVP APIs are known to have silly-slow setup time. So something hashing a pile of 234 byte objects may be slow no matter how good OpenSSL's bulk data thruput is. Benchmark small message thruput as well as bulk data thruput.

    Parting thought: There is a broader desire to wean us off of our OpenSSL dependency. I realize that's a hard thing so long as the _ssl module exists, but it is a long term desire of many. This kind of change would be one necessary step, regardless of when it is taken.

  2. msprotz commented on Nov 8, 2022

    @msprotz
    ContributorAuthor

    Thanks for your comment, Greg. Some related thoughts.

    • We have support for all of the algorithms in hashlib (md5, sha1, sha2, sha3, blake2). So there is a clear pathway to consolidating those implementations together into a single vendored library that is side-channel resistant, and that has proofs of correctness.
    • Getting rid of OpenSSL is feasible, but not today (as you accurately pointed out on gh-99108: Import SHA2-224 and SHA2-256 from HACL* #99109). For now, we are proposing pure C that is easy to compile, portable, fast, and secure. We have a technology for mixing ASM and C, or even using C compiler intrinsics -- that's what we used to beat OpenSSL in a 2020 paper, on one specific algorithm. But then there are numerous drawbacks:
      • your build becomes more complicated: you need to probe the current toolchain and make sure it even knows about e.g. that fancy AVX512 intrinsic you're about to use; or that it can use inline ASM; etc.
      • your runtime becomes more complicated: once the compiled binary is copied to another computer, you need to perform run-time CPU detection, so that in the event that you did manage to compile that AVX512 version, then you do use it if the processor you're running on happens to have those fancy instructions
      • your maintenance burden becomes greater, and you need to decide how many ASM versions you want to support, for which architecture families, etc.

    None of this is infeasible -- it's just a matter of deciding what is good for a Python-in-the-future where hashlib no longer depends on OpenSSL. For instance, you might say "let's maintain a portable C version, and one single ASM version that uses SHAEXT when available (fastest)", and this is something we could make happen within HACL*.

    Just to give you a sense of what we have right now, allow me to go into greater detail:

    • MD5, SHA1: legacy broken algorithms, no particular performance optimizations have been performed
    • SHA2: on-par with OpenSSL noasm+nohw, option to use SHAEXT when available but currently not part of gh-99108: Import SHA2-224 and SHA2-256 from HACL* #99109 -- happy to do that in a followup PR
    • SHA3: a few low-hanging fruits on our side to make performance better, happy to work on that if this is what it takes for Python to integrate it
    • Blake2: regular portable C, AVX and AVX2 versions, just like the reference implementation; same as SHA3, probably needs a few performance tweaks before integrating.

    Hope this helps. As you said, this kind of change is a first step in the right direction.

  3. added a commit that references this issue on Feb 7, 2023
  4. gpshead commented on Feb 7, 2023

    @gpshead
    Member

    Alright, the first PR is in. Checkbox time:

    • 2a) The initial HACL* SHA2 sha256 and sha224 implementation PR has been merged.
    • 2b) SHA384 & SHA512 support
    • 3) SHA3 support
    • SHA1 and MD5
    • HMAC for the above
    • is Blake2b competitive? It'll need a performance comparison as we currently ship blake2 code that from upstream blake2 that includes at least amd64 sse4.1 asm optimizations.
  5. msprotz commented on Feb 7, 2023

    @msprotz
    ContributorAuthor

    Great! sha384/sha512 is a no-brainer and I'll send a followup PR very soon. We have SHA3 as well, so I can send that in, too, with the caveat that we have a few performance optimizations for sha3 in the works, so we can either wait for those to land in HACL* then submit the Python PR, or first land SHA3 in Python, then send a followup PR to refresh the HACL* code once the performance tweaks have landed. Happy to hear your thoughts on this.

  6. added a commit that references this issue on Feb 14, 2023
  7. gpshead commented on Feb 14, 2023

    @gpshead
    Member

    Given that the SHA3 implementation we currently ship is a tiny poor performing one, no need to wait. I expect we'll pull in updates to the HACL* implementations as needed in the future when there is a motivating reason.

  8. added 3 commits that reference this issue on Feb 14, 2023
  9. self-assigned this
    on Feb 16, 2023
  10. added a commit that references this issue on Feb 22, 2023
  11. added a commit that references this issue on Feb 23, 2023
  12. added a commit that references this issue on Feb 23, 2023
  13. 69 remaining items

  14. picnixz commented on Nov 1, 2024

    @picnixz
    Member

    Python's HMAC expects any algorithm that can be passed to hashlib.new (see https://docs.python.org/3/library/hmac.html#hmac.new). So:

    • md5 (not supported)
    • sha1 (ok)
    • sha224 (not supported)
    • sha{256,384,512} (ok)
    • sha3_{224,256,384,512} (not supported)

    I'm using the Hacl_HMAC_compute_* functions in https://github.com/hacl-star/hacl-star/blob/main/dist/portable-gcc-compatible/Hacl_HMAC.h. Should I use another API?


    Technically, all the names that are in hashlib.algorithms_available should be available. But it's not really pressing as gp said. If HACL does not support a specific algorithm yet, we'll just fallback to the one that we already used.

  15. gpshead commented on Nov 1, 2024

    @gpshead
    Member

    It's perfectly fine for Python to fall back to the existing implementation when accelerated HMAC does not exist. It's a nice to have for the most widely used variants.

  16. picnixz commented on Nov 1, 2024

    @picnixz
    Member

    Great! I'll probably have something by the end of next week (I don't have much time in the next few days).

  17. msprotz commented on Nov 1, 2024

    @msprotz
    ContributorAuthor

    This was fairly easy to add and @R1kM and I just have a PR (see above) that adds all of the required algorithms. We'll merge as soon as the CI comes back green and then you'll be able to pull from that directly.

    Could you confirm this completely eliminates the need for fallback implementations? Thanks!

  18. picnixz commented on Nov 1, 2024

    @picnixz
    Member

    Could you confirm this completely eliminates the need for fallback implementations

    I think it should completely eliminates those needs. I can confirm it tomorrow or when I'll submit the PR but I think this should be good. Thank you for your quick reply!

  19. picnixz commented on Nov 1, 2024

    @picnixz
    Member

    FTR: As mentioned in #72570, not supporting HMAC+SHAKE was deliberate but we'll probably need to improve the docs for that as well (since it only says "any name accepted by hashlib.new()"). (well, the current implementation works for sha3 family although keccak-based hash functions are already protected against LE-attacks).

  20. added a commit that references this issue on Dec 8, 2024
  21. added 2 commits that reference this issue on Jan 12, 2025
  22. added 3 commits that reference this issue on Apr 4, 2025
  23. picnixz commented on Apr 7, 2025

    @picnixz
    Member

    We now have HMAC and all "always supported" hash functions implemented in HACL*. Future issues with those interfaces (e.g., #131876) should be separate. Thank you everyone involved in this issue for the work!

  24. msprotz commented on Apr 7, 2025

    @msprotz
    ContributorAuthor

    Can't believe this issue finally got closed! Thanks to everyone for your help merging and integrating this, congratulations to all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

stdlibStandard Library Python modules in the Lib/ directorytopic-SSLtype-featureA feature request or enhancement

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions