The traditional method for this is to hash the password you entered when logging in and compare it with the password in /etc/shadow and also use that password to decrypt a directory or partition.
PAM script can simplify this: you will decrypt the directory with the logon password (using EncFS), and if that works, you will be logged in immediately. /etc/shadow is no longer needed.
To prevent brute force attacks on the (disassembled) hard disk, the password must be good. Since a long password is hard to remember, you can keep it safe with TPM. A short password is sufficient for this as the TPM hardware prevents brute force attacks.
With the PAM module PAM-script you can control the Linux login with a shell script.The following script makes a login dependent on the successful decryption of a directory encrypted with EncFs. The EncFs password can be sealed using TPM2.
The following configuration is tested for Debian Buster with the MATE Desktop.
Debian packages needed: encfs, libpam-script
At first login any password can be used. With this password the EncFS encrypted directory .private with the “plain”-directory private will be created.
At next login .private will be decrypted by the given login password to private. If decryption succeeds, the login succeeds. The old password in /etc/shadow can then be deleted.
as root:
cd /usr/share/libpam-script/
for i in auth passwd ses_close ses_open; do
ln -s /etc/kalinx/pam_script_encfs pam_script_$i
done
mkdir -p /etc/kalinx
/etc/kalinx/pam_script_encfs
#!/bin/sh
test "$PAM_USER" = root && exit 1
log() {
out=$ka/log
test $id != 0 && out=/run/user/$id/kalinx.log
echo $1 >>$out
}
ka=/run/kalinx
id=$(id -ur)
name=$(basename $0)
#----------------------------------------------
if test $name = pam_script_auth; then
log "AUTH $PAM_USER - $(date) - $id - $PAM_SERVICE"
if test -e $ka/md_$PAM_USER; then
# screensaver or another login
read s md x <$ka/md_$PAM_USER
mdx=$(echo "$s$PAM_AUTHTOK" | md5sum | cut -c1-32)
test $mdx = "$md" && exit 0
exit 1
fi
test $id != 0 && exit 1
mkdir -p $ka/
id -u "$PAM_USER" >/dev/zero 2>&1 || exit 1
hm=$(eval echo ~$PAM_USER)
runuser $PAM_USER -c "cd;mkdir -p private .private"
encpw=$PAM_AUTHTOK
if test -f $hm/.private/.tpm2; then
hd=$(cat $hm/.private/.tpm2)
if tpm2_listpersistent | grep ":$hd "; then
pw=$(tpm2_unseal -H "$hd" -P "$PAM_AUTHTOK")
if test "$pw"; then
log "TPM unsealed encfs password"
encpw=$pw
x=tpm
else
log "TPM unseal failed"
fi
fi
fi
echo "$encpw" |
runuser $PAM_USER -c "encfs -S --standard $hm/.private/ $hm/private"
if test -r $hm/private; then
log "encfs mount failed"
exit 1
fi
echo 0 > $ka/cnt_$PAM_USER
s=$(dd if=/dev/urandom count=8 bs=1 2>/dev/null | base64)
md=$(echo "$s$PAM_AUTHTOK" | md5sum | cut -c1-32)
umask 077
echo $s $md $x>$ka/md_$PAM_USER
chown $PAM_USER $ka/md_$PAM_USER
#----------------------------------------------
elif test $name = pam_script_passwd; then
log "PASSWD $PAM_USER - $(date) - $id - $PAM_SERVICE"
test $id = 0 && exit 1
read s md x <$ka/md_$PAM_USER
hm=$(eval echo ~$PAM_USER)
if test "$x" = tpm; then
log "TPM2 password change ist not implemented yet"
exit 1
else
printf "%s\n%s\n" "$PAM_OLDAUTHTOK" "$PAM_AUTHTOK" |
encfsctl autopasswd $hm/.private
test $? != 0 && exit 1
fi
s=$(dd if=/dev/urandom count=8 bs=1 2>/dev/null | base64)
md=$(echo "$s$PAM_AUTHTOK" | md5sum | cut -c1-32)
umask 077
echo $s $md $x >/run/kalinx/md_$PAM_USER
#----------------------------------------------
elif test $name = pam_script_ses_open; then
test $PAM_SERVICE = systemd-user && exit 1
test -e /run/kalinx/cnt_$PAM_USER || exit 0
log "OPEN $PAM_USER - $(date) - $id - $PAM_SERVICE"
n=$(cat /run/kalinx/cnt_$PAM_USER)
echo $(($n+1)) > /run/kalinx/cnt_$PAM_USER
elif test $name = pam_script_ses_close; then
test -e /run/kalinx/cnt_$PAM_USER || exit 0
log "CLOSE $PAM_USER - $(date) - $id - $PAM_SERVICE"
n=$(($(cat /run/kalinx/cnt_$PAM_USER) - 1))
echo $n > /run/kalinx/cnt_$PAM_USER
if test $n = 0; then
hm=$(eval echo ~$PAM_USER)
fusermount -u $hm/private
rm /run/kalinx/*_$PAM_USER
log "CLOSE cleaned"
fi
fi
exit 0
chmod +x /etc/kalinx/pam_script_encfs
Debian package needed: tpm2-tools
We use TPM to keep the long EncFs password safe. For TPM a short handy password is sufficient, because TPM protects against bruteforce and dictionary attacks.
as root:
tpm2_createprimary -H o -g sha1 -G rsa -C /tmp/prim.ctx
tpm2_create -g sha256 -G keyedhash -u /tmp/o.pub -r /tmp/o.priv -c /tmp/prim.ctx -I - -K password
long_and_good_encfs_password
<Ctrl>D
tpm2_load -c /tmp/prim.ctx -u /tmp/o.pub -r /tmp/o.priv -C /tmp/load.ctx
tpm2_evictcontrol -A o -c /tmp/load.ctx -S 0x81000019
tpm2_unseal -H 0x81000019 -P password # test decrypt
history -c ; history -r # clear history
as regular user:
$ cd
$ mkdir -p .private
$ echo 0x81000019 >.private/.tpm2
You can then log in using either the EncFs password or the TPM2 password. The passwd command can only change the EncFS password.
# tpm2_createprimary -c /tmp/prim.ctx
# tpm2_create -C /tmp/prim.ctx -i - -p passw -u /tmp/o.publ -r /tmp/o.priv
long_and_good_encfs_password
<Ctrl>D
# tpm2_load -C /tmp/prim.ctx -r /tmp/o.priv -u /tmp/o.publ -c /tmp/load.ctx
# tpm2_evictcontrol -c /tmp/load.ctx 0x81000019
# tpm2_unseal -p passw -c 0x81000019
/etc/kalinx/pam_script_encfs
.
hd=$(cat $hm/.private/.tpm2)
if tpm2_getcap handles-persistent | grep -- "^- $hd$"; then
pw=$(tpm2_unseal -c "$hd" -p "$PAM_AUTHTOK")
.
cd
mv .ssh/id_rsa private
ln -s ../private/id_rsa .ssh/
mv .mozilla/firefox/*.default/key4.db private
ln -s ../../../../private/key4.db .mozilla/firefox/*.default/