Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .cspell.jsonc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"words": [
"bitcointalk",
"chacha",
"Cipolla",
"Codacy",
"Codecov",
Expand All @@ -14,12 +15,17 @@
"helloworld",
"hexdigest",
"hkdf",
"keccak",
"pycryptodome",
"pytest",
"readablize",
"secp",
"urandom",
"xcfl",
"xchacha"
],
"ignorePaths": [".cspell.jsonc", "LICENSE"]
"ignorePaths": [
".cspell.jsonc",
"LICENSE"
]
}
10 changes: 5 additions & 5 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: 2
updates:
- package-ecosystem: pip
directory: "/"
schedule:
interval: monthly
open-pull-requests-limit: 10
- package-ecosystem: pip
directory: "/"
schedule:
interval: monthly
open-pull-requests-limit: 3
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Release Notes

## 0.4.4

- Make `eth-keys` optional
- Drop Python 3.8
- Refactor `utils`
- Revamp documentation
- Bump dependencies

## 0.4.1 ~ 0.4.3

- Bump dependencies
Expand Down
2 changes: 1 addition & 1 deletion DETAILS.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,6 @@ Now we have the shared key, and we can use the `nonce` and `tag` to decrypt. Thi
b'helloworld'
```

> Strictly speaking, `nonce` != `iv`, but this is a little bit off topic, if you are curious, you can check [the comment in `utils/symmetric.py`](./ecies/utils/symmetric.py#L79).
> Strictly speaking, `nonce` != `iv`, but this is a little bit off topic, if you are curious, you can check [the comment in `utils/symmetric.py`](./ecies/utils/symmetric.py#L86).
>
> Warning: it's dangerous to reuse nonce, if you don't know what you are doing, just follow the default setting.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2018-2024 Weiliang Li
Copyright (c) 2018-2025 Weiliang Li

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
44 changes: 23 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,66 @@
# eciespy

[![Codacy Badge](https://api.codacy.com/project/badge/Grade/2a11aeb9939244019d2c64bce3ff3c4e)](https://app.codacy.com/gh/ecies/py/dashboard)
[![License](https://img.shields.io/github/license/ecies/py.svg)](https://github.com/ecies/py)
[![PyPI](https://img.shields.io/pypi/v/eciespy.svg)](https://pypi.org/project/eciespy/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/eciespy)](https://pypistats.org/packages/eciespy)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/eciespy.svg)](https://pypi.org/project/eciespy/)
[![CI](https://img.shields.io/github/actions/workflow/status/ecies/py/ci.yml?branch=master)](https://github.com/ecies/py/actions)
[![Codecov](https://img.shields.io/codecov/c/github/ecies/py.svg)](https://codecov.io/gh/ecies/py)
[![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/)
[![License](https://img.shields.io/github/license/ecies/py.svg)](https://github.com/ecies/py)

Elliptic Curve Integrated Encryption Scheme for secp256k1 in Python.

Other language versions:

- [Rust](https://github.com/ecies/rs)
- [TypeScript](https://github.com/ecies/js)
- [Rust](https://github.com/ecies/rs)
- [Golang](https://github.com/ecies/go)
- [WASM](https://github.com/ecies/rs-wasm)
- [Java](https://github.com/ecies/java)
- [Dart](https://github.com/ecies/dart)

You can also check a FastAPI web backend demo [here](https://github.com/ecies/py-demo).
You can also check a web backend demo [here](https://github.com/ecies/py-demo).

## Install

`pip install eciespy`

Or `pip install 'eciespy[eth]'` to install `eth-keys` as well.

## Quick Start

```python
>>> from ecies.utils import generate_eth_key, generate_key
>>> from ecies.utils import generate_key
>>> from ecies import encrypt, decrypt
>>> eth_k = generate_eth_key()
>>> sk_hex = eth_k.to_hex() # hex string
>>> pk_hex = eth_k.public_key.to_hex() # hex string
>>> data = b'this is a test'
>>> decrypt(sk_hex, encrypt(pk_hex, data))
b'this is a test'
>>> secp_k = generate_key()
>>> sk_bytes = secp_k.secret # bytes
>>> pk_bytes = secp_k.public_key.format(True) # bytes
>>> decrypt(sk_bytes, encrypt(pk_bytes, data))
b'this is a test'
>>> data = 'hello world🌍'.encode()
>>> sk = generate_key()
>>> sk_bytes = sk.secret # bytes
>>> pk_bytes = sk.public_key.format(True) # bytes
>>> decrypt(sk_bytes, encrypt(pk_bytes, data)).decode()
'hello world🌍'
```

Or just use a builtin command `eciespy` in your favorite [command line](#command-line-interface).

## API

### `ecies.encrypt(receiver_pk: Union[str, bytes], msg: bytes) -> bytes`
### `ecies.encrypt(receiver_pk: Union[str, bytes], data: bytes, config: Config = ECIES_CONFIG) -> bytes`

Parameters:

- **receiver_pk** - Receiver's public key (hex str or bytes)
- **msg** - Data to encrypt
- **data** - Data to encrypt
- **config** - Optional configuration object

Returns: **bytes**

### `ecies.decrypt(receiver_sk: Union[str, bytes], msg: bytes) -> bytes`
### `ecies.decrypt(receiver_sk: Union[str, bytes], data: bytes, config: Config = ECIES_CONFIG) -> bytes`

Parameters:

- **receiver_sk** - Receiver's private key (hex str or bytes)
- **msg** - Data to decrypt
- **data** - Data to decrypt
- **config** - Optional configuration object

Returns: **bytes**

Expand Down
30 changes: 19 additions & 11 deletions ecies/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from .config import ECIES_CONFIG, Config
from .utils import (
compat_eth_public_key,
bytes2pk,
decapsulate,
encapsulate,
generate_key,
Expand All @@ -18,7 +18,7 @@


def encrypt(
receiver_pk: Union[str, bytes], msg: bytes, config: Config = ECIES_CONFIG
receiver_pk: Union[str, bytes], data: bytes, config: Config = ECIES_CONFIG
) -> bytes:
"""
Encrypt with receiver's secp256k1 public key
Expand All @@ -27,8 +27,10 @@ def encrypt(
----------
receiver_pk: Union[str, bytes]
Receiver's public key (hex str or bytes)
msg: bytes
data: bytes
Data to encrypt
config: Config
Optional configuration object

Returns
-------
Expand All @@ -38,20 +40,22 @@ def encrypt(
if isinstance(receiver_pk, str):
pk = hex2pk(receiver_pk)
elif isinstance(receiver_pk, bytes):
pk = PublicKey(compat_eth_public_key(receiver_pk))
pk = bytes2pk(receiver_pk)
else:
raise TypeError("Invalid public key type")

ephemeral_sk = generate_key()
ephemeral_pk = ephemeral_sk.public_key.format(config.is_ephemeral_key_compressed)

sym_key = encapsulate(ephemeral_sk, pk, config)
encrypted = sym_encrypt(sym_key, msg, config)
sym_key = encapsulate(ephemeral_sk, pk, config.is_hkdf_key_compressed)
encrypted = sym_encrypt(
sym_key, data, config.symmetric_algorithm, config.symmetric_nonce_length
)
return ephemeral_pk + encrypted


def decrypt(
receiver_sk: Union[str, bytes], msg: bytes, config: Config = ECIES_CONFIG
receiver_sk: Union[str, bytes], data: bytes, config: Config = ECIES_CONFIG
) -> bytes:
"""
Decrypt with receiver's secp256k1 private key
Expand All @@ -60,8 +64,10 @@ def decrypt(
----------
receiver_sk: Union[str, bytes]
Receiver's private key (hex str or bytes)
msg: bytes
data: bytes
Data to decrypt
config: Config
Optional configuration object

Returns
-------
Expand All @@ -76,7 +82,9 @@ def decrypt(
raise TypeError("Invalid secret key type")

key_size = config.ephemeral_key_size
ephemeral_pk, encrypted = PublicKey(msg[0:key_size]), msg[key_size:]
ephemeral_pk, encrypted = PublicKey(data[0:key_size]), data[key_size:]

sym_key = decapsulate(ephemeral_pk, sk, config)
return sym_decrypt(sym_key, encrypted, config)
sym_key = decapsulate(ephemeral_pk, sk, config.is_hkdf_key_compressed)
return sym_decrypt(
sym_key, encrypted, config.symmetric_algorithm, config.symmetric_nonce_length
)
8 changes: 4 additions & 4 deletions ecies/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import sys

from ecies import decrypt, encrypt
from ecies.utils import generate_eth_key
from ecies.utils import generate_key, to_eth_address, to_eth_public_key

__description__ = "Elliptic Curve Integrated Encryption Scheme for secp256k1 in Python"

Expand Down Expand Up @@ -68,11 +68,11 @@ def main():

args = parser.parse_args()
if args.generate:
k = generate_eth_key()
k = generate_key()
sk, pk, addr = (
k.to_hex(),
k.public_key.to_hex(),
k.public_key.to_checksum_address(),
f"0x{to_eth_public_key(k.public_key).hex()}",
to_eth_address(k.public_key),
)
print("Private: {}\nPublic: {}\nAddress: {}".format(sk, pk, addr))
return
Expand Down
28 changes: 14 additions & 14 deletions ecies/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
from .elliptic import (
compat_eth_public_key,
decapsulate,
encapsulate,
generate_eth_key,
generate_key,
hex2pk,
hex2sk,
)
from .hex import decode_hex, sha256
from .elliptic import bytes2pk, decapsulate, encapsulate, generate_key, hex2pk, hex2sk
from .eth import generate_eth_key, to_eth_address, to_eth_public_key
from .hash import derive_key, sha256
from .hex import decode_hex
from .symmetric import sym_decrypt, sym_encrypt

__all__ = [
"sha256",
"decode_hex",
"sym_encrypt",
"sym_decrypt",
"generate_key",
"generate_eth_key",
"hex2sk",
"hex2pk",
"bytes2pk",
"decapsulate",
"encapsulate",
"compat_eth_public_key",
# eth
"generate_eth_key",
"to_eth_address",
"to_eth_public_key",
# hex
"decode_hex",
# hash
"sha256",
"derive_key",
]
Loading