When OpenSSL 3.x Refuses to Give You MD4: A Practical Guide to Providers
OpenSSL 3.x moved algorithms into pluggable providers, which is why MD4 and Blowfish suddenly vanish after an upgrade. Here's how the provider model works, how to configure it, and how to verify what's loaded.
05 Jul 2025, 11:36 UTC

You upgrade a server to a distribution shipping OpenSSL 3.x, and suddenly a legacy integration breaks: a digest that worked for years now fails, or a TLS peer using an old cipher gets rejected. Nothing is wrong with your code. OpenSSL 3.x moved its cryptographic algorithms into pluggable providers, and some algorithms you used to take for granted are no longer loaded by default. Understanding the provider model turns this from a mystery into a configuration decision.
What providers actually are
In OpenSSL 1.1.1 and earlier, algorithms were compiled into libcrypto and always available. OpenSSL 3.0 replaced this with a fetch model: when code asks for an algorithm through the EVP API (for example EVP_MD_fetch(NULL, "SHA256", NULL)), OpenSSL looks it up in whichever providers are currently loaded. No loaded provider offering that algorithm means the fetch fails cleanly.
The main providers you'll encounter:
- default — the standard set: AES, SHA-2, SHA-3, RSA, EC, and friends. Loaded automatically unless you configure otherwise.
- base — non-cryptographic support functions; typically paired with the FIPS provider.
- legacy — obsolete algorithms like MD4, RIPEMD-160, Blowfish, and DES. Not loaded unless you explicitly ask.
- fips — a separately validated module for FIPS 140 compliance. Requires its own installation and configuration.
This replaces the old ENGINE API, which is deprecated in 3.x. If you maintain hardware key integrations (HSMs, smart cards), the long-term migration path is a provider implementation, not an engine. That's a real engineering decision for long-lived systems: engines still limp along, but new development should target providers.
Activating providers through openssl.cnf
The operator-friendly part: you can change which algorithms are available to every OpenSSL-based application on a host without recompiling anything. Edit the active openssl.cnf (find the directory with openssl version -d; note the OPENSSL_CONF environment variable can override the location):
openssl_conf = openssl_init
[openssl_init]
providers = provider_sect
[provider_sect]
default = default_sect
legacy = legacy_sect
[default_sect]
activate = 1
[legacy_sect]
activate = 1Important subtlety: once you define a provider_sect, the default provider is no longer loaded implicitly. You must list and activate it yourself, as above, or you'll lose AES and SHA-2 along with gaining MD4. This is the most common misconfiguration.
Verifying what's actually loaded
Run these from a shell; no special privileges needed unless the config file is root-owned:
openssl list -providers
openssl list -digest-algorithmsWith only the default provider, the digest list won't include MD4 or RIPEMD-160. After activating legacy as above, they appear. That before/after diff is the fastest way to confirm your config took effect. If nothing changes, check that you're editing the config file OpenSSL actually reads — distribution builds ship different defaults, and OPENSSL_CONF wins over the built-in path.
For application code, the equivalent check is a small program calling EVP_MD_fetch(NULL, "MD4", NULL) and confirming it returns NULL (with an error on the OpenSSL error stack) when legacy isn't loaded. New code should always use EVP with explicit fetches; the old low-level calls like SHA1() still link and run, but they bypass the provider-aware path and are deprecated.
The trade-off: interoperability vs. attack surface
Loading the legacy provider is sometimes unavoidable — old file formats, ancient peers, regulatory archives. But it re-enables algorithms that are cryptographically broken, and it does so process-wide (or system-wide via openssl.cnf). Prefer loading it programmatically with OSSL_PROVIDER_load() only in the specific tool that needs it, rather than globally. Similarly, the FIPS provider's validation applies to a specific module version and configuration; upgrading the package or editing its config can silently change your compliance posture, so treat FIPS config changes as auditable events.
Closing
If you take one action: run openssl list -providers on your production hosts today and compare against what your applications actually need. If something needs legacy algorithms, scope that access as narrowly as possible, and if you're still building on the ENGINE API, put a provider migration on the roadmap before a future OpenSSL release forces the issue. Behavior here is version-sensitive — verify against your exact OpenSSL 3.x release before rolling config changes fleet-wide.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.