Image of the glider from the Game of Life by John Conway
Skip to content

Manual Authenticated File Encryption With OpenSSL

One thing that bothers me about OpenSSL is the lack of commandline support for AEAD ciphers, specifically AES in CCM and GCM block modes. Why does this matter? Suppose you want to save an encrypted file to disk, without GnuPG, because you don't want to get into key management. Further, suppose you want to send this data to a recipient or store it on a server outside of your full control. The authenticated encryption is important, otherwise the ciphertext is malleable and vulnerable to bit flipping.

So, when you get to the shell, you may try using AES in GCM mode with OpenSSL's "enc(1)" command, only to be left wanting. Here, we generate a key from /dev/urandom, convert it to hexadecimal, and provide the key as an argument on the command line.

$ LC_CTYPE=C tr -cd 'abcdefghjkmnpqrstuvwxyz23456789-' < /dev/urandom | head -c 20; echo
sec2tk24ppprcze33ucs
$ echo sec2tk24ppprcze33ucs | xxd -p
73656332746b323470707072637a6533337563730a
$ openssl enc -aes-256-gcm -k 73656332746b323470707072637a6533337563730a -out file.txt.aes -in file.txt
AEAD ciphers not supported by the enc utility
$ echo $?
1

So, rather than using GCM, however, we can build the authentication tag manually with HMAC-SHA-512, which OpenSSL does support. This means using a non-authenticated block cipher mode, such as CTR, as a first step, then authenticating the ciphertext manually as a second step.

Using our same password from the previous example, we'll do this in two steps now:

$ openssl enc -aes-256-ctr -k 73656332746b323470707072637a6533337563730a -out file.txt.aes -in file.txt
$ openssl dgst -sha512 -binary -mac HMAC -macopt hexkey:73656332746b323470707072637a6533337563730a -out file.txt.aes.mac file.txt.aes

Now you have three files- your plaintext file, your AES encrypted ciphertext file, and your HMAC-SHA-512 authentication file:

$ ls -l file.txt*
-rw-rw-r--. 1 aaron aaron 1050 Feb 27 10:26 file.txt
-rw-rw-r--. 1 aaron aaron 1066 Feb 27 10:27 file.txt.aes
-rw-rw-r--. 1 aaron aaron   64 Feb 27 10:28 file.txt.aes.mac

When sending or remotely storing the "file.txt.aes" file, you'll want to also make sure the "file.txt.aes.mac" authentication file is accompanied with it. Unfortunately, the OpenSSL dgst(1) command does not support verifying message authentication codes, so you'll have to script this manually. So, you'll need to generate a second file, maybe "file.txt.tmp.mac", then compare the two. If they match, you can decrypt the "file.txt.aes" ciphertext file. If not, discard the data.

This isn't elegant, and I wish enc(1) supported AEAD, but as it stands, it doesn't. So, you'll have to stick with doing things manually. However, this is something simple enough to script, and provides both data confidentiality and authenticity, which should be the goal of every ciphertext.

{ 2 } Comments

  1. v6ak | February 5, 2017 at 12:44 pm | Permalink

    This is NOT so simple. When verifying MAC, it is desirable not to provide any side channel. Usual string comparison methods stop when they find first difference, so they provide a timing side channel.

    Do you have any countermeasure against this? I know none in Bash, except some hacky solutions like comparing hashes, for example:

    salt=$(head -c16 /dev/urandom | base64)
    [ "$(sha256sum <<< "$salt$expected_mac")" == "$(sha256sum <<< "$salt$actual_mac")" ]

    This hack makes the side channel randomized, so attacker should not be able to find any correlation to differences between MACs from the information leaked from timing. I know, it is crazy and it is easy to misuse it. (Reusing the salt makes reasoning about security harder – it might be secure, but I recommend against this. Hashing concatenated values is also problematic in some other cases.) But it is the best I was able to create quickly in Bash.

    Also note that your solution is not suitable for many multiuser systems, where other users would be able to recover the key from /proc.

  2. Marcus | October 9, 2017 at 4:42 am | Permalink

    It is much worse!!
    I used aes-256-gcm to encrypt offline backups and that worked until after a "yum update" on a CentOS 7 system. Starting today it does not work and I cannot decrypt the offline backups.
    The current (not working) openssl version is openssl-1.0.2k-8.el7.x86_64 while the previous (working) version is openssl-1.0.1e-60.el7_3.1.x86_64.

Post a Comment

Your email is never published nor shared.