Weakness reference
CWE-325

Missing Cryptographic Step

This weakness occurs when a cryptographic algorithm or protocol is implemented incompletely—a required step is skipped, shortened, or omitted entirely. The…

01Summary

This weakness occurs when a cryptographic algorithm or protocol is implemented incompletely—a required step is skipped, shortened, or omitted entirely. The result is that the intended security guarantee (confidentiality, integrity, authenticity, or non-repudiation) is weakened or lost entirely, even though the code may appear to use encryption or signing. A missing step might be as subtle as failing to validate a signature before trusting data, or as obvious as omitting a key derivation function.

02How It Happens

Cryptographic algorithms and protocols are designed as sequences of interdependent steps. Each step serves a specific purpose: key derivation ensures the key is strong enough, initialization vectors prevent pattern leakage, signature verification confirms authenticity, and so on. When a developer skips a step—often because they don't understand why it's necessary, or believe it's redundant—the security property it protects is lost. This frequently happens in custom implementations, when developers copy incomplete examples, or when they optimize away a step they perceive as unnecessary. The code may still run and produce output that looks correct, making the flaw invisible without careful cryptographic review.

03Real-World Impact

Missing cryptographic steps can have severe consequences depending on which step is omitted. Skipping signature verification allows an attacker to forge or tamper with signed data. Omitting a key derivation function means weak passwords can be used directly as encryption keys, enabling brute-force attacks. Failing to use a random initialization vector causes identical plaintexts to encrypt to identical ciphertexts, leaking information about message patterns. In authentication protocols, skipping mutual verification allows man-in-the-middle attacks. The damage ranges from data tampering and forgery to complete loss of confidentiality or authentication.

04Vulnerable & Fixed Patterns

Vulnerable pattern
import hashlib
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend

# Vulnerable: missing IV and no authentication
def encrypt_data(plaintext, key):
    cipher = Cipher(
        algorithms.AES(key),
        modes.CBC(b'\x00' * 16),  # Fixed IV — defeats CBC security
        backend=default_backend()
    )
    encryptor = cipher.encryptor()
    return encryptor.update(plaintext) + encryptor.finalize()

# Vulnerable: signature not verified before use
def process_signed_message(signed_data, public_key):
    # Skips signature verification entirely
    message = signed_data[:-256]  # Assumes last 256 bytes are signature
    return message.decode()  # Trusts unverified data

Why it's vulnerable:
The first example uses a fixed, all-zero initialization vector, which is a cryptographic step that must be random; reusing the same IV with the same key leaks plaintext patterns. The second example completely omits signature verification, trusting data that claims to be signed without actually validating the signature.

Fixed pattern
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes

# Fixed: random IV, proper encryption
def encrypt_data(plaintext, key):
    iv = os.urandom(16)  # Generate random IV
    cipher = Cipher(
        algorithms.AES(key),
        modes.CBC(iv),
        backend=default_backend()
    )
    encryptor = cipher.encryptor()
    ciphertext = encryptor.update(plaintext) + encryptor.finalize()
    return iv + ciphertext  # Prepend IV for decryption

# Fixed: signature verified before trusting data
def process_signed_message(signed_data, public_key):
    message = signed_data[:-256]
    signature = signed_data[-256:]
    public_key.verify(signature, message, padding.PSS(...), hashes.SHA256())
    return message.decode()  # Only trusted after verification
Vulnerable pattern
<?php
// Vulnerable: no IV, deterministic encryption
function encrypt_password($password, $key) {
    $ciphertext = openssl_encrypt($password, 'aes-256-cbc', $key, 0, '');
    return $ciphertext;
}

// Vulnerable: signature not verified
function verify_jwt($token, $secret) {
    $parts = explode('.', $token);
    $payload = json_decode(base64_decode($parts[1]), true);
    // Skips signature verification — trusts payload directly
    return $payload;
}
?>

Why it's vulnerable:
The first example passes an empty string as the IV parameter, causing OpenSSL to use a default or zero IV, which repeats across encryptions and leaks patterns. The second example decodes a JWT payload without verifying the signature, allowing an attacker to forge any payload.

Fixed pattern
<?php
// Fixed: random IV generated and prepended
function encrypt_password($password, $key) {
    $iv = openssl_random_pseudo_bytes(16);
    $ciphertext = openssl_encrypt($password, 'aes-256-cbc', $key, 0, $iv);
    return base64_encode($iv . $ciphertext);  // Prepend IV
}

// Fixed: signature verified before trusting payload
function verify_jwt($token, $secret) {
    $parts = explode('.', $token);
    $header = $parts[0];
    $payload = $parts[1];
    $signature = base64_decode($parts[2]);
    
    $expected_sig = hash_hmac('sha256', "$header.$payload", $secret, true);
    if (!hash_equals($signature, $expected_sig)) {
        throw new Exception('Invalid signature');
    }
    return json_decode(base64_decode($payload), true);
}
?>

05Prevention Checklist

Use established libraries, not custom implementations.
Use cryptography, PyCryptodome, or libsodium (via sodium_* functions in PHP) rather than writing your own cipher or protocol code.
Follow the algorithm specification exactly.
If implementing a standard (AES-GCM, HMAC-SHA256, ECDSA), consult the official RFC or NIST document and verify every step is present.
Always generate and use random IVs/nonces.
Never reuse or hardcode an initialization vector; generate a new one for each encryption operation.
Verify signatures and MACs before processing data.
Use constant-time comparison (hash_equals() in PHP, hmac.compare_digest() in Python) and reject any data with an invalid signature.
Use authenticated encryption modes (GCM, ChaCha20-Poly1305).
These modes combine encryption and authentication in a single step, reducing the chance of omitting integrity checks.
Have cryptographic code reviewed by someone with expertise.
Cryptography is not forgiving; a single omitted step can nullify all security.

06Signs You May Already Be Affected

- Encrypted data that decrypts to the same ciphertext when encrypted multiple times with the same key (indicates missing or fixed IV). - Signed or authenticated data that is accepted even after modification, or acceptance of data without any signature verification attempt. - Cryptographic operations that succeed without any validation step (e.g., JWT tokens accepted without signature checks, encrypted messages decrypted without integrity verification).

07Related Recent Vulnerabilities