Main Content

CWE Rule 325

Missing Cryptographic Step

Since R2024a

Description

Rule Description

The product does not implement a required step in a cryptographic algorithm, resulting in weaker encryption than advertised by the algorithm.

Polyspace Implementation

The rule checker checks for these issues:

  • Context initialized incorrectly for cryptographic operation

  • Incorrect key for cryptographic algorithm

  • Missing cipher data to process

  • Missing cipher final step

  • Missing data for encryption, decryption or signing operation

  • Missing parameters for key generation

  • No data added into context

Examples

expand all

Issue

This issue occurs when you initialize an EVP_PKEY_CTX object for a specific public key cryptography operation but use the object for a different operation.

For instance, you initialize the context for encryption.

ret = EVP_PKEY_encrypt_init(ctx);
However, you use the context for decryption without reinitializing the context.
ret = EVP_PKEY_decrypt(ctx, out, &out_len, in, in_len);

The checker detects if the context object used in these functions has been initialized by using the corresponding initialization functions: EVP_PKEY_paramgen, EVP_PKEY_keygen, EVP_PKEY_encrypt, EVP_PKEY_verify, EVP_PKEY_verify_recover,EVP_PKEY_decrypt, EVP_PKEY_sign, EVP_PKEY_derive,and EVP_PKEY_derive_set_peer.

Risk

Mixing up different operations on the same context can lead to obscure code. It is difficult to determine at a glance whether the current object is used for encryption, decryption, signature, or another operation. The mixup can also lead to a failure in the operation or unexpected ciphertext.

Fix

After you set up a context for a certain family of operations, use the context for only that family of operations.For instance, use these pairs of functions for initialization and usage of the EVP_PKEY_CTX context object.

  • For encryption with EVP_PKEY_encrypt, initialize the context with EVP_PKEY_encrypt_init.

  • For signature verification with EVP_PKEY_verify, initialize the context with EVP_PKEY_verify_init.

  • For key generation with EVP_PKEY_keygen, initialize the context with EVP_PKEY_keygen_init.

If you want to reuse an existing context object for a different family of operations, reinitialize the context.

Example — Encryption Using Context Initialized for Decryption
#include <openssl/evp.h>

#define fatal_error() exit(-1)

int ret;
unsigned char *out_buf10;
size_t out_len10;
int func(unsigned char *src, size_t len, EVP_PKEY_CTX *ctx){
  if (ctx == NULL) fatal_error(); 

  ret = EVP_PKEY_decrypt_init(ctx); 
  if (ret <= 0) fatal_error();
  return EVP_PKEY_encrypt(ctx, out_buf10, &out_len10, src, len); //Noncompliant
}

In this example, the context is initialized for decryption but used for encryption.

Correction — Use One Family of Operations

One possible correction is to initialize the object for encryption.

#include <openssl/evp.h>

#define fatal_error() exit(-1)

int ret;
unsigned char *out_buf10;
size_t out_len10;
int func(unsigned char *src, size_t len, EVP_PKEY_CTX *ctx){
  if (ctx == NULL) fatal_error(); 

  ret = EVP_PKEY_encrypt_init(ctx); 
  if (ret <= 0) fatal_error();
  return EVP_PKEY_encrypt(ctx, out_buf10, &out_len10, src, len);
}
Issue

This issue occurs when you initialize a context object with a key for a specific algorithm but perform an operation that the algorithm does not support.

For instance, you initialize the context with a key for the DSA algorithm.

ret = EVP_PKEY_set1_DSA(pkey,dsa);
ctx = EVP_PKEY_CTX_new(pkey, NULL);
However, you use the context for encrypting data, an operation that the DSA algorithm does not support.
ret = EVP_PKEY_encrypt(ctx,out, &out_len, in, in_len);

Risk

If the algorithm does not support your cryptographic operation, you do not see the expected results. For instance, if you use the DSA algorithm for encryption, you might get unexpected ciphertext.

Fix

Use the algorithm that is appropriate for the cryptographic operation that you want to perform:

  • Diffie-Hellman (DH): For key derivation.

  • Digital Signature Algorithm (DSA): For signature.

  • RSA: For encryption and signature.

  • Elliptic curve (EC): For key derivation and signature.

Example — Encryption with DSA Algorithm
#include <openssl/evp.h>

#define fatal_error() exit(-1)

int ret;
unsigned char *out_buf;
size_t out_len;

int func(unsigned char *src, size_t len, DSA * dsa){
  EVP_PKEY_CTX *ctx;
  EVP_PKEY *pkey = NULL;

  pkey = EVP_PKEY_new();
  if(pkey == NULL) fatal_error();

  ret = EVP_PKEY_set1_DSA(pkey,dsa);
  if (ret <= 0) fatal_error();

  ctx = EVP_PKEY_CTX_new(pkey, NULL); 
  if (ctx == NULL) fatal_error();

  ret = EVP_PKEY_encrypt_init(ctx); 
  if (ret <= 0) fatal_error();
  return EVP_PKEY_encrypt(ctx, out_buf, &out_len, src, len);   //Noncompliant
}

In this example, the context object is initialized with a key associated with the DSA algorithm. However, the object is used for encryption, an operation that the DSA algorithm does not support.

Correction — Use RSA Algorithm

One possible correction is to initialize the context object with a key associated with the RSA algorithm.

#include <openssl/evp.h>
#include <openssl/rsa.h>

#define fatal_error() exit(-1)

int ret;
unsigned char *out_buf;
size_t out_len;

int func(unsigned char *src, size_t len, RSA * rsa){
  EVP_PKEY_CTX *ctx;
  EVP_PKEY *pkey = NULL;

  pkey = EVP_PKEY_new();
  if(pkey == NULL) fatal_error();

  ret = EVP_PKEY_set1_RSA(pkey,rsa);
  if (ret <= 0) fatal_error();

  ctx = EVP_PKEY_CTX_new(pkey, NULL); /* RSA key is set in the context */
  if (ctx == NULL) fatal_error();

  ret = EVP_PKEY_encrypt_init(ctx); /* Encryption operation is set in the context */
  if (ret <= 0) fatal_error();
  ret = EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING);
  if (ret <= 0) fatal_error();
  return EVP_PKEY_encrypt(ctx, out_buf, &out_len, src, len);  
}
Issue

This issue occurs when you perform the final step of a block cipher encryption or decryption incorrectly.

For instance, you do one of the following:

  • You do not perform update steps for encrypting or decrypting the data before performing a final step.

    /* Initialization of cipher context */
    ret = EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
    ...
    /* Missing update step */
    ...
    /* Final step */
    ret = EVP_EncryptFinal_ex(ctx, out_buf, &out_len);
  • You perform consecutive final steps without intermediate initialization and update steps.

    /* Initialization of cipher context */
    ret = EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
    ...
    /* Update step(s) */
    ret = EVP_EncryptUpdate(ctx, out_buf, &out_len, src, len);
    ...
    /* Final step */
    ret = EVP_EncryptFinal_ex(ctx, out_buf, &out_len);
    ...
    /* Missing initialization and update */
    ...
    /* Second final step */
    ret = EVP_EncryptFinal_ex(ctx, out_buf, &out_len);
  • You perform a cleanup of the cipher context and then perform a final step.

    /* Initialization of cipher context */
    ret = EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
    ...
    /* Update step(s) */
    ret = EVP_EncryptUpdate(ctx, out_buf, &out_len, src, len);
    ...
    /* Cleanup of cipher context */
    EVP_CIPHER_CTX_cleanup(ctx);
    ...
    /* Second final step */
    ret = EVP_EncryptFinal_ex(ctx, out_buf, &out_len);

Risk

Block ciphers break your data into blocks of fixed size. During encryption or decryption, the update step encrypts or decrypts your data in blocks. Any leftover data is encrypted or decrypted by the final step. The final step adds padding to the leftover data so that it occupies one block, and then encrypts or decrypts the padded data.

If you perform the final step before performing the update steps, or perform the final step when there is no data to process, the behavior is undefined. You can also encounter run-time errors.

Fix

Perform encryption or decryption in this sequence:

  • Initialization of cipher context

  • Update steps

  • Final step

  • Cleanup of context

Example — Missing Update Steps for Encryption Before Final Step

#include <openssl/evp.h>
#include <stdlib.h>
#define SIZE16 16

unsigned char *out_buf;
int out_len;
unsigned char key[SIZE16];
unsigned char iv[SIZE16];

void func(void) {
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    EVP_CIPHER_CTX_init(ctx);

    /* Initialization of cipher context */
    EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
    
    /* Missing update steps for encryption */
    
    /* Final encryption step */
    EVP_EncryptFinal_ex(ctx, out_buf, &out_len);            //Noncompliant
}

In this example, after the cipher context is initialized, there are no update steps for encrypting the data. The update steps are supposed to encrypt one or more blocks of data, leaving the final step to encrypt data that is left over in a partial block. If you perform the final step without previous update steps, the behavior is undefined.

Correction — Perform Update Steps for Encryption Before Final Step

Perform update steps for encryption before the final step. In the corrected code below, the routine EVP_EncryptUpdate performs the update steps.


#include <openssl/evp.h>
#include <stdlib.h>
#define SIZE16 16

unsigned char *out_buf;
int out_len;
unsigned char key[SIZE16];
unsigned char iv[SIZE16];

void func(unsigned char *src, int len) {
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    EVP_CIPHER_CTX_init(ctx);

    /* Initialization of cipher context */
    EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
    
    /* Update steps for encryption */
    EVP_EncryptUpdate(ctx, out_buf, &out_len, src, len);   
    
    /* Final encryption step */
    EVP_EncryptFinal_ex(ctx, out_buf, &out_len);           
}
Issue

This issue occurs when you do not perform a final step after your update steps for encrypting or decrypting data.

For instance, you do the following:

/* Initialization of cipher context */
ret = EVP_EncryptInit_ex(&ctx, EVP_aes_128_cbc(), NULL, key, iv);
...
/* Update step */
ret = EVP_EncryptUpdate(&ctx, out_buf, &out_len, src, len);
...
/* Missing final step */
...
/* Cleanup of cipher context */
EVP_CIPHER_CTX_cleanup(ctx);

Risk

Block ciphers break your data into blocks of fixed size. During encryption or decryption, the update step encrypts or decrypts your data in blocks. Any leftover data is encrypted or decrypted by the final step. The final step adds padding to the leftover data so that it occupies one block, and then encrypts or decrypts the padded data.

If you do not perform the final step, leftover data remaining in a partial block is not encrypted or decrypted. You can face incomplete or unexpected output.

Fix

After your update steps for encryption or decryption, perform a final step to encrypt or decrypt leftover data.

/* Initialization of cipher context */
ret = EVP_EncryptInit_ex(&ctx, EVP_aes_128_cbc(), NULL, key, iv);
...
/* Update step(s) */
ret = EVP_EncryptUpdate(&ctx, out_buf, &out_len, src, len);
...
/* Final step */
ret = EVP_EncryptFinal_ex(&ctx, out_buf, &out_len);
...
/* Cleanup of cipher context */
EVP_CIPHER_CTX_cleanup(ctx);
Example — Cleanup of Cipher Context Before Final Step

#include <openssl/evp.h>
#include <stdlib.h>
#define SIZE16 16

unsigned char *out_buf;
int out_len;
unsigned char key[SIZE16];
unsigned char iv[SIZE16];

void func(unsigned char *src, int len) {
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    EVP_CIPHER_CTX_init(ctx);

    /* Initialization of cipher context */
    EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
    
    /* Update steps for encryption */
    EVP_EncryptUpdate(ctx, out_buf, &out_len, src, len);   
    
    /* Missing final encryption step */
    
    /* Cleanup of cipher context */
    EVP_CIPHER_CTX_cleanup(ctx);  //Noncompliant
}

In this example, the cipher context ctx is cleaned up before a final encryption step. The final step is supposed to encrypt leftover data. Without the final step, the encryption is incomplete.

Correction — Perform Final Encryption Step

After your update steps for encryption, perform a final encryption step to encrypt leftover data. In the corrected code below, the routine EVP_EncryptFinal_ex is used to perform this final step.


#include <openssl/evp.h>
#include <stdlib.h>
#define SIZE16 16

unsigned char *out_buf;
int out_len;
unsigned char key[SIZE16];
unsigned char iv[SIZE16];

void func(unsigned char *src, int len) {
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    EVP_CIPHER_CTX_init(ctx);

    /* Initialization of cipher context */
    EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
    
    /* Update steps for encryption */
    EVP_EncryptUpdate(ctx, out_buf, &out_len, src, len);   
    
    /* Final encryption step */
    EVP_EncryptFinal_ex(ctx, out_buf, &out_len);
    
    /* Cleanup of cipher context */
    EVP_CIPHER_CTX_cleanup(ctx); 
}
Issue

This issue occurs when the data provided for an encryption, decryption, signing, or authentication operation is NULL or the data length is zero.

For instance, you unintentionally provide a NULL value for in or a zero value for in_len in this decryption operation:

ret = EVP_PKEY_decrypt(ctx, out, &out_len, in, in_len);
Or, you provide a NULL value for md or sig, or a zero value for md_len or sig_len in this verification operation:
ret = EVP_PKEY_verify(ctx, md, mdlen, sig, siglen);

Risk

With NULL data or zero length, the operation does not occur. The redundant operation often indicates a coding error.

Fix

Check the placement of the encryption, decryption, or signing operation. If the operation is intended to happen, make sure that the data provided is non-NULL. Set the data length to a nonzero value.

Example — Zero Data Length for Signing Operation
#include <openssl/evp.h>

#define fatal_error() exit(-1)

int ret;
int func(EVP_PKEY_CTX * ctx){
  if (ctx == NULL) fatal_error(); 
  unsigned char* sig = (unsigned char*) "0123456789";
  unsigned char* md = (unsigned char*) "0123456789";

  ret = EVP_PKEY_verify_init(ctx);
  if (ret <= 0) fatal_error();
  ret = EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256());
  if (ret <= 0) fatal_error();
  return EVP_PKEY_verify(ctx, sig, 0, md, 0);  //Noncompliant
}

In this example, the data lengths (third and fifth arguments to EVP_PKEY_verify) are zero. The operation fails.

Correction — Use Nonzero Data Length

One possible correction is to use a nonzero length for the signature and the data believed to be signed.

#include <openssl/evp.h>

#define fatal_error() exit(-1)

int ret;
int func(EVP_PKEY_CTX * ctx){
  if (ctx == NULL) fatal_error(); 
  unsigned char* sig = (unsigned char*) "0123456789";
  unsigned char* md = (unsigned char*) "0123456789";

  ret = EVP_PKEY_verify_init(ctx);
  if (ret <= 0) fatal_error();
  ret = EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256());
  if (ret <= 0) fatal_error();
  return EVP_PKEY_verify(ctx, sig, 10, md, 10); 
}
Issue

This issue occurs when you perform a key generation step with a context object without first associating the object with required parameters.

For instance, you associate a EVP_PKEY_CTX context object with an empty EVP_PKEY object params before key generation :

EVP_PKEY * params = EVP_PKEY_new();
...
EVP_PKEY_CTX * ctx = EVP_PKEY_CTX_new(params, NULL);
... 
EVP_PKEY_keygen(ctx, &pkey);

Risk

Without appropriate parameters, the key generation step does not occur. The redundant operation often indicates a coding error.

Fix

Check the placement of the key generation step. If the operation is intended, make sure that the parameters are set before key generation.

Certain algorithms use default parameters. For instance, if you specify the DSA algorithm when creating the EVP_PKEY_CTX object, a default key length of 1024 bits is used:

kctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DSA, NULL);
Specifying the algorithm during context creation is sufficient to avoid this defect. Only if you use the Elliptic Curve (EC) algorithm, you must also specify the curve explicitly before key generation.

However, the default parameters can generate keys that are too weak for encryption. Weak parameters can trigger another defect. To change default parameters, use functions specific to the algorithm. For instance, to set parameters, you can use these functions:

  • Diffie-Hellman (DH): Use EVP_PKEY_CTX_set_dh_paramgen_prime_len and EVP_PKEY_CTX_set_dh_paramgen_generator.

  • Digital Signature Algorithm (DSA): Use EVP_PKEY_CTX_set_dsa_paramgen_bits.

  • RSA: Use EVP_PKEY_CTX_set_rsa_padding, EVP_PKEY_CTX_set_rsa_pss_saltlen, EVP_PKEY_CTX_set_rsa_rsa_keygen_bits, and EVP_PKEY_CTX_set_rsa_keygen_pubexp.

  • Elliptic curve (EC): Use EVP_PKEY_CTX_set_ec_paramgen_curve_nid and EVP_PKEY_CTX_set_ec_param_enc.

Example — Empty Parameters During Key Generation
#include <openssl/evp.h>

#define fatal_error() exit(-1)

int ret;
int func(EVP_PKEY *pkey){
  EVP_PKEY * params = EVP_PKEY_new();
  if (params == NULL) fatal_error();

  EVP_PKEY_CTX * ctx = EVP_PKEY_CTX_new(params, NULL);
  if (ctx == NULL) fatal_error();

  ret = EVP_PKEY_keygen_init(ctx);
  if (ret <= 0) fatal_error();
  return EVP_PKEY_keygen(ctx, &pkey); //Noncompliant
}

In this example, the context object ctx is associated with an empty parameter object params. The context object does not have the required parameters for key generation.

Correction — Specify Algorithm During Context Creation

One possible correction is to specify an algorithm, such as RSA, during context creation. For stronger encryption, use 2048 bits for key length instead of the default 1024 bits.

#include <openssl/evp.h>
#include <openssl/rsa.h>

#define fatal_error() exit(-1)

int ret;
int func(EVP_PKEY *pkey){
  EVP_PKEY_CTX * ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); 
  if (ctx == NULL) fatal_error();

  ret = EVP_PKEY_keygen_init(ctx);
  if (ret <= 0) fatal_error();
  
  ret = EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048); 
  if (ret <= 0) fatal_error();
  
  return EVP_PKEY_keygen(ctx, &pkey); 
}
Issue

The issue occurs when you only update a message digest context with null data, or you perform a final step on a message digest context without performing any update step.

When you use message digest functions, you typically initialize a message digest context and perform at least one update step to add data into the context. You then sign, verify, or retrieve the data in the context as a final step.

The checker raises no defect if no information is available about the context. For instance, if the context is passed as an argument to the function that calls the hashing operation or if the context is declared outside the scope of the function. For example, no defect is raised in this code snippet.

void bar(unsigned char* src, int len, EVP_MD_CTX *ctx) {
    //ctx passed as argument of bar()
    EVP_DigestFinal(ctx, out_buf, &out_len); //no defect
}
EVP_MD_CTX glob_ctx;
void foo(unsigned char* src, int len) {
    //glob_ctx declared outside scope of foo()
    EVP_DigestFinal(&glob_ctx, out_buf, &out_len); //no defect
}

Risk

Performing an update step on a context with null data might result in a run-time error.

Performing a final step on a context with no data might result in unexpected behavior.

Fix

Perform at least one update operation with non-null data on a message digest context before you sign, verify, or retrieve the data in the context.

Example — No Update Step Before Final Step
#include <openssl/evp.h>
#include <stdio.h>

unsigned char out_buf[EVP_MAX_MD_SIZE];
unsigned int out_len;

void func(unsigned char* src, int len)
{
    EVP_MD_CTX ctx;
    EVP_MD_CTX_init(&ctx);


    EVP_DigestInit(&ctx, EVP_sha256());
    EVP_DigestUpdate(&ctx, src, len);
    EVP_MD_CTX_init(&ctx);
    EVP_DigestFinal(&ctx, out_buf, &out_len); //Noncompliant
}

In this example, the message digest context ctx is initialized and an update operation is performed to add data src into the context. The context is then reinitialized but no data is added to ctx before EVP_DigestFinal attempts to retrieve data from ctx, which results in an error.

Correction — Perform Final Step Before Reinitializing Context

One possible correction is to perform the final step that retrieves data from the context before you reinitialize the context.

#include <openssl/evp.h>
#include <stdio.h>

unsigned char out_buf[EVP_MAX_MD_SIZE];
unsigned int out_len;

void func(unsigned char* src, int len)
{
    EVP_MD_CTX ctx;
    EVP_MD_CTX_init(&ctx);


    EVP_DigestInit(&ctx, EVP_sha256());
    EVP_DigestUpdate(&ctx, src, len);
    EVP_DigestFinal(&ctx, out_buf, &out_len);
    EVP_MD_CTX_init(&ctx);
} 
Example — No Data Added to Context
#include <openssl/evp.h>
#include <stdio.h>

unsigned char out_buf[EVP_MAX_MD_SIZE];
unsigned int out_len;

void func(unsigned char* src, int len)
{
    EVP_MD_CTX ctx;
    EVP_MD_CTX_init(&ctx);
    size_t cnt = 0;

    EVP_DigestInit(&ctx, EVP_sha256());
    EVP_DigestUpdate(&ctx, src, cnt); //Noncompliant
    EVP_DigestFinal(&ctx, out_buf, &out_len);
}

In this example, zero bytes of data is hashed into the message digest context during the update operation. Retrieving data from the context in the final step results in unexpected behavior.

Correction — Add non-Null Data Into Context

A possible correction is to add data into the context during the update step before you retrieve data from the context.

#include <openssl/evp.h>
#include <stdio.h>

unsigned char out_buf[EVP_MAX_MD_SIZE];
unsigned int out_len;

void func(unsigned char* src, int len)
{
    EVP_MD_CTX ctx;
    EVP_MD_CTX_init(&ctx);

    EVP_DigestInit(&ctx, EVP_sha256());
    EVP_DigestUpdate(&ctx, src, len);
    EVP_DigestFinal(&ctx, out_buf, &out_len);
}

Check Information

Category: Cryptographic Issues

Version History

Introduced in R2024a