Commit 9abea36f authored by Vincent Texier's avatar Vincent Texier

issue #39 Create binary encrypted message from ed25519 pubkey and decrypt the...

issue #39 Create binary encrypted message from ed25519 pubkey and decrypt the message with ed25519 secret key

Use ed25519 to curve25519 key conversion
parent 61fd5c83
Pipeline #2982 passed with stages
in 2 minutes and 7 seconds
from typing import Union
import base58
from ..helpers import ensure_str, ensure_bytes
class Base58Encoder(object):
def encode(data):
def encode(data: Union[str, bytes]) -> str:
Return Base58 string from data
:param data: Bytes or string data
return ensure_str(base58.b58encode(ensure_bytes(data)))
def decode(data):
def decode(data: str) -> bytes:
Decode Base58 string data and return bytes
:param data: Base58 string
return base58.b58decode(data)
......@@ -3,13 +3,14 @@ duniter public and private keys
@author: inso
from typing import Union
import libnacl.public
from pylibscrypt import scrypt
from .base58 import Base58Encoder
from ..helpers import ensure_bytes
SEED_LENGTH = 32 # Length of the key
crypto_sign_BYTES = 64
SCRYPT_PARAMS = {'N': 4096,
......@@ -19,36 +20,83 @@ SCRYPT_PARAMS = {'N': 4096,
class SecretKey(libnacl.public.SecretKey):
def __init__(self, salt, password):
Raw Public Key Encryption Class
def __init__(self, salt: Union[str, bytes], password: Union[str, bytes]) -> None:
Create SecretKey key pair instance from salt and password credentials
:param salt: Salt credential
:param password: Password credential
salt = ensure_bytes(salt)
password = ensure_bytes(password)
seed = scrypt(password, salt,
self.public_key = PublicKey(Base58Encoder.encode(
def encrypt(self, pubkey, noonce, text):
def encrypt(self, pubkey: str, nonce: Union[str, bytes], text: Union[str, bytes]) -> str:
Encrypt message text with the public key of the recipient and a noonce
The nonce must be a 24 character string (you can use libnacl.utils.rand_nonce() to get one)
and unique for each encrypted message.
Return base58 encoded encrypted message
:param pubkey: Base58 encoded public key of the recipient
:param nonce: Unique nonce
:param text: Message to encrypt
text_bytes = ensure_bytes(text)
noonce_bytes = ensure_bytes(noonce)
nonce_bytes = ensure_bytes(nonce)
recipient_pubkey = PublicKey(pubkey)
crypt_bytes = libnacl.public.Box(self, recipient_pubkey).encrypt(text_bytes, noonce_bytes)
crypt_bytes = libnacl.public.Box(self, recipient_pubkey).encrypt(text_bytes, nonce_bytes)
return Base58Encoder.encode(crypt_bytes[24:])
def decrypt(self, pubkey, noonce, text):
def decrypt(self, pubkey: str, nonce: Union[str, bytes], text: str) -> str:
Decrypt encrypted message text with recipient public key and the unique nonce used by the sender.
:param pubkey: Public key of the recipient
:param nonce: Unique nonce used by the sender
:param text: Encrypted message
sender_pubkey = PublicKey(pubkey)
noonce_bytes = ensure_bytes(noonce)
nonce_bytes = ensure_bytes(nonce)
encrypt_bytes = Base58Encoder.decode(text)
decrypt_bytes = libnacl.public.Box(self, sender_pubkey).decrypt(encrypt_bytes, noonce_bytes)
decrypt_bytes = libnacl.public.Box(self, sender_pubkey).decrypt(encrypt_bytes, nonce_bytes)
return decrypt_bytes.decode('utf-8')
class PublicKey(libnacl.public.PublicKey):
def __init__(self, pubkey):
def __init__(self, pubkey: str) -> None:
Create instance of libnacl ed25519 sign PublicKey from a base58 public key
:param pubkey: Base58 public key
key = Base58Encoder.decode(pubkey)
def base58(self):
def base58(self) -> str:
Return a base58 encoded string of the public key
return Base58Encoder.encode(
def encrypt_seal(self, message: Union[str, bytes]) -> bytes:
Encrypt message with a curve25519 version of the ed25519 public key
:param message: Message to encrypt
curve25519_public_key = libnacl.crypto_sign_ed25519_pk_to_curve25519(
return libnacl.crypto_box_seal(ensure_bytes(message), curve25519_public_key)
......@@ -3,9 +3,11 @@ duniter public and private keys
@author: inso
from typing import Optional
import libnacl.sign
from pylibscrypt import scrypt
from .base58 import Base58Encoder
from ..helpers import ensure_bytes
......@@ -14,27 +16,27 @@ crypto_sign_BYTES = 64
class ScryptParams:
def __init__(self, N, r, p):
def __init__(self, n: int, r: int, p: int) -> None:
Init a ScryptParams instance with crypto parameters
:param int N: scrypt param N
:param int r: scrypt param r
:param int p: scrypt param p
:param n: scrypt param N
:param r: scrypt param r
:param p: scrypt param p
self.N = N
self.N = n
self.r = r
self.p = p
class SigningKey(libnacl.sign.Signer):
def __init__(self, salt, password, scrypt_params=None):
def __init__(self, salt: str, password: str, scrypt_params: Optional[ScryptParams] = None) -> None:
Init a SigningKey object from credentials
:param str salt: Secret salt passphrase credential
:param str password: Secret password credential
:param ScryptParams scrypt_params: ScryptParams instance
:param salt: Secret salt passphrase credential
:param password: Secret password credential
:param scrypt_params: ScryptParams instance
if scrypt_params is None:
scrypt_params = ScryptParams(4096, 16, 1)
......@@ -47,3 +49,15 @@ class SigningKey(libnacl.sign.Signer):
self.pubkey = Base58Encoder.encode(self.vk)
def decrypt_seal(self, message: bytes) -> str:
Decrypt message with a curve25519 version of the ed25519 key pair
:param message: Encrypted message
curve25519_public_key = libnacl.crypto_sign_ed25519_pk_to_curve25519(self.vk)
curve25519_secret_key = libnacl.crypto_sign_ed25519_sk_to_curve25519(
return libnacl.crypto_box_seal_open(message, curve25519_public_key, curve25519_secret_key).decode('utf-8')
import getpass
import sys
from duniterpy.key import SigningKey
if __name__ == '__main__':
if len(sys.argv) < 2:
# capture encrypted message filepath argument
signed_message_path = sys.argv[1]
# prompt hidden user entry
salt = getpass.getpass("Enter your passphrase (salt): ")
# prompt hidden user entry
password = getpass.getpass("Enter your password: ")
# Create key object
signing_key_instance = SigningKey(salt, password)
# open encrypted message file
with open(signed_message_path, 'rb') as file_handler:
encrypted_message =
# Decrypt the message!
message = signing_key_instance.decrypt_seal(encrypted_message)
print("Decrypted message:")
except ValueError as error:
message = str(error)
from duniterpy.key import PublicKey
ENCRYPTED_MESSAGE_FILENAME = 'duniter_encrypted_message.bin'
if __name__ == '__main__':
# Ask public key of the recipient
pubkeyBase58 = input("Enter public key of the message recipient: ")
# Enter the message
message = input("Enter your message: ")
# Encrypt the message, only the recipient secret key will be able to decrypt the message
pubkey_instance = PublicKey(pubkeyBase58)
encrypted_message = pubkey_instance.encrypt_seal(message)
# Save encrypted message in a file
with open(ENCRYPTED_MESSAGE_FILENAME, 'wb') as file_handler:
print("Encrypted message saved in file ./{0}".format(ENCRYPTED_MESSAGE_FILENAME))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment