Benchmark SSH Ciphers

Choosing a specific cipher to use for SSH can have a large performance impact when transferring files using tools that use SSH as a transport. For testing, I decided to benchmark the impact of using scp with various ciphers locally on my laptop as well as a VPS from Linode. The laptop has a Intel Xeon W-10885M CPU and is running Ubuntu Hirsute (21.04) while the Linode server is Debian Buster with two cores from a AMD EPYC 7501.

Available Ciphers

First the available list of ciphers needs to be checked. This may vary between the client and server, so check both to make sure that the ciphers are supported on both sides:

# Get a list of ciphers supported by the SSH client
ssh -Q cipher | sort -u

# Get a list of ciphers supported by the SSH server running locally
sudo sshd -T | grep ciphers | perl -pe 's/,/\n/g' | sort –u

# Get a list of ciphers supported by a remote SSH server (using nmap)
nmap --script ssh2-enum-algos -sV -p 22 hostname.of.server.to.check

# Get a list of ciphers supported by a remote SSH server (using SSH client)
ssh -vv hostname.of.server.to.check 'exit' 2>&1 | grep 'ciphers stoc' | awk '{print $4}' | tr , '\n' | sort | uniq

In my case the following ciphers are available on both sides:

aes128-ctr
aes192-ctr
aes256-ctr
aes128-gcm@openssh.com
aes256-gcm@openssh.com
chacha20-poly1305@openssh.com

Testing File

I will be testing by transferring a 100G file and a 5GB file for each cipher. To create the test file I use truncate:

truncate -s 100G 100G
truncate -s 5G 5G

Benchmarks

Now to run the benchmarks. Each benchmark will transfer the test file to /dev/null. To specify the cipher to use for each benchmark the Ciphers option will be provided. For context, the default cipher that is used without specifying any options is chacha20-poly1305@openssh.com.

# Set list of ciphers to test
CIPHERS=(aes128-ctr aes192-ctr aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com)

# Test each cipher 3 times with 100GB file
for i in `seq 1 3`; do
    for CIPHER in "${CIPHERS[@]}"; do
        echo "Test ${i} for 100GB file with cipher: ${CIPHER}"
        scp -o Ciphers=$CIPHER 100G localhost:/dev/null
    done
done

# Test each cipher 3 times with 5GB file
for i in `seq 1 3`; do
    for CIPHER in "${CIPHERS[@]}"; do
        echo "Test ${i} for 5GB file with cipher: ${CIPHER}"
        scp -o Ciphers=$CIPHER 5G localhost:/dev/null
    done
done

Results

Here is the results I got from the two sets of tests.

Laptop

Testing for a 100GB file:

CipherTest 1Test 2Test 3
aes128-ctr734.9MB/s (02:19)652.9MB/s (02:36)760.0MB/s (02:14)
aes192-ctr728.1MB/s (02:20)741.2MB/s (02:18)736.0MB/s (02:19)
aes256-ctr709.1MB/s (02:24)735.3MB/s (02:19)709.0MB/s (02:24)
aes128-gcm@openssh.com776.3MB/s (02:11)841.6MB/s (02:01)802.2MB/s (02:07)
aes256-gcm@openssh.com742.8MB/s (02:17)771.8MB/s (02:12)793.3MB/s (02:09)
chacha20-poly1305@openssh.com518.4MB/s (03:17)542.1MB/s (03:08)443.4MB/s (03:50)
100GB – Laptop Intel Xeon W-10885M

Testing for a smaller 5GB file gives similar results:

CipherTest 1Test 2Test 3
aes128-ctr523.4MB/s (00:09)776.6MB/s (00:06)721.7MB/s (00:07)
aes192-ctr746.0MB/s (00:06)710.9MB/s (00:07)745.5MB/s (00:06)
aes256-ctr715.0MB/s (00:07)756.0MB/s (00:06)657.1MB/s (00:07)
aes128-gcm@openssh.com862.2MB/s (00:05)857.2MB/s (00:05)796.9MB/s (00:06)
aes256-gcm@openssh.com834.7MB/s (00:06)796.3MB/s (00:06)833.9MB/s (00:06)
chacha20-poly1305@openssh.com500.3MB/s (00:10)502.0MB/s (00:10)503.6MB/s (00:10)
5GB – Laptop Intel Xeon W-10885M

As we can see, I got the best performance from the aes256-gcm@openssh.com cipher.

Linode

Again starting with a 100GB file:

CipherTest 1Test 2Test 3
aes128-ctr193.6MB/s (08:48)249.9MB/s (06:49)253.1MB/s (06:44)
aes192-ctr237.9MB/s (07:10)252.6MB/s (06:45)249.1MB/s (06:51)
aes256-ctr246.7MB/s (06:55)251.9MB/s (06:46)246.1MB/s (06:56)
aes128-gcm@openssh.com292.5MB/s (05:50)299.8MB/s (05:41)295.1MB/s (05:46)
aes256-gcm@openssh.com294.9MB/s (05:47)287.0MB/s (05:56)288.8MB/s (05:54)
chacha20-poly1305@openssh.com139.6MB/s (12:13)136.2MB/s (12:32)140.2MB/s (12:10)
100GB – Linode AMD EPYC 7501

Testing with the 5GB file also gives similar results:

CipherTest 1Test 2Test 3
aes128-ctr251.3MB/s (00:20)256.0MB/s (00:19)248.0MB/s (00:20)
aes192-ctr257.1MB/s (00:19)236.3MB/s (00:21)256.0MB/s (00:20)
aes256-ctr249.4MB/s (00:20)252.3MB/s (00:20)255.9MB/s (00:20)
aes128-gcm@openssh.com284.0MB/s (00:18)275.4MB/s (00:18)293.6MB/s (00:17)
aes256-gcm@openssh.com281.0MB/s (00:18)290.8MB/s (00:17)268.0MB/s (00:19)
chacha20-poly1305@openssh.com134.4MB/s (00:38)136.2MB/s (00:37)139.2MB/s (00:36)
5GB – Linode AMD EPYC 7501)

Summary

In summary, I find the best compromise between security and speed is the aes256-gcm@openssh.com cipher.

To set the preferred cipher the following can be configured in the SSH client configuration file:

# Global settings for all hosts
Host *
  Ciphers ^aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,chacha20-poly1305@openssh.com,aes128-ctr

Leave a Reply

Your email address will not be published.