Commit 821a3b9f authored by Vincent Texier's avatar Vincent Texier

[enh] #78 Add Ascii Armor encrypted and signed message creation and example

parent d790538f
from .signing_key import SigningKey, ScryptParams
from .signing_key import SigningKey
from .verifying_key import VerifyingKey
from .encryption_key import SecretKey, PublicKey
from .encryption_key import SecretKey, PublicKey, SCRYPT_PARAMS, SEED_LENGTH
from .ascii_armor import AsciiArmor
import base64
import libnacl
from typing import Optional, List
from duniterpy.key import SigningKey, PublicKey, SCRYPT_PARAMS, SEED_LENGTH
# Headers constants
BEGIN_MESSAGE_HEADER = "-----BEGIN DUNITER MESSAGE-----"
END_MESSAGE_HEADER = "-----END DUNITER MESSAGE-----"
BEGIN_SIGNATURE_HEADER = "-----BEGIN DUNITER SIGNATURE-----"
END_SIGNATURE_HEADER = "-----END DUNITER SIGNATURE-----"
# Version field values
AA_MESSAGE_VERSION = "Python Libnacl " + libnacl.__version__
AA_SIGNATURE_VERSION = "Python Libnacl " + libnacl.__version__
class AsciiArmor:
"""
Class to handle writing and reading of ascii armor messages
"""
@staticmethod
def encrypt(message: str, pubkey: str, signing_keys: Optional[List[SigningKey]] = None,
message_comment: Optional[str] = None, signatures_comment: Optional[str] = None) -> str:
"""
Encrypt a message in ascii armor format, optionally signing it
:param message: Utf-8 message
:param pubkey: Public key of recipient for encryption
:param signing_keys: Optional list of SigningKey instances
:param message_comment: Optional message comment field
:param signatures_comment: Optional signatures comment field
:return:
"""
pubkey_instance = PublicKey(pubkey)
base64_encrypted_message = base64.b64encode(pubkey_instance.encrypt_seal(message)) # type: bytes
script_field = AsciiArmor._get_scrypt_field()
# create block with headers
ascii_armor_msg = """
{begin_message_header}
Version: {version}
{script_field}
""".format(begin_message_header=BEGIN_MESSAGE_HEADER, version=AA_MESSAGE_VERSION,
script_field=script_field)
# add message comment if specified
if message_comment:
ascii_armor_msg += AsciiArmor._get_comment_field(message_comment)
# add encrypted message
ascii_armor_msg += """
{base64_encrypted_message}
""".format(base64_encrypted_message=base64_encrypted_message.decode('utf-8'))
# if no signature...
if signing_keys is None:
# add message tail
ascii_armor_msg += END_MESSAGE_HEADER
else:
# add signature blocks and close block on last signature
count = 1
for signing_key in signing_keys:
ascii_armor_msg += AsciiArmor._get_signature_block(message, signing_key, count == len(signing_keys),
signatures_comment)
count += 1
return ascii_armor_msg
@staticmethod
def _get_scrypt_field():
"""
Return the Scrypt field
:return:
"""
return "Scrypt: N={0};r={1};p={2};len={3}".format(SCRYPT_PARAMS['N'], SCRYPT_PARAMS['r'], SCRYPT_PARAMS['p'],
SEED_LENGTH)
@staticmethod
def _get_comment_field(comment: str) -> str:
"""
Return a comment field
:param comment: Comment text
:return:
"""
return "Comment: {comment}\n".format(comment=comment)
@staticmethod
def _get_signature_block(message: str, signing_key: SigningKey, close_block: bool = True,
comment: Optional[str] = None) -> str:
"""
Return a signature block
:param message: Message (not encrypted!) to sign
:param signing_key: The libnacl SigningKey instance of the keypair
:param close_block: Optional flag to close the signature block with the signature tail header
:param comment: Optional comment field content
:return:
"""
script_param_field = AsciiArmor._get_scrypt_field()
base64_signature = base64.b64encode(signing_key.signature(message))
block = """{begin_signature_header}
Version: {version}
Scrypt: {script_params}
""".format(begin_signature_header=BEGIN_SIGNATURE_HEADER, version=AA_SIGNATURE_VERSION,
script_params=script_param_field)
# add message comment if specified
if comment:
block += AsciiArmor._get_comment_field(comment)
block += """
{base64_signature}
""".format(base64_signature=base64_signature.decode('utf-8'))
if close_block:
block += END_SIGNATURE_HEADER
return block
import getpass
from duniterpy import __version__
from duniterpy.key import AsciiArmor, SigningKey
################################################
AA_ENCRYPTED_MESSAGE_FILENAME = 'duniter_aa_encrypted_message.txt'
if __name__ == '__main__':
# Ask public key of the recipient
pubkeyBase58 = input("Enter public key of the message recipient: ")
# prompt hidden user entry
salt = getpass.getpass("Enter your passphrase (salt): ")
# prompt hidden user entry
password = getpass.getpass("Enter your password: ")
# init SigningKey instance
signing_key = SigningKey.from_credentials(salt, password)
# Enter the message
message = input("Enter your message: ")
comment = "generated by Duniterpy {0}".format(__version__)
# Encrypt the message, only the recipient secret key will be able to decrypt the message
encrypted_message = AsciiArmor.encrypt(message, pubkeyBase58, [signing_key], message_comment=comment,
signatures_comment=comment)
# Save encrypted message in a file
with open(AA_ENCRYPTED_MESSAGE_FILENAME, 'w') as file_handler:
file_handler.write(encrypted_message)
print("Ascii Armor Encrypted message saved in file ./{0}".format(AA_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