Subkeys exported with --export-secret-keys will be imported successfully with --import but the subkeys will not actually work. If GnuPG's input is provided via standard input then it fails to run versions of pinentry that require use of the terminal. The error reported by GnuPG doesn't clearly indicate that pinentry failed and instead it reports that the secret key is missing.
For pinentry-tty, the error message is:
gpg: public key decryption failed: Invalid IPC response gpg: decryption failed: No secret key
For pinentry-curses, the error message is:
gpg: public key decryption failed: Inappropriate ioctl for device gpg: decryption failed: No secret key
The latter is the more useful message because it reveals that pinentry-curses tried to use the input file as a terminal. Using strace shows this (ioctl on fd 0):
ioctl(0, TCGETS, 0x7ffc0fba08d0) = -1 ENOTTY (Inappropriate ioctl for device)
Below is a script demonstrates the issue using --batch, but I can reproduce it via regular, interactive GnuPG commands. In my script I just use different homedirs to simulate different hosts. Also, I'm using Curve25519 so the example runs faster, but the bug isn't limited to this key type.
A host named "keygen" generates a key and exports it. Another host named "friend" encrypts a message. A third host named "import" imports the secret key and decrypts the message.
#!/bin/sh set -e gpg="gpg --quiet --homedir . --batch" keygen="$(mktemp -d "keygen-XXXXXX")" friend="$(mktemp -d "friend-XXXXXX")" import="$(mktemp -d "import-XXXXXX")" chmod 700 "$keygen" "$friend" "$import" cleanup() { rm -rf -- "$keygen" "$friend" "$import" } trap cleanup INT TERM EXIT pass="$1" ( cd "$keygen" $gpg --passphrase '' --quick-generate-key x ed25519 default none fpr="$($gpg --list-keys --with-colons | awk -F: '/^fpr/{print $10; exit}')" $gpg --passphrase "$pass" --quick-add-key "$fpr" cv25519 default none $gpg --list-secret-keys $gpg --export >../public.gpg echo $pass | $gpg --passphrase-fd 0 --pinentry-mode loopback \ --export-secret-keys >../secret.gpg ) ( cd "$friend" $gpg --import ../public.gpg echo "Let's meet at midnight" \ | $gpg --trust-model always --encrypt --recipient x >../msg.gpg ) ( cd "$import" echo $pass | $gpg --passphrase-fd 0 --pinentry-mode loopback \ --import ../secret.gpg $gpg --list-secret-keys $gpg --decrypt <../msg.gpg )
When the script is run without arguments, the subkey is unprotected. In this case everything works fine:
$ sh example.sh keygen-UBWzBs/pubring.kbx -------------------------------------------------------------------------------- sec ed25519 2019-08-12 [SC] 6DFDBB761EE6AED9F1303E1139FC599588A3C825 uid [ultimate] x ssb cv25519 2019-08-12 [E] import-hKIkzV/pubring.kbx -------------------------------------------------------------------------------- sec ed25519 2019-08-12 [SC] 6DFDBB761EE6AED9F1303E1139FC599588A3C825 uid [ unknown] x ssb cv25519 2019-08-12 [E] Let's meet at midnight
When a passphrase is set and pinentry is configured as pinentry-tty or pinentry-curses:
$ sh example.sh foo keygen-UBWzBs/pubring.kbx -------------------------------------------------------------------------------- sec ed25519 2019-08-12 [SC] 422C5F8B9CBDF139C765B60774C46E98E616C620 uid [ultimate] x ssb cv25519 2019-08-12 [E] import-hKIkzV/pubring.kbx -------------------------------------------------------------------------------- sec ed25519 2019-08-12 [SC] 422C5F8B9CBDF139C765B60774C46E98E616C620 uid [ unknown] x ssb cv25519 2019-08-12 [E] gpg: decryption failed: No secret key
It tries to prompt for the protection passphrase on the --decrypt line but fails.