-
-
Save lainedfles/2a7c14dd804d5d171699f01824dd7189 to your computer and use it in GitHub Desktop.
Check speed of ssh cipher(s) on your system
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Major improvements targeting QOL and usability
version()
andusage()
functionsUsage
Output
Additional examples
Error condition:
Verbose 2GB compressed /dev/random data limited to
aes128-ctr
:Send only:
Note difference in rate using blocking random vs zero: