Skip to content

Instantly share code, notes, and snippets.

@wmertens
Last active June 15, 2025 10:29
Show Gist options
  • Save wmertens/c4f2c4186c04dc5442bbe3396f2c12f6 to your computer and use it in GitHub Desktop.
Save wmertens/c4f2c4186c04dc5442bbe3396f2c12f6 to your computer and use it in GitHub Desktop.
ssh-crypt: bash function to encrypt using ssh-agent and openssl - see also comments below for other versions
#!/usr/bin/env bash
#
# ssh-crypt
#
# Bash function to encrypt/decrypt with your ssh-agent private key.
# Requires the commands gzip, ssh-add, ssh-keygen and openssl.
#
# Uses bash-specific extensions like <<<$var to securely pass data.
#
# [email protected] 2021-11-11 - MIT Licensed
#
# Copyright 2021 Wout Mertens
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
# associated documentation files (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge, publish, distribute,
# sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substantial
# portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
ssh-crypt() {
if [ "$1" != -e ] && [ "$1" != -d ]; then
echo "Usage: ssh-crypt -<e|d> [seed] [pubkey-match] < infile > outfile" >&2
echo >&2
echo "* -e for encrypt, -d for decrypt" >&2
echo "* seed is used to generate the secret, recommended so you don't use the same secret everywhere" >&2
echo "* pubkey-match is used to select the first matching pubkey in the ssh-agent" >&2
echo "* define CRYPT_PUBKEY to provide your own" >&2
return 2
fi
# === Select pubkey
local pk
if [ -n "$CRYPT_PUBKEY" ]; then
pk="$CRYPT_PUBKEY"
else
# we can't use ecdsa, it always gives different signatures
local keys=$(ssh-add -L | grep -v ecdsa)
if [ -n "$3" ]; then
keys=$(grep -- "$3" <<<"$keys")
fi
read pk <<<"$keys"
fi
if [ -z "$pk" ]; then
echo "!!! Could not select a public key to use - verify ssh-add -L"
return 1
fi
# === Generate secret
# We pass the pubkey as a file so ssh-keygen will look up the private key in the agent
local secretText=$(ssh-keygen -Y sign -n hi -q -f /dev/fd/4 4<<<"$pk" <<<"$2")
if [ $? -ne 0 ] || [ -z "$secretText" ]; then
echo "!!! Cannot generate secret, is ssh-agent available?" >&2
return 1
fi
# Get it on one line
local secret=$(openssl dgst -sha512 -r <<<"$secretText")
if [ $? -ne 0 ] || [ -z "$secret" ]; then
echo "!!! Cannot generate secret, is openssl available?" >&2
return 1
fi
# === Encrypt/decrypt
# specify all settings so openssl upgrades don't change encryption
local opts="-aes-256-cbc -md sha512 -pbkdf2 -iter 239823 -pass fd:4"
# we use gzip both for compression and detecting bad secrets early
if [ "$1" = -e ]; then
gzip | openssl enc -e $opts 4<<<"$secret"
else
openssl enc -d $opts 4<<<"$secret" | gzip -d
fi
}
# When sourcing this file for use in other scripts, this won't run
case "$0" in
*ssh-crypt*) ssh-crypt "$@" ;;
esac
@zdm
Copy link

zdm commented Jun 15, 2025

Another update.
It uses public SSH key, uploaded to GitHub.

Usage:

$GITHUB_USERNAME=<YOUR-USERNAME>

$encrypted=$( echo "text" | ssh-crypt.sh encrypt $GITHUB_USERNAME )

echo $encrypted | ssh-crypt.sh decrypt $GITHUB_USERNAME
#!/usr/bin/env bash

# NOTE: only RSA or ED25519 keys are supported

set -e

function ssh-crypt() {
    local operation=$1
    local github_username=$2

    local public_keys=$(curl --fail --silent "https://github.com/${github_username}.keys") || ""

    if [[ $public_keys == "" ]]; then
        echo "Unable to get SSH public key from GitHub" >&2

        return 1
    fi

    # create signature of github_username, that will be used as secret
    local secret=$(ssh-keygen -Y sign -n ssh-crypt -q -f /dev/fd/4 4<<< "$public_keys" <<< "$github_username" 2> /dev/null) || ""

    if [[ $secret == "" ]]; then
        echo "Private SSH key not found" >&2

        return 1
    fi

    local secret=$(echo $secret | base64 --wrap=0)

    case "$operation" in
        encrypt)
            gpg --symmetric --yes --batch --passphrase-fd=4 4<<< "$secret" | base64 --wrap=0

            echo
            ;;
        decrypt)
            base64 -d | gpg --decrypt --quiet --batch --passphrase-fd=4 4<<< "$secret"
            ;;
        *)
            echo "echo \"text\" | ssh-crypt encrypt \$GITHUB_USERNAME"
            echo "echo \"encrypted text\" | ssh-crypt decrypt \$GITHUB_USERNAME"

            return 1
            ;;
    esac
}

ssh-crypt "$@"

@wmertens
Copy link
Author

very nice @zdm! I updated the gist description to point to the versions here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment