Metadata-Version: 2.1
Name: eciespy
Version: 0.1.0
Summary: Elliptic Curve Integrated Encryption Scheme for secp256k1 in Python
Home-page: https://github.com/kigawas/eciespy
Author: Weiliang Li
Author-email: to.be.impressive@gmail.com
License: Apache-2.0
Keywords: secp256k1,crypto,elliptic curves,ecies,bitcoin,ethereum,cryptocurrency
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: Topic :: Security :: Cryptography
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: License :: OSI Approved :: Apache Software License
Description-Content-Type: text/markdown
Requires-Dist: eth-keys
Requires-Dist: pysha3
Requires-Dist: pycryptodomex
Requires-Dist: coincurve

# eciespy
Elliptic Curve Integrated Encryption Scheme for secp256k1 in Python


[![License](https://img.shields.io/github/license/kigawas/eciespy.svg)](https://github.com/kigawas/eciespy)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/eciespy.svg)](https://pypi.org/project/eciespy/)
[![PyPI](https://img.shields.io/pypi/v/eciespy.svg)](https://pypi.org/project/eciespy/)
[![Travis branch](https://img.shields.io/travis/kigawas/eciespy/master.svg)](https://travis-ci.org/kigawas/eciespy)
[![Codecov](https://img.shields.io/codecov/c/github/kigawas/eciespy.svg)](https://codecov.io/gh/kigawas/eciespy)

# Install
Install like `pip install eciespy` under Python 3.5 or 3.6.

# Quick Start

```python
>>> 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`](https://github.com/ofek/coincurve) and [`pycryptodome`](https://github.com/Legrandin/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](https://www.wikiwand.com/en/Elliptic-curve_Diffie%E2%80%93Hellman) 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`](https://github.com/ofek/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:

```python
>>> 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))`.

```python
>>> 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

```python
>>> 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
```python
>>> 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.
```python
>>> 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](https://pycryptodome.readthedocs.io/en/latest/src/examples.html#encrypt-data-with-aes).

```python
>>> 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'
```


