Missing Cryptographic Step
Description
Missing Cryptographic Step is a vulnerability that occurs when a product does not implement a required step in a cryptographic algorithm, resulting in weaker encryption or security than the algorithm is designed to provide. This can happen when developers omit computationally expensive steps to improve performance, misunderstand algorithm requirements, or work from incomplete specifications. The resulting implementation may appear to work but provides reduced security guarantees. Examples include HMAC implementations that cannot handle messages longer than a single block, encryption without proper initialization vector handling, or signature verification that skips critical validation steps.
Risk
Missing cryptographic steps can catastrophically weaken security while appearing to function correctly. The incomplete implementation may pass basic functional tests while being vulnerable to cryptographic attacks. Omitted steps often include those providing critical security properties - skipping an authentication step removes integrity protection, omitting key derivation steps may result in predictable keys, and missing verification steps can allow forged data to be accepted. The risk is amplified because these vulnerabilities may not be apparent through normal testing and require cryptographic expertise to identify. Attackers with knowledge of the specific omission can exploit it, potentially breaking encryption entirely or bypassing authentication. The impact depends on which step was omitted but can range from degraded security to complete cryptographic failure.
Solution
Implement cryptographic algorithms exactly as specified in their standards without optimization shortcuts that skip steps. Use well-tested cryptographic libraries rather than implementing algorithms from scratch. When implementing cryptographic primitives, follow specifications precisely and verify implementations against test vectors. Conduct cryptographic code reviews by security experts who understand the algorithm requirements. Test implementations with edge cases including very long messages, empty inputs, and boundary conditions. Avoid the temptation to skip "expensive" steps for performance - if performance is critical, choose faster algorithms rather than weakening implementations. Document any deviations from standard implementations and analyze their security impact. Use formal verification methods for critical cryptographic code where feasible.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Missing authentication or verification steps can allow attackers to bypass protection mechanisms, forging valid-looking data or credentials. |
| Confidentiality, Integrity | Scope: Confidentiality, Integrity Incomplete encryption implementations may be vulnerable to cryptanalytic attacks that recover plaintext. Missing integrity steps allow undetected data modification. |
| Non-Repudiation | Scope: Non-Repudiation Missing steps in digital signature implementations can allow signature forgery, undermining accountability and enabling attackers to hide their activities. |
Example Code
Vulnerable Code (Verilog/C)
The following examples demonstrate missing cryptographic steps:
// Vulnerable: HMAC implementation missing iterative hashing for long messages
module vulnerable_hmac_engine(
input wire clk,
input wire rst,
input wire [255:0] key,
input wire [511:0] message, // Only handles 512 bits!
input wire start,
output reg [255:0] hmac_out,
output reg done
);
// Vulnerable: Cannot process messages longer than 512 bits
// HMAC requires iterative hashing for longer messages
reg [255:0] inner_hash;
reg [255:0] outer_hash;
always @(posedge clk) begin
if (rst) begin
done <= 0;
end else if (start) begin
// XOR key with ipad
// Hash (key XOR ipad) || message
inner_hash <= sha256(key ^ 256'h3636...36, message);
// XOR key with opad
// Hash (key XOR opad) || inner_hash
outer_hash <= sha256(key ^ 256'h5c5c...5c, inner_hash);
hmac_out <= outer_hash;
done <= 1;
// Vulnerable: Messages > 512 bits are truncated or cause errors!
// Missing the iterative function to process message blocks
end
end
endmodule
// Vulnerable: Missing message padding in hash function
#include <string.h>
#include <stdint.h>
// Vulnerable: SHA-256 implementation missing padding step
void vulnerable_sha256(const uint8_t *message, size_t len, uint8_t *hash) {
uint32_t state[8] = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
};
// Process complete 512-bit blocks
size_t blocks = len / 64;
for (size_t i = 0; i < blocks; i++) {
sha256_transform(state, message + (i * 64));
}
// Vulnerable: Missing padding step!
// SHA-256 requires:
// 1. Append bit '1' to message
// 2. Append zeros until message is 448 bits mod 512
// 3. Append original message length as 64-bit big-endian
// Just copies state without proper finalization
memcpy(hash, state, 32);
// Result is incorrect for messages not exactly multiple of 64 bytes!
}
// Vulnerable: RSA signature without proper padding
int vulnerable_rsa_sign(const uint8_t *message, size_t msg_len,
const RSA_KEY *key, uint8_t *signature) {
uint8_t hash[32];
sha256(message, msg_len, hash);
// Vulnerable: Direct modular exponentiation without padding!
// Missing PKCS#1 v1.5 or PSS padding
// This allows signature forgery attacks
// Should be: padded = EMSA_PKCS1_v1_5_ENCODE(hash, key_size)
// Instead, just using raw hash
bignum_mod_exp(signature, hash, key->d, key->n);
return 0;
}
// Vulnerable: Missing key derivation function
int vulnerable_derive_key(const char *password, uint8_t *key) {
// Vulnerable: Direct hash of password
// Missing proper KDF (PBKDF2, scrypt, argon2)
sha256(password, strlen(password), key);
// No salt! No iterations! Vulnerable to rainbow tables!
return 0;
}
# Vulnerable: Missing authentication tag verification
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
def vulnerable_decrypt_gcm(key, nonce, ciphertext, tag):
# Vulnerable: Decrypting without verifying authentication tag!
cipher = Cipher(algorithms.AES(key), modes.GCM(nonce))
decryptor = cipher.decryptor()
# Just decrypt without authenticating
plaintext = decryptor.update(ciphertext)
# Vulnerable: Tag never verified!
# Should call: decryptor.finalize_with_tag(tag)
# Missing this step allows modified ciphertext to be accepted
return plaintext # May be forged data!
# Vulnerable: Challenge-response missing challenge verification
def vulnerable_authenticate(client_socket, expected_public_key):
# Generate challenge
challenge = os.urandom(32)
client_socket.send(challenge)
# Receive response
response = client_socket.recv(256)
# Vulnerable: Missing verification that response is for THIS challenge!
# Should verify: signature = sign(challenge, private_key)
# Instead just checks if any valid signature
try:
expected_public_key.verify(response, b'anything') # Wrong!
return True
except:
return False
Fixed Code (Verilog/C)
// Fixed: HMAC implementation with iterative hashing
module secure_hmac_engine(
input wire clk,
input wire rst,
input wire [255:0] key,
input wire [7:0] message_byte,
input wire message_valid,
input wire message_last,
input wire start,
output reg [255:0] hmac_out,
output reg done
);
// Fixed: Implements iterative hashing for arbitrary length messages
reg [511:0] block_buffer;
reg [6:0] block_offset;
reg [255:0] hash_state;
reg processing_inner;
// SHA-256 initial state
localparam [255:0] SHA256_INIT = {
32'h6a09e667, 32'hbb67ae85, 32'h3c6ef372, 32'ha54ff53a,
32'h510e527f, 32'h9b05688c, 32'h1f83d9ab, 32'h5be0cd19
};
always @(posedge clk) begin
if (rst) begin
done <= 0;
block_offset <= 0;
hash_state <= SHA256_INIT;
end else if (start) begin
// Initialize with (key XOR ipad)
block_buffer[511:256] <= key ^ 256'h3636363636...;
block_offset <= 32;
processing_inner <= 1;
end else if (message_valid && processing_inner) begin
// Fixed: Accumulate message bytes
block_buffer[511 - (block_offset * 8) -: 8] <= message_byte;
block_offset <= block_offset + 1;
// Fixed: Process complete blocks iteratively
if (block_offset == 63) begin
hash_state <= sha256_transform(hash_state, block_buffer);
block_offset <= 0;
end
end else if (message_last && processing_inner) begin
// Fixed: Apply proper padding
hash_state <= sha256_finalize(hash_state, block_buffer,
block_offset, total_length);
// Start outer hash
processing_inner <= 0;
// Process (key XOR opad) || inner_hash
end
// ... complete outer hash similarly
end
endmodule
// Fixed: SHA-256 with proper padding
#include <string.h>
#include <stdint.h>
void secure_sha256(const uint8_t *message, size_t len, uint8_t *hash) {
uint32_t state[8] = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
};
// Process complete 512-bit blocks
size_t blocks = len / 64;
for (size_t i = 0; i < blocks; i++) {
sha256_transform(state, message + (i * 64));
}
// Fixed: Proper padding implementation
uint8_t padding_block[128] = {0}; // May need 2 blocks
size_t remaining = len % 64;
// Copy remaining bytes
memcpy(padding_block, message + (blocks * 64), remaining);
// Fixed: Append bit '1'
padding_block[remaining] = 0x80;
// Fixed: Determine if we need one or two padding blocks
size_t padding_blocks;
if (remaining >= 56) {
padding_blocks = 2; // Need extra block for length
} else {
padding_blocks = 1;
}
// Fixed: Append length as 64-bit big-endian at end
uint64_t bit_len = len * 8;
size_t len_offset = (padding_blocks * 64) - 8;
for (int i = 0; i < 8; i++) {
padding_block[len_offset + i] = (bit_len >> (56 - i * 8)) & 0xff;
}
// Process padding block(s)
for (size_t i = 0; i < padding_blocks; i++) {
sha256_transform(state, padding_block + (i * 64));
}
// Fixed: Proper output formatting
for (int i = 0; i < 8; i++) {
hash[i*4 + 0] = (state[i] >> 24) & 0xff;
hash[i*4 + 1] = (state[i] >> 16) & 0xff;
hash[i*4 + 2] = (state[i] >> 8) & 0xff;
hash[i*4 + 3] = state[i] & 0xff;
}
}
// Fixed: RSA signature with proper padding
int secure_rsa_sign(const uint8_t *message, size_t msg_len,
const RSA_KEY *key, uint8_t *signature) {
uint8_t hash[32];
sha256(message, msg_len, hash);
// Fixed: Apply PKCS#1 PSS padding
uint8_t padded[key->size];
if (pkcs1_pss_encode(hash, sizeof(hash), key->size, padded) != 0) {
return -1;
}
// Sign the properly padded message
bignum_mod_exp(signature, padded, key->d, key->n);
return 0;
}
// Fixed: Proper key derivation
int secure_derive_key(const char *password, const uint8_t *salt,
size_t salt_len, uint8_t *key) {
// Fixed: Use proper KDF with salt and iterations
return PKCS5_PBKDF2_HMAC(
password, strlen(password),
salt, salt_len,
100000, // Iterations
EVP_sha256(),
32, // Key length
key
);
}
# Fixed: Proper GCM decryption with tag verification
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
def secure_decrypt_gcm(key, nonce, ciphertext, tag):
cipher = Cipher(algorithms.AES(key), modes.GCM(nonce, tag))
decryptor = cipher.decryptor()
plaintext = decryptor.update(ciphertext)
# Fixed: Verify authentication tag during finalization
# This will raise InvalidTag if authentication fails
decryptor.finalize()
return plaintext
# Fixed: Challenge-response with proper verification
def secure_authenticate(client_socket, expected_public_key):
# Generate unique challenge
challenge = os.urandom(32)
client_socket.send(challenge)
# Receive response
signature = client_socket.recv(256)
# Fixed: Verify signature is specifically for THIS challenge
try:
expected_public_key.verify(
signature,
challenge, # The actual challenge data
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
return True
except InvalidSignature:
return False
The fix implements all required cryptographic steps including padding, verification, and proper key derivation.
Exploited in the Wild
SSH Authentication Bypass (Various, 2001)
CVE-2001-1585 documented an SSH implementation missing challenge-response verification, allowing authentication bypass using just the public key.
Incomplete HMAC Implementations (Various Hardware)
Hardware implementations with limited memory have shipped HMAC engines that cannot process messages longer than their buffer size, weakening authentication.
Tools to Test/Exploit
-
Cryptographic Test Vectors — NIST test vectors for validating implementations.
-
Wycheproof — Google's test vectors for cryptographic libraries.
-
Cryptofuzz — Differential fuzzing for cryptographic libraries.
CVE Examples
- CVE-2001-1585 — Missing challenge-response step in SSH authentication.
References
-
MITRE Corporation. "CWE-325: Missing Cryptographic Step." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/325.html
-
NIST. "Secure Hash Standard." FIPS 180-4. https://csrc.nist.gov/publications/detail/fips/180/4/final
-
RFC 2104. "HMAC: Keyed-Hashing for Message Authentication." https://tools.ietf.org/html/rfc2104