Use of a Cryptographic Primitive with a Risky Implementation
Description
Use of a Cryptographic Primitive with a Risky Implementation occurs when a product implements a cryptographic algorithm using a non-standard, unproven, or disallowed/non-compliant cryptographic implementation. Cryptographic primitives must be mathematically reliable and extensively researched by cryptographers. When weaknesses are discovered in primitives, entire systems depending on them become vulnerable. Custom implementations are particularly risky—if ad-hoc cryptographic primitives are implemented, it is almost certain that the implementation will be vulnerable to attacks that are well understood by cryptographers. Hardware implementations pose additional risks since they cannot be patched post-deployment.
Risk
Risky cryptographic implementations have severe security implications. Encryption may be breakable by known attacks. Random number generation may be predictable. Keys may be derivable from observations. Side-channel attacks may be possible. Mathematical weaknesses may be exploited. Brute-force attacks may become practical. Compliance requirements may not be met. Hardware vulnerabilities cannot be patched.
Solution
Use well-established cryptographic libraries and implementations. Follow standards like NIST FIPS 140-3 for cryptographic module requirements. Use NIST-approved algorithms with recommended key lengths. Avoid custom or ad-hoc cryptographic implementations. Ensure random number generators are cryptographically secure. Test implementations against known attack vectors. Consider future-proofing against quantum attacks. Use hardware security modules for sensitive operations.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Read Application Data - Incorrect crypto usage could render encrypted data as unencrypted plaintext. High likelihood of exploitation when non-standard implementations are used. |
Example Code
Vulnerable Code
// Vulnerable: Using LFSR instead of proper TRNG for key generation
module vulnerable_key_generator (
input wire clk,
input wire reset_n,
input wire generate_key,
output reg [127:0] encryption_key,
output reg key_ready
);
// VULNERABLE: Linear Feedback Shift Register is predictable!
reg [31:0] lfsr_state;
// LFSR polynomial (predictable sequence)
wire feedback = lfsr_state[31] ^ lfsr_state[21] ^ lfsr_state[1] ^ lfsr_state[0];
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
lfsr_state <= 32'hDEADBEEF; // Fixed seed - even worse!
encryption_key <= 128'h0;
key_ready <= 1'b0;
end
else if (generate_key) begin
// Generate 128-bit key from 4 LFSR outputs
// VULNERABLE: Attacker can predict all future keys
// by observing current output
lfsr_state <= {lfsr_state[30:0], feedback};
encryption_key <= {encryption_key[95:0], lfsr_state};
if (key_generated_count == 4) begin
key_ready <= 1'b1;
end
end
end
endmodule
// Vulnerable: Custom "encryption" algorithm
module vulnerable_custom_crypto (
input wire clk,
input wire [127:0] plaintext,
input wire [127:0] key,
output reg [127:0] ciphertext
);
// VULNERABLE: Home-grown "encryption" - XOR with key
// Trivially broken - same plaintext = same ciphertext
always @(posedge clk) begin
ciphertext <= plaintext ^ key; // Simple XOR - NOT secure!
end
// Additional vulnerabilities:
// - No key schedule
// - No rounds/diffusion
// - Linear relationship between inputs and outputs
endmodule
// Vulnerable: Weak hash implementation
module vulnerable_hash (
input wire clk,
input wire [255:0] data,
output reg [31:0] hash
);
// VULNERABLE: Simple additive hash - easily collides
always @(posedge clk) begin
hash <= data[31:0] + data[63:32] + data[95:64] +
data[127:96] + data[159:128] + data[191:160] +
data[223:192] + data[255:224];
end
// Collisions trivially found by rearranging bytes
endmodule
// Vulnerable: Using weak/deprecated algorithms
#include <stdio.h>
#include <string.h>
// VULNERABLE: DES with 56-bit key - brute-forceable
void vulnerable_des_encrypt(uint8_t* data, uint8_t* key) {
DES_key_schedule schedule;
DES_set_key((DES_cblock*)key, &schedule);
DES_ecb_encrypt((DES_cblock*)data, (DES_cblock*)data, &schedule, DES_ENCRYPT);
}
// VULNERABLE: MD5 for password hashing - collision attacks known
void vulnerable_password_hash(const char* password, uint8_t* hash) {
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, password, strlen(password));
MD5_Final(hash, &ctx);
}
// VULNERABLE: Custom PRNG for key generation
uint32_t vulnerable_prng_state = 12345;
uint32_t vulnerable_random(void) {
// Linear congruential generator - predictable!
vulnerable_prng_state = vulnerable_prng_state * 1103515245 + 12345;
return vulnerable_prng_state;
}
void vulnerable_generate_key(uint8_t* key, size_t len) {
for (size_t i = 0; i < len; i++) {
key[i] = vulnerable_random() & 0xFF;
}
}
// VULNERABLE: ECB mode - patterns visible in ciphertext
void vulnerable_ecb_encrypt(uint8_t* data, size_t len, uint8_t* key) {
for (size_t i = 0; i < len; i += 16) {
AES_ecb_encrypt(data + i, data + i, key, AES_ENCRYPT);
// Same plaintext block = same ciphertext block
}
}
Fixed Code
// Fixed: Using true random number generator for key generation
module secure_key_generator (
input wire clk,
input wire reset_n,
input wire generate_key,
input wire [31:0] entropy_source, // Hardware entropy (thermal noise, etc.)
input wire entropy_valid,
output reg [127:0] encryption_key,
output reg key_ready
);
reg [31:0] entropy_pool [0:7];
reg [2:0] entropy_count;
reg [1:0] key_word_count;
// Whitening function (simplified - real implementation would use SHA-256)
function [31:0] whiten;
input [255:0] pool;
begin
// Cryptographic whitening of entropy
whiten = sha256_compress(pool)[31:0];
end
endfunction
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
encryption_key <= 128'h0;
key_ready <= 1'b0;
entropy_count <= 3'h0;
end
else begin
// Collect entropy from hardware source
if (entropy_valid) begin
entropy_pool[entropy_count] <= entropy_source;
entropy_count <= entropy_count + 1;
end
// Generate key when enough entropy collected
if (generate_key && entropy_count == 3'h7) begin
// FIXED: Use true random entropy, properly whitened
encryption_key[31:0] <= whiten({entropy_pool[0], entropy_pool[1],
entropy_pool[2], entropy_pool[3],
entropy_pool[4], entropy_pool[5],
entropy_pool[6], entropy_pool[7]});
key_ready <= 1'b1;
entropy_count <= 3'h0; // Reseed
end
end
end
endmodule
// Fixed: Using standard AES implementation
module secure_aes_engine (
input wire clk,
input wire reset_n,
input wire [127:0] plaintext,
input wire [127:0] key,
input wire start,
output reg [127:0] ciphertext,
output reg done
);
// Standard AES-128 implementation with all rounds
// Key schedule, SubBytes, ShiftRows, MixColumns, AddRoundKey
reg [3:0] round;
reg [127:0] state;
reg [127:0] round_keys [0:10];
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
state <= 128'h0;
round <= 4'h0;
done <= 1'b0;
end
else if (start) begin
// Key expansion
expand_key(key, round_keys);
// Initial round key addition
state <= plaintext ^ round_keys[0];
round <= 4'h1;
done <= 1'b0;
end
else if (round > 0 && round < 10) begin
// Main rounds
state <= add_round_key(
mix_columns(
shift_rows(
sub_bytes(state)
)
),
round_keys[round]
);
round <= round + 1;
end
else if (round == 10) begin
// Final round (no MixColumns)
ciphertext <= add_round_key(
shift_rows(
sub_bytes(state)
),
round_keys[10]
);
done <= 1'b1;
round <= 4'h0;
end
end
endmodule
// Fixed: Using proper cryptographic libraries and algorithms
#include <openssl/evp.h>
#include <openssl/rand.h>
// FIXED: AES-256 with proper key length
int secure_aes_encrypt(const uint8_t* plaintext, size_t len,
const uint8_t* key, const uint8_t* iv,
uint8_t* ciphertext) {
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
// Use AES-256-GCM (authenticated encryption)
EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv);
int out_len;
EVP_EncryptUpdate(ctx, ciphertext, &out_len, plaintext, len);
int final_len;
EVP_EncryptFinal_ex(ctx, ciphertext + out_len, &final_len);
EVP_CIPHER_CTX_free(ctx);
return out_len + final_len;
}
// FIXED: Using Argon2 for password hashing
#include <argon2.h>
int secure_password_hash(const char* password, uint8_t* hash) {
uint8_t salt[16];
RAND_bytes(salt, sizeof(salt));
// Argon2id - memory-hard, resistant to GPU attacks
return argon2id_hash_raw(
3, // Time cost
1 << 16, // Memory cost (64 MB)
4, // Parallelism
password, strlen(password),
salt, sizeof(salt),
hash, 32
);
}
// FIXED: Cryptographically secure random number generation
int secure_generate_key(uint8_t* key, size_t len) {
// Use OpenSSL's CSPRNG
if (RAND_bytes(key, len) != 1) {
return -1; // Error
}
return 0;
}
// FIXED: CBC mode with random IV
int secure_cbc_encrypt(uint8_t* data, size_t len,
uint8_t* key, uint8_t* iv) {
// Generate random IV
if (RAND_bytes(iv, 16) != 1) {
return -1;
}
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv);
int out_len;
EVP_EncryptUpdate(ctx, data, &out_len, data, len);
int final_len;
EVP_EncryptFinal_ex(ctx, data + out_len, &final_len);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
// FIXED: Use SHA-256 or SHA-3 for hashing
void secure_hash(const uint8_t* data, size_t len, uint8_t* hash) {
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(ctx, EVP_sha256(), NULL);
EVP_DigestUpdate(ctx, data, len);
EVP_DigestFinal_ex(ctx, hash, NULL);
EVP_MD_CTX_free(ctx);
}
CVE Examples
- CVE-2020-4778: MD5 usage instead of SHA-256
- CVE-2019-1543: ChaCha20-Poly1305 nonce reduction violating cipher requirements
- CVE-2020-6616: Bluetooth chip using low-entropy PRNG instead of hardware RNG
Related CWEs
- CWE-327: Use of a Broken or Risky Cryptographic Algorithm (parent)
- CWE-325: Missing Cryptographic Step (child)
- CWE-338: Use of Cryptographically Weak PRNG (related)
References
- MITRE Corporation. "CWE-1240: Use of a Cryptographic Primitive with a Risky Implementation." https://cwe.mitre.org/data/definitions/1240.html
- NIST FIPS 140-3: Security Requirements for Cryptographic Modules
- NIST CAVP (Cryptographic Algorithm Validation Program)