Linux login and encrypted directory

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, EncFS and TPM

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.

Using EncFS

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.

PAM-script

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

%s

" "$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

Optional TPM2 encryption of the EncFS password

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.

Changes for TPM2 4.0 (Debian Bullseye)

# 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 $home/.private/.tpm2) if tpm2_getcap handles-persistent | grep -- "^- $hd$"; then pw=$(tpm2_unseal -c "$hd" -p "$PAM_AUTHTOK") .

What you can do with it

Protect your private RSA key

cd mv .ssh/id_rsa private ln -s ../private/id_rsa .ssh/

Protect the firefox password encryption key

mv .mozilla/firefox/*.default/key4.db private ln -s ../../../../private/key4.db .mozilla/firefox/*.default/

References

PAM-script

EncFS

tpm2-tools

easylang.online/blog