Skip to content

[BUG] Compatibility: Support execution on FIPS-enabled Linux kernels (RHEL 9 / Rocky 9) #1844

@oberstet

Description

@oberstet

Porting OSS to the "Enterprise/Government" RHEL world, a potential issue is that Python's hashlib binds to the system OpenSSL. On RHEL 9 with FIPS mode enabled, OpenSSL will ruthlessly abort any call to a non-validated algorithm (like MD5) unless explicitly told it is not being used for security purposes.

Context

Deploying this project in regulated environments (US Defense / FedRAMP) requires the underlying OS to run in FIPS 140-2/3 mode.
Specifically, when running on RHEL 9 or Rocky Linux 9 (and potentially Amazon Linux 2023) where the kernel is booted with fips=1, the system OpenSSL library disables non-approved cryptographic algorithms.

The Problem

Currently, parts of the codebase might use hashlib.md5() (and possibly other non-FIPS algorithms). In a strict FIPS environment, invoking hashlib.md5() without specific flags causes the Python runtime to crash with a ValueError.

Example Traceback:

ValueError: [digital envelope routines] unsupported

Or in older OpenSSL versions:

ValueError: error:060800A3:digital envelope routines:EVP_DigestInit_ex:disabled for fips

While MD5 is broken for cryptographic security (signatures, collision resistance), it is frequently used for non-security purposes, such as:

  • Generating deterministic UUIDs
  • Cache keys
  • File fingerprinting / ETags
  • Internal ID generation

Proposed Solution

Python 3.9+ introduced the usedforsecurity flag in hashlib. We need to audit the codebase and update all instances of non-cryptographic hashing to explicitly flag them.

Before:

m = hashlib.md5(data)
# OR
m = hashlib.new('md5', data)

After:

# explicitly signal to OpenSSL that this is not for security
m = hashlib.md5(data, usedforsecurity=False) 
# OR
m = hashlib.new('md5', data, usedforsecurity=False)

Note that in the context of US Government compliance, the flag usedforsecurity=False does not mean "This is insecure." It effectively translates to:

    bypass_fips_allowlist=True

or

    i_accept_liability_for_using_math_nist_does_not_understand=True

and thus is (in my eyes) badly named (in Python).

Certain cryptographically secure algorithms are simply unknown or too new for NIST (again, professional opinion):

  • BLAKE2 / RIPEMD-160 are not broken. They are just "foreign" or "too new" for NIST. The fact that RHEL crashes on them is a policy enforcement implementation detail, not a security vulnerability in our code.

  • NIST FIPS standards lag behind modern cryptography by 5-10 years. (e.g., They are only just now finalizing PQC, while the industry has moved on). The fact that they force a crash on BLAKE2 (which is arguably safer than the approved SHA-1) is absurd, but it is the reality of the RHEL kernel.

Affected Environments

  • Python: CPython 3.11+, PyPy 3.11+
  • OS: RHEL 9, Rocky Linux 9, AlmaLinux 9 (UBI 9 images)
  • Architecture: x86_64, aarch64

Action Items

  • Audit hashlib.md5 usage: Grep the codebase for all instances of md5.
  • Audit hashlib.sha1 usage: SHA-1 is restricted in some newer FIPS profiles for signing, though usually allowed for HMAC. Check if usedforsecurity=False is applicable here as well.
  • Audit other algorithms: Ensure no usage of ARC4, Blowfish, or RIPEMD, which may be completely removed in FIPS builds regardless of flags.
  • Apply Fix: Update calls to use usedforsecurity=False where appropriate (targeting Python 3.9+ syntax).
  • Verify PyPy Compatibility: Ensure PyPy 3.11+ respects this flag or fails gracefully.
  • CI/Test: (Optional) Add a test case that mocks a FIPS environment or verifies the flag is passed.

Reference

A quick note on PyPy

PyPy 7.3.11+ (implementing Python 3.9) does support the usedforsecurity argument in the signature to maintain API compatibility, but historically, PyPy's _hashlib implementation was sometimes inconsistent in how it passed that flag down to the C-level OpenSSL EVP_MD_CTX_set_flags.

If running PyPy on RHEL 9, we definitely want to verify this manually. If PyPy ignores the flag and passes the call to a FIPS-locked OpenSSL, it will still crash.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions