Skip to content
Snippets Groups Projects
Commit 9abea36f authored by Vincent Texier's avatar Vincent Texier
Browse files

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
No related branches found
No related tags found
No related merge requests found
Pipeline #2982 passed
from typing import Union
import base58 import base58
from ..helpers import ensure_str, ensure_bytes from ..helpers import ensure_str, ensure_bytes
class Base58Encoder(object): class Base58Encoder(object):
@staticmethod @staticmethod
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))) return ensure_str(base58.b58encode(ensure_bytes(data)))
@staticmethod @staticmethod
def decode(data): def decode(data: str) -> bytes:
"""
Decode Base58 string data and return bytes
:param data: Base58 string
"""
return base58.b58decode(data) return base58.b58decode(data)
...@@ -3,13 +3,14 @@ duniter public and private keys ...@@ -3,13 +3,14 @@ duniter public and private keys
@author: inso @author: inso
""" """
from typing import Union
import libnacl.public import libnacl.public
from pylibscrypt import scrypt from pylibscrypt import scrypt
from .base58 import Base58Encoder from .base58 import Base58Encoder
from ..helpers import ensure_bytes from ..helpers import ensure_bytes
SEED_LENGTH = 32 # Length of the key SEED_LENGTH = 32 # Length of the key
crypto_sign_BYTES = 64 crypto_sign_BYTES = 64
SCRYPT_PARAMS = {'N': 4096, SCRYPT_PARAMS = {'N': 4096,
...@@ -19,7 +20,17 @@ SCRYPT_PARAMS = {'N': 4096, ...@@ -19,7 +20,17 @@ SCRYPT_PARAMS = {'N': 4096,
class SecretKey(libnacl.public.SecretKey): 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) salt = ensure_bytes(salt)
password = ensure_bytes(password) password = ensure_bytes(password)
seed = scrypt(password, salt, seed = scrypt(password, salt,
...@@ -29,26 +40,63 @@ class SecretKey(libnacl.public.SecretKey): ...@@ -29,26 +40,63 @@ class SecretKey(libnacl.public.SecretKey):
super().__init__(seed) super().__init__(seed)
self.public_key = PublicKey(Base58Encoder.encode(self.pk)) self.public_key = PublicKey(Base58Encoder.encode(self.pk))
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
:return:
"""
text_bytes = ensure_bytes(text) text_bytes = ensure_bytes(text)
noonce_bytes = ensure_bytes(noonce) nonce_bytes = ensure_bytes(nonce)
recipient_pubkey = PublicKey(pubkey) 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:]) 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
:return:
"""
sender_pubkey = PublicKey(pubkey) sender_pubkey = PublicKey(pubkey)
noonce_bytes = ensure_bytes(noonce) nonce_bytes = ensure_bytes(nonce)
encrypt_bytes = Base58Encoder.decode(text) 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') return decrypt_bytes.decode('utf-8')
class PublicKey(libnacl.public.PublicKey): 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) key = Base58Encoder.decode(pubkey)
super().__init__(key) super().__init__(key)
def base58(self): def base58(self) -> str:
"""
Return a base58 encoded string of the public key
"""
return Base58Encoder.encode(self.pk) return Base58Encoder.encode(self.pk)
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(self.pk)
return libnacl.crypto_box_seal(ensure_bytes(message), curve25519_public_key)
...@@ -3,9 +3,11 @@ duniter public and private keys ...@@ -3,9 +3,11 @@ duniter public and private keys
@author: inso @author: inso
""" """
from typing import Optional
import libnacl.sign import libnacl.sign
from pylibscrypt import scrypt from pylibscrypt import scrypt
from .base58 import Base58Encoder from .base58 import Base58Encoder
from ..helpers import ensure_bytes from ..helpers import ensure_bytes
...@@ -14,27 +16,27 @@ crypto_sign_BYTES = 64 ...@@ -14,27 +16,27 @@ crypto_sign_BYTES = 64
class ScryptParams: 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 Init a ScryptParams instance with crypto parameters
:param int N: scrypt param N :param n: scrypt param N
:param int r: scrypt param r :param r: scrypt param r
:param int p: scrypt param p :param p: scrypt param p
""" """
self.N = N self.N = n
self.r = r self.r = r
self.p = p self.p = p
class SigningKey(libnacl.sign.Signer): 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 Init a SigningKey object from credentials
:param str salt: Secret salt passphrase credential :param salt: Secret salt passphrase credential
:param str password: Secret password credential :param password: Secret password credential
:param ScryptParams scrypt_params: ScryptParams instance :param scrypt_params: ScryptParams instance
""" """
if scrypt_params is None: if scrypt_params is None:
scrypt_params = ScryptParams(4096, 16, 1) scrypt_params = ScryptParams(4096, 16, 1)
...@@ -47,3 +49,15 @@ class SigningKey(libnacl.sign.Signer): ...@@ -47,3 +49,15 @@ class SigningKey(libnacl.sign.Signer):
super().__init__(seed) super().__init__(seed)
self.pubkey = Base58Encoder.encode(self.vk) 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
:return:
"""
curve25519_public_key = libnacl.crypto_sign_ed25519_pk_to_curve25519(self.vk)
curve25519_secret_key = libnacl.crypto_sign_ed25519_sk_to_curve25519(self.sk)
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:
print("""
Usage:
python decrypt_message.py ENCRYPTED_MESSAGE_FILEPATH
""")
# 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 = file_handler.read()
# Decrypt the message!
try:
message = signing_key_instance.decrypt_seal(encrypted_message)
print("Decrypted message:")
except ValueError as error:
message = str(error)
print(message)
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:
file_handler.write(encrypted_message)
print("Encrypted message saved in file ./{0}".format(ENCRYPTED_MESSAGE_FILENAME))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment