mirror of
https://github.com/octoleo/Purse.git
synced 2024-12-29 12:32:39 +00:00
Version 2. Use symmetric crypto instead of keys. Don't write passwords in plaintext to disk. Choose passwords by character length. Add recommended gpg configuration.
This commit is contained in:
parent
288c4d112d
commit
72443fa6be
@ -8,8 +8,6 @@ This script uses GPG to manage an encrypted text file containing passwords.
|
|||||||
# Requirements
|
# Requirements
|
||||||
Requires `gpg`. Install with `brew install gpg` or `apt-get install gnupg` or build and install it from [source](https://www.gnupg.org/download/index.html).
|
Requires `gpg`. Install with `brew install gpg` or `apt-get install gnupg` or build and install it from [source](https://www.gnupg.org/download/index.html).
|
||||||
|
|
||||||
Requires `srm`. Install with `apt-get install secure-delete`. Already included in OS X.
|
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
git clone https://github.com/drduh/pwd.sh && cd pwd.sh
|
git clone https://github.com/drduh/pwd.sh && cd pwd.sh
|
||||||
@ -24,5 +22,6 @@ Type `r` to print stored passwords. Can be piped to `grep` and `pbcopy` or `xsel
|
|||||||
|
|
||||||
Type `d` to delete a password by Username/ID.
|
Type `d` to delete a password by Username/ID.
|
||||||
|
|
||||||
To reset/erase keys and passwords, `rm pwd.sh.*`.
|
The encrypted file `pwd.sh.safe` can be safely shared between machines over public channels (Google Drive, Dropbox, etc).
|
||||||
|
|
||||||
|
A sample `gpg.conf` configuration file is provided for your consideration.
|
||||||
|
27
gpg.conf
Normal file
27
gpg.conf
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# ~/.gnupg/gpg.conf
|
||||||
|
#
|
||||||
|
# Suggested GnuPG configuration.
|
||||||
|
|
||||||
|
auto-key-locate keyserver
|
||||||
|
|
||||||
|
keyserver hkps://hkps.pool.sks-keyservers.net
|
||||||
|
keyserver-options no-honor-keyserver-url
|
||||||
|
keyserver-options debug
|
||||||
|
keyserver-options verbose
|
||||||
|
|
||||||
|
# sudo curl -o /etc/gpg.pem https://sks-keyservers.net/sks-keyservers.netCA.pem
|
||||||
|
keyserver-options ca-cert-file=/etc/gpg.pem
|
||||||
|
|
||||||
|
personal-cipher-preferences AES256 AES192 AES CAST5
|
||||||
|
personal-digest-preferences SHA512 SHA384 SHA256 SHA224
|
||||||
|
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
|
||||||
|
cert-digest-algo SHA512
|
||||||
|
|
||||||
|
charset utf-8
|
||||||
|
fixed-list-mode
|
||||||
|
no-comments
|
||||||
|
no-emit-version
|
||||||
|
keyid-format 0xlong
|
||||||
|
list-options show-uid-validity
|
||||||
|
verify-options show-uid-validity
|
||||||
|
with-fingerprint
|
227
pwd.sh
227
pwd.sh
@ -1,217 +1,150 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
#
|
#
|
||||||
# pwd.sh
|
# Script for managing passwords in a symmetrically encrypted file using GnuPG.
|
||||||
#
|
|
||||||
# An interface to gpg for managing passwords.
|
|
||||||
|
|
||||||
set -o pipefail
|
set -o errtrace
|
||||||
set -o nounset
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
umask 077
|
|
||||||
|
|
||||||
safe=pwd.sh.safe
|
|
||||||
public=pwd.sh.pub
|
|
||||||
secret=pwd.sh.sec
|
|
||||||
|
|
||||||
del=$(which srm)
|
|
||||||
del_opts=("--force --zero")
|
|
||||||
|
|
||||||
gpg=$(which gpg)
|
gpg=$(which gpg)
|
||||||
gpg_opts=("--no-default-keyring --keyring ./${public} --secret-keyring ./${secret}")
|
safe=pwd.sh.safe
|
||||||
|
|
||||||
name="nobody@pwd.sh"
|
|
||||||
|
fail () {
|
||||||
|
# Print an error message and exit.
|
||||||
|
|
||||||
|
tput setaf 1 ; echo "Error: ${1}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
get_pass () {
|
get_pass () {
|
||||||
# Fancy prompt for fetching a password.
|
# Prompt for a password.
|
||||||
|
|
||||||
unset password
|
unset password
|
||||||
prompt="Password: "
|
prompt="${1}"
|
||||||
while IFS= read -p "$prompt" -r -s -n 1 char
|
while IFS= read -p "${prompt}" -r -s -n 1 char ; do
|
||||||
do
|
if [[ ${char} == $'\0' ]] ; then
|
||||||
if [[ $char == $'\0' ]] ; then
|
break
|
||||||
break
|
|
||||||
fi
|
fi
|
||||||
prompt='*'
|
prompt='*'
|
||||||
password+="$char"
|
password+="${char}"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if [ -z ${password+x} ] ; then
|
||||||
|
fail "No password provided"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
decrypt () {
|
decrypt () {
|
||||||
# Decrypt a gpg-encrypted file with a password.
|
# Decrypt with a password.
|
||||||
|
|
||||||
${gpg} ${gpg_opts} \
|
${gpg} \
|
||||||
--decrypt --armor --batch \
|
--decrypt --armor --batch \
|
||||||
--command-fd 0 --passphrase "${1}" "${2}" \
|
--command-fd 0 --passphrase "${1}" "${2}" 2>/dev/null
|
||||||
2>/dev/null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
encrypt () {
|
encrypt () {
|
||||||
# Encrypt and sign a file with a password.
|
# Encrypt with a password.
|
||||||
|
|
||||||
${gpg} ${gpg_opts} \
|
${gpg} \
|
||||||
--encrypt --armor --sign --batch \
|
--symmetric --armor --batch --yes \
|
||||||
--hidden-recipient "${name}" \
|
|
||||||
--yes \
|
|
||||||
--command-fd 0 --passphrase "${1}" \
|
--command-fd 0 --passphrase "${1}" \
|
||||||
--output "${2}" "${3}" \
|
--output "${2}" "${3}" 2>/dev/null
|
||||||
2>/dev/null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
read_pass () {
|
read_pass () {
|
||||||
# Reads a password.
|
# Read a password from safe.
|
||||||
|
|
||||||
if [ ! -s ${safe} ] ; then
|
if [ ! -s ${safe} ] ; then
|
||||||
echo "Empty safe, no passwords!"
|
fail "No passwords found"
|
||||||
exit 3
|
|
||||||
else
|
else
|
||||||
echo "Enter password to unlock ${safe}."
|
get_pass "Enter password to unlock ${safe}: " ; echo
|
||||||
get_pass ; echo
|
decrypt ${password} ${safe} || fail "Decryption failed"
|
||||||
decrypt ${password} ${safe}
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
create_id () {
|
|
||||||
# Creates a new Username/ID.
|
|
||||||
|
|
||||||
read -p "Username/ID: " id
|
|
||||||
|
|
||||||
read -p "Create random password? (y/n default: y) " rand_pass
|
|
||||||
if [ "${rand_pass}" == "n" ]; then
|
|
||||||
echo "Choose a password for '${id}'."
|
|
||||||
get_pass ; echo
|
|
||||||
user_pass=$password
|
|
||||||
else
|
|
||||||
user_pass=$(gen_pass)
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
write_pass () {
|
|
||||||
# Writes a password to safe.
|
|
||||||
|
|
||||||
echo "Enter password to unlock ${safe}."
|
|
||||||
get_pass ; echo
|
|
||||||
|
|
||||||
# Create a temporary file to decrypt passwords to.
|
|
||||||
# TODO(any): can this be done without writing to disk?
|
|
||||||
tmp_secret=$(mktemp -q /tmp/pwd.sh.XXXXXX)
|
|
||||||
|
|
||||||
# Decrypt safe, exclude specified ID in case of update/removal.
|
|
||||||
if [ -s ${safe} ] ; then
|
|
||||||
decrypt ${password} ${safe} | grep -v " ${id}" > ${tmp_secret}
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Append new password for ID, if one was provided.
|
|
||||||
if [ ! -z ${user_pass+x} ] ; then
|
|
||||||
echo "${user_pass} ${id}" >> ${tmp_secret}
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Encrypt plaintext to safe.
|
|
||||||
encrypt ${password} ${safe} ${tmp_secret}
|
|
||||||
|
|
||||||
# Remove temporary plaintext.
|
|
||||||
${del} ${del_opts} ${tmp_secret}
|
|
||||||
|
|
||||||
echo "Updated password for '${id}' in ${safe}."
|
|
||||||
|
|
||||||
unset id
|
|
||||||
unset password
|
|
||||||
unset user_pass
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
gen_pass () {
|
gen_pass () {
|
||||||
# Generate a random password.
|
# Generate a password.
|
||||||
|
|
||||||
read -p "Password length? (min/avg/max default: max) " pass_length
|
len=40
|
||||||
if [ "$pass_length" == "min" ]; then
|
read -p "Password length? (default: 40, max: 80) " length
|
||||||
len=6
|
|
||||||
elif [ "$pass_length" == "avg" ]; then
|
if [[ ${length} =~ ^[0-9]+$ ]] ; then
|
||||||
len=18
|
len=${length}
|
||||||
else
|
|
||||||
len=36
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
${gpg} --gen-random -a 0 ${len}
|
${gpg} --gen-random -a 0 80 | cut -c -${len}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
create_keys () {
|
write_pass () {
|
||||||
# Create public and private GnuPG keys.
|
# Write a password in safe.
|
||||||
|
|
||||||
echo "Choose a strong master password."
|
# If no password provided, clear the entry by writing an empty line.
|
||||||
get_pass ; echo
|
if [ -z ${userpass+x} ] ; then
|
||||||
|
new_entry=" "
|
||||||
|
else
|
||||||
|
new_entry="${userpass} ${username}"
|
||||||
|
fi
|
||||||
|
|
||||||
${gpg} ${gpg_opts} \
|
get_pass "Enter password to unlock ${safe}: " ; echo
|
||||||
--gen-key --batch <(
|
|
||||||
cat <<EOF
|
|
||||||
Key-Type: RSA
|
|
||||||
Key-Length: 4096
|
|
||||||
Expire-Date: 0
|
|
||||||
Name-Real: ${name}
|
|
||||||
Passphrase: ${password}
|
|
||||||
%commit
|
|
||||||
EOF
|
|
||||||
) 2>/dev/null
|
|
||||||
|
|
||||||
echo "Created keys: ${public} and ${secret}."
|
# If safe exists, decrypt it and filter out username, or bail on error.
|
||||||
|
# If successful, append new entry, or blank line.
|
||||||
unset password
|
# Filter out any blank lines.
|
||||||
|
# Finally, encrypt it all to a new safe file, or fail.
|
||||||
|
# If successful, update to new safe file.
|
||||||
|
( if [ -f ${safe} ] ; then
|
||||||
|
decrypt ${password} ${safe} | \
|
||||||
|
grep -v -e " ${username}$" || return
|
||||||
|
fi ; \
|
||||||
|
echo "${new_entry}") | \
|
||||||
|
grep -v -e "^[[:space:]]*$" | \
|
||||||
|
encrypt ${password} ${safe}.new - || fail "Write to safe failed"
|
||||||
|
mv ${safe}.new ${safe}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
create_safe () {
|
create_username () {
|
||||||
# Create an encrypted "safe" file to store passwords.
|
# Create a new username and password.
|
||||||
|
|
||||||
touch ${safe} ; chmod 0600 ${safe}
|
read -p "Username: " username
|
||||||
echo "Created encrypted safe file: ${safe}."
|
read -p "Generate password? (y/n, default: y) " rand_pass
|
||||||
|
if [ "${rand_pass}" == "n" ]; then
|
||||||
|
get_pass "Enter password for \"${username}\": " ; echo
|
||||||
|
userpass=$password
|
||||||
|
else
|
||||||
|
userpass=$(gen_pass)
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sanity_check () {
|
sanity_check () {
|
||||||
# Make sure all necessary programs are installed and files exist.
|
# Make sure required programs are installed and can be executed.
|
||||||
|
|
||||||
if [[ -z ${gpg} && ! -x ${gpg} ]] ; then
|
if [[ -z ${gpg} && ! -x ${gpg} ]] ; then
|
||||||
echo "GnuPG is not available!"
|
fail "GnuPG is not available"
|
||||||
exit 127
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z ${del} && ! -x ${del} ]] ; then
|
|
||||||
echo "srm is not available!"
|
|
||||||
exit 127
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f ${secret} ] ; then
|
|
||||||
echo "No keys found, creating new keys ..."
|
|
||||||
create_keys
|
|
||||||
else
|
|
||||||
chmod 0600 ${secret}
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f ${safe} ] ; then
|
|
||||||
echo "No safe found, creating new safe file ..."
|
|
||||||
create_safe
|
|
||||||
else
|
|
||||||
chmod 0600 ${safe}
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sanity_check
|
sanity_check
|
||||||
|
|
||||||
read -p "Read, write, or delete a password? (r/w/d default: r) " action
|
read -p "Read, write, or delete a password? (r/w/d, default: r) " action
|
||||||
|
|
||||||
if [ "${action}" == "w" ] ; then
|
if [ "${action}" == "w" ] ; then
|
||||||
create_id
|
create_username && write_pass
|
||||||
write_pass
|
|
||||||
elif [ "${action}" == "d" ] ; then
|
elif [ "${action}" == "d" ] ; then
|
||||||
read -p "Which Username/ID to delete? " id
|
read -p "Username to delete? " username
|
||||||
write_pass
|
write_pass
|
||||||
else
|
else
|
||||||
read_pass
|
read_pass
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
tput setaf 2 ; echo "Done"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user