Skip to content

Instantly share code, notes, and snippets.

@lainedfles
Forked from joeharr4/ssh-cipher-benchmark.sh
Last active September 25, 2024 10:54
Show Gist options
  • Save lainedfles/2a7c14dd804d5d171699f01824dd7189 to your computer and use it in GitHub Desktop.
Save lainedfles/2a7c14dd804d5d171699f01824dd7189 to your computer and use it in GitHub Desktop.
Check speed of ssh cipher(s) on your system
#!/bin/bash
# ssh-cipher-benchmark.sh - Assesses speed of SSH encryption between specific hosts.
# Usage:
# ssh-cipher-benchmark.sh [script options] [cipher selection]
# Default ciphers: all we can find...
#
# Note: In some cases, the first cipher tested runs faster than the others, regardless of order.
# Cause of this is not known, but changing the order of testing shows it to be true. Run the
# first one twice if you suspect this. Perhaps it is due to buffering?
#
# Hosted at:
# https://gist.github.com/dlenski/e42a08fa27e97b0dbb0c0024c99a8bc4#file-ssh-cipher-benchmark-sh
# Which was based on:
# Based on: http://www.systutorials.com/5450/improving-sshscp-performance-by-choosing-ciphers/#comment-28725
#
# Updated by Self Denial <[email protected]>
# Wed Sep 25 07:03:13 AM UTC 2024
# * Bidirectional testing
# * Overhaul arguments, additions like help, verbose, compression, size, etc.
# * Trap SIGINT SIGTERM for tmp cleanup
# * Introduce `version()` and `usage()` functions
# * Formatted table output as default
# * Basic validation of arguments including ciphers
#
# You should set up PublicKey authentication so that you don't have to type your
# password for every cipher tested.
version="0.1.1"
# Defaults
def_compression="no"
def_datasrc="zero"
def_host="localhost"
def_size=1000
def_table="yes"
def_verbose="no"
compression="${def_compression}"
datasrc="${def_datasrc}"
host="${def_host}"
size=${def_size}
table="${def_table}"
tmp=$(mktemp)
verbose="${def_verbose}"
trap "{ rm -rf "${tmp}"; exit 255; }" SIGINT SIGTERM
version() {
echo "${0##*/}, version ${version}"
}
usage() {
version
cat <<USAGE
Usage: ${0##*/} [script options] [cipher selection]
${0##*/}
Arguments are optional. All script options are of format name=value. Cipher selection is explicit. The defaults:
[general]
-h|--help # Display this page
-v|--verbose # Enable verbose output
-V|--version # Display the version and exit
[script options]
compression="${def_compression}" # Toggle SSH compression
direction="${def_direction}" # Override bidirectional default: send & recieve
host="${def_host}" # Set destination host
size=${def_size} # Use size in MB
datasrc="${def_datasrc}" # Datasource: zero, random, urandom
table="${def_table}" # Toggle table output
[cipher selection]
Read about Ciphers within "man ssh_config"
Examples:
Connect to remote host testing a single cipher without table formatting:
${0##*/} host=user@hostname table=no aes256-ctr
Test all ciphers connecting to localhost with 500 MB urandom compressed data:
${0##*/} datasrc=urandom compression=yes size=500
USAGE
exit ${1:-0}
}
# parse command line
_args=()
for a in ${@}; do
if [[ "${a,,}" =~ ^-(h|-help)$ ]]; then
usage
elif [[ "${a}" =~ ^-(v+|-verbose)$ ]]; then
verbose="yes"
continue
elif [[ "${a}" =~ ^-(V|-version)$ ]]; then
version
exit
elif [[ "${a}" =~ ^(-{1,2}.+)$ ]]; then
echo "Ignoring unrecognized option: '${BASH_REMATCH[1]}'"
continue
elif [[ "${a,,}" =~ ^compression=(yes|no)$ ]]; then
compression="${BASH_REMATCH[1],,}"
continue
elif [[ "${a,,}" =~ ^datasrc=(random|urandom|zero)$ ]]; then
datasrc="${BASH_REMATCH[1],,}"
continue
elif [[ "${a,,}" =~ ^direction=(send|receive)$ ]]; then
direction="${BASH_REMATCH[1],,}"
continue
elif [[ "${a,,}" =~ ^host=(.+)$ ]]; then
host="${BASH_REMATCH[1],,}"
continue
elif [[ "${a,,}" =~ ^size=([0-9]+)$ ]]; then
[[ ${BASH_REMATCH[1]} -gt 0 ]] && size=${BASH_REMATCH[1]}
continue
elif [[ "${a,,}" =~ ^table=(yes|no)$ ]]; then
table="${BASH_REMATCH[1],,}"
continue
fi
#_args+=("${a}")
_args[${#_args[@]}]="${a}"
done
remote=$host # machine to test
set -o pipefail
supported_ciphers="$(ssh -Q cipher)"
ciphers="${_args[@]}"
[[ -n "$ciphers" && "$verbose" == "yes" ]] && echo -e "User-supplied ciphers:\n$ciphers\n"
if [[ -z "$ciphers" ]]; then
ciphers=$(grep -E '^\s*Ciphers' /etc/ssh/sshd_config|sed 's/Ciphers//; s/,/ /g')
[[ -n "$ciphers" && "$verbose" == "yes" ]] && echo -e "/etc/ssh/sshd_config allows these ciphers:\n$ciphers\n"
fi
if [[ -z "$ciphers" ]]; then
ciphers="$supported_ciphers"
fi
[[ "$verbose" == "yes" ]] && echo -e "ssh -Q cipher reports these ciphers:\n$supported_ciphers\n"
if [[ -z "$ciphers" ]]; then
read -rd '' ciphers <<EOF
3des-cbc aes128-cbc aes128-ctr [email protected] aes192-cbc aes192-ctr
aes256-cbc aes256-ctr [email protected] arcfour arcfour128 arcfour256
blowfish-cbc cast128-cbc [email protected] [email protected]
EOF
[[ "$verbose" == "yes" ]] && echo "Default cipher test list: $ciphers\n"
fi
_invalid_cipher=0
min_width=2
max_width=$min_width
for i in ${ciphers,,}; do
[[ ${#i} -gt $max_width ]] && max_width=$((${#i} + $min_width))
_match=0
for s in ${supported_ciphers,,}; do [[ "${i}" == "${s}" ]] && _match=$(($_match + 1)); done
[[ ${_match} -ge 1 ]] || _invalid_cipher=$(($_invalid_cipher + 1));
done
if [[ ${_invalid_cipher} -gt 0 ]]; then
cat <<ERROR
Invalid cipher detected: '${i}'
Valid ciphers are:
${supported_ciphers//$/\n}
ERROR
usage 1
fi
echo "For each cipher, transfer ${size} MB of ${datasrc} data to/from ${remote} (compression=${compression})."
echo
ssh-test() {
[[ "$table" == "no" ]] && echo -n "$i "
if [[ "$1" == "send" ]]; then
dd if=/dev/${datasrc} bs=1000000 count=${size} 2> /dev/null |
ssh -c $i -o Compression=${compression} ${remote} "(time -p cat) > /dev/null" > $tmp 2>&1
else
ssh -c $i -o Compression=${compression} ${remote} "dd if=/dev/${datasrc} bs=1000000 count=${size} 2> /dev/null" 2>&1 | (time -p cat) > /dev/null 2> $tmp
fi
if [[ $? -eq 0 ]]; then
if [[ "$table" == "yes" ]]; then
grep real $tmp | awk -v cipher="$i" -v direction="$1" -v max_width=$max_width -v size=$size '{ printf "%-"max_width"s %-14s %s\n", cipher, size / $2" MB/s", direction" success" }'
else
grep real $tmp | awk -v direction="$1" -v size=$size '{print size / $2" MB/s "direction" success" }'
fi
else
if [[ "$table" == "yes" ]]; then
awk -v cipher="$i" -v direction="$1" -v max_width=$max_width -v remote="$remote" 'BEGIN { printf "%-"max_width"s %-14s %s\n", cipher, "0 MB/s", direction" failed, for why run: ssh -vc "cipher" "remote }'
else
echo "0 MB/s ${1} failed, for why run: ssh -vc $i ${remote}"
fi
fi
}
[[ "$table" == "yes" ]] && awk -v max_width=$max_width 'BEGIN { printf "%-"max_width"s %-14s %s\n", "CIPHER", "RATE", "STATUS" }' || echo "CIPHER RATE STATUS"
for i in ${ciphers,,}
do
if [[ -n "$direction" ]]; then
ssh-test "$direction"
else
ssh-test "send"
ssh-test "receive"
fi
done
rm -rf "${tmp}"
@lainedfles
Copy link
Author

Major improvements targeting QOL and usability

  • Bidirectional testing
  • Overhaul arguments, additions like help, verbose, compression, size, etc.
  • Trap SIGINT SIGTERM for tmp cleanup
  • Introduce version() and usage() functions
  • Formatted table output as default
  • Basic validation of arguments including ciphers

Usage

ssh-cipher-benchmark.sh, version 0.1.0
Usage:  ssh-cipher-benchmark.sh [script options] [cipher selection]
        ssh-cipher-benchmark.sh
Arguments are optional. All script options are of format name=value. Cipher selection is explicit. The defaults:
        [general]
        -h|--help         # Display this page
        -v|--verbose      # Enable verbose output
        -V|--version      # Display the version and exit

        [script options]
        compression="no"  # Toggle SSH compression
        direction=""      # Override bidirectional default: send & recieve
        host="localhost"  # Set destination host
        size=1000         # Use size in MB
        datasrc="zero"    # Datasource: zero, random, urandom
        table="yes"       # Toggle table output

        [cipher selection]
        Read about Ciphers within "man ssh_config"
Examples:
        Connect to remote host testing a single cipher without table formatting:
        ssh-cipher-benchmark.sh host=user@hostname table=no aes256-ctr 

        Test all ciphers connecting to localhost with 500 MB urandom compressed data:
        ssh-cipher-benchmark.sh datasrc=urandom compression=yes size=500

Output

[user@hostname ssh-cipher-benchmark]$ bash ssh-cipher-benchmark.sh host=remoteuser@remotehostname
For each cipher, transfer 1000 MB of zero data to/from remoteuser@remotehostname (compression=no).

CIPHER                           RATE            STATUS
3des-cbc                         0 MB/s          send failed, for why run: ssh -vc 3des-cbc remoteuser@remotehostname
3des-cbc                         0 MB/s          receive failed, for why run: ssh -vc 3des-cbc remoteuser@remotehostname
aes128-cbc                       0 MB/s          send failed, for why run: ssh -vc aes128-cbc remoteuser@remotehostname
aes128-cbc                       0 MB/s          receive failed, for why run: ssh -vc aes128-cbc remoteuser@remotehostname
aes192-cbc                       0 MB/s          send failed, for why run: ssh -vc aes192-cbc remoteuser@remotehostname
aes192-cbc                       0 MB/s          receive failed, for why run: ssh -vc aes192-cbc remoteuser@remotehostname
aes256-cbc                       0 MB/s          send failed, for why run: ssh -vc aes256-cbc remoteuser@remotehostname
aes256-cbc                       0 MB/s          receive failed, for why run: ssh -vc aes256-cbc remoteuser@remotehostname
aes128-ctr                       117.371 MB/s    send success
aes128-ctr                       114.155 MB/s    receive success
aes192-ctr                       115.075 MB/s    send success
aes192-ctr                       113.895 MB/s    receive success
aes256-ctr                       117.371 MB/s    send success
aes256-ctr                       113.895 MB/s    receive success
[email protected]           117.371 MB/s    send success
[email protected]           113.636 MB/s    receive success
[email protected]           114.943 MB/s    send success
[email protected]           113.766 MB/s    receive success
[email protected]    104.167 MB/s    send success
[email protected]    103.842 MB/s    receive success
Additional examples

Error condition:

[user@hostname ssh-cipher-benchmark]$ bash ssh-cipher-benchmark.sh bad-cipher
Invalid cipher detected: 'bad-cipher'

Valid ciphers are:
3des-cbc
aes128-cbc
aes192-cbc
aes256-cbc
aes128-ctr
aes192-ctr
aes256-ctr
[email protected]
[email protected]
[email protected]

ssh-cipher-benchmark.sh, version 0.1.0
Usage:  ssh-cipher-benchmark.sh [script options] [cipher selection]
        ssh-cipher-benchmark.sh
Arguments are optional. All script options are of format name=value. Cipher selection is explicit. The defaults:
        [general]
        -h|--help         # Display this page
        -v|--verbose      # Enable verbose output
        -V|--version      # Display the version and exit

        [script options]
        compression="no"  # Toggle SSH compression
        direction=""      # Override bidirectional default: send & recieve
        host="localhost"  # Set destination host
        size=1000         # Use size in MB
        datasrc="zero"    # Datasource: zero, random, urandom
        table="yes"       # Toggle table output

        [cipher selection]
        Read about Ciphers within "man ssh_config"
Examples:
        Connect to remote host testing a single cipher without table formatting:
        ssh-cipher-benchmark.sh host=user@hostname table=no aes256-ctr 

        Test all ciphers connecting to localhost with 500 MB urandom compressed data:
        ssh-cipher-benchmark.sh datasrc=urandom compression=yes size=500

Verbose 2GB compressed /dev/random data limited to aes128-ctr:

[user@hostname ssh-cipher-benchmark]$ bash ssh-cipher-benchmark.sh datasrc=random compression=yes size=2000 table=no -v aes128-ctr
User-supplied ciphers:
aes128-ctr

ssh -Q cipher reports these ciphers:
3des-cbc
aes128-cbc
aes192-cbc
aes256-cbc
aes128-ctr
aes192-ctr
aes256-ctr
[email protected]
[email protected]
[email protected]

For each cipher, transfer 2000 MB of random data to/from localhost (compression=yes).

CIPHER  RATE  STATUS
aes128-ctr  65.5523  MB/s  send success
aes128-ctr  65.8979  MB/s  receive success

Send only:

[user@hostname ssh-cipher-benchmark]$ bash ssh-cipher-benchmark.sh datasrc=random size=2000 aes128-ctr direction=send
For each cipher, transfer 2000 MB of random data to/from localhost (compression=no).

CIPHER        RATE            STATUS
aes128-ctr    547.945 MB/s    send success

Note difference in rate using blocking random vs zero:

[user@hostname ssh-cipher-benchmark]$ bash ssh-cipher-benchmark.sh size=2000 aes128-ctr
For each cipher, transfer 2000 MB of zero data to/from localhost (compression=no).

CIPHER        RATE            STATUS
aes128-ctr    1980.2 MB/s     send success
aes128-ctr    1694.92 MB/s    receive success

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