Search code examples
c++openssl

Send IV with cipher text and use it to decrypt cipher text in another function


I have two functions, one for encrypting and another for decrypting. I do not want use static IV, not safe, so I would like to prefix the cipher text with the 16-byte IV so then in the decrypt function I can get the first 16 of the cipher text (the IV used for encrypting originally) and use it to decrypt the text. How can I do this given my current code? This is all straight from a Wikipedia source.

I have tried appending the iv to the front of cipher text before exting function, but it never actually changes the cipher text. Am I supposed to change something else?

Encrypt:

int encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key,
            unsigned char *iv, unsigned char *ciphertext)
{
    EVP_CIPHER_CTX *ctx;

    int len;

    int ciphertext_len;

    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new()))
        handleErrors();

    /*
     * Initialise the encryption operation. IMPORTANT - ensure you use a key
     * and IV size appropriate for your cipher
     * In this example we are using 256 bit AES (i.e. a 256 bit key). The
     * IV size for *most* modes is the same as the block size. For AES this
     * is 128 bits
     */
    if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv))
        handleErrors();

    /*
     * Provide the message to be encrypted, and obtain the encrypted output.
     * EVP_EncryptUpdate can be called multiple times if necessary
     */
    if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
        handleErrors();
    ciphertext_len = len;

    /*
     * Finalise the encryption. Further ciphertext bytes may be written at
     * this stage.
     */
    if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len))
        handleErrors();
    ciphertext_len += len;

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);

    // APPEND IV AT THE BEGINNING OF CIPHER TEXT HERE

    return ciphertext_len;
}

Decrypt:

int decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key,
            unsigned char *iv, unsigned char *plaintext)
{
    EVP_CIPHER_CTX *ctx;

    int len;

    int plaintext_len;

    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new()))
        handleErrors();

    /*
     * Initialise the decryption operation. IMPORTANT - ensure you use a key
     * and IV size appropriate for your cipher
     * In this example we are using 256 bit AES (i.e. a 256 bit key). The
     * IV size for *most* modes is the same as the block size. For AES this
     * is 128 bits
     */
    if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv))
        handleErrors();

    /*
     * Provide the message to be decrypted, and obtain the plaintext output.
     * EVP_DecryptUpdate can be called multiple times if necessary.
     */
    if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
        handleErrors();
    plaintext_len = len;

    /*
     * Finalise the decryption. Further plaintext bytes may be written at
     * this stage.
     */
    if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len))
        handleErrors();
    plaintext_len += len;

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);

    return plaintext_len;
}

Solution

  • Your concern is correct, it is a must to use different IV's for each encryption.

    You can first copy IV to cyphertext and put the output after the IV like this:

    int encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key,
                unsigned char *iv, unsigned char *ciphertext)
    {
        EVP_CIPHER_CTX *ctx;
    
        int len;
    
        int ciphertext_len;
    
        /* Create and initialise the context */
        if (!(ctx = EVP_CIPHER_CTX_new()))
            handleErrors();
    
        /*
         * Initialise the encryption operation. IMPORTANT - ensure you use a key
         * and IV size appropriate for your cipher
         * In this example we are using 256 bit AES (i.e. a 256 bit key). The
         * IV size for *most* modes is the same as the block size. For AES this
         * is 128 bits
         */
        if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv))
            handleErrors();
    
        // Copy IV to ciphertext
        int iv_len = 16;
        memcpy(ciphertext, iv, iv_len);
        ciphertext_len = iv_len;
    
        /*
         * Provide the message to be encrypted, and obtain the encrypted output.
         * EVP_EncryptUpdate can be called multiple times if necessary
         */
        if (1 != EVP_EncryptUpdate(ctx, ciphertext + ciphertext_len, &len, plaintext, plaintext_len))
            handleErrors();
        ciphertext_len += len;
    
        /*
         * Finalise the encryption. Further ciphertext bytes may be written at
         * this stage.
         */
        if (1 != EVP_EncryptFinal_ex(ctx, ciphertext + ciphertext_len, &len))
            handleErrors();
        ciphertext_len += len;
    
        /* Clean up */
        EVP_CIPHER_CTX_free(ctx);
    
        return ciphertext_len;
    }
    

    and call decrypt like:

    int iv_len = 16;
    decrypt(ciphertext+iv_len, ciphertext_len, key, ciphertext, plaintext);
    

    Make sure that ciphertext is capable of holding both IV and cyphertext.

    But it looks cleaner to me if you do this packing not in encrypt function but on the calling site. With this approach you keep the encrypt and decrypt function as is, and on the calling sites:

    // encryption
    int iv_len = 16;
    int ciphertext_len = encrypt(plaintext, plaintext_len, key, iv, ciphertext);
    unsigned char * packed = malloc(ciphertext_len + iv_len);
    memcpy(packed, iv, iv_len);
    memcpy(packed+iv_len, ciphertext, ciphertext_len);
    ...
    
    // decryption
    int iv_len = 16;
    decrypt(ciphertext+iv_len, ciphertext_len, key, ciphertext, plaintext);