Skip to main content

Elliptic Curve Integrated Encryption Scheme for secp256k1 in Python

Project description

eciespy

Elliptic Curve Integrated Encryption Scheme for secp256k1 in Python

License PyPI - Python Version PyPI Travis branch Codecov

Install

Install like pip install eciespy under Python 3.5 or 3.6.

Quick Start

>>> from ecies.utils import generate_eth_key
>>> from ecies import encrypt, decrypt
>>> k = generate_eth_key()
>>> prvhex = k.to_hex()
>>> pubhex = k.public_key.to_hex()
>>> data = b'this is a test'
>>> decrypt(prvhex, encrypt(pubhex, data))
b'this is a test'

Or just use a builtin command eciespy -h in your favorite command line.

API

ecies.encrypt(receiver_pubhex: str, msg: bytes) -> bytes

Parameters:

  • receiver_pubhex - Receiver's ethereum public key hex string
  • msg - Data to encrypt

ecies.decrypt(receiver_prvhex: str, msg: bytes) -> bytes

Parameters:

  • receiver_prvhex - Receiver's ethereum private key hex string
  • msg - Data to decrypt

Mechanism

This library combines secp256k1 and AES-256-GCM (powered by coincurve and pycryptodome) to provide an API of encrypting with secp256k1 public key and decrypting with secp256k1's private key. It has two steps:

  1. Use ECDH to calculate an AES session key;

    Notice that the server public key is generated every time when ecies.encrypt is invoked, thus, the calculated AES session key varies.

  2. Use this AES session key to encrypt/decrypt the data under AES-256-GCM.

Basically the encrypted data will be like this:

+-------------------------------+----------+----------+-----------------+
| 65 Bytes                      | 16 Bytes | 16 Bytes | == Image size   |
+-------------------------------+----------+----------+-----------------+
| Server Public Key(Disposable) | Nonce/IV | Tag/MAC  | Encrypted Image |
+-------------------------------+----------+----------+-----------------+
| server_pub                    | nonce    | tag      | encrypted_image |
+-------------------------------+----------+----------+-----------------+
|           Secp256k1           |              AES-256-GCM              |
+-------------------------------+---------------------------------------+

Secp256k1

So, how do we calculate the ECDH key under secp256k1? If you use library like coincurve, you just simply call k1.ecdh(k2.public_key.format()), then uh-huh, you got it! Let's see how to do it in simple Python snippets:

>>> from coincurve import PrivateKey
>>> k1 = PrivateKey(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01')
>>> k2 = PrivateKey(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02')
>>> k1.public_key.format(False).hex() # 65 bytes, False means uncompressed key
'0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8'
>>> k2.public_key.format(False).hex() # 65 bytes
'04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a'
>>> k1.ecdh(k2.public_key.format()).hex()
'b1c9938f01121e159887ac2c8d393a22e4476ff8212de13fe1939de2a236f0a7'
>>> k2.ecdh(k1.public_key.format()).hex()
'b1c9938f01121e159887ac2c8d393a22e4476ff8212de13fe1939de2a236f0a7'

However, as a hacker like you with strong desire to learn something, you must be curious about the magic under the ground.

In one sentence, the secp256k1's ECDH key of k1 and k2 is nothing but sha256(k2.public_key.multiply(k1)).

>>> k1.to_int()
1
>>> shared_pub = k2.public_key.multiply(bytes.fromhex(k1.to_hex()))
>>> shared_pub.point()
(89565891926547004231252920425935692360644145829622209833684329913297188986597,
 12158399299693830322967808612713398636155367887041628176798871954788371653930)
>>> import hashlib
>>> h = hashlib.sha256()
>>> h.update(shared_pub.format())
>>> h.hexdigest()  # here you got the ecdh key same as above!
'b1c9938f01121e159887ac2c8d393a22e4476ff8212de13fe1939de2a236f0a7'

Let's discuss in details. The "multiply" here means multiplying a point of a public key on elliptic curve (like (x, y)) with a scalar (like k). Here k is the integer format of a private key, for instance, a simple 1, and (x, y) here is an extremely large number pair like (89565891926547004231252920425935692360644145829622209833684329913297188986597, 12158399299693830322967808612713398636155367887041628176798871954788371653930). A point multiplying a scalar will still come to a point, and the point can be use to calculate the public key (compressed or uncompressed format).

  • Compressed
>>> point = (89565891926547004231252920425935692360644145829622209833684329913297188986597, 12158399299693830322967808612713398636155367887041628176798871954788371653930)
>>> prefix = '02' if point[1] % 2 == 0 else '03'
>>> compressed_key_hex = prefix + hex(point[0])[2:]
>>> compressed_key = bytes.fromhex(compressed_key_hex)
>>> compressed_key.hex()
'02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5'
  • Uncompressed
>>> uncompressed_key_hex = '04'+ hex(point[0])[2:] + hex(point[1])[2:]
>>> uncompressed_key = bytes.fromhex(uncompressed_key_hex)
>>> uncompressed_key.hex()
'04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a'

Then, the shared key between k1 and k2 is the sha256 hash of the compressed key.

>>> h = hashlib.sha256()
>>> h.update(compressed_key)
>>> h.hexdigest()
'b1c9938f01121e159887ac2c8d393a22e4476ff8212de13fe1939de2a236f0a7'

AES

Now we have the shared key, and we can use the nonce and tag to decrypt. The example derives from pycryptodome's documentation.

>>> from Cryptodome.Cipher import AES
>>> key = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>> nonce = b'\xf3\xe1\xba\x81\r,\x89\x00\xb1\x13\x12\xb7\xc7%V_'
>>> tag = b'\xec;q\xe1|\x11\xdb\xe3\x14\x84\xda\x94P\xed\xcfl'
>>> data = b'\x02\xd2\xff\xed\x93\xb8V\xf1H\xb9'
>>> decipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
>>> decipher.decrypt_and_verify(data, tag)
b'helloworld'

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

eciespy-0.1.0.tar.gz (7.2 kB view hashes)

Uploaded Source

Built Distribution

eciespy-0.1.0-py3-none-any.whl (8.2 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page