With verifiable encryption, Bob can prove to Alice that he has used a given encryption key of a ciphertext with a NIZK (Non-interactive Zero Knowledge Proof). In this case we will use ElGamal encryption and generate a proof of the public key which has been used for the encryption. If Bob uses Trent's public key to encrypt some ciphertext for Alice, then Bob can produce a proof that it has been encrypted with Trent's public key. Alice will then be able to check this against Trent's public key.
Theory
We initially create a private key as a random number and a public key of:
With standard ElGamal encryption, we generate a random value to give:
We then create a symmetric key from this elliptic curve point:
and where just converts a point on the curve to a byte array value that is the length of the required symmetric encryption key (such as for 32 bytes in the case of 256-bit Anubis).
Next, we compute the ciphertext values of:
and where is the value converted into a scalar value. We then append these together to create the additional data that will be used for the symmetric key encryption of the message:
We then generate a nonce value () and then perform symmetric key encryption on the message:
The ciphertext then has values of , , , and . , are points on the curve, and the value and are byte array values. To decrypt, we take the private key () and derive:
Here is an overview of the method:
To generate the proof, we generate a random value () and a blinding factor () to give two points on the elliptic curve:
Next, we create the challenge bytes with:
We take this value and hash it (), and create a scalar value with () to produce:
We then create two Schnorr proof values:
To verify the proof, we reconstruct :
This works because:
We then reconstruct the challenge with:
We take this value and hash it (), and create a scalar value with () to produce:
This value is then checked against the challenge in the proof, and if they are the same, the proof is verified.
package main
import (
"fmt"
"os"
"github.com/pedroalbanese/ecka-eg/core/curves"
"github.com/pedroalbanese/ecka-eg/elgamal"
)
func main() {
argCount := len(os.Args[1:])
val := "hello"
if argCount > 0 {
val = os.Args[1]
}
domain := []byte("MyDomain")
bls12381g1 := curves.BLS12381G1()
ek, dk, _ := elgamal.NewKeys(bls12381g1)
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(bls12381g1)
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")
}
}
Documentation
BSI TR-03111 ECKA-EG (Elliptic Curve Key Agreement based on ElGamal)
This project is licensed under the ISC License.