Skip to content

pedroalbanese/ecka-eg

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ecka-eg

ISC License GoDoc Go Report Card

ECKA-EG Key Agreement Protocol

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 x and a public key of:

Q = x \cdot G

With standard ElGamal encryption, we generate a random value r to give:

t = r \cdot Q

We then create a symmetric key from this elliptic curve point:

AEADKey = \text{Derive}(t)

and where Derive 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:

C1 = r \cdot G
C2 = M \cdot H + r \cdot Q

and where M is the msg 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:

AAD = C1 || C2

We then generate a nonce value (Nonce) and then perform symmetric key encryption on the message:

cipher = \text{EncAEADKey}(\text{msg}, \text{Nonce}, \text{AAD})

The ciphertext then has values of C1, C2, Nonce, and cipher. C1, C2 are points on the curve, and the Nonce value and cipher are byte array values. To decrypt, we take the private key (x) and derive:

t = x \cdot C1
AEADKey = \text{Derive}(t)
AAD = C1 || C2
msg = \text{DecAEADKey}(\text{cipher}, \text{Nonce}, \text{AAD})

Here is an overview of the method:

To generate the proof, we generate a random value (r) and a blinding factor (b) to give two points on the elliptic curve:

R1 = r \cdot G
R2 = r \cdot Q + b \cdot H

Next, we create the challenge bytes with:

chall = C1 || C2 || R1 || R2 || \text{Nonce}

We take this value and hash it (H()), and create a scalar value with (ek) to produce:

c = H(\text{chall}) \cdot ek

We then create two Schnorr proof values:

S1 = b - c \cdot m S2 = r - c \cdot b

To verify the proof, we reconstruct R1:

R1 = c \cdot C1 + S2 \cdot G

We reconstruct R2:

R2 = c \cdot C2 + S1 \cdot Q + S1 \cdot H

This works because:

\begin{align*} R2 & = c \cdot C2 + S1 \cdot Q + S1 \cdot H \ & = c \cdot (b \cdot Q + m \cdot H) + (r - cb) \cdot Q + (b - cm) \cdot H \ & = (cb + r - cb) \cdot Q + (cm + b - cm) \cdot H \ & = r \cdot Q + b \cdot H \end{align*}

We then reconstruct the challenge with:

chall = C1 || C2 || R1 || R2 || \text{Nonce}

We take this value and hash it (H()), and create a scalar value with (ek) to produce:

c = H(\text{chall}) \cdot ek

This value is then checked against the challenge in the proof, and if they are the same, the proof is verified.

Usage

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)

License

This project is licensed under the ISC License.

Copyright (c) 2020-2024 Pedro F. Albanese - ALBANESE Research Lab.