Recently, I came across a technical use-case, where a system required a combination of symmetric and asymmetric encryption for communication. To be more precise, the payload was encrypted using a generated symmetric key and that key was encrypted using the public key (asymmetric encryption) of the target system.

High-level Fundamentals

Recalling cs fundamentals, symmetric encryption uses the same key for encryption and decryption. On the other hand, asymmetric encryption uses two different keys1. This provides a one-way encryption of messages, which only the owner of the private key - in this case the receiver - can decrypt2. The second part of the key pair, the public key, is freely shared with anyone.

Assuming Adam wants to send Bob a secret message, why isn’t only asymmetric encryption used? Why combine it with symmetric encryption?

Unfortunately, asymmetric encryption is comparably slow and requires larger key sizes. For example when using the asymmetric encryption method with a 2048 bit key, only 1960 bits can used (11 byte are lost). These 1960 bits (245 bytes) limit also the maximum payload size to namely 245 bytes. Typical symmetric encryption ciphers like AES-256 require only 256 bit and huge payloads are split by the cipher itself into blocks and processed.

How much slower is asymmetric encryption?

To put this to the test, I used golang due to its rich standard library. Following we see some example code, which is downloadable in full at crypto.go and crypto-test.go and can be run using go test ./... -bench=.:

var key = []byte("passphrasewhichneedstobe32bytes!")

// asymmetric encryption
var keySize = 8 * len(key)
var privateKey, publicKey = genAsymKey(keySize)

// data for benchmarking
var textLength = keySize/8 - 11
var text = strings.Repeat("A", textLength)

func BenchmarkAsymmetricEncryption(b *testing.B) {
	for i := 0; i < b.N; i++ {
		EncryptAndDecryptAsymmetric(privateKey, publicKey, []byte(text))
	}
}

The result shows that for small keys of 256 bit and therefore a payload of 21 byte, asymmetric encryption is two orders of magnitude slower:

BenchmarksymmetricEncryption-8            3749260               306.1 ns/op
BenchmarkAsymmetricEncryption-8             31653             36864   ns/op

And this value significantly worsens with larger keys (n=2048bit), which requires 2075887 ns/op resulting in a ~56x slow down although the key is only 8x larger.

Conclusion

This explains the best practise of creating a symmetric key to encrypt the payload, then encrypting the symmetric key using the public key of the receiver Bob, throwing the symmetric key away and finally sending the asymmetric encrypted symmetric key plus symmetric encrypted payload to the receiver. Only Bob is able to decrypt the payload. And in comparison to symmetric keys, no key needs to be shared, which cannot be leaked by an unknown party or in transport either. It uses the best of both world, is quick and secure.


  1. In the case of RSA, the private key consist of two large primes. The product of such primes results in the public key. ↩︎

  2. This is simplified, assuming large enough keys, the message is large enough and various attacks are not possible. ↩︎