Commit 358a7736 authored by Vincent Texier's avatar Vincent Texier

[enh] #78 AA create support custom scrypt params

parent 7f52f90a
......@@ -4,7 +4,7 @@ from re import compile
from typing import Optional, List, Dict, Any
from duniterpy.key import SigningKey, PublicKey, VerifyingKey
from .constants import SCRYPT_PARAMS, SEED_LENGTH
from duniterpy.key.scrypt_params import ScryptParams
# Headers constants
BEGIN_MESSAGE_HEADER = "-----BEGIN DUNITER MESSAGE-----"
......@@ -62,10 +62,10 @@ class AsciiArmor:
"""
Class to handle writing and parsing of ascii armor messages
"""
@staticmethod
def create(message: str, pubkey: Optional[str] = None, signing_keys: Optional[List[SigningKey]] = None,
message_comment: Optional[str] = None, signatures_comment: Optional[str] = None) -> str:
message_comment: Optional[str] = None, signatures_comment: Optional[str] = None,
scrypt_params: Optional[ScryptParams] = None) -> str:
"""
Encrypt a message in ascii armor format, optionally signing it
......@@ -74,6 +74,8 @@ class AsciiArmor:
:param signing_keys: Optional list of SigningKey instances
:param message_comment: Optional message comment field
:param signatures_comment: Optional signatures comment field
:param scrypt_params: Optional ScryptParams instance
:return:
"""
# if no public key and no signing key...
......@@ -81,6 +83,10 @@ class AsciiArmor:
# We can not create an Ascii Armor Message
raise MISSING_PUBLIC_KEY_AND_SIGNING_KEY_EXCEPTION
if scrypt_params is None:
scrypt_params = ScryptParams()
# TODO: improve cleaning of spaces and tab at end of lines
# remove last newline of the message if any
message = message.strip("\n\r")
......@@ -91,10 +97,10 @@ class AsciiArmor:
# if encrypted message...
if pubkey:
# add encrypted message fields, todo: pass scrypt params as arguments
# add encrypted message fields
ascii_armor_block += """{version_field}
{script_field}
""".format(version_field=AsciiArmor._get_version_field(), script_field=AsciiArmor._get_scrypt_field())
""".format(version_field=AsciiArmor._get_version_field(), script_field=AsciiArmor._get_scrypt_field(scrypt_params))
# add message comment if specified
if message_comment:
......@@ -111,6 +117,7 @@ class AsciiArmor:
ascii_armor_block += """{base64_encrypted_message}
""".format(base64_encrypted_message=base64_encrypted_message.decode('utf-8'))
else:
# TODO: Dash escape cleartext
# clear text message
ascii_armor_block += message + "\n"
......@@ -123,7 +130,7 @@ class AsciiArmor:
count = 1
for signing_key in signing_keys:
ascii_armor_block += AsciiArmor._get_signature_block(message, signing_key, count == len(signing_keys),
signatures_comment)
signatures_comment, scrypt_params)
count += 1
return ascii_armor_block
......@@ -138,14 +145,18 @@ class AsciiArmor:
return "Version: {version}".format(version=VERSION_FIELD_VALUE)
@staticmethod
def _get_scrypt_field() -> str:
def _get_scrypt_field(scrypt_params: Optional[ScryptParams] = None) -> str:
"""
Return the Scrypt field
:param scrypt_params: Optional ScryptParams instance
:return:
"""
return "Scrypt: N={0};r={1};p={2};len={3}".format(SCRYPT_PARAMS['N'], SCRYPT_PARAMS['r'], SCRYPT_PARAMS['p'],
SEED_LENGTH)
if scrypt_params is None:
scrypt_params = ScryptParams()
return "Scrypt: N={0};r={1};p={2};len={3}".format(scrypt_params.N, scrypt_params.r, scrypt_params.p,
scrypt_params.seed_length)
@staticmethod
def _get_comment_field(comment: str) -> str:
......@@ -159,7 +170,8 @@ class AsciiArmor:
@staticmethod
def _get_signature_block(message: str, signing_key: SigningKey, close_block: bool = True,
comment: Optional[str] = None) -> str:
comment: Optional[str] = None,
scrypt_params: Optional[ScryptParams] = None) -> str:
"""
Return a signature block
......@@ -167,15 +179,20 @@ class AsciiArmor:
: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
:param scrypt_params: Optional ScriptParams instance
:return:
"""
if scrypt_params is None:
scrypt_params = ScryptParams()
base64_signature = base64.b64encode(signing_key.signature(message))
block = """{begin_signature_header}
{version_field}
{script_field}
""".format(begin_signature_header=BEGIN_SIGNATURE_HEADER, version_field=AsciiArmor._get_version_field(),
script_field=AsciiArmor._get_scrypt_field())
script_field=AsciiArmor._get_scrypt_field(scrypt_params))
# add message comment if specified
if comment:
......@@ -193,6 +210,8 @@ class AsciiArmor:
return block
# TODO: add parse from credentials to use scrypt field creating SigningKey
@staticmethod
def parse(ascii_armor_block: str, signing_key: Optional[SigningKey] = None,
sender_pubkeys: Optional[List[str]] = None) -> dict:
......@@ -298,6 +317,9 @@ class AsciiArmor:
# if we are on a signature fields zone...
if cursor_status == ON_SIGNATURE_FIELDS:
# TODO: Handle Dash escaped cleartext
# parse field
m = regex_fields.match(line.strip())
if m:
......
......@@ -2,8 +2,8 @@
crypto_sign_BYTES = 64
# Scrypt
SEED_LENGTH = 32
SCRYPT_PARAMS = {'N': 4096,
'r': 16,
'p': 1
'p': 1,
'seed_length': 32
}
......@@ -3,14 +3,14 @@ duniter public and private keys
@author: inso
"""
from typing import Union
from typing import Union, Optional
import libnacl.public
from pylibscrypt import scrypt
from .scrypt_params import ScryptParams
from .base58 import Base58Encoder
from ..helpers import ensure_bytes
from .constants import SEED_LENGTH, SCRYPT_PARAMS
class SecretKey(libnacl.public.SecretKey):
......@@ -18,18 +18,21 @@ class SecretKey(libnacl.public.SecretKey):
Raw Public Key Encryption Class
"""
def __init__(self, salt: Union[str, bytes], password: Union[str, bytes]) -> None:
def __init__(self, salt: Union[str, bytes], password: Union[str, bytes],
scrypt_params: Optional[ScryptParams] = None) -> None:
"""
Create SecretKey key pair instance from salt and password credentials
:param salt: Salt credential
:param password: Password credential
:param scrypt_params: Optional ScriptParams instance
"""
if scrypt_params is None:
scrypt_params = ScryptParams()
salt = ensure_bytes(salt)
password = ensure_bytes(password)
seed = scrypt(password, salt,
SCRYPT_PARAMS['N'], SCRYPT_PARAMS['r'], SCRYPT_PARAMS['p'],
SEED_LENGTH)
seed = scrypt(password, salt, scrypt_params.N, scrypt_params.r, scrypt_params.p, scrypt_params.seed_length)
super().__init__(seed)
self.public_key = PublicKey(Base58Encoder.encode(self.pk))
......
from typing import Optional
from .constants import SCRYPT_PARAMS
class ScryptParams:
"""
Class to simplify handling of scrypt parameters
"""
def __init__(self, n: Optional[int] = SCRYPT_PARAMS['N'], r: Optional[int] = SCRYPT_PARAMS['r'],
p: Optional[int] = SCRYPT_PARAMS['p'],
seed_length: Optional[int] = SCRYPT_PARAMS['seed_length']) -> None:
"""
Init a ScryptParams instance with crypto parameters
:param n: Optional scrypt param N, default see constant SCRYPT_PARAMS
:param r: Optional scrypt param r, default see constant SCRYPT_PARAMS
:param p: Optional scrypt param p, default see constant SCRYPT_PARAMS
:param seed_length: Optional scrypt param seed_length, default see constant SCRYPT_PARAMS
"""
self.N = n
self.r = r
self.p = p
self.seed_length = seed_length
......@@ -4,32 +4,17 @@ duniter public and private keys
@author: inso
"""
from re import compile, MULTILINE, search
from typing import Optional, Union, TypeVar, Type, Dict
from typing import Optional, Union, TypeVar, Type
import libnacl.sign
import pyaes
from libnacl.utils import load_key
from pylibscrypt import scrypt
from duniterpy.key.constants import SEED_LENGTH, SCRYPT_PARAMS
from .scrypt_params import ScryptParams
from .base58 import Base58Encoder
from ..helpers import ensure_bytes, xor_bytes, convert_seedhex_to_seed, convert_seed_to_seedhex
class ScryptParams:
def __init__(self, n: int, r: int, p: int) -> None:
"""
Init a ScryptParams instance with crypto parameters
:param n: scrypt param N
:param r: scrypt param r
:param p: scrypt param p
"""
self.N = n
self.r = r
self.p = p
# required to type hint cls in classmethod
SigningKeyType = TypeVar('SigningKeyType', bound='SigningKey')
......@@ -47,7 +32,7 @@ class SigningKey(libnacl.sign.Signer):
@classmethod
def from_credentials(cls: Type[SigningKeyType], salt: Union[str, bytes], password: Union[str, bytes],
scrypt_params: Optional[Dict[str, int]] = None) -> SigningKeyType:
scrypt_params: Optional[ScryptParams] = None) -> SigningKeyType:
"""
Create a SigningKey object from credentials
......@@ -56,13 +41,11 @@ class SigningKey(libnacl.sign.Signer):
:param scrypt_params: ScryptParams instance
"""
if scrypt_params is None:
scrypt_params = SCRYPT_PARAMS
scrypt_params = ScryptParams()
salt = ensure_bytes(salt)
password = ensure_bytes(password)
seed = scrypt(password, salt,
scrypt_params['N'], scrypt_params['r'], scrypt_params['p'],
SEED_LENGTH)
seed = scrypt(password, salt, scrypt_params.N, scrypt_params.r, scrypt_params.p, scrypt_params.seed_length)
return cls(seed)
......
from duniterpy.key import VerifyingKey, SigningKey
from duniterpy.key.constants import SCRYPT_PARAMS
from duniterpy.key.scrypt_params import ScryptParams
from duniterpy.documents.peer import Peer
from duniterpy.documents.ws2p.heads import *
import unittest
......@@ -7,7 +7,7 @@ import unittest
class TestVerifyingKey(unittest.TestCase):
def test_from_sign_to_verify(self):
sign_key = SigningKey.from_credentials("saltsalt", "passwordpassword", SCRYPT_PARAMS)
sign_key = SigningKey.from_credentials("saltsalt", "passwordpassword", ScryptParams())
verify_key = VerifyingKey(sign_key.pubkey)
self.assertEqual(verify_key.vk, sign_key.vk)
......
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