Hybrid Public Key with GoWith ECIES we use the public key from Elliptic Curve Cryptography in order to derive a symmetric key. In this case we create a symmetric key from Elliptic Curve Cryptography, and then use this to encrypt with AES-GCM. In this case we will use the Cloudflar CIRCL library: |
Outline
With ECC (Elliptic Curve Cryptography), we have an opportunity to use both the power of public-key encryption, with the speed and security of symmetric key encryption. And, so we slowly move to the best practice for encryption, where there’s an increasing consensus around:
- Public key encryption curve: P256, P384, P521, X25519 and X448.
- Hashing method for key derivation (HKDF): SHA256, SHA384 and SHA512.
- Symmetric key: 128-bit AES GCM and 256-bit AES GCM.
All of the above methods are compatible with most systems. For this Bob and Alice will pick a curve to define their key pair, and then use given hashing methods to derive an encryption key. This is normally achieved with HKDF (HMAC Key Derivation Function). For the actual encryption, we can use symmetric-key encryption, as this is the most efficient and much faster than public key encryption. Overall, with this, there is a general move towards using AEAD (Authenticated Encryption with Additional Data). A typical mode for this is GCM. So let’s build a hybrid encryption method with Golang.
Now, let’s say that Bob will send an encrypted message to Alice. Alice will then generate a key pair (a public key and a private key). She then sends her public key to Bob, and he then uses this to derive a symmetric key for the encryption (\(S\)). He then encrypts the message using \(K\) and with AES GCM. Bob receives the cipher (\(C\)) and a value of \(R\). From \(R\), she can then derive S from her private key. With this key, she can decrypt the cipher text to derive the plaintext message.
Theory
In this method Alice generates a random private key (\(d_A\)) and the takes a point on an elliptic curve (\(G\)) and then determines her public key (\(Q_A\)):
\(Q_A = d_A \times G\)
G and \(Q_A\) are thus points on an elliptic curve. Alice then sends \(Q_A\) to Bob. Next Bob will generate:
\(R = r \times G\)
\(S = r \times Q_A\)
and where r is a random number generated by Bob. The symmetric key (S) is then used to encrypt a message.
Alice will then receive the encrypted message along with \(R\). She is then able to determine the same encryption key with::
\(S = d_A \times R\)
which is:
\(S = d_A \times (r \times G)\)
\(S = r \times (d_A \times G)\)
\(S = r \times Q_A\)
and which is the same as the key that Bob generated.
The method is illustrated here:
Sample run
A sample run is:
Public key type: HPKE_KEM_P256_HKDF_SHA256 Params kem_id: 16 kdf_id: 1 aead_id: 2 Key exchange parameters: Ciphersize: 65 EncapsulationSeedSize: 32 PrivateKeySize: 32 PublicKeySize: 65 SeedSize: 32 SharedKeySize: 32 Cipher parameters: Key Length: 32 Key derivation function: Extract size: 32 Message: Testing 123 Cipher: 8f74d03eaf0f30d2689657d8e34b8fcb69b0cab7648d4d69b9f305 Decipher: Testing 123
Coding
The code is based on [here]:
package main import ( "crypto/rand" // "encoding/hex" "fmt" "os" "strconv" "github.com/cloudflare/circl/hpke" ) func main() { kemID := int(hpke.KEM_P256_HKDF_SHA256) kdfID := int(hpke.KDF_HKDF_SHA256) aeadID := int(hpke.AEAD_AES128GCM) msg := "Hello" argCount := len(os.Args[1:]) if argCount > 0 { msg = os.Args[1] } if argCount > 1 { kemID, _ = strconv.Atoi(os.Args[2]) } if argCount > 2 { kdfID, _ = strconv.Atoi(os.Args[3]) } if argCount > 3 { aeadID, _ = strconv.Atoi(os.Args[4]) } suite := hpke.NewSuite(hpke.KEM(kemID), hpke.KDF(kdfID), hpke.AEAD(aeadID)) info := []byte("Test") Bob_pub, Bob_private, _ := hpke.KEM(kemID).Scheme().GenerateKeyPair() Bob, _ := suite.NewReceiver(Bob_private, info) Alice, _ := suite.NewSender(Bob_pub, info) enc, sealer, _ := Alice.Setup(rand.Reader) Alice_msg := []byte(msg) aad := []byte("Additional data") ct, _ := sealer.Seal(Alice_msg, aad) opener, _ := Bob.Setup(enc) Bob_msg, _ := opener.Open(ct, aad) if (kemID!=48) {fmt.Printf("Public key type:\t%s\n", Bob_pub.Scheme().Name()) } fmt.Printf(" Params\t%s\n", suite.String()) fmt.Printf("\nKey exchange parameters:\n") fmt.Printf(" Ciphersize:\t\t%d\n", hpke.KEM(kemID).Scheme().CiphertextSize()) fmt.Printf(" EncapsulationSeedSize:\t%d\n", hpke.KEM(kemID).Scheme().EncapsulationSeedSize()) fmt.Printf(" PrivateKeySize:\t%d\n", hpke.KEM(kemID).Scheme().PrivateKeySize()) fmt.Printf(" PublicKeySize:\t\t%d\n", hpke.KEM(kemID).Scheme().PublicKeySize()) fmt.Printf(" SeedSize:\t\t%d\n", hpke.KEM(kemID).Scheme().SeedSize()) fmt.Printf(" SharedKeySize:\t\t%d\n", hpke.KEM(kemID).Scheme().SharedKeySize()) fmt.Printf("\nCipher parameters:\n") fmt.Printf(" Key Length:\t%d\n", hpke.AEAD(aeadID).KeySize()) fmt.Printf("\nKey derivation function:\n") fmt.Printf(" Extract size:\t%d\n", hpke.KDF(kdfID).ExtractSize()) fmt.Printf("\nMessage:\t%s\n", Alice_msg) // fmt.Printf("Cipher:\t%x\n", hex.EncodeToString(ct)) fmt.Printf("Cipher:\t%x\n", ct) fmt.Printf("Decipher:\t%s\n", Bob_msg) }