We can, of course, encrypt data with RSA OAEP, but the keys are fairly large, and it is fairly process intensive. Our saviour has been the usage of elliptic curve cryptography (ECC). But, we can actually directly encrypt data with ECC, and so we use a key encapsulation method (KEM), and which is also used in PQC methods. With this, we use a Diffie-Hellman (DH) key exchange method — but do it in an offline way.
Let’s say that Bob wants to encrypt data for Alice. For this, Bob will have a private key of b, and a public key of b.G, and Alice will have a private key of a, and a public key of a.G. The base point on the elliptic curve is then G, and where a.G, is the point G added to itself for a times. Bob initially gets Alice’s public key (a.G) in a trusted way. He will then take his private key (b), and perform a Diffie-Hellman operation to give a resultant point of a.B.G. Next, he feeds this into the HKDF method to expand and extract into an AES key, along with some context data. This will derive a secret key (SecretKey), and which will be used to encrypt some plaintext:
Bob will also serialise his public key and send it along with the message. This will also have a hash value which will show that the encrypted message and serialised public key have not been tampered with.
To decrypt, Alice deserialises Bob’s public key to produce b.G. She will then take her private key (a), and perform a Diffie-Hellman operation to get a.b.G. We feed this into an HKDF function with the context, and which will produce the same secret key as Bob (SecretKey). Alice will then be able to decrypt the ciphertext, and reveal the message.
With Hybrid Public Key Encryption (HPKE), we use a recipient’s public key to encrypt plaintext using an asymmetric key encapsulation mechanism (KEM). The key is derived using a key derivation function (KDF), and where the data is encrypted with an authenticated encryption with additional data (AEAD) function. In this case, we will either use: DHKEM_P256_HKDF_SHA256 (using P256 as the KEM), DHKEM_P384_HKDF_SHA384 (using P384 as the KEM), DHKEM_P521_HKDF_SHA512 (using P521 as the KEM) or DHKEM_X25519_HKDF_SHA256 (using X25519 as the KEM). In each case, HKDF is used to generate the key.
The code is [here]:
package main
import (
"fmt"
"os"
"strconv"
"github.com/tink-crypto/tink-go/v2/hybrid/hpke"
"github.com/tink-crypto/tink-go/v2/hybrid"
"github.com/tink-crypto/tink-go/v2/keyset"
)
func main() {
msg:="Testing"
context:="My Context"
method:=hpke.DHKEM_P256_HKDF_SHA256
m:=1
argCount := len(os.Args[1:])
if (argCount>0) {msg = os.Args[1]}
if (argCount>1) {context =os.Args[2]}
if (argCount>2) {m,_ =strconv.Atoi(os.Args[3])}
if (m==2) {method=hpke.DHKEM_P384_HKDF_SHA384
} else if (m==3) {method=hpke.DHKEM_P521_HKDF_SHA512
} else if (m==4) {method=hpke.DHKEM_X25519_HKDF_SHA256
}
plaintext := []byte(msg)
contextInfo := []byte(context)
params,_ := hpke.NewParameters(hpke.ParametersOpts{
KEMID: method,
KDFID: hpke.HKDFSHA256,
AEADID: hpke.AES256GCM,
Variant: hpke.VariantTink,
})
km := keyset.NewManager()
keyID, _ := km.AddNewKeyFromParameters(params)
if err := km.SetPrimary(keyID); err != nil {
}
privateKeyHandle, _ := km.Handle()
publicKeyHandle, _ := privateKeyHandle.Public()
encrypter, _ := hybrid.NewHybridEncrypt(publicKeyHandle)
decrypter, _ := hybrid.NewHybridDecrypt(privateKeyHandle)
ciphertext,_ := encrypter.Encrypt(plaintext, contextInfo)
decrypted,_ := decrypter.Decrypt(ciphertext, contextInfo)
fmt.Printf("Plaintext: %s\n\n",msg)
fmt.Printf("Method: %s\n\n",method)
fmt.Printf("Private key: %s\n\n",privateKeyHandle)
fmt.Printf("Public key: %s\n\n",publicKeyHandle)
fmt.Printf("Ciphertext: %x\n\n",ciphertext)
fmt.Printf("Decrypted: %s\n",decrypted)
}
A sample run for DHKEM-P256-HKDF-SHA256 is [here]:
Plaintext: Testing
Method: DHKEM-P256-HKDF-SHA256
Private key: primary_key_id:1203322961 key_info:{type_url:"type.googleapis.com/google.crypto.tink.HpkePrivateKey" status:ENABLED key_id:1203322961 output_prefix_type:TINK}
Public key: primary_key_id:1203322961 key_info:{type_url:"type.googleapis.com/google.crypto.tink.HpkePublicKey" status:ENABLED key_id:1203322961 output_prefix_type:TINK}
Ciphertext: 0147b94051043bdeb59abad6caebff20753dadfc0bf8a2d6131b6935f149afdc84b450dda4316d79bae3529eaafec63197a55ffd30c186ffea74bbcb811bfb2b93dacfeaab3b4cc8352534fd5f2e7252abac03553b16d607ea791df7c9
Decrypted: Testing