[PATCH] Add -e option in show to encrypt clipboard and help

Emilio Garcia contacto at emiliogarcia.com.ar
Wed Jun 4 02:17:47 UTC 2025


From: danielemiliogarcia <contacto at emiliogarcia.com.ar>

Many modern password managers offer features to protect copied passwords
from being exposed via clipboard sniffers. This patch introduces the `-e`
flag to the `show` command, which copies the encrypted password to the
clipboard instead of the plaintext.

This improves security in environments where clipboard sniffing is a
concern. It is the responsibility of the receiving application to decrypt
the password. The encryption is handled via GPG, using system defaults,
which can optionally integrate with hardware security modules (HSMs).

I use this feature with a browser extension that performs the decryption.
More information:

Chrome extension: https://chromewebstore.google.com/detail/clippass/hnpffflijhlfemngdipepjkjkcgfolpf
Source code: https://github.com/danielemiliogarcia/clippass
Usage demo: https://www.youtube.com/watch?v=kHnr-87B-f0

Signed-off-by: danielemiliogarcia <contacto at emiliogarcia.com.ar>
---
 src/password-store.sh | 79 ++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 74 insertions(+), 5 deletions(-)

diff --git a/src/password-store.sh b/src/password-store.sh
index 22e818f..805282b 100755
--- a/src/password-store.sh
+++ b/src/password-store.sh
@@ -197,6 +197,72 @@ clip() {
 	echo "Copied $2 to clipboard. Will clear in $CLIP_TIME seconds."
 }
 
+clipenc() {
+    local plaintext="$1"
+    if [[ -z $plaintext ]]; then
+        die "Error: No plaintext provided for encryption."
+    fi
+
+    # Determine GPG recipients dynamically
+    set_gpg_recipients ""  # Use the top-level .gpg-id or the appropriate directory
+    if [[ ${#GPG_RECIPIENTS[@]} -eq 0 ]]; then
+        die "Error: No GPG recipients found. Please run 'pass init' first."
+    fi
+
+    # Prepare GPG recipient arguments
+    local gpg_recipients=("${GPG_RECIPIENT_ARGS[@]}")
+
+    # Handle clipboard tools for Wayland and X11
+    if [[ -n $WAYLAND_DISPLAY ]]; then
+        local copy_enc_cmd=( bash -c "echo -n \"$plaintext\" | gpg --encrypt --armour ${gpg_recipients[*]} | wl-copy" )
+		local copy_cmd=( wl-copy )
+        local paste_cmd=( wl-paste -n )
+        [[ $X_SELECTION == primary ]] && copy_enc_cmd+=( --primary ) && paste_cmd+=( --primary )
+        local display_name="$WAYLAND_DISPLAY"
+    elif [[ -n $DISPLAY ]]; then
+        local copy_enc_cmd=( bash -c "echo -n \"$plaintext\" | gpg --encrypt --armour ${gpg_recipients[*]} | xclip -selection $X_SELECTION" )
+		local copy_cmd=( xclip -selection "$X_SELECTION" )
+        local paste_cmd=( xclip -o -selection $X_SELECTION )
+        local display_name="$DISPLAY"
+    else
+        die "Error: No X11 or Wayland display detected"
+    fi
+
+    # Prevent conflicts with existing clipboard sleep processes
+    local sleep_argv0="password store sleep on display $display_name"
+    pkill -f "^$sleep_argv0" 2>/dev/null && sleep 0.5
+
+    # Store previous clipboard contents
+    local before="$("${paste_cmd[@]}" 2>/dev/null | $BASE64)"
+
+    # Execute the copy encrypted command
+    "${copy_enc_cmd[@]}" || die "Error: Could not encrypt data and copy to the clipboard"
+	# we can't re-encrypt to compare, cause every encryption will be different as it uses random nonces, so we will backup the value
+	local what_was_put_in_clip="$("${paste_cmd[@]}" | $BASE64)"
+
+    # Manage clipboard history and auto-clear
+	# Run in a disowned subprocess that sends outputs to /dev/null
+    (
+		( exec -a "$sleep_argv0" bash <<<"trap 'kill %1' TERM; sleep '$CLIP_TIME' & wait" )
+		local now="$("${paste_cmd[@]}" | $BASE64)"
+
+		if [[ $now != "$what_was_put_in_clip" ]]; then
+			# Clipboard content changed, so we assume the user has manually copied something else
+			# Do not restore clipboard content
+			before="$now"
+		fi
+
+		# Clear clipboard history (if supported by KDE Klipper)
+		qdbus org.kde.klipper /klipper org.kde.klipper.klipper.clearClipboardHistory &>/dev/null
+
+		# Restore previous clipboard contents
+		echo "$before" | $BASE64 -d | "${copy_cmd[@]}"
+    ) >/dev/null 2>&1 & disown
+
+    echo "Encrypted data copied to clipboard for recipients: ${GPG_RECIPIENTS[*]}"
+    echo "Will clear in $CLIP_TIME seconds."
+}
+
 qrcode() {
 	if [[ -n $DISPLAY || -n $WAYLAND_DISPLAY ]]; then
 		if type feh >/dev/null 2>&1; then
@@ -284,8 +350,8 @@ cmd_usage() {
 	        List passwords.
 	    $PROGRAM find pass-names...
 	    	List passwords that match pass-names.
-	    $PROGRAM [show] [--clip[=line-number],-c[line-number]] pass-name
-	        Show existing password and optionally put it on the clipboard.
+	    $PROGRAM [show] [--clip[=line-number],-c[line-number]] [--enc[=line-number],-e[line-number]] [--qrcode[=line-number],-q[line-number]] pass-name
+	        Show existing password and optionally: put it on the clipboard or put it encrypted on the clipboard or show it as a QR code.
 	        If put on the clipboard, it will be cleared in $CLIP_TIME seconds.
 	    $PROGRAM grep [GREPOPTIONS] search-string
 	        Search for password files containing search-string when decrypted.
@@ -367,23 +433,24 @@ cmd_init() {
 
 cmd_show() {
 	local opts selected_line clip=0 qrcode=0
-	opts="$($GETOPT -o q::c:: -l qrcode::,clip:: -n "$PROGRAM" -- "$@")"
+	opts="$($GETOPT -o q::c::e:: -l qrcode::,clip::,enc:: -n "$PROGRAM" -- "$@")"
 	local err=$?
 	eval set -- "$opts"
 	while true; do case $1 in
 		-q|--qrcode) qrcode=1; selected_line="${2:-1}"; shift 2 ;;
 		-c|--clip) clip=1; selected_line="${2:-1}"; shift 2 ;;
+		-e|--enc) enc=1; selected_line="${2:-1}"; shift 2 ;;
 		--) shift; break ;;
 	esac done
 
-	[[ $err -ne 0 || ( $qrcode -eq 1 && $clip -eq 1 ) ]] && die "Usage: $PROGRAM $COMMAND [--clip[=line-number],-c[line-number]] [--qrcode[=line-number],-q[line-number]] [pass-name]"
+	[[ $err -ne 0 || ( $qrcode -eq 1 && $clip -eq 1 ) ]] && die "Usage: $PROGRAM $COMMAND [--clip[=line-number],-c[line-number]] [--enc[=line-number],-e[line-number]] [--qrcode[=line-number],-q[line-number]] [pass-name]"
 
 	local pass
 	local path="$1"
 	local passfile="$PREFIX/$path.gpg"
 	check_sneaky_paths "$path"
 	if [[ -f $passfile ]]; then
-		if [[ $clip -eq 0 && $qrcode -eq 0 ]]; then
+		if [[ $clip -eq 0 && $qrcode -eq 0 && $enc -eq 0 ]]; then
 			pass="$($GPG -d "${GPG_OPTS[@]}" "$passfile" | $BASE64)" || exit $?
 			echo "$pass" | $BASE64 -d
 		else
@@ -392,6 +459,8 @@ cmd_show() {
 			[[ -n $pass ]] || die "There is no password to put on the clipboard at line ${selected_line}."
 			if [[ $clip -eq 1 ]]; then
 				clip "$pass" "$path"
+			elif [[ $enc -eq 1 ]]; then
+				clipenc "$pass" "$path"
 			elif [[ $qrcode -eq 1 ]]; then
 				qrcode "$pass" "$path"
 			fi
-- 
2.25.1




More information about the Password-Store mailing list