Passwords are dangerous
Identity on a multi-tenant service, at some point in time, requires you to prove your identity in some form. Passwords are supposed to be restricted knowledge which should be possessed by only a single person. That’s the ideal case and is also true in some cases. With the expanding online lives of humans and dependence on multi-tenant platforms, it’s becoming harder and harder for humans to keep track of that “restricted” knowledge. It’s a very risky practice to use the same passwords at multiple locations for obvious reasons. In case one platform is compromised, by extension, the other is too. It’s also not good to use similar or derivable passwords depending on the platform.
Passwords were meant for machines not humans
Creating and remembering complicated and distinct passwords are not something which humans are good at. Humans are good at deriving and observing patterns, something that is not good for password creation.
Solution to passwords
Machines are dumb too, they can only function according to the instructions they were giving which makes them not a good candidate for generating passwords. But over the years, machines have got a lot better in generating pseudo-randomness for cryptographic purposes. This article will refer to password generation and random number generation interchangeably because they are essentially all bits and can be interpreted in any fashion.
So, let the machines generate passwords for your accounts and distinct and random passwords with no correlations. Easier said than done.
Platforms are always on the look for methods to generate streams of bits with as much entropy as possible. Depending on what the password will be used for, their sources may differ to provide stronger guarantees for randomness. The random number generation algorithms can be as simple as using a complex function over a fixed seed or using environmental events to generate randomness. A list of generators are present at Wikipedia - List of random number generators.
One of the simplest sources is /dev/urandom
which is a block device serving as an interface to the kernel’s random number
generator with a little lesser entropy guarantees.
$ head -c 16 /dev/urandom | base64
$ head -c 16 /dev/urandom | xxd -ps
$ LC_ALL=C bash -c 'head /dev/urandom | tr -dc a-z0-9 | head -c16'; echo
All of these mechanisms use /dev/urandom
for generating entropy and then
transforming it to a more usable format. For stronger guarantees, /dev/random
might also be used.
We have strong passwords, what next
We have strong passwords now which are not even harder to remember. That’s not good! You would just end up writing the passwords on a Post It note which is even worse.
The thought is not bad actually, instead of storing it on a Post It note, you could put it into a file and then protect the file using another password. This means, you only have to remember one strong password and others are protected using that password.
Congratulations, we have decoded the basic mechanisms for
Not to forget, the password managers in browsers.
Password management, the old school way
There are 3 parts to managing the passwords for the modern day,
- Generate a strong password for every login
- Protect all passwords using a strong password like a password manager
- Sync those encrypted passwords across all devices and have a backup for disaster management.
Most of the password managers offer the above features, but what if we could do it ourselves.
Enter man pass(1)
or
zx2c4/pass
which is a UNIX-style
password management CLI tool which offers all the above features.
Using pass for your password workflow
Installation instructions are mostly as-is on the man page for the utility but there are a few things to keep in mind.
Prerequisites
The utility requires gnupg2
to be
installed and configured on the host. Generally GnuPG
can be installed using
the standard package managers.
Once GPG is setup, you will need to generate a key for pass
to use.
$ gpg --full-generate-key
This would prompt for
- What algorithm to use, you could use
ECC (sign and encrypt)
since we need to encrypt the passwords. - Algorithm parameters, for ECC, the curve to be used, you could use
Curve 25519
- Expiry for the key
Once this is done, an identity should be provided
- Full Name, eg
Foo Bar
- Email, eg
foo@bar.com
This would have the identity Foo Bar <foo@bar.com>
Note: You will need to remember the password used to encrypt the gpg key itself. If you loose that, it won’t be possible to recover the passwords.
Once the key is generated, list the keys as such
$ gpg --list-secret-keys
sec ed25519 2021-10-27 [SC] [expires: 2022-10-27]
545C94506B84898B3AD27D0682FCD8F82659B1DD
uid [ultimate] Foo Bar <foo@bar.com>
ssb cv25519 2021-10-27 [E] [expires: 2022-10-27]
Install an xclip
,
pbcopy
or an equivalent tool
for clipboard manipulation.
Installation and initial setup
Installation for pass
is again fairly simple and is available on all major
platforms using their default package managers. Refer to the official documentation
for the steps.
To initialise the pass utility
$ pass init "your-gpg-key-id"
This step initialises pass and tells it which key to use when encrypting or decrypting your secrets. For example, we want to use the key generated above, we would have
$ pass init 545C94506B84898B3AD27D0682FCD8F82659B1DD
This should create a file ~/.password-store/.gpg-id
with the keyId as the
contents.
At this point, pass
is ready to be used. But before that, we need to address
the third requirement.
Syncing passwords
In order to sync the passwords or have access to the passwords on a different device, you will need to perform the following actions.
According to the documentation, we would be using github
as our storage
backend as pass
uses git
to manage the password history which makes it
easier to rollback and keep track of when were passwords altered or added.
$ pass git init
$ pass git remote add origin https://github.com/foo/bar
This would convert the ~/.password-store
as a git repository pointing to the
github URL https://github.com/foo/bar.git
. Any other git backend also can be
used as it delegates the source control to the git
.
You will want to pull before adding a password and push after adding a password in order to avoid conflicts.
$ pass git pull
$ # update passwords
$ pass git push
This will push all the changes to the git repository. Make sure to have the git repo as private.
You have the encrypted passwords, now you need the gpg keys that were used to encrypt it.
$ gpg --export-secret-key 545C94506B84898B3AD27D0682FCD8F82659B1DD > pass.asc
This will export your key and will be encrypted with the creation password so it can be hosted on a non-secure channel, either the same git repo or any other location.
We are locked and loaded
Once all these steps are done, we are good to store passwords and take control of our own security.
$ pass git pull
# Insert password
$ pass insert example.com/username
# Get a password, make sure to use with a `-c` flag to write to clipboard rather
# than stdout
$ pass -c example.com/username
# edit passwords
$ pass edit example.com/username
# delete passwords
$ pass rm example.com/username
# copy passwords to a different folder
$ pass cp exmaple.com/username foobar.com/username
# move passwords to a different folder
$ pass mv example.com/username example.com/newusername
$ pass mv example.com/username personal/example.com/username
$ pass git push
Refer to the man page for pass
for more detailed usage examples. The man page
has a convention of storing the password as {{ website }}/{{ username }}
but
you are free to organize it as you want.
Setting up pass on a different machine
The above procedure sets up the frame to use pass
on a different machine.
Actual steps to do it are
Setup GnuPG
Download the exported secret key pass.asc
on the machine and then import the
key using.
$ gpg --import pass.asc
Setup pass
The pass
utility needs to be installed on the new host and then setup using
the same gpg key id as found in ~/.password-store/.gpg-id
. Point the git repo
to the same git repo and pull.
$ pass init "same-gpg-key-id"
$ pass git remote add origin https://github.com/foo/bar
$ pass git pull origin master
And that’s it!
Getting to the guts
Note: I went towards understanding the utility backwards without reading the implementation and trying to reverse-engineer the utility, so please bear with me.
Manually reading
If you inspect the folder ~/.password-store
, you will see a filesystem
structure same as the way passwords were stored and how pass list
shows.
$ file ~/.password-store/example.com/username.gpg
Since it’s a wrapper over GPG, it can be decrypted using gpg
itself,
$ gpg --decrypt ~/.password-store/example.com/username.gpg
This should give out the correct password cleanly.
Manually writing
If we can read through the passwords using the standard gpg
utility, we should
be able to write as well. And that’s true!
$ echo "password" > /tmp/pass.txt
$ gpg --encrypt -o ~/.password-store/example.com/newusername.gpg -r "Foo Bar <foo@bar.com" /tmp/pass.txt
$ pass example.com/newusername
Note that we need to supply the identity of the key here which can again be
found using the gpg --list-secret-keys
command.
Where I lost 2 hours of my life
This would all work easily in the regular world. But… but but but, when have versions spared us. GnuPG is no exception to this.
I had initialised the passwords using
$ gpg --version
gpg (GnuPG) 2.3.3
libgcrypt 1.9.4
And was trying to decrypt on a linux (Ubuntu) machine. Everything worked, except
the decryption of existing passwords. gpg
would not throw any error, even the
exit code was 0.
Initially I assumed that it wasn’t writing the password on stdout which would be
a nice thing to do, but that too wasn’t the case. I tried using the -v
switch
and got some more information
gpg: public key is XXXX
gpg: using subkey XXXX instead of primary key XXXX
And that’s it, nothing more.
Tried using the -vvv
switch to get more information, I would get an error
message
gpg: public key is XXXX
gpg: using subkey XXXX instead of public key XXXX
gpg: public key encrypted data; good DEK
:unknown package: type 20, length 95
<Some hex data>
And then nothing!
The breakthrough
In the process of hunting for this error, I came across the issue
mozilla/sops #896
which seemed
to have a similar issue and had mentioned incompatibility between v2.3.x and
v2.2.x.
The version information for the linux system,
$ gpg --version
gpg (GnuPG) 2.2.4
libgcrypt 1.8.1
Bingo! Now time to test the above issue. Docker to the rescue,
I pulled the image vladgh/gpg
with a
version 2.3 on the linux system, followed the instructions on the page,
replaced the pass
command with the following in ~/.bash_profile
,
pass() {
docker run --rm --it \
-v /home/$USER/.gnupg:/root/.gnupg \
-v /home/$USER/.password-store/:/root/.password-store/ \
-e GPG_TTY=/dev/console vladgh/gpg -d "/root/.password-store/$1.gpg"
}
And, voila, it works almost as good as pass but only for decryption! The only downside is that it won’t honor the GPG password cache and will ask for the GPG key’s password.
Transfer my passwords
Now that we have a way to manually push passwords into pass, we could now insert
passwords from other stores. For example, I was trying to import logins from my
Firefox browsers which has a convenient way to export all logins into a CSV
file. That CSV file can be cleaned and then a simple export gpg
command should
work.
insert_password() {
website=$1;shift;
username=$1;shift;
password=$1;shift;
echo $password > /tmp/pass.txt
folder="~/.password-store/$website"
mkdir -p $folder
output="$folder/$username.gpg"
rm -f $output
gpg --encrypt -o $output \
-r "Foo bar <foo@bar.com>" \
/tmp/pass.txt
}
This function can be called from within a script as
insert_password "example.com" "username" "password"
And would serve as a batch update.
Community plugins
pass
is a really extensible tool and allows users to write extensions for it
to add more functionality. The repo
tijn/awesome-password-store
has a list of community built extensions which provide some interesting
functionality, including the one for bulk importing, GUI interfaces, browser
plugins and much more.
Note to the readers
In case something is very obvious about GPG that I didn’t understand which lead me to run around like a crazy noob, please feel free to use the reddit link. This was more of a reverse engineering style of journey without reading the source or much about GPG.
Discussion link: /r/cybersecurity passwords over GnuPG