1
2
OpenSSL is a project comprising (1) a core library and (2) a toolkit. The core library offers an API for developers of secure applications. The toolkit offers a series of command-line tools to perform basic security operations (for example certificate creation, digests, etc.) OpenSSL is pre-installed in the majority of Unix-like systems. On Microsoft system, it is provided within the Cygwin package. Cygwin is a library offering a Unix-like API (through cygwin.dll) plus a collection of Unix-like command-line tools. It comprises the OpenSSL package as well as the gcc compiler. 3
The OpenSSL library is logically divided in different headers: openssl/crypto.h offering basic ciphers; openssl/evp.h offering a high-level interface to the crypto.h operations; openssl/ssh.h offering secure transport protocols (SSL, TLS, DTLS); openssl/rand.h offering routines for the generation of pseudo-random quantities. 4
In the present slides, we still refer to the OpenSSL API function version 1.0.*. 5
6
The logical representation of an encryption is a function, taking a key and a variablesized plaintext as input, and returning a variable-sized ciphertext as output. Implementing encryption and decryption in this way is not efficient neither practical. It is not efficient because if the plaintext is big, we have to maintain in memory a big quantity of data at once. It is not practical because sometimes we do not have the entire plaintext/ciphertext at the time we must encrypt/decrypt it. This is typical in encrypted communications. The majority of cryptographic libraries uses incremental functions, which update an encryption context step-by-step. This is done in higher-level languages as well, for example Java, C#, and Python. 7
This slide shows the pseudo-code of an incremental encryption operation. We must first initialize the context, giving the various parameters (cipher, mode, key, iv). Then we cycle giving a series of plaintext fragments to the encrypter (context update). The encrypter gives back a series of ciphertext fragments. Finally, we finalize the context, retrieving the last ciphertext fragment. The decryption operation is done in the same fashion. Note that in case of short plaintexts that are completely available, the simplest solution to encrypt is to perform a context initialize, a SINGLE context update giving as input the whole plaintext (so without the cycle), and a context finalize. 8
Since the encryption works on plaintext blocks of fixed size, the encrypt finalize function adds the necessary padding to the plaintext last block before encrypting it. OpenSSL uses the PKCS#7 standard for padding, by which the padding bytes have the same value of the padding length (e.g., 2 bytes each of value 0x02). The padding is ALWAYS added, so if the plaintext length is already a multiple of the block, a block-long padding is added (e.g., 16 bites of value 0x10 in AES-128). As a consequence, the ciphertext length is always (strictly) greater than the plaintext length. On the other hand, the length of the ciphertext fragments is always a multiple of the block size. So if I do a encrypt update on 1 byte with AES-128, the output fragment will be 0 bytes (there are not enough bytes to execute an AES-128 cipher block). The left bytes are temporarily stored in the context. The context finalization will encrypt the remaining bits (plus the padding), and it will return a final ciphertext fragment. The decrypt finalize function also checks for the validity of the padding, returns an error in case, and deletes it. If the padding is not valid, the ciphertext is surely corrupted, so the decrypted data must be discarded. However, this is not a secure method for message authentication (there are ~1/256 probability that a corrupted ciphertext is taken as valid). 9
These OpenSSL API functions realize the incremental encryption/decryption. The length of the fragments returned by EVP_EncryptUpdate(), EVP_EncryptFinal(), EVP_DecryptUpdate() is always multiple of the block. The buffer containing the ciphertext must always be larger than the one containing the plaintext. Allocating len_plaintext+block_size is safe. EVP_EncryptFinal() adds the necessary padding. EVP_DecryptFinal() checks for the validity of the padding (if it is not valid, it returns 0) and discards it. So the final fragment returned by EVP_DecryptFinal() does NOT include the padding. It is important to call EVP_CIPHER_CTX_cleanup() before context deallocation, because such a function erases the key stored inside the context data structure. Missing to call EVP_CIPHER_CTX_cleanup() could cause the key remain stored in unallocated memory, which could eventually lead to key compromise. 10
These example code realizes a simple encryption of a static text with AES-128 in ECB mode, with a key hardcoded in the program (security by obscurity!). 11
12
These example code realizes the relative decryption. The key is still hardcoded in the program (security by obscurity!). 13
14
This is the output of the example encryption program. 15
Note that the ECB mode is vulnerable to electronic-codebook analysis, because the same plaintext block results in the same ciphertext block. To avoid this, we have to use more advanced modes, like CBC. 16
This slide shows the most common ciphers (plus the modes) used in OpenSSL. It is recommended to always use AES, the other ciphers are obsolete and insecure. AES with 128-bit keys (in CBC mode) is fine for 99% of applications. Use AES with 256-bit keys (in CBC mode) only if you want TOP SECRET security (less efficient than 128-bit keys). 17
Note that ECB modes do not have an IV, so EVP_CIPHER_iv_length(EVP_aes_128_ecb()) will always return 0. 18
The generation of unpredictable random numbers is often an underrated aspect of security systems, causing many vulnerabilities. Generating good (i.e. truly unpredictable) random numbers requires to select a good Pseudo-Random Number Generator (PRNG) and good seeds for it. It is always preferable to use a cryptographyoriented library like OpenSSL to generate unpredictable random numbers. This example code shows the generation of a random key and a random IV for successive encryption. RAND_poll() (only from OpenSSL 1.1.0+) seeds the PRNG with a good seed, extracted from the /dev/urandom virtual device on UNIX-like operating systems and a combination of CryptGenRandom() and other randomicity sources on Windows. Calling RAND_poll() is not strictly necessary, because it is automatically called at the program start. It is preferable to reseed the PRNG with RAND_poll() only after a huge generation of random numbers. RAND_bytes() generates a number of random bytes, and stores them in the specified buffer. RAND_poll() is available only since OpenSSL 1.1.0. In previous versions of OpenSSL (1.0.x), the PRNG was automatically seeded from /dev/urandom only if available. Otherwise (for example in Win32 systems /dev/urandom is not available), the PRNG had to be seeded by hand, which is a very risky operation. For Win32 operating systems with OpenSSL 1.0.x, a good way to do that is calling RAND_screen() which takes randomicity from the current content on the display. 19
20
21
22
The logical representation of a hash algorithm is a function, taking a variable-sized message as input, and returning a fixed-sized digest as output. Implementing digest creation in this way is not efficient. Indeed, if the message is big, we have to maintain in memory a big quantity of data at once. The majority of cryptographic libraries uses instead incremental functions, which update a hashing context step-by-step. This is done in higher-level languages as well, for example Java, C#, Python. 23
This slide shows the pseudo-code of an incremental hashing operation. It is very similar to the incremental encrypting operation, except that the context_update function does not return any data. The context_finalize function returns the digest. The hash verification simply re-computes the digest, and then compares it to the received one. The verification is positive if they are equal. 24
These OpenSSL API functions (in <openssl/evp.h>) realize the incremental hash. The buffer passed to EVP_DigestFinal will receive the digest, so it must be sized accordingly. 25
These example code realizes a simple hash of a static text with SHA-256. Always use EVP_MD_CTX_init() after the context allocation, and EVP_MD_CTX_cleanup() before context deallocation. 26
The length of the fragments returned by EVP_EncryptUpdate(), EVP_EncryptFinal() is always multiple of the block. Pay attention not to overwrite the fragment returned by EVP_EncryptUpdate() with the one returned by EVP_EncryptFinal(). A counter or a shifting pointer to the buffer must be used. EVP_EncryptFinal() adds the necessary padding before encrypting. 27
The CRYPTO_memcmp() function (defined in <openssl/crypto.h>) is useful for digest checking. It is NOT safe to use the standard memcmp() function to compare two digests, because it makes the system vulnerable to timing attacks. In fact, the runtime of memcmp() depends on the inputs: if they differ in the first bytes, the runtime will be short; if they differ in the last bytes only, it will be long. An adversary can make the system check several (wrong) digests. By measuring the runtime each time, she can learn how many initial bytes are correct. In this way, the complexity of guessing the correct digest is linear with the length of the digest, instead of exponential. On the contrary, CRYPTO_memcmp() has a constant runtime, and it is recommended to check digests. 28
These example code realizes a digest verification. 29
This slide shows the most common hash algorithms used in OpenSSL. It is recommended to use SHA-256, the other algorithms are obsolete or will become. MD5 (=Message Digest 5) is an obsolete algorithm, completely broken from the security point of view. A 2013 research showed how to find colliding texts (birthday attack) for MD5 in <1sec of processing time on a common PC. Preimage attacks are known too, even if not realized in practice yet. SHA-1 (=Secure Hash Algorithm 1) offers medium security. Theoretical attacks are known, and the first attack developed in practice was announced in February 2017. It is difficult to realize; it requires 6,500 years of CPU computation to complete the first phase of the attack, and 110 years of GPU computation to complete the second phase. SHA-256 (part of the SHA-2 family) offers good security. Neither practical nor theoretical attacks are known. 30
31
Within security applications, keyed hash algorithms (HMAC) are more useful than pure ones, because they are used for authenticating communications. The logical representation of a keyed hash algorithm is a function, taking a key and a variable-sized message as input, and returning a fixed-size digest as output. The majority of cryptographic libraries uses incremental functions for keyed hash algorithms as well. Note that HMAC algorithms do not impose constraints on the key length. However, keys of the same size of the digests are implicitly recommended by the HMAC RFC (rfc2104). This is because if the key is shorter than the digest, then it will be easier to guess the key, thus the security is weaker. Otherwise, a key longer than the digest is useless, since it makes more convenient to guess directly the digest. 32
This slide shows the pseudo-code of an incremental keyed hash operation. Note that we have to pass the key to the context_initialize function. 33
These OpenSSL API functions realize the incremental keyed hash. It is necessary to include <openssl/hmac.h>, since HMAC functionalities are not included in the usual <openssl/evp.h> header. 34
There is also a function to compute an HMAC on-the-fly, without inizializing and destroying the context. This function is useful to simplify the code when the message to be authenticated has a short and fixed size (for example a nonce). 35
36
The authentication strength is given by the minimum between the key length and the digest length. Therefore, using an HMAC key longer than the digest is useless, since it adds no security. For example, if a 64-byte key is employed with HMAC-SHA256, then the authentication strength will not be 64 bytes but 32 bytes. 37
38
39
By checking the HMAC, the server is sure that the legitimate client has produced it, but he does not know when. It could actually be an old HMAC. In other words, the freshness of the HMAC is not guaranteed. This leaves space for a simple attack called replay attack. The adversary eavesdrops (i.e., intercepts) the communication and then replays it afterwards, pretending to be the legitimate client. 40
A simple way to guarantee the freshness of an HMAC is to include a timestamp in it. The server checks that the timestamp is not too old, for example (max) 2 minutes ago (timestamp tolerance). In this way, the adversary can replay the communication only after (max) 2 minutes from the legitimate one. The timestamp-based countermeasure is not very secure nor practical, because it relies on the configuration of the server and the client machines. If the server clock and the client clock are misaligned of more than the timestamp tolerance, the protocol will fail. On the other hand, relaxing too much the timestamp tolerance lowers the security level. A better solution requires the generation of a nonce (=number used once) by the server. The server generates a nonce and sends it in clear to the client. The client must include the nonce in the HMAC. Hence, the server is sure of the freshness of the HMAC. The nonce can be a counter or a random quantity. 41
42
43
An asymmetric cryptosystem uses two keys, one of which is private, the other public. It usually provides for four operations (apart from key generation): public encryption (E_kpub), private decryption (E^-1_kprv), private encryption (E_kprv), public decryption (E^-1_kpub). The public encryption is undone by the private decryption. These two operations are used in the digital envelope technique. The private encryption is undone by the public decryption. These two operations are used in the digital signature technique. 44
An asymmetric key is not a simple string of bits like a symmetric key, but it has an internal structure. This slide shows how an RSA public/private key is internally represented in OpenSSL. The first two BIGNUM's represent the public key: the modulus n, and the public exponent e. All the BIGNUM's together represent the private key, in particular the private exponent d. We will not deal with this data structure, as we will use the high-level OpenSSL API (#include<openssl/evp.h>). 45
The most famous and widespread asymmetric cryptosystems are RSA and EC. RSA (Rivest-Shamir-Adleman, from the names of its inventors) is the oldest and most famous one. It is based on the NP-hardness of the factorization problem. RSA is very famous because it is quite simple to understand and implement. It is widespread in many applications. However, to obtain high levels of security it requires very long keys (the length of an RSA key is given by the number of bits of the modulus), and the encryption/decryption operations are inefficient. RSA cryptosystem is nowadays technologically obsolete, surpassed by Elliptic Curve cryptography (EC). EC gives the same security of RSA with far shorter keys. It is based on the NP-hardness of the discrete logarithm problem. An inefficient 7680-bit RSA key is equivalent to an efficient 384-bit EC key. 46
This table shows the security equivalence between RSA and EC keys and the correspondent effective strength, as reported by SECG. SECG is an industry consortium to develop cryptography standards. It is not straightforward to determine the effective strength of an RSA key, since the complexity of the factorization problem is not easy to compute. A heuristic formula (elaborated from RFC3766) is: strength = -5.64 + 2.77*cubrt(len*ln(2)*(ln(len*ln(2)))^2), where len is the length of the key (i.e. of the modulus) in bits, cubrt() indicates the cube root, ln() indicates the natural logarithm, and strength is the effective strength (in bits). This formula applies for Diffie-Hellman as well, to compute the effective strength given the modulus' length in bits. The effective strength of an EC key is always half the key length (e.g. 160-bit keys give 80-bit strength). This is because the best known algorithms for solving discrete logarithm run in 2^(len/2) time, where len is the length of the key in bits. When creating an EC key pair, it is necessary to specify the curve name, which comprises also the length of the key. Common curves are P-256 (named prime256v1 in OpenSSL) and P-384 (named secp384r1 in OpenSSL). 47
The digital envelope technique encrypts a message in such a way that only who knows a particular private key can decrypt it. In contrast to symmetric encryption, no preshared secret is needed. A straightforward way to do that is to encrypt the whole message with the public key of the recipient. This is very inefficient, because symmetric encryption is extremely slow compared to symmetric one. A better solution is to encrypt the whole message with a randomly generated symmetric key, and then encrypt only the symmetric key with the public key. 48
Digital envelope can also be multi-addressed, in case we want to send the same confidential message to several recipients. This is done by encrypting the symmetric key with the public key of each recipient. 49
These are the OpenSSL command-line tools to create a private key and to extract the public key from a private key (both RSA and EC). All the keys are saved in PEM-format files. 50
PEM (Privacy-Enhanced Mail) is a 1993 IETF standard for securing email communications using asymmetric cryptography. It became obsolete once PGP has been published, but the correspondent file format became widespread. The PEM format is a textual one, in which cryptographic quantities are surrounded by tags, for example -----BEGIN PUBLIC KEY-----, -----END PUBLIC KEY-----. It can contain public or private keys (both RSA and EC), digital certificates, Diffie-Hellman parameters, and so on. Another common format is DER, which is a binary format. 51
These code snippets show how to load a public key (actually, an array of 1 public keys) and a private key from PEM files with OpenSSL. 52
This code snippet shows how to encrypt a message with a public key (actually, with an array of 1 public keys) with OpenSSL. 53
This code snippet shows how to decrypt a ciphertext with a private key with OpenSSL. 54
An EVP_PKEY data structure represents a private or public key (both RSA or EC). These API functions allocate and deallocate an EVP_PKEY data structure. 55
These API functions allocate and load a public or private key (both RSA and EC) from a PEM-format file. 56
This API function initializes a context for (multi-addressed) digital envelope. It takes as input one (or more) public key(s) and returns as output the encrypted text and the encrypted symmetric key. It contextually generates a random symmetric key and an initialization vector, so the PRNG must be seeded properly. The buffer ek[0] must accommodate at least EVP_PKEY_size(pubk[0]) bytes. The buffer iv must accommodate at least EVP_CIPHER_iv_length(type) bytes. Remember that the cipher context must be previously allocated with malloc() and EVP_CIPHER_CTX_init(), and finally deallocated with EVP_CIPHER_CTX_cleanup() and free(). Note: EVP_SealInit() and all the OpenSSL API functions for digital envelope support ONLY RSA cryptosystem. Although digital envelope technique based on EC is technologically possible (cfr. the standard ECIES: Elliptic-Curve Integrated Encryption Scheme), it is NOT implemented by OpenSSL (version 1.0.1k). 57
These API functions update and finalize a digital envelope context. They act in a similar manner to EVP_EncryptUpdate() and EVP_EncryptFinal(). 58
This API function initializes a context for envelope decryption. It takes as input a private key and an encrypted symmetric key. Remember that the cipher context must be previously allocated with malloc() and EVP_CIPHER_CTX_init(), and finally deallocated with EVP_CIPHER_CTX_cleanup() and free(). 59
These API functions update and finalize an envelope decryption context. They act in a similar manner to EVP_DecryptUpdate() and EVP_DecryptFinal(). 60
61
62