Table of contents
Introduction
OpenSSL is an open source toolkit that implements security protocols such as SSL and a lot of encryption algorithms like RSA, AES and several hash functions like SHA1 and MD5. this library gains great respect among developers due to its open source nature and C-Style interface which open bridge to many other languages. one of the weak point of this library is lack of introductory material for newbies developer and poor documentation on openssl.org. this article will aim to give you a brief and to point tutorial about some of algorithm and sample usage.
OpenSSL doesn’t come prebuilt from its origin but you can download the source from openssl.org and compile it yourself or you can download prebuilt binary for win32 environment from the following link and this is what this article example is based on. This article will assume you installed OpenSSL in the following path C:\OpenSSL-Win32 and you added C:\OpenSSL-Win32\include to compiler header search path and you did the same thing with library C:\OpenSSL-Win32\lib\VC\static (this article will link to static version of library for sake of simplicity you should use dynamic library in production environment).
- It Is popular and its part of many large software like Apache, Oracle, PHP, web browsers, and most operating system.
- Fast, lightweight and easy to use interface.
- Has been around for a while and during its lifetime it survive a lot of attacks like Timing attack, predictable private key attack and most these vulnerabilities have been fixed over time.
- Open source nature will open entire horizon to read the source and better understand how algorithm work and drive your way through library.
- Its free under apache-style license for commercial product under minimal simple conditions.
Cryptography is the science of hiding data. the process of hiding data is called encryption (also called encipher) and the result data is named cipher and the reverse operation is decryption (decipher), these two processes depend on a Key to lock or unlock data. the key has two basic properties: length which measure in bit like 256-bit key, and weather a key is symmetric or not. Symmetric key is used for both encryption and decryption, asymmetric key algorithm’s use two keys one for encryption (public key) and the other for decryption (private key). Some algorithms are one-way encryption algorithm which produce only cipher the cipher is called digest, and a lot of hash functions fall into that category.
Categories of OpenSSL
Hash function is one-way encryption algorithm which produced constant length data called digest, no matter how large the input data is the digest length is constant. these algorithm guarantees if a single bit of data changed it will produce complete different digest depending on this data. OpenSSL implement wide verity of hash functions, this article will only explain the most popular one MD5 and SHA-1.
You might wonder why anyone need one-way encryption algorithm and here are some scenarios where it’s used:
- Most modern file system attach file-hash within its catalog.
- Download manager and torrent client guarantee that the file is downloaded successfully by comparing two hashes together.
- Development environment detect which source file have changed by calculating the current hash and compare it to hash embedded in object file *.o and consequently decide whether to recompile the file or not .
- In general file hash guarantee that the file isn’t tempered or edited during its transmission from origin to destination.
- Software company generate hashes for their executable which is signed with their SSL certificate to guarantee authenticity of the product.
- Cloud-based anti-virus like herdprotect.com calculate file hash locally and get the result of already scanned file if available, otherwise it uploads the file, scan it, and store file hash and result on server for later retrieval.
The MD5 message-digest algorithm is a widely used cryptographic hash function producing a 128-bit (16-byte) hash value, typically expressed in text format as a 32-digit hexadecimal number. the following snippet show you how to use the MD5 algorithm on simple text array:
First include the header file and link to library:
#include "openssl/md5.h"
#pragma comment(lib,"libeay32MD.lib")
Then use MD5
function which accept three parameters: the input data as string, length of that string, and buffer of 16-byte length that will receive the result. the third parameter will be null if error happened. to ensure you get the expected result from algorithm. compare you result with online service onlinemd5.com :
char inbuffer [] = "Truth comes out of error more easily than out of confusion." ;
unsigned char outbuffer[MD5_DIGEST_LENGTH];
char digest[33] = {0} ;
MD5((unsigned char*)&inbuffer, strlen(inbuffer), (unsigned char*)&outbuffer);
for(int i = 0; i < MD5_DIGEST_LENGTH; i++)
sprintf(&digest[i*2], "%02x", (unsigned int)outbuffer[i]);
cout << digest << endl ;
SHA stand for Secure Hash Algorithm, it produces 160-bit (20-byte or 40-hex) hash digest. this algorithm releases its first version SHA-0 in 1993 and forth version SHA-3 released in late 2012.the following snippet show you how to use the SHA-1 hash function on sample text array:
First include the header file and link to library:
#include "openssl/sha.h"
#pragma comment(lib,"libeay32MD.lib")
Then use SHA1
function which accept same parameter as MD5 function:
char inbuffer[] = "Be nice to people on your way up because you'll need them on your way down.";
unsigned char outbuffer[SHA_DIGEST_LENGTH];
SHA1((const unsigned char*) inbuffer, strlen(inbuffer) , outbuffer);
char digest[41] = "\0";
for(int i = 0 ; i < SHA_DIGEST_LENGTH ; i++)
sprintf(&digest[i*2],"%02x",outbuffer[i]);
cout << digest << endl ;
the code accompanied with this article:
- shows more than one way to calculate hash digest.it also has more example about other versions of same algorithm and demonstrate how to calculate hash for entire input file with full documentation in code describing each step (the example here is short to save article space).
- Demonstrate usage of high level interface
EVP_
. Openssl come with unified high level interface to call all its algorithm function through it. all functions of this interface start with EVP_
prefix and defined in <openssl/evp.h>
header. - Demonstrate usage of other hash function like MDC-2 and Whirlpool.
OpenSSL come with openssl.exe (found in Bin folder) which allow you to generate hash directly from command line. use the following command line to calculate hash of input file:
openssl dgst -sha1 -out filename [inputfile1...]
openssl dgst -md5 -out filename [inputfile1...]
note that it can calculate hash for different files concurrently and if out-filename parameter is missing the output will be displayed on screen by default .and if the openssl.exe is in your environment search path it can be called directly from code using system() function .
Data Encryption Standard or DES is algorithm that use 56-bit symmetric key for data encryption. the algorithm is developed by IBM in early 70’s and is now considered to be insecure for many applications. This is mainly due to the 56-bit key size being too small. There are also some analytical results which demonstrate theoretical weaknesses in the cipher, although they are infeasible to mount in practice. The algorithm is believed to be practically secure in the form of Triple DES or 3DES (released in 1999), although there are theoretical attacks. In recent years, the cipher has been superseded by the AES algorithm. Triple des operate on 3*56-bit keys or 168-bit as whole. it uses first key to encrypt, a second key to decrypt and third key to encrypt again. both DES and triple DES operate on different mode to produce the outcome cipher and here are the most famous modes.
In this mode the plain text is divided into blocks of length 64-bits each.it start by XOR-ing plain text with pre-initialized data(64-bit) called initialization vector (IV) and then encrypt data producing 64-bit cipher-block. this cipher-block become IV for next plain-text block and so on. at the end if the plain text size is less than 64-bit it will be padded with zeros(null) and finalize the encryption .in decryption the XOR-ing with IV happen after decryption and the previous cipher-block become IV for the next cipher block.
The following example demonstrate how to encrypt and decrypt text array using DES-CBC mode:
char inbuffer[64] = "If you see a turtle on a fence post, he has had some help." ;
int inbufferSize = strlen(inbuffer);
unsigned char outbuffer[64] ;
DES_cblock key = {0x44 ,0x6f ,0x4a ,0x6a ,0x71 ,0x71 ,0x73 ,0x74 };
DES_cblock iv = {0x6c ,0x6c ,0x4b ,0x4b ,0x71 ,0x54 ,0x31 ,0x75};
DES_key_schedule schedule;
DES_set_odd_parity(&key);
DES_set_key_checked(&key, &schedule);
DES_ncbc_encrypt((const unsigned char*)inbuffer,outbuffer,inbufferSize,&schedule,&iv,DES_ENCRYPT);
cout << inbuffer << endl ;
cout << base64_encode(outbuffer,64) << endl ;
DES_cblock iv2 = {0x6c ,0x6c ,0x4b ,0x4b ,0x71 ,0x54 ,0x31 ,0x75};
DES_ncbc_encrypt((const unsigned char*)outbuffer,(unsigned char*)inbuffer,64 ,&schedule,&iv2,DES_DECRYPT);
cout << inbuffer << endl ;
first we define two variables key
and iv
of type DES_cblock
which is typedef for unsigned char [8]
.the key in DES algorithm must have odd-parity (the count of 1’s in binary represenation is odd ) and this is the job of DES_set_odd_parity
function. the key then is converted to architecture dependent form using DES_set_key_checked
function and store the result in struct of type DES_key_schedule
which is used later in encryprion or decryption .
DES_ncbc_encrypt
function is used for both encryption and decryption depending on the last parameter whether it is one or zero . the first paramter is the input data to encrypt or cipher data to decrypt.the second paramter is buffer that will receive the outcome result.the third parameter is input buffer length in byte . the forth paramter is pointer to schedule object we created eralier . the fifth paramter is initialization vector iv
and final paramter is integer deciding whether to encrypt or decrypt .
CBC mode exist in many other forms (with minimal changes) like Propagating Cipher Block Chaining (PCBC) , Cipher Feedback (CFB) , Output Feedback (OFB) , RSA CBC mode (XCBC) .a brief description of those mode with an example of Triple DES-CBC and EVP_ interface is acompanied with source code of this article .
This is the simplest form of DES. In this mode each block of data is encrypted or decrypted independent from each other and without need of initialization vector. this give you the freedom to encrypt or decrypt certain block of data or change the block order after encryption or decryption as you wish.
The following example demonstrate how to encrypt and decrypt text array using DES-ECB mode:
DES_cblock key;
DES_cblock seed = {0x58 ,0x48 ,0x54 ,0x4f ,0x36 ,0x65 ,0x69 ,0x47};
RAND_seed(seed, DES_KEY_SZ);
DES_random_key(&key);
DES_key_schedule schedule;
DES_set_key_checked(&key, &schedule);
string text = "The best education in the world is that got by struggling to get a living.";
DES_cblock outbuffer , inbuffer = {0};
string cipher , back ;
for(int i = 0 , size = text.length() ; i < size ; i+=8)
{
text.copy((char*)inbuffer,8,i);
DES_ecb_encrypt((const_DES_cblock*)inbuffer,(DES_cblock*)outbuffer,&schedule,DES_ENCRYPT);
cipher.append((const char*)outbuffer,8);
ZeroMemory(inbuffer,8);
}
cout << base64_encode((const unsigned char *)cipher.c_str(),cipher.length()) << endl ;
for(int i = 0 , size = cipher.length() ; i < size ; i+=8)
{
cipher.copy((char*)outbuffer,8,i);
DES_ecb_encrypt((const_DES_cblock*)outbuffer,(DES_cblock*)inbuffer,&schedule,DES_DECRYPT);
back.append((const char*)inbuffer,8);
}
cout << back << endl ;
the code use same steps as DES_CBC code except the key is generated automatically using DES_random_key
function . for this function to work properly the random number genrator must be seeded with a call to RAND_seed
function . DES_ecb_encrypt
function operate on two buffer of length 64-bit each as first two argument , a pointer to schedule
object and integer flag to encrypt or decrypt .
Advanced Encryption Standard (AES) also named Rijndael (is a play on the names of the two inventors Joan Daemen and Vincent Rijmen) is a symmetric block cipher that can process data blocks of size 128 bits, using cipher keys with lengths of 128 (AES-128), 192(AES-192), and 256 bits (AES-256). The Algorithm Developed in 2001 and took 5 more years for standardization process .in 2002 AES has been adopted by US government to protect classified information and now used worldwide.
The key size used for an AES cipher specifies the number of repetitions of transformation rounds (substitution and permutation of bits) that convert the input into the final cipher. The number of cycles of repetition are as follows:
The following example demonstrate how to encrypt and decrypt array of plain text using 128-bit key length:
const unsigned char userKey[] = { 0x54 ,0x68 ,0x65 ,0x20 ,0x67 ,0x72 ,0x65 ,0x61,
0x74 ,0x20 ,0x70 ,0x6c ,0x65 ,0x61 ,0x73 ,0x75 };
AES_KEY key ;
AES_set_encrypt_key(userKey,128,&key);
string input = "The great pleasure in life is doing what people say you cannot do." ;
string cipher , back ;
unsigned char inbuffer[AES_BLOCK_SIZE] = {0};
unsigned char outbuffer[AES_BLOCK_SIZE] ={0};
for ( int i = 0 , size = input.length() ; i < size ; i+=AES_BLOCK_SIZE)
{
input.copy((char*)inbuffer,AES_BLOCK_SIZE,i);
AES_encrypt(inbuffer,outbuffer,&key);
cipher.append((const char*)outbuffer,AES_BLOCK_SIZE);
cout << base64_encode(outbuffer,AES_BLOCK_SIZE) ;
ZeroMemory(inbuffer,AES_BLOCK_SIZE);
}
AES_set_decrypt_key(userKey,128,&key);
for ( int i = 0 , size = cipher.length() ; i < size ; i+=AES_BLOCK_SIZE)
{
cipher.copy((char*)outbuffer,AES_BLOCK_SIZE,i);
AES_decrypt(outbuffer,inbuffer,&key);
back.append((const char*)inbuffer,AES_BLOCK_SIZE);
}
cout << "\n\n" << back << "\n";
AES_set_encrypt_key
function expect three parameters the user key (usually expressed in hex), the length of that key depend of second parameter which is key length in bit (other possible value 192 and 256) and if the user passed array is bigger than second parameter length the remaining character is ignored ,the third parameter is architecture dependent form of the key of type AES_KEY
. After that we define two intermediate buffer inbuffer
, outbuffer
both of length 16-byte. AES_encrypt
function operate iteratively on those buffer using the AES_KEY
we craeted earlier . AES_decrypt
function operate in similier fashion except the need to call AES_set_decrypt_key
function before decryption which expect same paramters(and must match to work) as AES_set_encrypt_key
.
the code accompanied with this article:
- implement more demos about AES modes like CBC and EBC with various key length .
- Show how to encrypt and decrypt using high level interafce EVP_ .
Finally you can encrypt directly from command line prompt using command:
openssl enc -aes-[256|192|128]-[cbc|ebc] -in plainText.txt -out Cipher.data -pass arg [-d|-e]
RSA was invented by three mathematicians Ron Rivest, Adi Shamir, and Leonard Adleman in 1977.The Algorithm depend on two mathematically related keys public key (published key) for encryption and private key for decryption (kept secret).
The way the algorithm work is pure math I will refer you to another article and video demonstration of how algorithm operate internally and I’ll stick to presenting a working examples:
Where do you see RSA cryptography?
- Software package signing: a signature is generated by private key and got validated by public key on the other end.
- Secure Shell Communication (SSH): where key pair used for authentication and only computer with private key can establish a connection to computer with public key.
- Most networking protocols that end with s like HTTPS and FTPS.
Key pair generation
To generate a key pair, use the following command line:
openssl genrsa -out private.pem 4096
this will generate RSA key pair with length 4096-bit and save the result to private.pem file. any value less than 1024-bit for key length is insecure and any value greater than 4096 is impractical. the file content will look something like this:
-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEAuwg0331lvqcxxk7QWhWe/lnoofUvbN7mXzmczWQ63oIlIEvl
. . .
ANdcujGYIFrAUpge7BWRtHhb19xSMdnrqmTnq8g6plHSKJKZVJaMC0mkzELo
-----END RSA PRIVATE KEY-----
To extract the public key from the generated key pair, use the following command line:
openssl rsa -pubout -in private.pem -out public.pem
this will store the public key in public.pem file. if the -out parameter is missing the key will be displayed on screen by default.
To view key components, use the following command line:
openssl rsa -text -in private.pem
this will display two prime numbers p and q , the modulus n , two exponents e and d along with key data.
You can generate key pair directly in code using RSA_generate_key_ex function :
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/rand.h>
#pragma comment(lib,"libeay32MD.lib")
unsigned char seed[] = {0x58 ,0x48 ,0x54 ,0x4f ,0x36 ,0x65 ,0x69 ,0x47};
RAND_seed(seed,8);
RSA* rsa = RSA_new();
int bits = 4096;
unsigned long e = RSA_F4 ; BIGNUM* bne = BN_new(); BN_set_word(bne,e);
RSA_generate_key_ex(rsa,bits,bne,NULL);
BIO* bio_private = BIO_new(BIO_s_mem());
BIO* bio_public = BIO_new(BIO_s_mem());
PEM_write_bio_RSAPrivateKey(bio_private, rsa, NULL, NULL, 0, NULL, NULL);
PEM_write_bio_RSAPublicKey(bio_public, rsa);
int private_len = BIO_pending(bio_private);
int public_len = BIO_pending(bio_public);
char* private_key = new char[private_len + 1];
char* public_key = new char[public_len + 1];
BIO_read(bio_private, private_key, private_len);
BIO_read(bio_public, public_key, public_len);
private_key[private_len]=0;
public_key[public_len]=0;
cout << private_key << "\n\n\n\n" ;
cout << public_key << "\n\n\n\n" ;
RSA_free(rsa);
BN_free(bne);
BIO_free(bio_private);
BIO_free(bio_public);
delete [] private_key;
delete [] public_key;
RAND_cleanup();
RSA
in main struct responsible for holding key components, this key is allocated by RSA_new()
and deallocated RSA_free()
function . in general all objects that allocated via API must be manpiulated and deallocated by API . bne is an pointer of type BIGNUM
that will hold public exponent (e
= 65537), this object is allocated by BN_new()
, manipulated by BN_set_word()
, and freed by BN_free()
functions respectively. RSA_generate_key_ex()
function dose the actual key generation and expect the following argument : key pointer , key length in bit , BIGNUM
pointer containing public exponent e
, callback function during prime number generation and it's usually null. OpenSSL define abstraction layer for input and output that use same interface to handle many IO devices (socket, terminal, memory, files), this interface is called BIO
. BIO
object is allocated via BIO_new()
which expect BIO
method as parameter. The Function BIO_s_mem()
return in-memory method type. PEM_write_bio_RSAPrivateKey()
and PEM_write_bio_RSAPublicKey()
functions extract private and public keys to BIO
objects respectively . BIO_pending()
function return count of bytes read or written by last read or write operation (it’s BIO length in our case). BIO_read()
copy BIO internal buffer to char string we already created and then i printed those buffers on screen.
After creating key pair, you use those functions to encrypt and decrypt:
int RSA_public_encrypt(int flen, const unsigned char *from, unsigned char *to,
RSA *rsa,int padding);
int RSA_public_decrypt(int flen, const unsigned char *from, unsigned char *to,
RSA *rsa,int padding);
int RSA_private_encrypt(int flen, const unsigned char *from, unsigned char *to,
RSA *rsa,int padding);
int RSA_private_decrypt(int flen, const unsigned char *from, unsigned char *to,
RSA *rsa,int padding);
The first two pair encrypt the data using the public key and decrypt it using private key. the latter two use private key to encrypt and public key to decrypt data (less common). The usage of the first two function is demonstrated is the following snippet:
void testRSA_PublicEncrypt()
{
char *public_key ="-----BEGIN PUBLIC KEY-----\n\
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyb5Uhv+IZBgER1ayzbJirMm3O\n\
YXglBBNMBC4RAupw9HZT2f/vrmtshnFSmwfiJvcjXpK7OmLHQZs2WHBgaqiRnSwu\n\
EcLDNIduiSRfZtFNWsvqe9yYb5jpYoY4cbiz0xXFIAwFtV2MOfoMSwldsM8E4TFv\n\
P6QAno7/ti16LQ6ftwIDAQAB\n\
-----END PUBLIC KEY-----\n";
char* private_key = "-----BEGIN RSA PRIVATE KEY-----\n\
MIICXgIBAAKBgQCyb5Uhv+IZBgER1ayzbJirMm3OYXglBBNMBC4RAupw9HZT2f/v\n\
rmtshnFSmwfiJvcjXpK7OmLHQZs2WHBgaqiRnSwuEcLDNIduiSRfZtFNWsvqe9yY\n\
b5jpYoY4cbiz0xXFIAwFtV2MOfoMSwldsM8E4TFvP6QAno7/ti16LQ6ftwIDAQAB\n\
AoGBAJFBMF+m+oFwV9KS5OGy150VkjndMpa/eI37IR0MCJknCGQ+JJCSpjRURU//\n\
kC1Tlv+d70imwN5/08Arwl9atBlpk0EgWF/8tbHc++jT0B3BZ4cEV464bvLDEIAR\n\
dj+RHn1QeJCVfaourWdq3GS+0M3tyA9E3Jnn2ar3EG762n8xAkEA2ijrquxoC2U6\n\
s4LFkEanqqM5KpP5p4N5nl4z4IkrkxKGk9stndLwjRA2Iepg0OK9M7RpIUzvrcRA\n\
rMsRuMgGmwJBANFixmQkmU4/KdmxHsR3Rd+8F0lJX5Dw4q4/+eLHIZCvTo92xbuN\n\
kMf9EYk3ezG4agqvVf/hPXfwYbspgdd5DxUCQQCPOMgnCVbxDD8ydIrhQhF3C/tO\n\
waDn4X+pgazLHyKVRldFoGHdOAumgFsZfvaajBCsbieKrii3ypyvFA4JYoA7AkB+\n\
5JKAvCFgZy0QraOMww/IgG/ITTwqVaG6ojDpO27fBS7iCMPaXvfAC2EmPEZfong5\n\
U3sV4EXlOvuvdn8mu0nlAkEAnkXDheA8/YTj+Kj8t8EL/dIGXIcek2nCJgMoJqx0\n\
llxmewvy094b0ZgadxZWv3fhJ7ZDbjlC2tSwfdLuNsw3sQ==\n\
-----END RSA PRIVATE KEY-----\n";
char* msg = "The great pleasure in life is doing what people say you cannot do.";
char* cipher,*back ;
int cipherSize;
{
RSA* rsa = RSA_new() ;
BIO* mem_bio = BIO_new_mem_buf(public_key, strlen(public_key));
rsa = PEM_read_bio_RSA_PUBKEY(mem_bio, &rsa,NULL, NULL);
cipherSize = RSA_size(rsa)+1;
cipher = new char [cipherSize];
cipherSize = RSA_public_encrypt(strlen(msg), (unsigned char *)msg,
(unsigned char *)cipher, rsa, RSA_PKCS1_OAEP_PADDING);
cout << base64_encode((const unsigned char*)cipher,cipherSize) << "\n\n\n" ;
BIO_free(mem_bio);
RSA_free(rsa);
}
{
RSA* rsa = RSA_new() ;
BIO* mem_bio = BIO_new_mem_buf(private_key, strlen(private_key));
rsa = PEM_read_bio_RSAPrivateKey(mem_bio, &rsa,NULL, NULL);
back = new char [cipherSize];
cipherSize = RSA_private_decrypt(cipherSize,(unsigned char *)cipher,
(unsigned char *)back,rsa,RSA_PKCS1_OAEP_PADDING);
back[cipherSize]=0;
cout << back << "\n\n\n" ;
BIO_free(mem_bio);
RSA_free(rsa);
}
delete[] cipher ;
delete[] back ;
}
First we define the keys as string and load them to BIO
object via BIO_new_mem_buf()
function . BIO
object is convert to RSA* key
via a call to PEM_read_bio_RSA_PUBKEY()
function which return the public key or null in case of failure. RSA_public_encrypt()
function dose the actual encrypt passing message length as first argument ,then message itself , buffer to hold the result, this buffer must be less than RSA_size(rsa)-41
for RSA_PKCS1_OAEP_PADDING
schema , the fourth parameter is a public key to encrypt with , and finally the padding schema which is predefine constant , the function return actual data length written to out buffer. Decrypting follow same step as encrypting except the calls to PEM_read_bio_RSAPrivateKey()
and RSA_private_decrypt()
functions.
the code accompanied with this article:
- show more demo on how to save generated key to file and load them back and how to construct a key from string.
- demonstrate how to use private key to encrypt and public key to decrypt a whole file.
Final word
After this article I barley touch the surface of vast API provided by openssl. I did my best to make the examples complete, realistic, comprehensive as much as I can. Dear reader if you implemented more examples about those algorithms, I will be happy to add them to my code with full credit given to you, just leave the code in comment section. Also note that code supplied with this article have minimal error checking, don’t copy and paste the code and expect it to work everywhere.
Hope this article was a helpful effort :) .
Reference and Credit