Secrets in the shell

Posted (last modified Tue 25 June 2024) in code crypto hash sha512 bash cli ccrypt

Ever since I started using the pass CLI as my password manager, I've found myself putting all sorts of stuff in there; usernames, email, urls, crypto addresses, api keys, you name it.

It makes total sense that some of these items are in there. For example, I store the url to a service together with the password, usually accompanied by the username and the email used [1]. No password recoveries needed.

However, at some point I also started putting in things like crypto addresses, or even token smart contract addresses in there.

That seemed less of a good fit.

One thing is that it spams the password directory.

Another, perhaps more sinister, issue is that it's pretty clear for anyone reading the directory what items you are storing data for.

So what if I want to store key/value pairs, and at the same time I want to hide what I am storing?

Hiding in plain cipher text

A simple solution is to hash the key together with some passphrase as "salt", along the lines of this:

key=$1
# read passphrase from stdin
stty -echo
echo -n "passphrase: "
read passphrase
stty echo
hash_key() {
        # dump the passphrase (assuming mktemp stores in volatile memory)
        ktt=$(mktemp)
        kt=$(mktemp)
        chmod 200 $kt
        echo $passphrase > $kt
        chmod 600 $kt
        # the salted key; first add the hashed passphrase (sha512)
        kc=$(sha512sum $kt | awk '{print $1;}' > $ktt)
        # remove the stored passphrase
        shred $kt
        # then add the key to the mix, and hash again
        echo $kp >> $ktt
        kc=$(sha512sum $ktt | awk '{print $1;}')
}

The kc variable at the end of the script above now contains the salted hash of the key.

Specifically, in this implementation, given key "foo" and passphrase "bar", the resulting key will be:

a9454592edbed78c3abb739dd88567bc92cc4ea1feee8480c7204d48d7d0e9ce86f9c79d55e39751ceb3f2774913841056293a5e8e8f440a3f281dffabd6f540.

Added value

Now we can encrypt the store the corresponding value, and store it in a file that has that literal hash as the key.

Using ccrypt as an example:

data_dir=$HOME/.obfuscated
key=$1
vp=$2
echo -n "$vp" > $t
# create a file for ccrypt to read, because supplying any other way in shell is not so safe.
passfile=$(mktemp)
chmod 600 $passfile
echo -n $passphrase > $passfile
chmod 400 $passfile
ccrypt -k $passfile $t
shred $passfile
if [ "$?" -gt "0" ]; then
        >&2 echo set key fail
        exit 1
fi
# run the code in the previous section
hash_key
mkdir -vp $data_dir
cp $t.cpt $data_dir/$kc
shred $t.cpt

At the end of this operation, the encrypted value is stored in a file whose name is the salted hash of the key.

Key Mastering

Let's say you want to store some Ethereum addresses that are of interest to you, but should be of interest to noone else.

So, let's choose a prefix for "Ethereum addresses." Let it be etha

Next, let's pick an identifier that we will remember, that describes the purpose of the address. In this case it's for "governance of the organization 'Xy Zzy Co.'". The key then becomes ethagovxyzzyco.

Last, for a bit of extra fun, let's add some random stuff at the end of the string, like shilling!shilling?oh,shilling. Thus, we finally end up with ethagovxyzshilling!shilling?oh,shilling`.

What we need to memorize here is:

  • whenever referring to an Ethereum address. we use etha
  • whenever referring to governance. we use gov
  • whenever referring to an entity, we use the name of the organization less whitespace and special characters.
  • the three above are added one after another
  • add the extra fun

There will of course always be a pattern to how you create and add similar structures for other categories, actions and entities.

But the point here is to show that it doesn't take that much to have some advantage of plausible deniability: Even faced with the 5 Dollar Wrench Problem, if you are able to hold your ground, there is no feasible way to prove that you don't own something. Bite the bullet, absorb the pain, deny ownership, and you'll be fine.

Perils and pitfalls

None of this is in no way safe by itself.

Some concerns are named below. Surely there are others, too.

Out of sight, but still mined

You may find it a pain in the ass to type the passphrase for the key salt every time.

An obvious workaround for that is to store the passphrase in a file.

But keep in mind that, if you do that, the key obfuscation is going to be less safe than your regular passwords storage.

That is to say: If the attacker can read your passwords ciphertext, most likely the attacked can read you obfuscation passphrase, too.

Out of mind, out of luck

Maybe the most important thing to realize in this solution is that there is no way to recall the original keys from the file names under which the values are stored.

Instead, the idea is that you decide on a naming scheme for certain crucial values you want to store, and remember how to reconstruct them. [2]

Shell shill

The code above has been adapted from a bash tool I've already written for the purpose, given the name Clortho.

Shell script is surely a very poor choice for this kind of thing, but at the time I simply wanted to write shell so that's how it ended up.

I probably will rewrite it for another environment at some point.

But for now it illustrates the point.

[1]I use a different email for each service I sign up to, and for every other context I have to leave my email for something. I highly recommend this practice, aswell as running your own mail server. It really isn't that hard.
[2]I'm not going to lie: That kind of thing takes practice. As in, you need to remind yourself from time to time what your scheme is. Perhaps that is an unthinkable proposition in the age of the Smartphone and the illusion of auxiliary instant truths. But I come from a time where I knew all numbers and addresses I needed to know by heart. The more you do it, the more you remember. The less you do it, the more you are dependent on others. What others do you depend on? And so on it goes...