How to encrypt files with symmetric key using openssl

Main picture of the post: How to encrypt files with symmetric keys using openssl

The symmetric encryption is the most used cryptographic operation to protect the confidentiality of the information whether it is personal information or corporate sensitive data.

The main reason why symmetric encryption is preferred in most of the cases instead of asymmetric encryption is not because is more secure but more performant. Therefore, for encrypting high amounts of data, the symmetric encryption is normally the chosen one for this purpose.

Although the asymmetric encryption is more secure, the symmetric encryption algorithms like AES still provide a great security level, and there are no signs that in the following years will be deemed as unsafe. That’s why AES is still considered a standard for protecting sensitive information from different governments around the world.

The AES encryption algorithm

Nowadays, AES standard supports 3 different key lengths that are all of them considered as safe. The key bit lengths are 128, 192 and 256 and, as you may suppose, the longer the key the more secure it is but, the more secure the lower the performance is. So you need to balance performance and security level depending on the encryption requirements of the project you are facing.

Currently, symmetric keys lower than 128 bits are not safe enough, and that’s why algorithms like 3DES (112 bits of key length) are not considered anymore for this kind of encryption.

However, bear in mind that 128 bits of key length would be the next one to be deemed as obsolete to provide enough security level and this would entail the migration of such encryption to a safer one with an applicable operational costs.

Encrypt and decrypt a file with AES using openssl

There are several ways to achieve this goal like using any python module to do this operation or with GnuPG but, in this post, the main tool to perform this operations will be openssl.

Let’s begin to get the AES encryption algorithms that it is supported by openssl:

$ openssl list-cipher-commands | grep -i aes
aes-128-cbc
aes-128-ecb
aes-192-cbc
aes-192-ecb
aes-256-cbc
aes-256-ecb

For the following example, we will use “aes-256-cbc” encryption algorithm to encrypt a plain text file:

$ openssl enc -aes-256-cbc -pass pass:thisisatest1 -in plain.txt -out encrypted
$ file plain.txt
plain.txt: ASCII text
$ file encrypted
encrypted: data

Then let’s decrypt the recent encrypted file:

$ openssl enc -aes-256-cbc -pass pass:thisisatest1 -d -in encrypted -out decrypted
$ cat plain.txt 
hello world
$ cat decrypted
hello world

That’s it, simple as that. No key management and with one single command you can encrypt or decrypt a file with the protection of a password.

Nevertheless, you may wonder where the key went since AES, as other encryption algorithms, relies on keys to perform the cryptographic operations. This question and other security aspects will be covered in the next section of the post.

Improve the security posture of openssl encryption by passing the password through alternate ways

In the previous section, we showed the required command lines to encrypt or decrypt a file but there are certain aspects that we can improve to increase the overall security of the process like avoiding of passing the password in parameters. The issue with passing the password in the command line is that it could be logged in files or command history.

Therefore, to get rid of the password in the command line when encrypting or decrypting with openssl, you just need to omit the option “-pass” so openssl will ask you to introduce it in a prompt:

$ openssl enc -aes-256-cbc -in plain.txt -out encrypted
enter aes-256-cbc encryption password:
Verifying - enter aes-256-cbc encryption password:
$ file plain.txt 
plain.txt: ASCII text
$ file encrypted 
encrypted: data
$ openssl enc -aes-256-cbc -d -in encrypted -out decrypted
enter aes-256-cbc decryption password:
$ file decrypted 
decrypted: ASCII text
$ cat plain.txt 
Hello World
$ cat decrypted 
Hello World

If you introduce a wrong password, then the result won’t be the plain text file:

$ openssl enc -aes-256-cbc -d -in encrypted -out decrypted2
enter aes-256-cbc decryption password:
bad decrypt
139645204400016:error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:evp_enc.c:618:
$ file decrypted2
decrypted2: empty

By doing this encryption or decryption without passing the password in the command line, we are not revealing the password in history or logs reducing the risk of leaking sensitive information:

$ history | tail
   12  openssl enc -aes-256-cbc -in plain.txt -out encrypted
   13  file plain.txt 
   14  file encrypted 
   15  openssl enc -aes-256-cbc -d -in encrypted -out decrypted
   16  file decrypted 
   17  cat plain.txt 
   18  cat decrypted 
   19  openssl enc -aes-256-cbc -d -in encrypted -out decrypted2
   20  file decrypted2
   21  history | tail

Despite that this is the most secure way to introduce a password to perform such cryptographic operations, it is not automation friendly since it will pause the execution due to the password prompt. In order to overcome this issue and keep the password as safe as possible, we can use an environment variable set with the password value by using the read command and reuse it for the next openssl encryptions:

$ echo "environment variable test" > plain2.txt
$ read SECRET
topsecret1
$ ls
plain2.txt
$ openssl enc -aes-256-cbc -in plain2.txt -out encrypted2 -pass pass:$SECRET
$ ls
encrypted2  plain2.txt
$ file encrypted2 
encrypted2: data
$ openssl enc -aes-256-cbc -d -in encrypted2 -out decrypted2 -pass pass:$SECRET
$ ls
decrypted2  encrypted2  plain2.txt
$ cat decrypted2 
environment variable test

Checking the commands history, no clear text passwords are shown by using this method:

$ history | tail
   46  echo "environment variable test" > plain2.txt
   47  read SECRET
   48  ls
   49  openssl enc -aes-256-cbc -in plain2.txt -out encrypted2 -pass pass:$SECRET
   50  ls
   51  file encrypted2 
   52  openssl enc -aes-256-cbc -d -in encrypted2 -out decrypted2 -pass pass:$SECRET
   53  ls
   54  cat decrypted2 
   55  history | tail

Another way to avoid prompts, but keeping the password as safe as possible is by creating a file with the password and protect it with the correct permissions set (chmod 600 or chmod g-rwx,o-rwx), and hide it (doted file name):

$ echo "using a password file" > plain3.txt
$ cat > .passwd
topsecret2
$ chmod 600 .passwd
$ ls
plain3.txt
$ openssl enc -aes-256-cbc -in plain3.txt -out encrypted3 -pass file:.passwd
$ ls 
encrypted3  plain3.txt
$ file encrypted3 
encrypted3: data
$ openssl enc -aes-256-cbc -d -in encrypted3 -out decrypted3 -pass file:.passwd
$ ls
decrypted3  encrypted3  plain3.txt
$ cat decrypted3
using a password file
$ cat > wrong_passwd
wrong1
$ ls
decrypted3  encrypted3  plain3.txt  wrong_passwd
$ openssl enc -aes-256-cbc -d -in encrypted3 -out wrong_decrypted -pass file:wrong_passwd
bad decrypt
140314079197072:error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:evp_enc.c:618:
$ ls
decrypted3  encrypted3  plain3.txt  wrong_decrypted  wrong_passwd
$ file wrong_decrypted
wrong_decrypted: data

After the previous sequence of commands, if we inspect the history we will see no passwords again:

$ history | tail
   84  file encrypted3 
   85  openssl enc -aes-256-cbc -d -in encrypted3 -out decrypted3 -pass file:.passwd
   86  ls
   87  cat decrypted3
   88  cat > wrong_passwd
   89  ls
   90  openssl enc -aes-256-cbc -d -in encrypted3 -out wrong_decrypted -pass file:wrong_passwd
   91  ls
   92  file wrong_decrypted
   93  history | tail

Use PBDKF2 method to improve security against brute force attacks

Openssl versions previous 1.1.1 provided a weak key derivation from passwords that attackers could break by using brute force. This means that an attacker with enough computation power could discover in short time the key used to encrypt the protected file, hence, decrypt it and finally steal the information.

In order to reduce the risk of dictionary or brute force attacks, we can use a more robust PBDKF (Password Based Derivation Key Function) method but, in order to leverage openssl in this terms, it needs to be on version 1.1.1 or above.

In the following example, the openssl is used to encrypt and decrypt with AES256, CBC mode, with PBKDF2, 310000 of iterations, SHA256 and salt:

$ echo "PBKDF plain text" > plain.txt
$ cat plain.txt 
PBKDF plain text
$ openssl enc -aes-256-cbc -pbkdf2 -iter 310000 -md sha256 -salt -in plain.txt -out encrypted
enter aes-256-cbc encryption password:
Verifying - enter aes-256-cbc encryption password:
$ ls
encrypted  plain.txt
$ file encrypted 
encrypted: openssl enc'd data with salted password
$ openssl enc -aes-256-cbc -d -pbkdf2 -iter 310000 -md sha256 -salt -in encrypted -out decrypted
enter aes-256-cbc decryption password:
$ ls
decrypted  encrypted  plain.txt
$ cat decrypted 
PBKDF plain text

The “-pbkdf2” enforce openssl to use this key derivation method instead of the default method which is weak and not recommendable. This key derivation can be configured with “-iter”, which sets the number of hashing operations that needs to be done on the password, the “-md” that sets the hashing algorithm used, and “-salt” to append on password a random value before being hashed.

The combination of these parameters will make the brute force being more harder since each of the password tested, needs to compute 310000 times the PBKDF2-HMAC-SHA256 operation. Note that this 310000 iterations or above are the recommended by OWASP best practices for the algorithm PBKDF2-HMAC-SHA256.

On the other hand, the decryption iterations needs to be the exact number as the used during encryption otherwise the decryption will fail:

$ openssl enc -aes-256-cbc -pbkdf2 -iter 310000 -md sha256 -salt -in plain.txt -out encrypted -pass pass:_T3sting1
$ cat plain.txt 
PBKDF plain text
$ file encrypted 
encrypted: openssl enc'd data with salted password
$ openssl enc -aes-256-cbc -d -pbkdf2 -iter 290000 -md sha256 -salt -in encrypted -out decrypted -pass pass:_T3sting1
bad decrypt
140395810730432:error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:../crypto/evp/evp_enc.c:564:
$ file decrypted 
decrypted: Non-ISO extended-ASCII text, with no line terminators
$ openssl enc -aes-256-cbc -d -pbkdf2 -iter 310000 -md sha256 -salt -in encrypted -out decrypted -pass pass:_T3sting1
$ cat decrypted 
PBKDF plain text

This time, the password is passed through command line just for showing the not matching iteration issue when decrypting. The recommendation is still to not passing the password in clear text on the same command line.

Use a different encryption or cipher mode

In all of the examples showed previously, the AES256 encryption algorithm was used alongside with CBC (Cipher Block Chaining) mode. This encryption mode is vulnerable to oracle padding attacks and does not provide authentication unlike the GCM (Galois/Counter Mode). However, this doesn’t mean that GCM mode is the best for every scenario because it is, for example, vulnerable to cycling attacks or forgery attacks.

Nevertheless, in the next example, the openssl will encrypt with CTR (Counter) mode:

$ echo "encrypt with CTR mode" > plain.txt
$ openssl enc -aes-256-ctr -pbkdf2 -iter 310000 -md sha256 -salt -in plain.txt -out encrypted
enter aes-256-ctr encryption password:
Verifying - enter aes-256-ctr encryption password:
$ ls
encrypted  plain.txt
$ cat plain.txt 
encrypt with CTR mode
$ file encrypted 
encrypted: openssl enc'd data with salted password
$ openssl enc -aes-256-ctr -d -pbkdf2 -iter 310000 -md sha256 -salt -in encrypted -out decrypted
enter aes-256-ctr decryption password:
$ ls
decrypted  encrypted  plain.txt
$ cat decrypted 
encrypt with CTR mode

The CTR mode is not vulnerable to oracle padding attacks but it lacks authentication. Finally, the cipher mode ECB is not recommended because it doesn’t hide well the patterns. This means that similar texts encrypted with the same key will produce a similar cipher text.