Automating LUKS Decryption with KeePassXC Secret Service

Managing encrypted external drives often requires a trade-off between security and convenience. By combining udisksctl with the Freedesktop Secret Service API, you can securely store your drive passphrases inside a KeePassXC database and allow local scripts to handle decryption instantly without manual typing.


🔑 Prerequisites

Before the script can look up keys, your password manager must be configured to act as a system keyring daemon.

🛠️ Step 1: Enable Secret Service Integration

Open KeePassXC and navigate to Tools > Settings > General > Secret Service Integration. Check the box for Enable KeePassXC Freedesktop Secret Service integration. If you want to isolate your infrastructure keys, you can optionally choose to expose only a specific database group.

🔍 Step 2: Extract the Drive UUID

Because traditional Linux device letters (like /dev/sdb) can shift depending on the order devices are connected, you must target the drive using its unique, immutable hardware UUID. Run the following command in your terminal to fetch it:

lsblk -dno UUID /dev/sdb

📝 Step 3: Configure the Database Entry

Create a new entry inside your exposed KeePassXC group. Set the Title of the entry to be the exact UUID string returned by the lsblk command above. Place your raw LUKS encryption passphrase in the Password field and save your database.


💻 The Complete Deployment Script

Save the following code block to /home/simon/.local/bin/usb-unlock.sh and ensure it is marked as executable (chmod +x). It features a hybrid execution flow: passing a direct block device path skips straight to background decryption (ideal for bar scripts), while invoking it with list spawns an interactive visual picker.

#!/bin/bash

set -e

ACTION=$1   # list, unlock, mount, unmount, poweroff
DEV=$2      # e.g., /dev/sdb
MAPPER=$3   # e.g., /dev/dm-1

case "$ACTION" in
    list)
        devices=()

        while read -r line; do
            NAME="" FSTYPE="" SIZE="" MODEL=""
            eval "$line"

            if [ "$FSTYPE" = "crypto_LUKS" ]; then
                MAP_COUNT=$(lsblk -pno NAME "$NAME" | wc -l)

                if [ "$MAP_COUNT" -eq 1 ]; then
                    # State 1: Completely locked
                    devices+=("$NAME" "$SIZE" "[Locked] ${MODEL:-Raw LUKS Drive}")
                else
                    # State 2: Unlocked, but check if it's missing a mountpoint
                    CHILD_MAPPER=$(lsblk -pnro NAME "$NAME" | tail -n 1)
                    if [ -z "$(lsblk -dno MOUNTPOINTS "$CHILD_MAPPER")" ]; then
                        devices+=("$NAME" "$SIZE" "[Unlocked] ${MODEL:-Raw LUKS Drive}")
                    fi
                fi
            fi
        done < <(lsblk -Pp -o NAME,FSTYPE,SIZE,MODEL)

        if [ ${#devices[@]} -eq 0 ]; then
            zenity --info --title="Unlock Drive" --text="No action required. All encrypted drives are either locked or already mounted." --width=400 2>/dev/null
            exit 0
        fi

        CHOICE=$(zenity --list --title="Select Encrypted Drive" \
            --text="Select a drive to process:" \
            --column="Device" --column="Size" --column="Status / Model" \
            --width=550 --height=350 \
            "${devices[@]}" 2>/dev/null || true)

        if [ -n "$CHOICE" ]; then
            "$0" unlock "$CHOICE"
        fi
        ;;

    unlock)
        MAP_COUNT=$(lsblk -pno NAME "$DEV" | wc -l)

        # If already unlocked, bypass passphrase step and just mount it
        if [ "$MAP_COUNT" -gt 1 ]; then
            CHILD_MAPPER=$(lsblk -pnro NAME "$DEV" | tail -n 1)
            if [ -z "$(lsblk -dno MOUNTPOINTS "$CHILD_MAPPER")" ]; then
                udisksctl mount -b "$CHILD_MAPPER"
            fi
        else
            # Truly locked -> pull password and decrypt
            UUID=$(lsblk -dno UUID "$DEV")
            PASS=""

            if command -v secret-tool &>/dev/null; then
                PASS=$(secret-tool lookup Title "$UUID" 2>/dev/null)
            fi

            if [ -z "$PASS" ]; then
                PASS=$(kdialog --title "Unlock Drive" --password "KeePassXC lookup failed. Enter passphrase for $DEV" 2>/dev/null)
            fi

            if [ -n "$PASS" ]; then
                if udisksctl unlock -b "$DEV" --key-file=<(printf "%s" "$PASS"); then
                    sleep 0.4
                    CHILD_MAPPER=$(lsblk -pnro NAME "$DEV" | tail -n 1)
                    udisksctl mount -b "$CHILD_MAPPER"
                fi
            fi
        fi
        ;;

    mount)
        TARGET="${MAPPER:-$DEV}"
        udisksctl mount -b "$TARGET"
        ;;

    unmount)
        TARGET="${MAPPER:-$DEV}"
        udisksctl unmount -b "$TARGET"
        ;;

    poweroff)
        if [ -n "$MAPPER" ]; then
            udisksctl unmount -b "$MAPPER" 2>/dev/null || true
        else
            udisksctl unmount -b "$DEV" 2>/dev/null || true
        fi
        udisksctl power-off -b "$DEV"
        ;;
esac

Posted

11:47 AM May 25, 2026