How Do I Take Care of My Cryptographic Keys
So, as you start to jump down the rabbit hole of cryptography, you will start to face another question:
How can I take care of my keys?
Well, it’s a long story for everyone, and here’s mine.
Asymmetric
There are symmetric and asymmetric cryptography, and I will walk you through.
OpenPGP Keys
If you have followed my blog for a long time, you might know that I have had two YubiKeys and a couple of smartcards. Those are all being used for OpenPGP (not exclusively though).
The thing about these smartcards (and smartcard-like devices) is that you cannot export your keys from them. That’s also one of their features. But, devices can get lost or stolen. You encrypt and sign all your data exclusively using this device, in the event of the device is broken, all of your things are gone, forever.
Technically not forever. Keys can get cracked as the technology develops. But at the time of that happens, we might all be dead, for a long time.
So you should backup your keys.
I personally symmetricly encrypt my already-encrypted OpenPGP keys using two different passphrase, two different technologies. I use AES-256-CBC and PBKDF2 to encrypt my keys. After that, I upload them to AWS S3. Alternatively, I store all my keys to the persistent partition of my Tails USB.
For bills that is less than 0.1$ (10 cents), AWS will get them waived. And that’s perfect for what I am using it for. 10 cents is plenty for key storage.
S/MIME Certificates
Similar to OpenPGP. But since PKCS12 is a thing, I just combine the private key, public key, and the root/intermediate CAs to one PFX file, protected by password. Both of them are uploaded to S3 and also retained offline.
For those of you who are wondering, I loaded the certificate onto my YubiKeys, so generally I won’t touch the copy, through its lifetime.
I never use S/MIME to encrypt anything other than Emails, so usually I just import them to my computer’s keystore. Not a big deal since I’m on Linux.
Certificate Authorities
I personally have two (technically one because the earlier one was deprecated) certificate authorities.
The RSA one was deprecated due to lack of protection, weak password, weak algorithm (DES3, not cracked but not safe at all), as well as potentially leaked private key, made me to give this CA up. We don’t talk about that here.
For the ECC one, I use OpenSSL’s EC function to encrypt the private key using AES-256 directly. The password was generated using openssl rand -base64 128
and was encrypted and stored in the same folder. You need to use my OpenPGP key to decrypt that key.
That’s the Root CA. For the intermediate ones, well, I will admit that I didn’t do much, not even a password. Since I wrote a certificate signing program by myself (the client side send the CSR and requested intermediate to the server via encrypted WebSocket and the server will automatically sign that and return the full-chain certificate. Clients’ private keys are never gone online.), a password protection will do nothing but to add hassle. So… No.
I did backed those keys up though.
Symmetric
Symmetric are A LOT FASTER than asymmetric. Due to the hardware acceleration by basically all modern CPUs (AES-NI instruction).
Usually I don’t encrypt the files using asymmetric cipher. Instead, I generated some random string, use them to encrypt files, calculate the hashes (or sign the original document), get that string and encrypt them use my asymmetric keys, store them to the database and we are good to go.
Since this will require manual intervention (you know, you need to all these steps), I wrote a script. It basically looks like this and you will kinda know what’s happening.
#!/usr/bin/env python3
import sqlite3, os, sys, random
dbfile = "db.sqlite3"
def calc_sha256sum(filename: str, BUF_SIZE=262144): # Read data as 256 kilobytes blocks, to avoid too much RAM consumption.
sha256 = hashlib.sha256()
with open(filename, 'rb') as f:
while True:
data = f.read(BUF_SIZE)
if not data:
break
sha256.update(data)
return sha256.hexdigest()
def gen_random(length: int):
return ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(int(length)))
def store_to_db(encrypted_filename: str, sha256: str, ciphertext: str):
-- snip --
filename = str(sys.argv[1])
plaintext = gen_random(128)
sha256sum = calc_sha256sum(filename)
os.system('gpg --batch --yes --symmetric --cipher-algo AES256 --compression-algo None --passphrase {} "{}"'.format(plaintext, filename))
with open("{}.password".format(filename), 'w') as passwordfile:
passwordfile.write(plaintext) # Dangerous but nevermind
os.system('gpg --armor --encrypt "{}.password"'.format(filename))
os.remove('{}.password'.format(filename))
with open("{}.password.asc".format(filename)) as cipherfile:
ciphertext = cipherfile.read()
os.remove('{}.password.asc'.format(filename))
store_to_db(filename + ".gpg", sha256sum, ciphertext)
sys.exit(0)
Pretty much.
The downside of this solution is that I MUST take GOOD care of this SQLite database. So I just upload to some random cloud storage. That’s fine because the table contents are encrypted anyways.
Conclusion
So this is my solution. Might be implemented in the future. But for now, it works perfectly.