Verifiable Encryption With ElGamal and Kryptology using Golang

With verifiable encryption, Bob can generate a Non-interactive Zero-Knowledge Proof (NIZKP) that a certain encryption key has been used to…

Verifiable Encryption With ElGamal and Kryptology using Golang

With verifiable encryption, Bob can generate a Non-interactive Zero-Knowledge Proof (NIZKP) that a certain encryption key has been used to encrypt a message to ciphertext. Alice can then receive the ciphertext, and then check the encryption key that has been used, and verify it. An example of this might be where Bob sends Alice an encryption key and uses Trent’s public key to encrypt it. Bob then creates a proof of the key and sends it to Alice with the ciphertext. Alice checks the proof and makes sure it has used Trent’s public key. Alice can then pass the ciphertext to Trent, and then who can also produce a proof of successful decryption.

ElGamal encryption has many great advantages over other public key methods, including the usage of homomorphic encryption. The following defines the basic method of using ElGamal with elliptic curve cryptography:

To create the proof:

We then get two Schnorr proof values. To verify:

In this way, we use a hybrid method of encryption, and where we derive the symmetric key we want to use, and then use AEAD to link it to the ciphertext. We can use the Kryptology library developed by Coinbase [here] to implement verifiable encryption using the ElGamal method [here]:

package main
import (
"fmt"
	"os"
	"github.com/coinbase/kryptology/pkg/core/curves"
"github.com/coinbase/kryptology/pkg/verenc/elgamal"
)
func main() {
	argCount := len(os.Args[1:])
val := "hello"
if argCount > 0 {
val = os.Args[1]
}
	domain := []byte("MyDomain")
	k256 := curves.K256()
ek, dk, _ := elgamal.NewKeys(k256)
	msgBytes := []byte(val)
	cs, proof, _ := ek.VerifiableEncrypt(msgBytes, &elgamal.EncryptParams{
Domain: domain,
MessageIsHashed: true,
GenProof: true,
ProofNonce: domain,
})
	fmt.Printf("=== ElGamal Verifiable Encryption ===\n")
fmt.Printf("Input text: %s\n", val)
fmt.Printf("=== Generating keys ===\n")
res1, _ := ek.MarshalBinary()
fmt.Printf("Public key %x\n", res1)
res2, _ := dk.MarshalBinary()
fmt.Printf("Private key %x\n", res2)
fmt.Printf("=== Encrypting and Decrypting ===\n")
res3, _ := cs.MarshalBinary()
fmt.Printf("\nCiphertext: %x\n", res3)
dbytes, _, _ := dk.VerifiableDecryptWithDomain(domain, cs)
fmt.Printf("\nDecrypted: %s\n", dbytes)
	fmt.Printf("\n=== Checking proof===\n")
rtn := ek.VerifyDomainEncryptProof(domain, cs, proof)
if rtn == nil {
fmt.Printf("Encryption has been verified\n")
} else {
fmt.Printf("Encryption has NOT been verified\n")
}
	fmt.Printf("=== Now we will try with the wrong proof ===\n")
ek2, _, _ := elgamal.NewKeys(k256)
cs, proof2, _ := ek2.VerifiableEncrypt(msgBytes, &elgamal.EncryptParams{
Domain: domain,
MessageIsHashed: true,
GenProof: true,
ProofNonce: domain,
})
	rtn = ek.VerifyDomainEncryptProof(domain, cs, proof2)
if rtn == nil {
fmt.Printf("Encryption has been verified\n")
} else {
fmt.Printf("Encryption has NOT been verified\n")
}
}

A sample run shows that a valid encryption key produces a valid proof, and then an invalid one generates an incorrect proof [here]:

=== ElGamal Verifiable Encryption ===
Input text: Hello
=== Generating keys ===
Public key 2103f8647f379938aecf076e08fe2612593abb9f2b4c528ee5192d801471debb92ca09736563703235366b31
Private key 20b6ac09315a6f38134c32dcba6bdb2d075e1acc76fa68de9630743a7aa7d2164409736563703235366b31
=== Encrypting and Decrypting ===
Ciphertext: 2102eecf68362fbf3c611b1d093d7f73cca746e66095a4d2fe276f35549065a36ffe21037cde7d60f85ded441a405e72792d88b2200a3baaee05282c7c9901b920a36c040c24d788cc27a0863bc41b5a0215ee1e287a2dcb11c0727747e67a6f759e7b4cb54c7109736563703235366b3101
Decrypted: Hello
=== Checking proof===
Encryption has been verified
=== Now we will try with the wrong proof ===
Encryption has NOT been verified

In this we can see that the first proof will work, but the second one is generated from a different encryption key, and will fail.