API BIOvs EPI EVP: What are the differences in functionality between OpenSSL BIO and OpenSSL EVP?

80 views Asked by At

I have read the manual of the two APIs: OpenSSL BIO and EVP, but I have questions that I cannot answer. I have searched the net without finding any documents about them.

We can say that the BIO API deals with input/output management in an abstract way, providing an object that is useful for various uses (files, sockets...), while the EVP API deals with providing functions that concern the encryption part (symmetric encryption, digital signature, hash algorithms...).

From the manual we understand that BIO offers filters such as bio_f_base64, bio_f_cipher that allow us to combine them with the encryption algorithms offered by EVP.

I would like to understand what is the difference between the two functions:

cipher = BIO_new(BIO_f_cipher());
                      BIO_set_cipher(cipher, EVP_des_ede3_cbc(), key, NULL, 1);

and

EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)

I would like to understand what are the cases where I would be better off using an EVP rather than BIO.

And if there is any overlap in functionality between the two families.

1

There are 1 answers

0
Shane Powell On

They are different layers of abstraction.

The EVP_xxx methods is a abstraction over cryptographic functions. They don't do any IO abstraction, you have to pass in buffers manually. You are in control of the buffer lifetimes.

The BIO methods are as you stated a I/O abstraction layer.

The BIO_f_cipher creates a BIO pipeline that wrappers calls to EVP_CipherInit(), EVP_CipherUpdate() and EVP_CipherFinal() functions.

So to your specific question, BIO_f_cipher/BIO_set_cipher has nothing to do with EVP_EncryptUpdate directly. Those functions only call EVP_CIPHER_CTX_new/EVP_CipherInit. If you called the BIO_write/BIO_read (or caused a operation that caused BIO_write/BIO_read to be called) then EVP_CipherUpdate will be called.

EVP_CipherXxx functions are a abstraction layer over the EVP_EncryptXxx/EVP_DecryptXxx functions. EVP_EncryptXxx functions is specifically for encrypting data. EVP_DecryptXxx functions is specifically for decrypting data. EVP_CipherXxx functions allows you to write code that can both encrypt or decrypt for the same code and does it depending on the EVP_CipherXxx setup.

So a BIO_f_cipher created BIO can use EVP_EncryptUpdate indirectly to get it's work done. EVP_EncryptUpdate has nothing to do with BIO objects.

You would use BIO setups when you want to pipeline a bunch of IO operations easily.

e.g. read file pipe through to encrypt data pipe through to base64 data pipe through to write file.

#include <string.h>
#include <openssl/bio.h>
#include <openssl/evp.h>

bool test_bio_pipeline()
{
   unsigned char key[32];
   unsigned char iv[32];
   memset(key, 'k', 32);
   memset(iv, 'i', 32);

   BIO * infile = BIO_new_file( "file.in", "r" );
   BIO * in_chain = infile;

   BIO * outfile = BIO_new_file( "file.out.txt", "w" );
   BIO * cipher = BIO_new( BIO_f_cipher() );
   BIO_set_cipher(cipher, EVP_aes_256_cbc(), key, iv, 1);
   BIO * base64 = BIO_new( BIO_f_base64() );

   BIO * out_chain = cipher;
   out_chain = BIO_push( out_chain, base64);
   out_chain = BIO_push( out_chain, outfile);

   int const buffer_len = 1024;
   char buffer[ buffer_len ];
   int bytes_read = BIO_read( in_chain,  buffer, buffer_len );
   while( bytes_read > 0 )
   {
      int const bytes_written = BIO_write( out_chain, buffer, bytes_read );
      if( bytes_written <= 0 )
      {
         BIO_free_all(in_chain);
         BIO_free_all(out_chain);
         return false;
      }
      bytes_read = BIO_read( in_chain, buffer, buffer_len );
   }

   BIO_flush(out_chain);
   BIO_free_all(in_chain);
   BIO_free_all(out_chain);
   return true;
}

You would use EVP_EncryptUpdate directly if you can't use the BIO pipeline for whatever reason (e.g. already existing IO code) or you are doing something very specific in it's setup that BIO_f_cipher does not allow for.