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):
@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)))
@staticmethod
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,
SCRYPT_PARAMS['N'], SCRYPT_PARAMS['r'], SCRYPT_PARAMS['p'],
SEED_LENGTH)
SCRYPT_PARAMS['N'], SCRYPT_PARAMS['r'], SCRYPT_PARAMS['p'],
SEED_LENGTH)
super().__init__(seed)
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)
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
:return:
"""
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)
super().__init__(key)
def base58(self):
def base58(self) -> str:
"""
Return a base58 encoded string of the public key
"""
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
@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):
super().__init__(seed)
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))
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