ECC Encryption / Decryption

In this section we shall explain how to implement **elliptic-curve based public-key encryption / decryption** (asymmetric encryption scheme based on ECC). This is **non-trivial** and usually involves a design of hybrid encryption scheme, involving ECC cryptography, ECDH key exchange and symmetric encryption algorithm.

Assume we have a ECC **private-public key pair**. We want to encrypt and decrypt data using these keys. By definition, **asymmetric encryption** works as follows: if we **encrypt data by a private key**, we will be able to **decrypt** the ciphertext later by the corresponding **public key**:

The above process can be directly applied for the **RSA** cryptosystem, but not for the **ECC**. The elliptic curve cryptography (ECC) **does not directly provide encryption** method. Instead, we can design a **hybrid encryption scheme** by using the **ECDH** (Elliptic Curve Diffieâ€“Hellman) key exchange scheme to derive a **shared secret key** for symmetric data encryption and decryption.

This is how most **hybrid encryption schemes** works (the encryption process):

This is how most **hybrid encryption schemes** works (the decryption process):

Let's get into details how to design and implement an **ECC-based hybrid encryption scheme**.

ECC-Based Secret Key Derivation (using ECDH)

Assume we have a **cryptographic elliptic curve** over finite field, along with its generator point **G**. We can use the following two functions to calculate a **shared a secret key** for **encryption** and **decryption** (derived from the ECDH scheme):

**calculateEncryptionKey**(pubKey) --> (sharedECCKey, ciphertextPubKey)- 1.Generate
**ciphertextPrivKey**=*new**random**private key*. - 2.Calculate
**ciphertextPubKey**= ciphertextPrivKey * G. - 3.Calculate the ECDH shared secret:
**sharedECCKey**= pubKey * ciphertextPrivKey. - 4.Return both the
**sharedECCKey**+**ciphertextPubKey**. Use the**sharedECCKey**for symmetric encryption. Use the randomly generated**ciphertextPubKey**to calculate the decryption key later.

**calculateDecryptionKey**(privKey, ciphertextPubKey) --> sharedECCKey- 1.Calculate the the ECDH shared secret:
**sharedECCKey**= ciphertextPubKey * privKey. - 2.Return the
**sharedECCKey**and use it for the decryption.

The above calculations use the same math, like the **ECDH** algorithm (see the previous section). Recall that EC points have the following property:

- (
**a***G**) *= (*b***b***G**) **a*

Now, assume that *a* = privKey, *a* * **G** = pubKey, *b* = ciphertextPrivKey, *b* * **G** = ciphertextPubKey.

The above equation takes the following form:

- pubKey * ciphertextPrivKey = ciphertextPubKey * privKey =
**sharedECCKey**

This is what exactly the above two functions calculate, directly following the **ECDH key agreement** scheme. In the hybrid encryption schemes the encapsulated **ciphertextPubKey** is also known as "**ephemeral key**", because it is used temporary, to derive the symmetric encryption key, using the ECDH key agreement scheme.

ECC-Based Secret Key Derivation - Example in Python

The below Python code uses the **ECC private-public key pair** for the message recipient (based on the **secret shared key** (for encryption) and ephemeral **ciphertext public key** (for ECDH) from the recipient's **public key** and later derive the same **secret shared key** (for decryption) from the recipient's **private key** and the generated earlier ephemeral **ciphertext public key**:

`tinyec`

library to generate a `brainpoolP256r1`

curve) and then derive a 1

from tinyec import registry

2

import secrets

3

â€‹

4

curve = registry.get_curve('brainpoolP256r1')

5

â€‹

6

def compress_point(point):

7

return hex(point.x) + hex(point.y % 2)[2:]

8

â€‹

9

def ecc_calc_encryption_keys(pubKey):

10

ciphertextPrivKey = secrets.randbelow(curve.field.n)

11

ciphertextPubKey = ciphertextPrivKey * curve.g

12

sharedECCKey = pubKey * ciphertextPrivKey

13

return (sharedECCKey, ciphertextPubKey)

14

â€‹

15

def ecc_calc_decryption_key(privKey, ciphertextPubKey):

16

sharedECCKey = ciphertextPubKey * privKey

17

return sharedECCKey

18

â€‹

19

privKey = secrets.randbelow(curve.field.n)

20

pubKey = privKey * curve.g

21

print("private key:", hex(privKey))

22

print("public key:", compress_point(pubKey))

23

â€‹

24

(encryptKey, ciphertextPubKey) = ecc_calc_encryption_keys(pubKey)

25

print("ciphertext pubKey:", compress_point(ciphertextPubKey))

26

print("encryption key:", compress_point(encryptKey))

27

â€‹

28

decryptKey = ecc_calc_decryption_key(privKey, ciphertextPubKey)

29

print("decryption key:", compress_point(decryptKey))

Copied!

The code is pretty simple and demonstrates that we can generate a pair { **secret key** + **ciphertext public key** } from given EC **public key** and later we can recover the same **secret key** from the pair { **ciphertext public key** + **private key** }. The above code produces output like this:

1

private key: 0x2e2921b4cde59cdf01e7a014a322abd530b3015085c31cb6e59502da761d29e9

2

public key: 0x850d3873cf4ac50ddb54ddbd27f8225fc43bd3f4c2cc0a4f9d1f9ce15fc4eb711

3

ciphertext pubKey: 0x71586f9999d3ee050005054bc681c1d96c5eb054ca15b080ba245e495627003b0

4

encryption key: 0x9d13d3f8f9747669432f575731926b5ed99a6883f00146cbd3203ffa7ff8b1ae1

5

decryption key: 0x9d13d3f8f9747669432f575731926b5ed99a6883f00146cbd3203ffa7ff8b1ae1

Copied!

It is clear from the above output that the **encryption key** (derived from the public key) and the **decryption key** (derived from the corresponding private key) **are the same**. This is due to the above discussed property of the ECC:

`pubKey * ciphertextPrivKey = ciphertextPubKey * privKey`

. These keys will be used for data encryption and decryption in an integrated encryption scheme. The above output will be different if you run the code (due to the randomness used to generate `ciphertextPrivKey`

, but the encryption and decryption keys will always be the same (the ECDH shared secret).The above demonstrated mechanism for generating a shared ephemeral secret key, based on a ECC key pair, is an example of **KEM** (key encapsulation mechanism), based on the ECC and ECDH.

ECC-Based Hybrid Encryption / Decryption - Example in Python

Once we have the **secret key**, we can use it for **symmetric data encryption**, using a symmetric encryption scheme like AES-GCM or ChaCha20-Poly1305. Let's implement a fully-functional **asymmetric ECC encryption and decryption** hybrid scheme. It will be based on the **AES-256-GCM** authenticated symmetric cipher.

`brainpoolP256r1`

curve and the We shall use the

`tinyec`

and `pycryptodome`

Python libraries respectively for ECC calculations and for the AES cipher:1

pip install tinyec

2

pip install pycryptodome

Copied!

Let's examine this full **ECC + AES hybrid encryption** example:

1

from tinyec import registry

2

from Crypto.Cipher import AES

3

import hashlib, secrets, binascii

4

â€‹

5

def encrypt_AES_GCM(msg, secretKey):

6

aesCipher = AES.new(secretKey, AES.MODE_GCM)

7

ciphertext, authTag = aesCipher.encrypt_and_digest(msg)

8

return (ciphertext, aesCipher.nonce, authTag)

9

â€‹

10

def decrypt_AES_GCM(ciphertext, nonce, authTag, secretKey):

11

aesCipher = AES.new(secretKey, AES.MODE_GCM, nonce)

12

plaintext = aesCipher.decrypt_and_verify(ciphertext, authTag)

13

return plaintext

14

â€‹

15

def ecc_point_to_256_bit_key(point):

16

sha = hashlib.sha256(int.to_bytes(point.x, 32, 'big'))

17

sha.update(int.to_bytes(point.y, 32, 'big'))

18

return sha.digest()

19

â€‹

20

curve = registry.get_curve('brainpoolP256r1')

21

â€‹

22

def encrypt_ECC(msg, pubKey):

23

ciphertextPrivKey = secrets.randbelow(curve.field.n)

24

sharedECCKey = ciphertextPrivKey * pubKey

25

secretKey = ecc_point_to_256_bit_key(sharedECCKey)

26

ciphertext, nonce, authTag = encrypt_AES_GCM(msg, secretKey)

27

ciphertextPubKey = ciphertextPrivKey * curve.g

28

return (ciphertext, nonce, authTag, ciphertextPubKey)

29

â€‹

30

def decrypt_ECC(encryptedMsg, privKey):

31

(ciphertext, nonce, authTag, ciphertextPubKey) = encryptedMsg

32

sharedECCKey = privKey * ciphertextPubKey

33

secretKey = ecc_point_to_256_bit_key(sharedECCKey)

34

plaintext = decrypt_AES_GCM(ciphertext, nonce, authTag, secretKey)

35

return plaintext

36

â€‹

37

msg = b'Text to be encrypted by ECC public key and ' \

38

b'decrypted by its corresponding ECC private key'

39

print("original msg:", msg)

40

privKey = secrets.randbelow(curve.field.n)

41

pubKey = privKey * curve.g

42

â€‹

43

encryptedMsg = encrypt_ECC(msg, pubKey)

44

encryptedMsgObj = {

45

'ciphertext': binascii.hexlify(encryptedMsg[0]),

46

'nonce': binascii.hexlify(encryptedMsg[1]),

47

'authTag': binascii.hexlify(encryptedMsg[2]),

48

'ciphertextPubKey': hex(encryptedMsg[3].x) + hex(encryptedMsg[3].y % 2)[2:]

49

}

50

print("encrypted msg:", encryptedMsgObj)

51

â€‹

52

decryptedMsg = decrypt_ECC(encryptedMsg, privKey)

53

print("decrypted msg:", decryptedMsg)

Copied!

Run the above code example: https://repl.it/@nakov/ECC-based-hybrid-encryption-decryption-in-Python.

The above example starts from generating an ECC public + private **key pair** for the message recipient: **encrypt** the message **decrypt** is later back to its original form.

`pubKey`

+ `privKey`

, using the `tinyec`

library. These keys will be used to `msg`

through the hybrid encryption scheme (asymmetric ECC + symmetric AES) and to Next, we **encrypt**

`msg`

by using the `pubKey`

and we obtain as a result the following set of output: { `ciphertext`

, `nonce`

, `authTag`

, `ciphertextPubKey`

}. The `ciphertext`

is obtained by the symmetric AES-GCM encryption, along with the `nonce`

(random AES initialization vector) and `authTag`

(the MAC code of the encrypted text, obtained by the GCM block mode). Additionally, we obtain a randomly generated ephemeral public key `ciphertextPubKey`

, which will be encapsulated in the encrypted message and will be used to recover the AES symmetric key during the decryption (using the ECDH key agreement scheme, as it was show before).To **decrypt** the encrypted message, we use the data produced during the encryption { **exception**.

`ciphertext`

, `nonce`

, `authTag`

, `ciphertextPubKey`

}, along with the decryption `privateKey`

. The result is the decrypted plaintext message. We use authenticated encryption (GCM block mode), so if the decryption key or some other parameter is incorrect, the decryption will fail with an Internally, the **ECC key-pair** for the ciphertext and calculates the symmetric encryption shared ECC key **256-bit AES secret key** (integer) though hashing the point's **AES-256-GCM** cipher (from **encrypts** the message by the 256-bit shared secret key **output**

`encrypt_ECC(msg, pubKey)`

function first generates an ephemeral `sharedECCKey = ciphertextPrivKey * pubKey`

. This key is an EC point, so it is then transformed to `x`

and `y`

coordinates. Finally, the `pycryptodome`

) `secretKey`

and produces as `ciphertext`

+ `nonce`

+ `authTag`

.The **256-bit AES secret key** though hashing the point's **AES-256-GCM cipher** is used to **decrypt** the

`decrypt_ECC(encryptedMsg{ciphertext, nonce, authTag, ciphertextPubKey}, privKey)`

function internally first calculates the symmetric encryption shared ECC key `sharedECCKey = privKey * ciphertextPubKey`

. It is an EC point, so it should be first transformed to `x`

and `y`

coordinates. Then the `ciphertext`

+ `nonce`

+ `authTag`

by the 256-bit shared secret key `secretKey`

. The produced output is the original plaintext message (or an exception in case of incorrect decryption key or unmatching `authTag`

).The output from the above code looks like this:

1

original msg: b'Text to be encrypted by ECC public key and decrypted by its corresponding ECC private key'

2

encrypted msg: {'ciphertext': b'b5953b3082fcefdbde91dd3c03cf83dde0822c19be6ae906a634db65115295e7cbcd7a1a492d69ba5be91990c70d8df9dc84360cf554f155ef81ce1f0ad44bd9fdabbc5f960517089262b3390e61b37610012bee4e6bcae335', 'nonce': b'9d55f4b5c87fff773d0457f3b23a953e', 'authTag': b'5c9d339778925aa4e44f43252a28681d', 'ciphertextPubKey': '0x21dbc985b625f2a42d0f86fc234b49b55477928bae73dfac73bafd9bed50abe70'}

3

decrypted msg: b'Text to be encrypted by ECC public key and decrypted by its corresponding ECC private key'

Copied!

Enjoy the above example, **play with it**, try to understand how exactly it works, try to change the underlying ECC curve, try to change the symmetric encryption algorithm, try to decrypt the ciphertext with wrong private key.

Last modified 7mo ago