On SSH ciphers, MACs and key exchange algorithms

Inspired by a some question on StackExchange on the taxonomy of Ciphers/MACs/Kex available in SSH, I wondered what would be the fastest combination of Ciphers, MACs and KexAlgorithms that OpenSSH has to offer.

I've tested with OpenSSH 6.6 (released 2014-03-14) on a Debian/Jessie system (ThinkPad E431). Initially I ran these tests against an SSH server in a virtual machine but realized that the server is not supporting newer Cipher/MAC/KexAlgorithm combinations, so before I ran the actual benchmark I ran ssh-features.sh to test all working combinations. Later on I ended up running the performance test on localhost, making the evaluation step obsolete. Still, I decided to keep it around so that one can peform the benchmark on real-world situations where the remote SSH server is not located on localhost :-)

This OpenSSH version supports 15 different Ciphers, 18 MAC algorithms and 8 Key-Exchange algorithms - that's 2160 combinations to test. ssh-performance.sh will go through the output of ssh-features.sh and transfer a certain amount of data from local /dev/zero to remote /dev/null. Connecting to localhost was fast so I opted to transfer 4GB of data.

Before we get into the details, let's see the top-5 combinations of the results:

cipher: aes192-ctr mac: umac-64-etm@openssh.com kex: ecdh-sha2-nistp256 - 6 seconds
cipher: aes192-ctr mac: umac-64-etm@openssh.com kex: diffie-hellman-group1-sha1 - 6 seconds
cipher: aes128-ctr mac: umac-64-etm@openssh.com kex: ecdh-sha2-nistp384 - 6 seconds
cipher: aes192-ctr mac: umac-64@openssh.com kex: ecdh-sha2-nistp384 - 6 seconds
cipher: aes192-ctr mac: umac-64@openssh.com kex: diffie-hellman-group-exchange-sha1 - 6 seconds
The UMAC message authentication code has been introduced in OpenSSH 4.7 (released 2007-09-04) and is indeed the fastest MAC in this little contest. Looking at the results reveals that there indeed some variation in the results when it comes to different MAC or Kex choices. Iterating through all ciphers, we calculate the average run time of each combination:
$ for c in `awk '{print $4}' ssh-performance.log | sort | uniq`; do
     printf "cipher: $c  "
     grep -w $c ssh-performance.log | awk '{sum+=$(NF-1); n++} END {print sum/n}'
done | sort -nk3
cipher: aes128-gcm@openssh.com  8.8125
cipher: aes256-gcm@openssh.com  9.23611
cipher: aes128-ctr  15.6875
cipher: aes192-ctr  15.6944
cipher: aes256-ctr  16.1319
cipher: arcfour     20.26391)
cipher: arcfour128  20.3403
cipher: arcfour256  20.5278
cipher: aes128-cbc  21.125
cipher: aes192-cbc  22.4583
cipher: chacha20-poly1305@openssh.com  23.2361
cipher: aes256-cbc  23.9722
cipher: blowfish-cbc  55.6875
cipher: cast128-cbc  59.5139
cipher: 3des-cbc  200.854
So, aes128-gcm@openssh.com (included in OpenSSH 6.2, released 2013-03-22) comes out fastest across all combinations while 3des-cbc is indeed the slowest cipher. While the major performance factor is still the choice of the cipher, both MAC and Kex still play a role. As an example, let's look at aes192-ctr mac, the results range from 6 to 46 seconds:
cipher: aes192-ctr mac: umac-64-etm@openssh.com kex: ecdh-sha2-nistp256 - 6 seconds
[...]
cipher: aes192-ctr mac: hmac-sha2-256-etm@openssh.com kex: ecdh-sha2-nistp256 - 46 seconds
Let's see how MAC and Kex choices rank up across all (15) different ciphers. That is, we calculate the average time for each MAC:
$ for m in `awk '{print $6}' ssh-performance.log | sort | uniq`; do
     printf "mac: $m  "
     grep -w $m ssh-performance.log | awk '{sum+=$(NF-1); n++} END {print sum/n}'
done | sort -nk3
mac: umac-64@openssh.com  28.45
mac: umac-64-etm@openssh.com  28.8167
mac: umac-128-etm@openssh.com  29.8583
mac: umac-128@openssh.com  30.1
mac: hmac-sha1-96  33.4417
mac: hmac-sha1-96-etm@openssh.com  33.5167
mac: hmac-md5-etm@openssh.com  33.6333
mac: hmac-sha1  33.7104
mac: hmac-md5-96  33.7792
mac: hmac-md5-96-etm@openssh.com  33.8167
mac: hmac-md5  33.825
mac: hmac-sha1-etm@openssh.com  34.2
mac: hmac-sha2-512-etm@openssh.com  38.2333
mac: hmac-sha2-512  38.2833
mac: hmac-ripemd160-etm@openssh.com  43.775
mac: hmac-ripemd160  43.7792
mac: hmac-sha2-256  44.3792
mac: hmac-sha2-256-etm@openssh.com  44.45
And again for the key exchange algorithms:
$ for k in `awk '{print $8}' ssh-performance.log | sort | uniq`; do
     printf "kex: $k  "
     grep -w $k ssh-performance.log | awk '{sum+=$(NF-1); n++} END {print sum/n}'
done | sort -nk3
kex: ecdh-sha2-nistp256  35.2926
kex: diffie-hellman-group14-sha1  35.3148
kex: diffie-hellman-group1-sha1  35.4296
kex: diffie-hellman-group-exchange-sha256  35.563
kex: ecdh-sha2-nistp521  35.563
kex: diffie-hellman-group-exchange-sha1  35.6926
kex: ecdh-sha2-nistp384  35.8333
kex: curve25519-sha256@libssh.org  35.8667
The differences for Kex here are in the sub-second range, so even the recently added Curve25519 option would not be much of a performance impact here.

So, what do we make of all this? Another StackExchange question suggests that SSH in general holds up pretty good security-wise and even dismisses problems with CBC. Assuming all of that is true, what can we do to get the most performance when transferring big files over SSH? Let's look at the defaults again, from ssh_config(5) of OpenSSH 6.6:
Ciphers: aes128-ctr aes192-ctr aes256-ctr arcfour256 arcfour128 aes128-gcm@openssh.com [...]
MACs:    hmac-md5-etm@openssh.com hmac-sha1-etm@openssh.com umac-64-etm@openssh.com [...]
KexAlgorithms: curve25519-sha256@libssh.org ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 [...]
So, according to the results of this little contest, a faster default for a recent version of OpenSSH could be:
  Ciphers aes128-gcm@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,\
          aes128-ctr,arcfour256,arcfour128
  MACs umac-128-etm@openssh.com,umac-64-etm@openssh.com,umac-128@openssh.com,umac-64@openssh.com
  KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256
Notes:
  • The GCM ciphers have been implemented with OpenSSH 6.2 (released 2013-03-22).
  • The EtM (Encrypt-then-MAC) modes and 128-bit UMAC variants have only been supported since OpenSSH 6.2 (released 2013-03-22).
  • The KexAlgorithms option has been added with OpenSSH 5.7 (released 2011-01-24)
As always, when it comes to benchmarks: other SSH implementations (e.g. HPN-SSH) or different setups will most certainly return different results. So please test yourself before drawing any conclusions from these results.

Update October 2014):
OpenSSH 6.7 disables the CBC ciphers by default because of vulnerabilites found when used with SSH (found in 2008).

Update (June 2016):
new benchmark script and new results (averaged over 3 runs):
### Top-5 overall
cipher: aes128-ctr   mac: umac-64@openssh.com  kex: curve25519-sha256@libssh.org -  10 seconds avg.
cipher: aes128-ctr   mac: umac-64@openssh.com  kex: ecdh-sha2-nistp521 -  10 seconds avg.
cipher: aes128-ctr   mac: umac-64@openssh.com  kex: diffie-hellman-group-exchange-sha256 -  10 seconds avg.
cipher: aes128-ctr   mac: umac-64@openssh.com  kex: diffie-hellman-group14-sha1 -  10 seconds avg.
cipher: aes128-ctr   mac: umac-64@openssh.com  kex: ecdh-sha2-nistp256 -  9 seconds avg.

If we were to use the Secure Secure Shell recommendations and exclude some of the weaker choices, we would lose only a few seconds (and would basically switch to chacha20-poly1305):
### Top-6 overall
cipher: aes128-gcm@openssh.com mac: hmac-sha2-256-etm@openssh.com kex: curve25519-sha256@libssh.org -  14 seconds avg.
cipher: chacha20-poly1305@openssh.com mac: umac-128-etm@openssh.com kex: diffie-hellman-group-exchange-sha256 -  13 seconds avg.
cipher: chacha20-poly1305@openssh.com mac: hmac-ripemd160-etm@openssh.com kex: curve25519-sha256@libssh.org -  13 seconds avg.
cipher: chacha20-poly1305@openssh.com mac: hmac-sha2-256-etm@openssh.com kex: curve25519-sha256@libssh.org -  13 seconds avg.
cipher: chacha20-poly1305@openssh.com mac: hmac-sha2-256-etm@openssh.com kex: diffie-hellman-group-exchange-sha256 -  13 seconds avg.
cipher: chacha20-poly1305@openssh.com mac: umac-128-etm@openssh.com kex: curve25519-sha256@libssh.org -  12 seconds avg.