Module refinery.units.crypto.keyderive

Implements key derivation routines. These are mostly meant to be used as modifiers for multibin expressions that can be passed as key arguments to modules in refinery.units.crypto.cipher.

Expand source code Browse git
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Implements key derivation routines. These are mostly meant to be used as
modifiers for multibin expressions that can be passed as key arguments to
modules in `refinery.units.crypto.cipher`.
"""
from ... import arg, Unit
from ....lib.argformats import number

try:
    from Crypto.Hash import SHA as SHA1
except ImportError:
    from Crypto.Hash import SHA1

from enum import Enum
from Crypto.Hash import MD2, MD4, MD5, SHA256, SHA512, SHA224, SHA384


__all__ = ['arg', 'HASH', 'KeyDerivation']


class HASH(Enum):
    MD2 = MD2
    MD4 = MD4
    MD5 = MD5
    SHA1 = SHA1
    SHA256 = SHA256
    SHA512 = SHA512
    SHA224 = SHA224
    SHA384 = SHA384


class KeyDerivation(Unit, abstract=True):

    def __init__(
        self,
        size: arg(help='The number of bytes to generate.', type=number),
        salt: arg(help='Salt for the derivation.'),
        hash: arg.option(choices=HASH, metavar='hash',
            help='Specify one of these algorithms (default is {default}): {choices}') = None,
        iter: arg.number(metavar='iter', help='Number of iterations; default is {default}.') = None,
        **kw
    ):
        return super().__init__(salt=salt, size=size, iter=iter, hash=arg.as_option(hash, HASH), **kw)

    @property
    def hash(self): return self.args.hash.value

Sub-modules

refinery.units.crypto.keyderive.CryptDeriveKey

Reference: https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptderivekey

refinery.units.crypto.keyderive.DESDerive
refinery.units.crypto.keyderive.PasswordDeriveBytes
refinery.units.crypto.keyderive.hkdf
refinery.units.crypto.keyderive.hmac
refinery.units.crypto.keyderive.kblob
refinery.units.crypto.keyderive.pbkdf1
refinery.units.crypto.keyderive.pbkdf2
refinery.units.crypto.keyderive.unixcrypt

Classes

class arg (*args, action=refinery.units.arg.omit, choices=refinery.units.arg.omit, const=refinery.units.arg.omit, default=refinery.units.arg.omit, dest=refinery.units.arg.omit, help=refinery.units.arg.omit, metavar=refinery.units.arg.omit, nargs=refinery.units.arg.omit, required=refinery.units.arg.omit, type=refinery.units.arg.omit, group=None, guess=False)

This child class of Argument is specifically an argument for the add_argument method of an ArgumentParser from the argparse module. It can also be used as a decorator for the constructor of a refinery unit to better control the argument parser of that unit's command line interface. Example:

class prefixer(Unit):
    @arg('prefix', help='this data will be prepended to the input.')
    def __init__(self, prefix): pass

    def process(self, data):
        return self.args.prefix + data

Note that when the init of a unit has a return annotation that is a base class of itself, then all its parameters will automatically be forwarded to that base class.

Expand source code Browse git
class arg(Argument):
    """
    This child class of `refinery.units.Argument` is specifically an argument for the
    `add_argument` method of an `ArgumentParser` from the `argparse` module. It can also
    be used as a decorator for the constructor of a refinery unit to better control
    the argument parser of that unit's command line interface. Example:
    ```
    class prefixer(Unit):
        @arg('prefix', help='this data will be prepended to the input.')
        def __init__(self, prefix): pass

        def process(self, data):
            return self.args.prefix + data
    ```
    Note that when the init of a unit has a return annotation that is a base class of
    itself, then all its parameters will automatically be forwarded to that base class.
    """

    class delete: pass
    class omit: pass

    def __init__(
        self, *args: str,
            action   : Union[omit, str]           = omit, # noqa
            choices  : Union[omit, Iterable[Any]] = omit, # noqa
            const    : Union[omit, Any]           = omit, # noqa
            default  : Union[omit, Any]           = omit, # noqa
            dest     : Union[omit, str]           = omit, # noqa
            help     : Union[omit, str]           = omit, # noqa
            metavar  : Union[omit, str]           = omit, # noqa
            nargs    : Union[omit, int, str]      = omit, # noqa
            required : Union[omit, bool]          = omit, # noqa
            type     : Union[omit, type]          = omit, # noqa
            group    : Optional[str]              = None, # noqa
            guess    : bool                       = False # noqa
    ) -> None:
        kwargs = dict(action=action, choices=choices, const=const, default=default, dest=dest,
            help=help, metavar=metavar, nargs=nargs, required=required, type=type)
        kwargs = {key: value for key, value in kwargs.items() if value is not arg.omit}
        self.group = group
        self.guess = guess
        super().__init__(*args, **kwargs)

    def update_help(self):
        if 'help' not in self.kwargs:
            return

        class formatting(dict):
            arg = self

            def __missing__(self, key):
                if key == 'choices':
                    return ', '.join(self.arg.kwargs['choices'])
                if key == 'default':
                    default = self.arg.kwargs['default']
                    if not isbuffer(default):
                        return str(default)
                    if default.isalnum():
                        return default.decode('latin-1')
                    return F'H:{default.hex()}'
                if key == 'varname':
                    return self.arg.kwargs.get('metavar', self.arg.destination)

        try:
            self.kwargs.update(
                help=self.kwargs['help'].format_map(formatting()))
        except Exception:
            pass

    def __rmatmul__(self, method):
        self.update_help()
        return super().__rmatmul__(method)

    @staticmethod
    def as_option(value: Optional[Any], cls: Enum) -> Enum:
        if value is None or isinstance(value, cls):
            return value
        if isinstance(value, str):
            try: return cls[value]
            except KeyError: pass
            needle = value.upper()
            for item in cls:
                if item.name.upper() == needle:
                    return item
        try:
            return cls(value)
        except Exception as E:
            raise ValueError(F'Could not transform {value} into a {cls.__name__}.') from E

    @staticmethod
    def switch(
        *args: str, off=False,
        help : Union[omit, str] = omit,
        dest : Union[omit, str] = omit,
        group: Optional[str] = None,
    ) -> Argument:
        """
        A convenience method to add argparse arguments that change a boolean value from True to False or
        vice versa. By default, a switch will have a False default and change it to True when specified.
        """
        return arg(*args, group=group, help=help, dest=dest, action='store_false' if off else 'store_true')

    @staticmethod
    def binary(
        *args: str,
        help : Union[omit, str] = omit,
        dest : Union[omit, str] = omit,
        metavar : Optional[str] = None,
        group: Optional[str] = None,
    ) -> Argument:
        """
        Used to add argparse arguments that contain binary data.
        """
        return arg(*args, group=group, help=help, dest=dest, type=multibin, metavar=metavar or 'B')

    @staticmethod
    def number(
        *args: str,
        bound: Union[omit, Tuple[int, int]] = omit,
        help : Union[omit, str] = omit,
        dest : Union[omit, str] = omit,
        metavar : Optional[str] = None,
        group: Optional[str] = None,
    ) -> Argument:
        """
        Used to add argparse arguments that contain a number.
        """
        nt = number
        if bound is not arg.omit:
            lower, upper = bound
            nt = nt[lower:upper]
        return arg(*args, group=group, help=help, dest=dest, type=nt, metavar=metavar or 'N')

    @staticmethod
    def option(
        *args: str, choices: Enum,
        help : Union[omit, str] = omit,
        dest : Union[omit, str] = omit,
        metavar: Optional[str] = None,
        group: Optional[str] = None,
    ) -> Argument:
        """
        Used to add argparse arguments with a fixed set of options, based on an enumeration.
        """
        cnames = [c.name for c in choices]
        metavar = metavar or choices.__name__
        return arg(*args, group=group, help=help, metavar=metavar, dest=dest, choices=cnames, type=str)

    @staticmethod
    def choice(
        *args: str, choices : List[str],
        help    : Union[omit, str] = omit,
        metavar : Union[omit, str] = omit,
        dest    : Union[omit, str] = omit,
        nargs   : Union[omit, int, str] = omit,
        group   : Optional[str] = None,
    ):
        """
        Used to add argparse arguments with a fixed set of options, based on a list of strings.
        """
        return arg(*args, group=group, type=str, metavar=metavar, nargs=nargs,
            dest=dest, help=help, choices=choices)

    @property
    def positional(self) -> bool:
        return any(a[0] != '-' for a in self.args)

    @property
    def destination(self) -> str:
        """
        The name of the variable where the contents of this parsed argument will be stored.
        """
        for a in self.args:
            if a[0] != '-':
                return a
        try:
            return self.kwargs['dest']
        except KeyError:
            for a in self.args:
                if a.startswith('--'):
                    dest = a.lstrip('-').replace('-', '_')
                    if dest.isidentifier():
                        return dest
            raise AttributeError(F'The argument with these values has no destination: {self!r}')

    @classmethod
    def infer(cls, pt: inspect.Parameter) -> Argument:
        """
        This class method can be used to infer the argparse argument for a Python function
        parameter. This guess is based on the annotation, name, and default value.
        """

        def needs_type(item):
            return item.get('action', 'store') == 'store'

        def get_argp_type(annotation_type):
            if issubclass(annotation_type, (bytes, bytearray, memoryview)):
                return multibin
            if issubclass(annotation_type, int):
                return number
            if issubclass(annotation_type, slice):
                return sliceobj
            return annotation_type

        name = pt.name.replace('_', '-')
        default = pt.default
        guessed_pos_args = []
        guessed_kwd_args = dict(dest=pt.name, guess=True)
        annotation = pt.annotation

        if isinstance(annotation, str):
            try: annotation = eval(annotation)
            except Exception: pass

        if annotation is not pt.empty:
            if isinstance(annotation, Argument):
                if annotation.kwargs.get('dest', pt.name) != pt.name:
                    raise ValueError(
                        F'Incompatible argument destination specified; parameter {pt.name} '
                        F'was annotated with {annotation!r}.')
                guessed_pos_args = annotation.args
                guessed_kwd_args.update(annotation.kwargs)
                guessed_kwd_args['guess'] = False
                guessed_kwd_args['group'] = annotation.group
            elif isinstance(annotation, type):
                if not issubclass(annotation, bool) and needs_type(guessed_kwd_args):
                    guessed_kwd_args.update(type=get_argp_type(annotation))
                elif not isinstance(default, bool):
                    raise ValueError('Default value for boolean arguments must be provided.')

        if not guessed_pos_args:
            guessed_pos_args = guessed_pos_args or [F'--{name}' if pt.kind is pt.KEYWORD_ONLY else name]

        if pt.kind is pt.VAR_POSITIONAL:
            oldnargs = guessed_kwd_args.setdefault('nargs', ZERO_OR_MORE)
            if oldnargs not in (ONE_OR_MORE, ZERO_OR_MORE, REMAINDER):
                raise ValueError(F'Variadic positional arguments has nargs set to {oldnargs!r}')
            return cls(*guessed_pos_args, **guessed_kwd_args)

        if default is not pt.empty:
            if isinstance(default, Enum):
                default = default.name
            if isinstance(default, (list, tuple)):
                guessed_kwd_args.setdefault('nargs', ZERO_OR_MORE)
                if not pt.default:
                    default = pt.empty
                else:
                    guessed_kwd_args.setdefault('default', pt.default)
                    default = default[0]
            else:
                guessed_kwd_args.setdefault('default', default)
                if pt.kind is pt.POSITIONAL_ONLY:
                    guessed_kwd_args.setdefault('nargs', OPTIONAL)

        if default is not pt.empty:
            if isinstance(default, bool):
                guessed_kwd_args['action'] = 'store_false' if default else 'store_true'
            elif needs_type(guessed_kwd_args) and 'type' not in guessed_kwd_args:
                guessed_kwd_args['type'] = get_argp_type(type(default))

        return cls(*guessed_pos_args, **guessed_kwd_args)

    def merge_args(self, them: Argument) -> None:
        def iterboth():
            yield from them.args
            yield from self.args
        if not self.args:
            self.args = list(them.args)
            return
        sflag = None
        lflag = None
        for a in iterboth():
            if a[:2] == '--': lflag = lflag or a
            elif a[0] == '-': sflag = sflag or a
        self.args = []
        if sflag: self.args.append(sflag)
        if lflag: self.args.append(lflag)
        if not self.args:
            self.args = list(them.args)

    def merge_all(self, them: Argument) -> None:
        for key, value in them.kwargs.items():
            if value is arg.delete:
                self.kwargs.pop(key, None)
                continue
            self.kwargs[key] = value
        self.merge_args(them)
        self.guess = self.guess and them.guess
        self.group = self.group or them.group

    def __copy__(self) -> Argument:
        cls = self.__class__
        clone = cls.__new__(cls)
        clone.kwargs = dict(self.kwargs)
        clone.args = list(self.args)
        clone.group = self.group
        clone.guess = self.guess
        return clone

    def __repr__(self) -> str:
        return F'arg({super().__repr__()})'

    def __call__(self, init: Callable) -> Callable:
        parameters = inspect.signature(init).parameters
        try:
            inferred = arg.infer(parameters[self.destination])
            inferred.merge_all(self)
            init.__annotations__[self.destination] = inferred
        except KeyError:
            raise ValueError(F'Unable to decorate because no parameter with name {self.destination} exists.')
        return init

Ancestors

Class variables

var delete
var omit

Static methods

def as_option(value, cls)
Expand source code Browse git
@staticmethod
def as_option(value: Optional[Any], cls: Enum) -> Enum:
    if value is None or isinstance(value, cls):
        return value
    if isinstance(value, str):
        try: return cls[value]
        except KeyError: pass
        needle = value.upper()
        for item in cls:
            if item.name.upper() == needle:
                return item
    try:
        return cls(value)
    except Exception as E:
        raise ValueError(F'Could not transform {value} into a {cls.__name__}.') from E
def switch(*args, off=False, help=refinery.units.arg.omit, dest=refinery.units.arg.omit, group=None)

A convenience method to add argparse arguments that change a boolean value from True to False or vice versa. By default, a switch will have a False default and change it to True when specified.

Expand source code Browse git
@staticmethod
def switch(
    *args: str, off=False,
    help : Union[omit, str] = omit,
    dest : Union[omit, str] = omit,
    group: Optional[str] = None,
) -> Argument:
    """
    A convenience method to add argparse arguments that change a boolean value from True to False or
    vice versa. By default, a switch will have a False default and change it to True when specified.
    """
    return arg(*args, group=group, help=help, dest=dest, action='store_false' if off else 'store_true')
def binary(*args, help=refinery.units.arg.omit, dest=refinery.units.arg.omit, metavar=None, group=None)

Used to add argparse arguments that contain binary data.

Expand source code Browse git
@staticmethod
def binary(
    *args: str,
    help : Union[omit, str] = omit,
    dest : Union[omit, str] = omit,
    metavar : Optional[str] = None,
    group: Optional[str] = None,
) -> Argument:
    """
    Used to add argparse arguments that contain binary data.
    """
    return arg(*args, group=group, help=help, dest=dest, type=multibin, metavar=metavar or 'B')
def number(*args, bound=refinery.units.arg.omit, help=refinery.units.arg.omit, dest=refinery.units.arg.omit, metavar=None, group=None)

Used to add argparse arguments that contain a number.

Expand source code Browse git
@staticmethod
def number(
    *args: str,
    bound: Union[omit, Tuple[int, int]] = omit,
    help : Union[omit, str] = omit,
    dest : Union[omit, str] = omit,
    metavar : Optional[str] = None,
    group: Optional[str] = None,
) -> Argument:
    """
    Used to add argparse arguments that contain a number.
    """
    nt = number
    if bound is not arg.omit:
        lower, upper = bound
        nt = nt[lower:upper]
    return arg(*args, group=group, help=help, dest=dest, type=nt, metavar=metavar or 'N')
def option(*args, choices, help=refinery.units.arg.omit, dest=refinery.units.arg.omit, metavar=None, group=None)

Used to add argparse arguments with a fixed set of options, based on an enumeration.

Expand source code Browse git
@staticmethod
def option(
    *args: str, choices: Enum,
    help : Union[omit, str] = omit,
    dest : Union[omit, str] = omit,
    metavar: Optional[str] = None,
    group: Optional[str] = None,
) -> Argument:
    """
    Used to add argparse arguments with a fixed set of options, based on an enumeration.
    """
    cnames = [c.name for c in choices]
    metavar = metavar or choices.__name__
    return arg(*args, group=group, help=help, metavar=metavar, dest=dest, choices=cnames, type=str)
def choice(*args, choices, help=refinery.units.arg.omit, metavar=refinery.units.arg.omit, dest=refinery.units.arg.omit, nargs=refinery.units.arg.omit, group=None)

Used to add argparse arguments with a fixed set of options, based on a list of strings.

Expand source code Browse git
@staticmethod
def choice(
    *args: str, choices : List[str],
    help    : Union[omit, str] = omit,
    metavar : Union[omit, str] = omit,
    dest    : Union[omit, str] = omit,
    nargs   : Union[omit, int, str] = omit,
    group   : Optional[str] = None,
):
    """
    Used to add argparse arguments with a fixed set of options, based on a list of strings.
    """
    return arg(*args, group=group, type=str, metavar=metavar, nargs=nargs,
        dest=dest, help=help, choices=choices)
def infer(pt)

This class method can be used to infer the argparse argument for a Python function parameter. This guess is based on the annotation, name, and default value.

Expand source code Browse git
@classmethod
def infer(cls, pt: inspect.Parameter) -> Argument:
    """
    This class method can be used to infer the argparse argument for a Python function
    parameter. This guess is based on the annotation, name, and default value.
    """

    def needs_type(item):
        return item.get('action', 'store') == 'store'

    def get_argp_type(annotation_type):
        if issubclass(annotation_type, (bytes, bytearray, memoryview)):
            return multibin
        if issubclass(annotation_type, int):
            return number
        if issubclass(annotation_type, slice):
            return sliceobj
        return annotation_type

    name = pt.name.replace('_', '-')
    default = pt.default
    guessed_pos_args = []
    guessed_kwd_args = dict(dest=pt.name, guess=True)
    annotation = pt.annotation

    if isinstance(annotation, str):
        try: annotation = eval(annotation)
        except Exception: pass

    if annotation is not pt.empty:
        if isinstance(annotation, Argument):
            if annotation.kwargs.get('dest', pt.name) != pt.name:
                raise ValueError(
                    F'Incompatible argument destination specified; parameter {pt.name} '
                    F'was annotated with {annotation!r}.')
            guessed_pos_args = annotation.args
            guessed_kwd_args.update(annotation.kwargs)
            guessed_kwd_args['guess'] = False
            guessed_kwd_args['group'] = annotation.group
        elif isinstance(annotation, type):
            if not issubclass(annotation, bool) and needs_type(guessed_kwd_args):
                guessed_kwd_args.update(type=get_argp_type(annotation))
            elif not isinstance(default, bool):
                raise ValueError('Default value for boolean arguments must be provided.')

    if not guessed_pos_args:
        guessed_pos_args = guessed_pos_args or [F'--{name}' if pt.kind is pt.KEYWORD_ONLY else name]

    if pt.kind is pt.VAR_POSITIONAL:
        oldnargs = guessed_kwd_args.setdefault('nargs', ZERO_OR_MORE)
        if oldnargs not in (ONE_OR_MORE, ZERO_OR_MORE, REMAINDER):
            raise ValueError(F'Variadic positional arguments has nargs set to {oldnargs!r}')
        return cls(*guessed_pos_args, **guessed_kwd_args)

    if default is not pt.empty:
        if isinstance(default, Enum):
            default = default.name
        if isinstance(default, (list, tuple)):
            guessed_kwd_args.setdefault('nargs', ZERO_OR_MORE)
            if not pt.default:
                default = pt.empty
            else:
                guessed_kwd_args.setdefault('default', pt.default)
                default = default[0]
        else:
            guessed_kwd_args.setdefault('default', default)
            if pt.kind is pt.POSITIONAL_ONLY:
                guessed_kwd_args.setdefault('nargs', OPTIONAL)

    if default is not pt.empty:
        if isinstance(default, bool):
            guessed_kwd_args['action'] = 'store_false' if default else 'store_true'
        elif needs_type(guessed_kwd_args) and 'type' not in guessed_kwd_args:
            guessed_kwd_args['type'] = get_argp_type(type(default))

    return cls(*guessed_pos_args, **guessed_kwd_args)

Instance variables

var positional
Expand source code Browse git
@property
def positional(self) -> bool:
    return any(a[0] != '-' for a in self.args)
var destination

The name of the variable where the contents of this parsed argument will be stored.

Expand source code Browse git
@property
def destination(self) -> str:
    """
    The name of the variable where the contents of this parsed argument will be stored.
    """
    for a in self.args:
        if a[0] != '-':
            return a
    try:
        return self.kwargs['dest']
    except KeyError:
        for a in self.args:
            if a.startswith('--'):
                dest = a.lstrip('-').replace('-', '_')
                if dest.isidentifier():
                    return dest
        raise AttributeError(F'The argument with these values has no destination: {self!r}')

Methods

def update_help(self)
Expand source code Browse git
def update_help(self):
    if 'help' not in self.kwargs:
        return

    class formatting(dict):
        arg = self

        def __missing__(self, key):
            if key == 'choices':
                return ', '.join(self.arg.kwargs['choices'])
            if key == 'default':
                default = self.arg.kwargs['default']
                if not isbuffer(default):
                    return str(default)
                if default.isalnum():
                    return default.decode('latin-1')
                return F'H:{default.hex()}'
            if key == 'varname':
                return self.arg.kwargs.get('metavar', self.arg.destination)

    try:
        self.kwargs.update(
            help=self.kwargs['help'].format_map(formatting()))
    except Exception:
        pass
def merge_args(self, them)
Expand source code Browse git
def merge_args(self, them: Argument) -> None:
    def iterboth():
        yield from them.args
        yield from self.args
    if not self.args:
        self.args = list(them.args)
        return
    sflag = None
    lflag = None
    for a in iterboth():
        if a[:2] == '--': lflag = lflag or a
        elif a[0] == '-': sflag = sflag or a
    self.args = []
    if sflag: self.args.append(sflag)
    if lflag: self.args.append(lflag)
    if not self.args:
        self.args = list(them.args)
def merge_all(self, them)
Expand source code Browse git
def merge_all(self, them: Argument) -> None:
    for key, value in them.kwargs.items():
        if value is arg.delete:
            self.kwargs.pop(key, None)
            continue
        self.kwargs[key] = value
    self.merge_args(them)
    self.guess = self.guess and them.guess
    self.group = self.group or them.group

Inherited members

class HASH (value, names=None, *, module=None, qualname=None, type=None, start=1)

An enumeration.

Expand source code Browse git
class HASH(Enum):
    MD2 = MD2
    MD4 = MD4
    MD5 = MD5
    SHA1 = SHA1
    SHA256 = SHA256
    SHA512 = SHA512
    SHA224 = SHA224
    SHA384 = SHA384

Ancestors

  • enum.Enum

Class variables

var MD2
var MD4
var MD5
var SHA1
var SHA256
var SHA512
var SHA224
var SHA384
class KeyDerivation (size, salt, hash=None, iter=None, **kw)
Expand source code Browse git
class KeyDerivation(Unit, abstract=True):

    def __init__(
        self,
        size: arg(help='The number of bytes to generate.', type=number),
        salt: arg(help='Salt for the derivation.'),
        hash: arg.option(choices=HASH, metavar='hash',
            help='Specify one of these algorithms (default is {default}): {choices}') = None,
        iter: arg.number(metavar='iter', help='Number of iterations; default is {default}.') = None,
        **kw
    ):
        return super().__init__(salt=salt, size=size, iter=iter, hash=arg.as_option(hash, HASH), **kw)

    @property
    def hash(self): return self.args.hash.value

Ancestors

Subclasses

Instance variables

var hash
Expand source code Browse git
@property
def hash(self): return self.args.hash.value

Inherited members