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:
Cipher | Test 1 | Test 2 | Test 3 |
---|---|---|---|
aes128-ctr | 734.9MB/s (02:19) | 652.9MB/s (02:36) | 760.0MB/s (02:14) |
aes192-ctr | 728.1MB/s (02:20) | 741.2MB/s (02:18) | 736.0MB/s (02:19) |
aes256-ctr | 709.1MB/s (02:24) | 735.3MB/s (02:19) | 709.0MB/s (02:24) |
aes128-gcm@openssh.com | 776.3MB/s (02:11) | 841.6MB/s (02:01) | 802.2MB/s (02:07) |
aes256-gcm@openssh.com | 742.8MB/s (02:17) | 771.8MB/s (02:12) | 793.3MB/s (02:09) |
chacha20-poly1305@openssh.com | 518.4MB/s (03:17) | 542.1MB/s (03:08) | 443.4MB/s (03:50) |
Testing for a smaller 5GB file gives similar results:
Cipher | Test 1 | Test 2 | Test 3 |
---|---|---|---|
aes128-ctr | 523.4MB/s (00:09) | 776.6MB/s (00:06) | 721.7MB/s (00:07) |
aes192-ctr | 746.0MB/s (00:06) | 710.9MB/s (00:07) | 745.5MB/s (00:06) |
aes256-ctr | 715.0MB/s (00:07) | 756.0MB/s (00:06) | 657.1MB/s (00:07) |
aes128-gcm@openssh.com | 862.2MB/s (00:05) | 857.2MB/s (00:05) | 796.9MB/s (00:06) |
aes256-gcm@openssh.com | 834.7MB/s (00:06) | 796.3MB/s (00:06) | 833.9MB/s (00:06) |
chacha20-poly1305@openssh.com | 500.3MB/s (00:10) | 502.0MB/s (00:10) | 503.6MB/s (00:10) |
As we can see, I got the best performance from the aes256-gcm@openssh.com
cipher.
Linode
Again starting with a 100GB file:
Cipher | Test 1 | Test 2 | Test 3 |
---|---|---|---|
aes128-ctr | 193.6MB/s (08:48) | 249.9MB/s (06:49) | 253.1MB/s (06:44) |
aes192-ctr | 237.9MB/s (07:10) | 252.6MB/s (06:45) | 249.1MB/s (06:51) |
aes256-ctr | 246.7MB/s (06:55) | 251.9MB/s (06:46) | 246.1MB/s (06:56) |
aes128-gcm@openssh.com | 292.5MB/s (05:50) | 299.8MB/s (05:41) | 295.1MB/s (05:46) |
aes256-gcm@openssh.com | 294.9MB/s (05:47) | 287.0MB/s (05:56) | 288.8MB/s (05:54) |
chacha20-poly1305@openssh.com | 139.6MB/s (12:13) | 136.2MB/s (12:32) | 140.2MB/s (12:10) |
Testing with the 5GB file also gives similar results:
Cipher | Test 1 | Test 2 | Test 3 |
---|---|---|---|
aes128-ctr | 251.3MB/s (00:20) | 256.0MB/s (00:19) | 248.0MB/s (00:20) |
aes192-ctr | 257.1MB/s (00:19) | 236.3MB/s (00:21) | 256.0MB/s (00:20) |
aes256-ctr | 249.4MB/s (00:20) | 252.3MB/s (00:20) | 255.9MB/s (00:20) |
aes128-gcm@openssh.com | 284.0MB/s (00:18) | 275.4MB/s (00:18) | 293.6MB/s (00:17) |
aes256-gcm@openssh.com | 281.0MB/s (00:18) | 290.8MB/s (00:17) | 268.0MB/s (00:19) |
chacha20-poly1305@openssh.com | 134.4MB/s (00:38) | 136.2MB/s (00:37) | 139.2MB/s (00:36) |
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
2024 Update
Since I wrote this in 2021 I decided to run the tests again on a desktop PC with a relatively newer processor. I got the following results for the 5GB file:
Cipher | Test 1 | Test 2 | Test 3 |
---|---|---|---|
aes128-ctr | 1.8GB/s | 1.8GB/s | 1.9GB/s |
aes192-ctr | 1.8GB/s | 1.8GB/s | 1.7GB/s |
aes256-ctr | 1.8GB/s | 1.8GB/s | 1.7GB/s |
aes128-gcm@openssh.com | 2.0GB/s | 2.0GB/s | 1.9GB/s |
aes256-gcm@openssh.com | 2.0GB/s | 2.0GB/s | 2.0GB/s |
chacha20-poly1305@openssh.com | 927.8MB/s | 913.0MB/s | 918.3MB/s |
Switching to the 100GB file I get these results:
Cipher | Test 1 | Test 2 | Test 3 |
---|---|---|---|
aes128-ctr | 1.9GB/s | 1.7GB/s | 1.7GB/s |
aes192-ctr | 1.7GB/s | 1.7GB/s | 1.8GB/s |
aes256-ctr | 1.7GB/s | 1.7GB/s | 1.7GB/s |
aes128-gcm@openssh.com | 2.0GB/s | 2.0GB/s | 2.0GB/s |
aes256-gcm@openssh.com | 1.9GB/s | 1.9GB/s | 1.9GB/s |
chacha20-poly1305@openssh.com | 899.2MB/s | 889.3MB/s | 902.7MB/s |
The tests were executed from a RAM based file system. In summary, the tests again show the AES GCM ciphers gave the best performance with the CTR ciphers just behind. Using AES GCM (128 or 256) the 100G transfer is complete in roughly 51 seconds.