I've been getting a flurry of emails at work, reminding me that my passwords are about to expire on several Unix and Linux machines in our production datacenter. They have a policy in place, where the password much be changed every 90 days, and I have to keep my current password for at least 7 before changing it, and I can't use any password that has been used previously, let alone, the insane requirements for the password. So, rather than fight it, I thought I would make this easy on myself.
First, I'm a big fan of SSH key authentication. Because I'm allowed to use SSH authentication, I have my public SSH key on all the servers in the datacenter. When my password is about to expire, I get an email notice once per day two weeks in advance. I can use this email as an opportunity to execute a script that will change all the passwords on all the servers for me. In the script, I'll have it grab some data from /dev/urandom, and create a sha1sum of the input. An encrypted version of the hash will then be saved locally to disk, which will be encrypted with my GnuPG key, and emailed to myself, should I need the password for something other than SSH. Lastly, just so the password can't be compromised, only the encrypted versions of the password remain on disk. The hashes themselves are shell variables that are cleared when the script exits. Further, I've changed the permissions on my home directory, where my SSH keys and GnuPG keys exist, such that everything sensitive is only accessible to myself. I realize that convenience comes at the sacrifice of a bit of security. My laptop is running full disk encryption, and my password to guard my account is strong. I am the only one on my machine, and I expect it to stay that way. As such, I'm not worried about anything getting compromised.
All of this is stored in a simple shell script, shown below. You will need the "expect" and "sha1sum" packages installed on your system before executing this script. You will need a GnuPG key pair generated for encrypting and decrypting data. You'll need SSH keys created and distributed to each server beforehand. You should probably have your SSH keys added to your SSH agent, as well as your GnuPG key added to a GPG agent before executing the script, to save you some serious typing. I won't cover that here, but Seahorse is a great utility for managing GPG and SSH keys. Of course, your SSH keys and GPG keys should be passphrase protected.
# License: public domain
if [[ -f newpass.gpg ]]; then
mv -f newpass.gpg oldpass.gpg
OLDPASSWD="$(gpg -d oldpass.gpg)"
# Change "Your Name" to fit the user ID that matches in your GPG key
dd if=/dev/urandom count=100 2> /dev/null | sha1sum -b - | \
gpg -ar "Your Name" -e - > newpass.gpg
# Change "firstname.lastname@example.org" to match the email you wish to send this to
cat newpass.gpg | mail -s "Password for servers" email@example.com
NEWPASSWD="$(gpg -d newpass.gpg)"
# Change "server1 server2 sever3" to match the hostnames of the servers you'll loop over
# Change "domain.tld" to match the FQDN for your servers
for host in server1 server2 server3; do
EXPECT=$(expect -c "
spawn ssh $host.domain.tld
expect \"(current) UNIX password: \"
expect \"New UNIX password: \"
expect \"Retype new password: \"
Initially, when I started writing this script, I wanted it to run in cron locally on my laptop. As I began building the script, I realized this wasn't a secure move, for a couple of reasons. First, as already mentioned, I'm using SSH authentication using public key cryptography. All of my SSH keys are passphrase protected. I didn't want to store the passphrase in the script, so I could automate the process, and I didn't want to remove the passphrase or generate new keys that didn't have a passphrase. Further, wanting to encrypt the data, and send it to myself via email required that I store my GnuPG passphrase on disk as well. I didn't like this idea either, as I'm already storing the new and old encrypted passwords on disk from the script, and that's enough. No need to compromise security any further. So, I'll run this script by hand.
However, we have a problem. You will be typing passphrases galore in this script if you have a decent number of hosts to loop through. So, as mentioned, it would probably be best to take advantage of an SSH and GPG agent to cache your passphrases to ease the pain before executing the script.
Looking over the script a bit. First thing to note, is it is sending the same password to every server. You might not want this. If so, feel free to modify the script to fit your needs. Second, the sha1sum hash is never stored on disk. Rather, it's just stored in variables OLDPASS and NEWPASS. The idea between the old passwords and the new passwords, is so we can provide the current password when updating, as well as the old.
We're pulling from /dev/urandom as a source for semi-random data. Yes, you can pull from /dev/random if it makes you sleep better at night. Also, we're not pulling a lot of data, because ultimately, the SHA1 hash will be strong enough as it is. You'll notice too that because we're using STDIN for our data source, the hash contains a space, asterisk and hyphen following the hash, and we're keeping it. I figured no reason to remove it, as spaces, asterisks and hyphens are valid UNIX password characters. If your company has a more draconian password policy than mine does, requiring specifically more than say 3 or 5 non-alphanumeric characters, then just append those to the end of hash before encrypting to disk. Maybe something like the string "!@#$%".
Lastly, we're emailing the encrypted password to ourselves, so no worries about compromising there, plus that gives us an extra backup in case we lose our disk that is storing the encrypted passwords. This also gives flexibility to where we can retrieve the password, provided we have access to the Internet and our GPG keys. Then we're using "expect" to send the passwd command to the server and send our old and new passwords as prompted for each server. You might need to change the expected prompt depending on your GNU/Linux or Unix derivative ("New RedHat password: " for example).
That's it! Simple enough. If you have any questions, or improvements, please post them in the comments. Thanks!