Ethereum Classic Elliptic Curve Integrated Encryption Scheme

in #ethereum5 years ago (edited)

Ethereum Classic (ETC) uses a multistep protocol to send private messages between network nodes. This is referred to as the Elliptic Curve Integrated Encryption Scheme (ECIES).

Procedure

The first ETC ECIES step is to create an “ephemeral” private and public key pair. The second step is to create a shared secret by multiplying the ephemeral private key and the public key of the other node. See the Elliptic Curve Diffie Hellman algorithm for further details on this. The third step is to use this shared secret to derive an AES (Advanced Encryption Standard) key and an HMAC (hash based message authentication code) secret using the ETC key derivation function. The fourth step is to encrypt the message which involves creating a random “initialization vector”. The fifth step is to create an HMAC for the initialization vector and the encrypted message. The last step is to send the following components to the other node:

1. ephemeral public key
2. initialization vector
3. encrypted message
4. HMAC


ETC uses AES in “counter mode” with 128 bit keys. The initialization vector and its derivatives are encrypted then exclusive OR’d with the message to produce the encrypted version.

Implentation

An example Python ETC ECIES implementation is provided below which uses the Python Cryptography Toolkit (pycrypto) package:

#!/usr/bin/env python3

import Crypto.Cipher.AES
import Crypto.Util.Counter
import hashlib
import random

N  = 115792089237316195423570985008687907852837564279074904382605163141518161494337
P  = 115792089237316195423570985008687907853269984665640564039457584007908834671663
Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240
Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424

PUBLIC_KEY_SIZE   = 64
SHA256_BLOCK_SIZE = 64
AES_KEY_SIZE      = 16
AES_BLOCK_SIZE    = 16
HMAC_SIZE         = 32
NUM_BITS_PER_BYTE = 8

def invert(number, modulus):
        """
        Finds the inverses of natural numbers.
        """

        result = 1
        power  = number
        for e in bin(modulus - 2)[2:][::-1]:
                if int(e):
                        result = (result * power) % modulus
                power = (power ** 2) % modulus

        return result

def add(pair_1, pair_2):
        """
        Finds the sums of two pairs of natural numbers.
        """

        if   pair_1 == [0, 0]:
                result = pair_2
        elif pair_2 == [0, 0]:
                result = pair_1
        else:
                if pair_1 == pair_2:
                        temp = 3 * pair_1[0] ** 2
                        temp = (temp * invert(2 * pair_1[1], P)) % P
                else:
                        temp = pair_2[1] - pair_1[1]
                        temp = (temp * invert(pair_2[0] - pair_1[0], P)) % P
                result = (temp ** 2 - pair_1[0]  - pair_2[0]) % P
                result = [result, (temp * (pair_1[0] - result) - pair_1[1]) % P]

        return result

def multiply(number, pair):
        """
        Finds the products of natural numbers and pairs of natural numbers.
        """

        result = [0, 0]
        power  = pair[:]
        for e in bin(number)[2:][::-1]:
                if int(e):
                        result = add(result, power)
                power = add(power, power)

        return result

def make_private_key():
        """
        Creates random private keys.
        """

        return random.randint(1, N)

def get_public_key(private_key):
        """
        Calculates public keys from private keys.
        """

        return multiply(private_key, (Gx, Gy))

def get_shared_secret(private_key, public_key):
        """
        Calculates shared secrets from public and private keys.
        """

        return multiply(private_key, public_key)[0]

def kdf(secret):
        """
        Implements the key derivation function.
        """

        secret = secret.to_bytes(PUBLIC_KEY_SIZE // 2, "big")
        key    = hashlib.sha256(b"\x00\x00\x00\x01" + secret).digest()

        return key

def hmac(secret, message):
        """
        Calculates hash based message authenticaion codes (HMACs).
        """

        secret = hashlib.sha256(secret).digest()
        secret = secret.ljust(SHA256_BLOCK_SIZE, b"\x00")
        ipad   = SHA256_BLOCK_SIZE * bytes.fromhex("36")
        opad   = SHA256_BLOCK_SIZE * bytes.fromhex("5c")
        key_1  = bytes([x ^ y for x, y in zip(secret, ipad)])
        key_2  = bytes([x ^ y for x, y in zip(secret, opad)])
        hash_  = hashlib.sha256(key_1 + message).digest()
        hmac_  = hashlib.sha256(key_2 +   hash_).digest()

        return hmac_

def aes_ctr_encrypt(message, key, iv):
        """
        Encrypts messages using AES in counter mode.
        """

        iv      = int.from_bytes(iv, 'big')
        counter = Crypto.Util.Counter.new(AES_KEY_SIZE * NUM_BITS_PER_BYTE,
                                          initial_value = iv)
        aes_ctr = Crypto.Cipher.AES.new(key,
                                        Crypto.Cipher.AES.MODE_CTR,
                                        counter = counter)

        return aes_ctr.encrypt(message)

def ecies_send(message, receiv_pub_key):
        """
        Creates the ECIES byte string to send a message.
        """

        eph_priv_key   = make_private_key()
        eph_pub_key    = get_public_key(eph_priv_key)
        eph_pub_key[0] = eph_pub_key[0].to_bytes(PUBLIC_KEY_SIZE // 2, "big")
        eph_pub_key[1] = eph_pub_key[1].to_bytes(PUBLIC_KEY_SIZE // 2, "big")
        eph_pub_key    = b"\x04" + eph_pub_key[0] + eph_pub_key[1]
        shared_secret  = get_shared_secret(eph_priv_key, receiv_pub_key)
        kdf_key        = kdf(shared_secret)
        aes_key        = kdf_key[:AES_KEY_SIZE]
        hmac_secret    = kdf_key[AES_KEY_SIZE:]
        iv             = random.getrandbits(AES_BLOCK_SIZE * NUM_BITS_PER_BYTE)
        iv             = iv.to_bytes(AES_BLOCK_SIZE, "big")
        ciphertext     = aes_ctr_encrypt(message, aes_key, iv)
        hmac_          = hmac(hmac_secret, iv + ciphertext)

        return eph_pub_key + iv + ciphertext + hmac_

def ecies_receive(received, receiv_priv_key):
        """
        Extracts the message from an ECIES byte string.
        """

        eph_pub_key   = received[:PUBLIC_KEY_SIZE + 1]
        eph_pub_key   = [eph_pub_key[1:][:PUBLIC_KEY_SIZE // 2],
                         eph_pub_key[1:][PUBLIC_KEY_SIZE // 2:]]
        eph_pub_key   = [int.from_bytes(e, "big") for e in eph_pub_key]
        iv_index      = PUBLIC_KEY_SIZE + 1
        iv            = received[iv_index:iv_index + AES_BLOCK_SIZE]
        ciphertext    = received[iv_index + AES_BLOCK_SIZE:-HMAC_SIZE]
        hmac_         = received[-HMAC_SIZE:]
        shared_secret = get_shared_secret(receiv_priv_key, eph_pub_key)
        kdf_key       = kdf(shared_secret)
        aes_key       = kdf_key[:AES_KEY_SIZE]
        hmac_secret   = kdf_key[AES_KEY_SIZE:]
        message       = b""
        if hmac(hmac_secret, iv + ciphertext) == hmac_:
                message += aes_ctr_encrypt(ciphertext, aes_key, iv)

        return message


Here is an example of its usage in a Python shell:

>>> receiv_priv_key = make_private_key()

>>> receiv_priv_key
65220784268995169636487104126103071511455089901114970914953057647529653418334

>>> receiv_pub_key = get_public_key(receiv_priv_key)

>>> receiv_pub_key
[27571019357111177175074932706008560366518621617269680530116383769747086735803, 6491692355663560906178933703971523490656680637639444902444063317701241768362]

>>> message = b"This is the message."

>>> sent = ecies_send(message, receiv_pub_key)

>>> sent
b"\x04\xde\x02\x8a\x1b\x9er\x99s\xc8\xe5\x08m\xd3\xfa@\xc6,\x04\xe6%\x9e\xd3Y\xc1k\xc1X\x9c\xef\xfb^\x86K\xbb\xef\xb6]\xbc5\xec\rC|\xc7\xc2\xf5\x16\xa4s%_x7\xd64W\xb2<\x18\x03#t\xc4S\x16Z\xeb>j\x1f=\xd8\xb5_\x91v\xf4'\xe9\x88\xb5\x0b\x83\xedr\xd5`\xd8\x9e\x06`\xfc\xa1\xd6\xf8[~\x01WwT[2\x08\xd3\x11\xf3v\xb2\\\xd2\xc7\x87\t\xa0J\xc7\x17\xef\xd5\xedt\xf4\xcave\x02\xf9\xed\xdf\x95\x82"

>>> ecies_receive(sent, receiv_priv_key)
b'This is the message.'

Feedback

Feel free to leave any comments or questions below. You can also contact me by email at cs@etcplanet.org or by clicking any of these icons:

Acknowledgements

I would like to thank IOHK (Input Output Hong Kong) for funding this effort.

License

This work is licensed under the Creative Commons Attribution ShareAlike 4.0 International License.

Sort:  

Congratulations @cseberino! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 3 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!