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

ImpactDetails
ConfidentialityScope: 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

  • 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

  1. MITRE Corporation. "CWE-1240: Use of a Cryptographic Primitive with a Risky Implementation." https://cwe.mitre.org/data/definitions/1240.html
  2. NIST FIPS 140-3: Security Requirements for Cryptographic Modules
  3. NIST CAVP (Cryptographic Algorithm Validation Program)