Flat File Encryption with OpenSSL and GPG

An SSH server also runs with several types of host keys (which do not normally use a password), usually of 2048 bits in size. A host's private RSA key can be used with my crypter script by generating a compatible public key with this command:

# openssl rsa -in /etc/ssh/ssh_host_rsa_key -pubout
writing RSA key
-----END PUBLIC KEY-----

Since I will be using this RSA keypair for batch transfers, I will be recording the clear-text password for this key in the ~/.pas file. Because of this, the RSA key likely should not be used for SSH. OpenSSL is able to read passwords from a variety of other sources, so if you remove the ~/.pas file and supply the password from a more secure source, the use of a single RSA key for both SSH network sessions and OpenSSL flat file encryption becomes more of an option. Alternately, use a key without a password, and dispense with the ${PW} clauses above.

You cannot use the RSA keypair for the bulk of the encryption of a large amount of data, because RSA can encode only small amounts of information, as is detailed in the manual page:

$ man rsautl | col -b | awk '/NOTES/,/^$/'
  rsautl, because it uses the RSA algorithm directly, can only be used
  to sign or verify small pieces of data.

Echoing a string of zeros to a text file, the maximum size of the clear-text input for RSA encryption with my 2868-bit RSA key is 348 bytes:

$ for((x=0;x<348;x+=1));do echo -n 0 >> bar;done

$ ll bar
-rw-rw-r--. 1 ol7_user ol7_user 348 Jul  7 17:49 bar

$ openssl rsautl -encrypt -pubin -inkey ~/.pub.key -in bar |
        openssl base64

$ echo -n 0 >> bar

$ ll bar
-rw-rw-r--. 1 ol7_user ol7_user 349 Jul  7 17:49 bar

$ openssl rsautl -encrypt -pubin -inkey ~/.pub.key -in bar |
        openssl base64
RSA operation error
139936549824416:error:0406D06E:rsa routines:
RSA_padding_add_PKCS1_type_2:data too large for key size:

Similar testing with a 2048-bit RSA key yields a maximum of 245 bytes (slightly smaller than the size of the key).

Because of this limitation, I will generate a random 64-character password with OpenSSL's random number generator, encrypt a copy of it in the output with the public key, then use it for symmetric encryption for the bulk of the data as the "session key". For this example, I will use the following password obtained from OpenSSL in this manner:

$ openssl rand -base64 48

If I store this file as /tmp/skey, I can see the encryption take place:

$ openssl rsautl -encrypt -pubin -inkey ~/.pub.key -in /tmp/skey |
        openssl base64

Note above the call to the base64 function—the encrypted output is a binary file, and it cannot be displayed directly with the standard ASCII seven-bit character set. The base64 encoder changes binary data into a stream of printable characters suitable for transmission over seven-bit channels—email, for example. This performs the same function as uuencode, using different ASCII symbols.

If I record the output of the encryption in the /tmp/ekey file, I can decrypt it with the private key:

$ openssl base64 -d < /tmp/ekey |
        openssl rsautl -decrypt -inkey ~/.prv.key
Enter pass phrase for .prv.key:

Note above in the decryption section that very old versions of the OpenSSL rsautl command did not allow passwords to be specified on the command line. Therefore, an unencrypted copy of the key must be created before RSA decryption of the session key can take place. That procedure is documented in the comments for legacy systems and versions.

With the session key in hand, I next compute SHA-256 digest checksums of all the input files and record the encrypted results in the output. OpenSSL's version of the sha256sum utility differs slightly in formatting from the conventional version. Also included are SHA-1, RIPEMD-160 and MD5 checksums below:

$ sha256sum /etc/resolv.conf
04655aaa80ee78632d616c1...4bd61c70b7550eacd5d10e8961a70  /etc/resolv.conf

$ openssl dgst -sha256 /etc/resolv.conf
SHA256(/etc/resolv.conf)= 04655aaa80ee78632d6...1c70b7550eacd5d10e8961a70

$ openssl dgst -sha1 /etc/resolv.conf
SHA1(/etc/resolv.conf)= adffc1b0f9620b6709e299299d2ea98414adca2c
$ openssl dgst -ripemd160 /etc/resolv.conf
RIPEMD160(/etc/resolv.conf)= 9929f6385e3260e52ba8ef58a0000ad1261f4f31
$ openssl dgst -md5 /etc/resolv.conf
MD5(/etc/resolv.conf)= 6ce7764fb66a70f6414e9f56a7e1d15b

The SHA-family of digests were all created by the NSA, to whom we owe a great debt for their publication. The RIPEMD-160 digest was developed by researchers in Belgium and is an open alternative to SHA with no known flaws, but it is slower than SHA-1 and was released afterwards, so it is not used as often. MD5 digests should not be used beyond basic media error detection as they are vulnerable to tampering.

The script adjusts the format produced by OpenSSL to more closely mimic the standard utility, then uses the AES128-CBC symmetric cipher to code the digest for all the input files after printing a delimiter (__:). Very old versions of the OpenSSL utility might lack SHA-256—notes in the script detail downgrading to the weaker SHA-1 when using legacy systems (MD5 never should be used). The man dgst command will give full details on OpenSSL's digest options if the manual pages are available.

Finally, the script enters the main encryption loop where each file is processed with AES128-CBC, encoded with base64, separated by delimiters, then sent to STDOUT under the intention that the script be redirected/piped to a file or program for further processing. Information on OpenSSL's various symmetric ciphers can be found with the man enc command when the manual pages are accessibly installed. An informative and amusing cartoon has been published online covering AES' history and theory of operation, for those who have a deeper interest in our chosen symmetric cipher. The GPG website currently advocates Camellia and Twofish in addition to AES, and Camellia can be found in OpenSSL.

OpenSSL can be called to encrypt a file to the standard output with AES like so:

openssl enc -aes-128-cbc -salt -a -e -pass file:pw.txt
 ↪-in file.txt > file.aes

The encryption is undone like so:

openssl enc -aes-128-cbc -d -salt -a -pass file:pw.txt -in file.aes

Here is an example of a complete run of the script:

$ ln -s crypter.sh encrypter
$ ln -s crypter.sh decrypter
$ chmod 755 crypter.sh

$ ./encrypter /etc/resolv.conf /etc/hostname > foo

$ ./decrypter < foo
04655aaa80ee78632d616c1...4bd61c70b7550eacd5d10e8961a70 /etc/resolv.conf
4796631793e89e4d6b5b203...37a4168b139ecdaee6a4a55b03468 /etc/hostname
resolv.conf: ok
hostname: ok

To use this script, or otherwise use the OpenSSL utility for secure communication, it is only necessary to send a public key to a distant party. Assuming that the integrity of the public key is verified between the sender and receiver (that is, via an SHA-256 sum over the phone or another trusted channel), the sender can create a session key, then use it to encode and send arbitrary amounts of data through any untrusted yet reliable transfer medium with reasonable confidence of secrecy.

Note that the decryption block uses shell arrays, which are limited to 1024 elements in some versions (ksh88, pdksh). That will be a hard file limit in those cases.


Charles Fisher has an electrical engineering degree from the University of Iowa and works as a systems and database administrator for a Fortune 500 mining and manufacturing corporation.