I know, the general advice is "keep your hands off crypto stuff". And the standard way to encrypt backup data securely would be using GnuPG. However, for a rather academic exercise, I would like to think through a protocol that would work just with OpenSSL's standard tools (i. e. dgst, enc, rsautl & Co) invoked from a POSIX shell.
The scenario includes a more or less trusted system on and from which backups are created - this could e. g. be a mail or file server. Then there's a storage which is not 100% to be trusted (e. g. a cloud storage, or a backup server not entirely under my control). So the purpose of the crypto is to protect confidentiality and integrity of the data while stored on the untrusted storage. This protection should also work in case the trusted system gets compromised - at least for data stored before the trusted system was compromised.
So here's a kind of protocol that should fulfill the aforementioned criteria:
Step 1: RSA Key Pair
This step only needs to be performed once. An RSA key pair is needed. This key pair shall not be created on the "trusted" system, but instead on a really trusted box (e. g. a trusted work station or wherever you'd trust to keep your GPG key ring). Since the private key is only needed for recovery, it shall never enter the realms of either the trusted system or the untrusted storage. Key size needs to be 4096 bits or above (we come to that later). OpenSSL usually offers key generation and separation by those commands:
EDIT: switched to PKCS#8 for the keys, being probably more resistant to brute force attacks
openssl genpkey -aes-256-cbc -algorithm RSA -pkeyopt 'rsa_keygen_bits:8192' -out private.key
openssl pkey -in private.key -out public.key -pubout
Step 2: Generate Session Secrets
For each encryption process, dedicated secrets (key, initialisation vector and salt) shall be generated:
EDIT: removed salt as per Ctulhu's comment
key=$(openssl rand -hex 32)
iv=$(openssl rand -hex 16)
hmacpw=$(openssl rand -base64 48)
Please note: I am aware that this will have the secrets unprotected in the trusted machine's memory. However, if an attacker has access to the machine in a way he can skim the backup process' memory, he probably already has access to the data to be protected by these secrets.
Step 3: Compress and Encrypt
Data shall be compressed before encryption. Compression is required anyway, so I consider it a good idea to do it before encrypting the data:
EDIT: removed salt as per Ctulhu's comment
data-generator | xz -zc | openssl enc -e -aes-256-ctr -K $key -iv $iv -out message.enc
Step 4: Generate HMAC and Pack up Keys
The last step would be to generate a HMAC of the just encrypted data, and pack it up together with the session secrets in an RSA encrypted file, using the public key for encryption:
EDIT: removed salt as per Ctulhu's comment
hmac=$(openssl dgst -sha512 -hmac $hmacpw -hex message.enc | sed 's/^.*=[[:space:]]//g')
echo "${key}:${iv}:${hmacpw}:${hmac}" | openssl rsautl -inkey public.key -pubin -encrypt -out message.key
And here it becomes obvious why the RSA key had to be that large: hex digits in shell strings are counted as one byte each, not half a byte. Therefore, the size of the key string sums up to (2x32 + 2x16 + 64 + 128 + 5) = 293 Bytes = 2344 Bits.
Again, I'm aware that exposing secrets on a command line is generally a bad idea, but given the circumstances, I do not see how this would compromise confidentiality or integrity of data which are available in plain text on said system, where an attacker could much easier compromise their privacy or integrity. However, if I would really go and implement this, I'd rather use named pipes to avoid having the secrets popping up in the process list.
Now my questions:
- What did I miss? Do you see any flaw or potential attack vector I haven't noticed yet?
- Would it be more secure to create a signature with
openssl dgst -sha512 -sign private.key -out message.sig message.enc
, given that this would require the private key to reside on the more or less trusted system?