OpenSSL 3.4: Breaking Changes, Migration Strategies, and What Developers Need to Know
OpenSSL 3.4 introduces significant breaking changes affecting cryptographic defaults and legacy algorithm support. Learn what's changing, how to migrate, and how to audit your dependencies.
Understanding OpenSSL 3.4’s Impact
OpenSSL 3.4 represents a major step forward in cryptographic security, but it comes with a cost: breaking changes that affect applications relying on older, weaker algorithms and configurations. If your application hasn’t been updated since OpenSSL 1.1.1 reached end-of-life (September 2023), you’re likely to encounter compatibility issues when upgrading.
This post will walk you through the key changes in OpenSSL 3.4, show you how to identify affected code, and provide practical migration strategies.
What’s Changing in OpenSSL 3.4
1. Legacy Algorithm Deprecation
OpenSSL 3.4 formally removes support for several legacy cryptographic algorithms that were already deprecated in OpenSSL 3.0–3.3:
- DES and 3DES: Single and triple Data Encryption Standard ciphers
- RC2, RC4: Stream ciphers vulnerable to practical attacks
- MD5: Cryptographic hash function with known collision vulnerabilities
- SHA1: In many contexts (though still available for backward compatibility)
- DSA: Digital Signature Algorithm in some configurations
Attempting to use these algorithms without explicitly enabling the legacy provider will result in EVP_R_UNSUPPORTED_ALGORITHM errors.
2. Default TLS Configuration Changes
- TLS 1.0 and 1.1 disabled by default: These are no longer negotiated unless explicitly enabled
- Minimum key size enforcement: RSA keys must be at least 1024 bits; ECDSA curves must be at least P-256
- Stricter cipher suite defaults: Only strong AEAD ciphers (AES-GCM, ChaCha20-Poly1305) are enabled by default
3. Config File Parsing Changes
OpenSSL 3.4 makes the configuration file format stricter:
- Deprecated directives will cause warnings or errors
- Path handling is more strict regarding absolute vs. relative paths
- Some legacy config options no longer have any effect
4. Provider System Refinements
The provider architecture introduced in OpenSSL 3.0 is now more strictly enforced:
- You must explicitly load the legacy provider to use deprecated algorithms
- Default provider behavior is more restrictive
- Custom FIPS configuration requires explicit provider setup
Identifying Affected Code
Before upgrading, audit your codebase for the following patterns:
Check for Direct Algorithm Usage
// Dangerous: Using DES directly (will fail in 3.4)
DES_cblock key;
DES_key_schedule schedule;
DES_set_key(&key, &schedule);
DES_ecb_encrypt(&plaintext, &ciphertext, &schedule, DES_ENCRYPT);
// Dangerous: Using MD5 (will fail in 3.4)
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, data, len);
MD5_Final(digest, &ctx);
Check for Legacy TLS Versions
// Dangerous: Requesting TLS 1.0
SSL_CTX_set_min_proto_version(ctx, TLS1_VERSION);
SSL_CTX_set_max_proto_version(ctx, TLS1_VERSION);
Check for Weak Key Sizes
# Dangerous: 512-bit RSA key (too small for OpenSSL 3.4)
openssl genrsa 512 > key.pem
# Safe: 2048-bit RSA key
openssl genrsa 2048 > key.pem
Dependency Audit
Many libraries still use weak algorithms internally. You can scan your Node.js, Python, or Java dependencies:
For Node.js:
npm list --all | grep -E '(openssl|crypto|tls|ssl)'
For Python:
pip show cryptography requests
For Java:
# Check your pom.xml or gradle.build for legacy crypto libraries
grep -r "bouncycastle\|commons-crypto" .
Step-by-Step Migration Guide
Step 1: Test with OpenSSL 3.4 in a Staging Environment
Do not upgrade production immediately. Instead:
# On Linux (Ubuntu/Debian)
wget https://www.openssl.org/source/openssl-3.4.0.tar.gz
tar xzf openssl-3.4.0.tar.gz
cd openssl-3.4.0
./Configure --prefix=/opt/openssl-3.4
make && make install
# Test your app against this version
LD_LIBRARY_PATH=/opt/openssl-3.4/lib /opt/openssl-3.4/bin/openssl version
Step 2: Enable Legacy Provider Temporarily (if needed)
If you identify code using deprecated algorithms that you can’t immediately refactor, enable the legacy provider:
In C/C++ code:
#include <openssl/provider.h>
int main() {
OSSL_LIB_CTX *libctx = OSSL_LIB_CTX_new();
if (!OSSL_PROVIDER_load(libctx, "legacy")) {
// Error handling
return 1;
}
// Now legacy algorithms are available
// But use with caution — this is temporary
OSSL_LIB_CTX_free(libctx);
return 0;
}
In Node.js:
// Set environment variable
process.env.OPENSSL_CONF = '/path/to/custom-openssl.cnf';
// Or use legacy-provider flag
// node --openssl-legacy-provider app.js
In Python:
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
# Use cryptography library instead of directly calling OpenSSL
# This provides better abstraction and forward compatibility
Step 3: Update to Modern Algorithms
Replace deprecated algorithms with modern equivalents:
| Deprecated | Modern Replacement |
|---|---|
| DES/3DES | AES-256-GCM |
| RC4 | ChaCha20-Poly1305 |
| MD5 | SHA-256 (or SHA-3) |
| SHA1 | SHA-256 |
| DSA | ECDSA with P-256 or EdDSA |
| TLS 1.0/1.1 | TLS 1.3 (minimum 1.2) |
Example: Updating from DES to AES-GCM
// Old code (DES — insecure)
#include <openssl/des.h>
DES_cblock key;
DES_key_schedule schedule;
DES_set_key(&key, &schedule);
DES_ecb_encrypt(&plaintext, &ciphertext, &schedule, DES_ENCRYPT);
// New code (AES-GCM — secure)
#include <openssl/evp.h>
#include <string.h>
unsigned char key[32]; // 256-bit key
unsigned char iv[12]; // 96-bit IV (nonce) for GCM
unsigned char ciphertext[128];
int ciphertext_len;
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
// Initialize encryption context
EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv);
// Encrypt plaintext
EVP_EncryptUpdate(ctx, ciphertext, &ciphertext_len, plaintext, plaintext_len);
// Finalize and get tag for authentication
unsigned char tag[16];
EVP_EncryptFinal_ex(ctx, ciphertext + ciphertext_len, &ciphertext_len);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag);
EVP_CIPHER_CTX_free(ctx);
Step 4: Update TLS Configuration
For web servers (Nginx):
# nginx.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers on;
# Minimum key sizes (enforced by OpenSSL 3.4)
ssl_certificate_key /path/to/2048-rsa-or-p256-key.pem;
For Apache:
# httpd.conf or ssl.conf
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder on
Step 5: Generate Modern Cryptographic Material
# Generate 2048-bit RSA key (minimum for OpenSSL 3.4)
openssl genrsa -out server.key 2048
# Or use ECDSA with P-256 (better for modern systems)
openssl ecparam -genkey -name prime256v1 -out server.key
# Generate self-signed certificate
openssl req -new -x509 -key server.key -out server.crt -days 365
# Verify key size
openssl rsa -in server.key -text -noout | grep "Private-Key"
Testing Your Migration
Use the Webhook Tester to test TLS connections and verify cipher suites:
# Test TLS version and ciphers
openssl s_client -connect yourserver.com:443 -tls1_2
# Should show:
# TLSv1.2 ... (ECDHE-RSA-AES256-GCM-SHA384)
# Not deprecated algorithms
For API testing with modern TLS, use the API Request Builder tool to validate that your endpoints work correctly with OpenSSL 3.4.
Common Pitfalls and How to Avoid Them
Pitfall 1: Enabling Legacy Provider Globally
Don’t do this:
# BAD: This keeps legacy algorithms enabled forever
export OPENSSL_CONF=/path/to/config-with-legacy
Do this instead:
# GOOD: Enable only when testing
OPENSSL_CONF=/path/to/legacy-config node app.js
# Then remove this line once you've updated your code
Pitfall 2: Ignoring Dependency Updates
Your direct code might be fine, but your dependencies might not. Run:
npm audit
pip check
# etc.
Update libraries that support OpenSSL 3.4. If a library doesn’t, consider alternatives.
Pitfall 3: Testing Only HTTPS
Remember that OpenSSL 3.4 changes affect all cryptographic operations, not just TLS:
- File encryption/decryption
- Digital signatures
- Certificate handling
- VPN/IPsec connections
Test all of these in your application.
Pitfall 4: Using Weak Key Sizes “Just for Testing”
Even for development, use proper key sizes:
# BAD for testing
openssl genrsa 512 > test.key
# GOOD for testing
openssl genrsa 2048 > test.key
Why It Matters
OpenSSL 3.4’s breaking changes exist for a reason: legacy algorithms have known weaknesses. MD5 can be forged in seconds. DES can be brute-forced in hours. These algorithms should not be used in production under any circumstances.
By enforcing these changes, OpenSSL protects you from accidentally using weak cryptography. The migration effort is worth the security guarantee.
Quick Reference Checklist
- [ ] Test your application with OpenSSL 3.4 in staging
- [ ] Audit your code for deprecated algorithms
- [ ] Audit your dependencies for legacy crypto usage
- [ ] Update all TLS configurations to use TLS 1.2+ only
- [ ] Generate new cryptographic keys with proper sizes
- [ ] Replace deprecated algorithms with modern alternatives
- [ ] Test encryption/decryption, signatures, and certificate operations
- [ ] Monitor for errors in production after upgrading
- [ ] Document your migration decisions for team reference
Resources
If you need to analyze or test cryptographic hashes during your migration, try the Hash Generator tool to verify that SHA-256 hashes are being computed correctly in your updated code. For validating JSON payloads in API authentication, the JSON Formatter and JWT Decoder tools can help you verify token structure and claims.