Overview
This vulnerability occurs when an application uses cryptographic algorithms that are known to be weak, broken, or deprecated. This includes using outdated hashing algorithms like MD5 or SHA1 for password storage, using weak encryption ciphers like DES, or employing cryptographic modes that are susceptible to attacks (like ECB mode for block ciphers).Business Impact
Using broken cryptography provides a false sense of security. 🛡️ Attackers can often break this weak protection easily, leading to the compromise of sensitive data like passwords, PII, or financial information. If password hashes are cracked, attackers gain access to user accounts. If encryption is broken, all encrypted data is exposed.Reference Details
CWE ID: CWE-327
OWASP Top 10 (2021): A02:2021 - Cryptographic Failures
Severity: High
Framework-Specific Analysis and Remediation
Modern frameworks generally default to strong algorithms (e.g., bcrypt/Argon2 for passwords, AES-GCM for encryption). This vulnerability arises when developers:- Explicitly choose a weak algorithm (often for legacy compatibility or misunderstanding).
- Use outdated libraries or configurations.
- Implement crypto manually instead of using framework defaults.
- Python
- Java
- .NET(C#)
- PHP
- Node.js
- Ruby
Framework Context
Django defaults to PBKDF2_SHA256 for passwords, which is acceptable but bcrypt or Argon2 are stronger. For encryption, developers might use the olderhashlib for passwords or weak pycryptodome configurations.Vulnerable Scenario 1: Using MD5 for Passwords
A legacy system or custom user model uses MD5 directly.Copy
# models.py (Custom User Model)
import hashlib
def check_password(self, raw_password):
# DANGEROUS: MD5 is broken and easily cracked.
return hashlib.md5(raw_password.encode()).hexdigest() == self.password_hash
Vulnerable Scenario 2: Weak Encryption Cipher
Usingpycryptodome with DES or ECB mode.Copy
# utils/encryption.py
from Crypto.Cipher import DES, AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad # Added import
def encrypt_data_des(key, data):
# DANGEROUS: DES has a small key size (56-bit) and is broken.
cipher = DES.new(key, DES.MODE_ECB) # ECB mode is also insecure
return cipher.encrypt(pad(data.encode(), DES.block_size)) # Added encode() and block_size
def encrypt_data_aes_ecb(key, data):
# DANGEROUS: AES itself is strong, but ECB mode leaks patterns.
cipher = AES.new(key, AES.MODE_ECB)
return cipher.encrypt(pad(data.encode(), AES.block_size)) # Added encode() and block_size
Mitigation and Best Practices
Use Django’s default password hashing (django.contrib.auth.hashers). If you need stronger hashing, configure PASSWORD_HASHERS to prioritize bcrypt or Argon2. For encryption, use AES with GCM mode (provides integrity) or CBC mode with a random IV and MAC.Secure Code Example
Copy
# settings.py (Secure Password Hashers)
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.Argon2PasswordHasher', # Preferred
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', # Legacy support only
]
# utils/encryption.py (Secure AES-GCM)
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
def encrypt_data_aes_gcm(key, data):
# SECURE: AES-GCM provides confidentiality and integrity.
nonce = get_random_bytes(12) # GCM recommended nonce size
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
ciphertext, tag = cipher.encrypt_and_digest(data.encode())
# Store or transmit nonce + ciphertext + tag
return nonce + ciphertext + tag
Testing Strategy
Check thepassword field in your user database. Hashes should start with prefixes like argon2$, bcrypt$, or pbkdf2_sha256$, not just raw hex. For encryption, write unit tests ensuring the correct algorithm (AES) and mode (GCM/CBC) are used.Framework Context
Spring Security defaults toBCryptPasswordEncoder, which is secure. Vulnerabilities arise from using older PasswordEncoder implementations or Java’s Cipher class with weak algorithms.Vulnerable Scenario 1: Using MD5/SHA1 PasswordEncoder
Configuring Spring Security to use an old, insecurePasswordEncoder.Copy
// config/SecurityConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder; // Use specific class
// ... other imports ...
@Bean
public PasswordEncoder passwordEncoder() {
// DANGEROUS: MD5 is broken.
// Also avoid SHA1PasswordEncoder, PlaintextPasswordEncoder.
// Use of deprecated classes might also be flagged.
return new MessageDigestPasswordEncoder("MD5");
}
Vulnerable Scenario 2: Using Weak Cipher Algorithm/Mode
Usingjavax.crypto.Cipher with DES or ECB mode.Copy
// service/EncryptionService.java
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public byte[] encryptDesEcb(byte[] keyBytes, byte[] data) throws Exception {
// DANGEROUS: DES/ECB is insecure. Check key length for DES (8 bytes).
SecretKeySpec key = new SecretKeySpec(keyBytes, "DES");
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
Mitigation and Best Practices
UseBCryptPasswordEncoder (default) or Argon2PasswordEncoder. For encryption, use AES/GCM/NoPadding. Ensure a random IV/nonce is generated for each encryption.Secure Code Example
Copy
// config/SecurityConfig.java (Secure)
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
// ... other imports ...
@Bean
public PasswordEncoder passwordEncoder() {
// SECURE: BCrypt is the default and recommended.
// Or use Argon2PasswordEncoder.
return new BCryptPasswordEncoder();
}
// service/EncryptionService.java (Secure AES-GCM)
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Arrays; // Added import
public byte[] encryptAesGcm(byte[] keyBytes, byte[] data) throws Exception {
// SECURE: AES/GCM/NoPadding is recommended.
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// Use a random 12-byte nonce (GCM standard)
byte[] nonce = new byte[12];
new SecureRandom().nextBytes(nonce);
GCMParameterSpec spec = new GCMParameterSpec(128, nonce); // 128-bit tag length
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] ciphertext = cipher.doFinal(data);
// Prepend nonce to ciphertext for storage/transmission
byte[] result = new byte[nonce.length + ciphertext.length];
System.arraycopy(nonce, 0, result, 0, nonce.length);
System.arraycopy(ciphertext, 0, result, nonce.length, ciphertext.length);
return result;
}
Testing Strategy
Check password hashes in the database; they should start with$2a$ or $argon2id$. Write unit tests for encryption code, asserting that AES/GCM/NoPadding is the algorithm specified.Framework Context
ASP.NET Core Identity defaults toPBKDF2 with HMAC-SHA256, which is acceptable. Vulnerabilities can arise from using older membership providers or configuring weaker hashers. Manual encryption might use weak SymmetricAlgorithm subclasses.Vulnerable Scenario 1: Legacy Membership Provider Hasher
Configuring ASP.NET Identity to use an older, weaker hashing algorithm for compatibility.Copy
// Startup.cs (ConfigureServices)
using Microsoft.AspNetCore.Identity; // Added namespace
using Microsoft.Extensions.DependencyInjection; // Added for IServiceCollection
using Microsoft.EntityFrameworkCore; // Added for DbContext example
// ... other using statements ...
services.AddIdentity<IdentityUser, IdentityRole>(options => { // Assuming IdentityUser, replace if needed
// DANGEROUS: V2 uses SHA1, V3 uses PBKDF2. Avoid V2.
options.Password.RequireDigit = false; // Example option
options.PasswordHasherCompatibilityMode = PasswordHasherCompatibilityMode.IdentityV2;
})
.AddEntityFrameworkStores<ApplicationDbContext>(); // Example chaining, assuming ApplicationDbContext
Vulnerable Scenario 2: Using Weak Algorithm (DES/TripleDES)
Manually encrypting data usingDESCryptoServiceProvider.Copy
// Services/EncryptionService.cs
using System.Security.Cryptography;
using System.IO; // Added namespace for MemoryStream etc.
public byte[] EncryptDes(byte[] key, byte[] data)
{
// DANGEROUS: DES is broken. Avoid TripleDES too.
using (var des = DES.Create())
{
des.Key = key; // Ensure key length is correct for DES (8 bytes)
des.Mode = CipherMode.ECB; // DANGEROUS: ECB is weak
des.Padding = PaddingMode.PKCS7; // Specify padding explicitly
using (var encryptor = des.CreateEncryptor(des.Key, des.IV)) // IV is often ignored/zeros in ECB
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(data, 0, data.Length);
} // Ensures flush
return ms.ToArray();
}
}
}
Mitigation and Best Practices
Use the default ASP.NET Core Identity password hasher (PBKDF2 HMAC-SHA256 or newer). For custom encryption, useAesGcm (preferred, .NET Core 3.0+) or AesCng / AesCryptoServiceProvider with CBC mode and an HMAC for integrity.Secure Code Example
Copy
// Startup.cs (ConfigureServices - Secure Default)
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; // Added namespace for IServiceCollection
// ... other using statements ...
// Assuming ApplicationDbContext and IdentityUser exist
public void ConfigureServices(IServiceCollection services) // Example method signature
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); // Example DBContext
services.AddDefaultIdentity<IdentityUser>(options => {
// Default hasher is secure (PBKDF2 HMAC-SHA256)
options.SignIn.RequireConfirmedAccount = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>();
}
// Services/EncryptionService.cs (Secure AES-GCM)
using System.Security.Cryptography;
using System; // Added namespace for Convert, ArgumentOutOfRangeException etc.
public byte[] EncryptAesGcm(byte[] key, byte[] data)
{
// SECURE: AES-GCM provides confidentiality and integrity.
// Requires .NET Core 3.0+
// Ensure key length is valid (16, 24, 32 bytes)
if (key.Length != 16 && key.Length != 24 && key.Length != 32)
throw new ArgumentOutOfRangeException(nameof(key), "Key length must be 16, 24, or 32 bytes.");
using (var aes = new AesGcm(key))
{
// GCM standard nonce size is 12 bytes
var nonce = new byte[AesGcm.NonceByteSizes.MaxSize]; // Use MaxSize for flexibility or specify 12
RandomNumberGenerator.Fill(nonce);
// GCM standard tag size is 16 bytes (128 bits)
var tag = new byte[AesGcm.TagByteSizes.MaxSize]; // Use MaxSize or specify 16
var ciphertext = new byte[data.Length];
aes.Encrypt(nonce, data, ciphertext, tag);
// Return nonce + tag + ciphertext for storage/transmission
byte[] result = new byte[nonce.Length + tag.Length + ciphertext.Length];
Buffer.BlockCopy(nonce, 0, result, 0, nonce.Length);
Buffer.BlockCopy(tag, 0, result, nonce.Length, tag.Length);
Buffer.BlockCopy(ciphertext, 0, result, nonce.Length + tag.Length, ciphertext.Length);
return result;
}
}
Testing Strategy
Check password hashes in theAspNetUsers table. They should be long, non-obvious strings (Base64 encoded PBKDF2). Write unit tests for encryption code, asserting the use of AesGcm or Aes.Framework Context
Laravel defaults tobcrypt for password hashing (Hash::make), which is secure. Vulnerabilities occur if developers manually use md5() or sha1(). For encryption, Laravel uses AES-256-CBC by default (Crypt::encrypt), which needs careful handling regarding integrity.Vulnerable Scenario 1: Using md5() for Passwords
A developer manually hashes a password using md5().Copy
// app/Http/Controllers/RegisterController.php
use App\Models\User; // Added namespace
use Illuminate\Http\Request; // Added namespace for type hinting if needed
// ... other imports ...
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
// DANGEROUS: md5() is broken.
'password' => md5($data['password']),
]);
}
Vulnerable Scenario 2: Using openssl_encrypt with Weak Parameters
Manually encrypting data using openssl_encrypt with DES or a weak mode.Copy
// app/Utils/Encryption.php
function encrypt_weak($key, $data) {
// DANGEROUS: 'DES-ECB' is broken. IV length needs to match cipher (8 bytes for DES)
$iv = openssl_random_pseudo_bytes(8);
$ciphertext = openssl_encrypt($data, 'DES-ECB', $key, OPENSSL_RAW_DATA, $iv); // ECB often ignores IV
// It's better to store IV even if ECB ignores it, but the main issue is DES-ECB itself
return base64_encode($iv . $ciphertext); // Encoding IV and ciphertext
}
Mitigation and Best Practices
Always useHash::make() for passwords. For encryption, use Laravel’s built-in Crypt::encryptString() and Crypt::decryptString(). It uses AES-256-CBC or AES-128-CBC with a MAC for integrity. For stronger integrity, consider AES-GCM (requires PHP 7.1+ and OpenSSL extension).Secure Code Example
Copy
// app/Http/Controllers/RegisterController.php (Secure)
use Illuminate\Support\Facades\Hash;
use App\Models\User; // Added namespace
// ... other imports ...
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
// SECURE: Uses Laravel's default (bcrypt).
'password' => Hash::make($data['password']),
]);
}
// app/Utils/Encryption.php (Secure using Laravel's Crypt)
use Illuminate\Support\Facades\Crypt;
function encrypt_secure($data) {
// SECURE: Uses AES-CBC with MAC by default (from APP_KEY and cipher in config/app.php).
return Crypt::encryptString($data);
}
function decrypt_secure($encrypted_data) {
try { // Add try-catch for decryption errors
return Crypt::decryptString($encrypted_data);
} catch (\Illuminate\Contracts\Encryption\DecryptException $e) {
// Handle exception: log error, return null, etc.
\Log::error("Decryption failed: " . $e->getMessage()); // Example logging
return null;
}
}
Testing Strategy
Check password hashes in the database; they should start with$2y$ (bcrypt). For encryption, ensure Crypt:: facade is used or manually check openssl_encrypt calls for AES-256-GCM or AES-256-CBC (and verify MAC is checked on decryption).Framework Context
Node’s built-incrypto module offers many algorithms. Vulnerabilities arise from choosing weak ones like md5, sha1, or weak ciphers like des. The popular bcrypt library is the standard for passwords.Vulnerable Scenario 1: Using crypto.createHash with MD5/SHA1
Hashing passwords using the built-in crypto module with a weak algorithm.Copy
// utils/auth.js
const crypto = require('crypto');
function hashPasswordWeak(password) {
// DANGEROUS: MD5 is broken. SHA1 is also weak.
return crypto.createHash('md5').update(password).digest('hex');
}
Vulnerable Scenario 2: Using Weak Cipher/Mode
Usingcrypto.createCipheriv with DES or AES in ECB mode.Copy
// utils/encryption.js
const crypto = require('crypto');
function encryptDesEcb(key, data) {
// DANGEROUS: 'des-ecb' is broken. Key must be 8 bytes for DES.
const cipher = crypto.createCipheriv('des-ecb', key, null); // IV ignored in ECB
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
}
function encryptAesEcb(key, data) {
// DANGEROUS: 'aes-128-ecb', 'aes-192-ecb', 'aes-256-ecb' leak patterns.
// Ensure key length matches (16, 24, 32 bytes).
const cipher = crypto.createCipheriv('aes-128-ecb', key, null);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted; // IV usually omitted for ECB
}
Mitigation and Best Practices
Use thebcrypt library for password hashing (bcrypt.hashSync, bcrypt.compareSync). For encryption, use aes-256-gcm which provides integrity.Secure Code Example
Copy
// utils/auth.js (Secure with bcrypt)
const bcrypt = require('bcrypt');
const saltRounds = 12; // Recommended salt rounds
function hashPasswordSecure(password) {
// SECURE: Uses bcrypt with a salt (salt generated automatically)
return bcrypt.hashSync(password, saltRounds);
}
function comparePassword(password, hash) {
return bcrypt.compareSync(password, hash);
}
// utils/encryption.js (Secure AES-GCM)
const crypto = require('crypto');
function encryptAesGcm(key, data) {
// SECURE: AES-256-GCM is recommended. Key must be 32 bytes.
const iv = crypto.randomBytes(12); // 96-bit nonce recommended for GCM
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
const tag = cipher.getAuthTag(); // Get the authentication tag
// Return IV + Tag + Ciphertext for storage/transmission
return iv.toString('hex') + ':' + tag.toString('hex') + ':' + encrypted; // Use separator
}
Testing Strategy
Check password hashes in the database; they should start with$2b$. Write unit tests for encryption, ensuring aes-256-gcm is specified.Framework Context
Rails’has_secure_password uses bcrypt by default, which is secure. Vulnerabilities occur with manual hashing using Digest::MD5 or Digest::SHA1, or using OpenSSL::Cipher with weak algorithms.Vulnerable Scenario 1: Using Digest::MD5
Manually hashing a password in a model or service.Copy
# app/models/user.rb
require 'digest'
class User < ApplicationRecord # Ensure inheriting from ApplicationRecord
# Example of manual hashing (vulnerable)
def password=(new_password)
# DANGEROUS: MD5 is broken. Also avoid Digest::SHA1.
self.password_digest = Digest::MD5.hexdigest(new_password)
end
end
Vulnerable Scenario 2: Using Weak OpenSSL Cipher
Encrypting data using DES or ECB mode.Copy
# lib/encryption.rb
require 'openssl'
def encrypt_des_ecb(key, data)
# DANGEROUS: 'DES-ECB' is broken. Ensure key length is correct (8 bytes).
cipher = OpenSSL::Cipher.new('DES-ECB')
cipher.encrypt
cipher.key = key
# ECB mode typically uses a null or static IV, which is insecure
# cipher.iv = some_static_or_null_iv
encrypted = cipher.update(data) + cipher.final
return encrypted # IV might be omitted or static
end
Mitigation and Best Practices
Usehas_secure_password in your User model. For encryption, use aes-256-gcm via OpenSSL::Cipher.Secure Code Example
Copy
# app/models/user.rb (Secure)
class User < ApplicationRecord
# SECURE: Uses bcrypt by default. Requires 'bcrypt' gem.
has_secure_password
end
# lib/encryption.rb (Secure AES-GCM)
require 'openssl'
require 'securerandom' # For generating keys if needed
def encrypt_aes_gcm(key, data)
# SECURE: AES-256-GCM is recommended. Key must be 32 bytes.
cipher = OpenSSL::Cipher.new('aes-256-gcm')
cipher.encrypt
cipher.key = key
iv = cipher.random_iv # Generate random IV/nonce (12 bytes recommended for GCM)
cipher.auth_data = "" # Optional additional authenticated data
encrypted = cipher.update(data) + cipher.final
tag = cipher.auth_tag # Get the authentication tag (16 bytes recommended)
# Return IV + Tag + Ciphertext (ensure consistent lengths)
# Ensure proper encoding/decoding when storing/retrieving (e.g., Base64)
{ iv: Base64.strict_encode64(iv),
tag: Base64.strict_encode64(tag),
ciphertext: Base64.strict_encode64(encrypted) }
end
Testing Strategy
Check thepassword_digest column in your database; it should start with $2a$. Write unit tests for encryption code, asserting the cipher name is aes-256-gcm.
