# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['blake2signer', 'blake2signer.tests']

package_data = \
{'': ['*']}

setup_kwargs = {
    'name': 'blake2signer',
    'version': '0.4.0',
    'description': 'A library to use Blake2 in keyed hashing mode to sign and verify signed data',
    'long_description': '# Blake2Signer\n\nThe goal of this module is to provide a simple way to securely sign data using Blake2 in keyed hashing mode (read more about that in the [hashlib docs](https://docs.python.org/3/library/hashlib.html#blake2)).\n\n The main use case is to sign cookies or similar data. There are much better packages for other use cases or more general use cases so if you feel this module doesn\'t satisfy your needs consider using "itsdangerous", Django\'s signer, "pypaseto", "pyjwt" or others like those. My idea is to keep this module as simple as possible without much room to become a *footgun*.\n\nThis project began initially as a Gist but I decided to create a package because I think it can be useful as a small (~600 LoC counting tests), simple (quite straightforward) and fast data signer (see more below).\n\n## Goals\n\n* Be safe and secure.\n* Be simple and straightforward.\n* Follow [semver](https://semver.org/).\n* Be always typed.\n* No dependencies (besides dev).\n* 100% coverage.\n\n### Secondary goals\n\n* If possible, maintain current active Python versions (3.7+).\n\n## Requirements\n\n* Python 3.7+\n\n## Usage\n\nThis module provides three classes:\n\n* `Blake2SerializerSigner`: a signer class that handles data serialization, compression and encoding along with signing and timestamped signing.\n* `Blake2Signer`: a signer class that simply salts, signs and verifies signed data as bytes or string.\n* `Blake2TimestampSigner`: a signer class that simply salts, signs and verifies signed timestamped data as bytes or string.\n\n**You should generally go for Blake2SerializerSigner**, given that it\'s the most versatile of the three.\n\nIn all classes you can choose between **blake2b** (default) or **blake2s** as hasher: the first one is optimized for 64b platforms and the second, for 8-32b platforms (read more about them in their [official site](https://blake2.net/)).  \nThe digest size is configurable with a secure minimum of 16 bytes enforced. The secret is enforced to be of a secure minimum size of 16 bytes with no size limit since it\'s derived to produce the key. Additionally a salt is internally generated for every signature providing non-deterministic signatures.\n\n### Examples\n\nThe following examples are working code and should run as-is.\n\n#### Tl; Dr\n\n```python\n"""Tl;dr example."""\n\nfrom datetime import timedelta\n\nfrom blake2signer import Blake2SerializerSigner\nfrom blake2signer import errors\n\nsecret = b\'secure-secret-that-nobody-knows!\'\n# some arbitrary data to sign\ndata = {\'message\': \'attack at dawn\', \'extra\': [1, 2, 3, 4]}\n\n# Define same signer to `dumps` and `loads`.\nsigner = Blake2SerializerSigner(\n    secret,\n    max_age=timedelta(days=1),\n    personalisation=b\'the-cookie-signer\',  # Always set it different per instance\n)\n\n# Sign and i.e. store the data in a cookie\nsigned = signer.dumps(data)  # Compression is enabled by default\n# If compressing data turns out to be detrimental then data won\'t be\n# compressed. If you know that from beforehand and don\'t need compression, you\n# can disable it:\n# signed = signer.dumps(data, use_compression=False)\ncookie = {\'data\': signed}\n\n# To verify and recover data simply use loads: you will either get the data or\n# a `SignerError` subclass exception.\ntry:\n    unsigned = signer.loads(cookie.get(\'data\', \'\'))\nexcept errors.SignedDataError:\n    # Can\'t trust on given data\n    unsigned = {}\n\nprint(unsigned)  # {\'message\': \'attack at dawn\', \'extra\': [1, 2, 3, 4]}\n```\n\nIt is always a good idea to set the `personalisation` parameter which can be any arbitrarily long bytes (it defaults to the class name).  \nFor example if you use a signer for cookies set something like `b\'cookies-signer\'` or if you use it for some user-related data signing it could be `b\'user-data-signer\'`, or when used for signing a special value it could be `b\'the-special-value-signer`, etc.\n\n#### More Examples\n\nBoth the `secret` and `personalisation` parameters are derived so they have no size limit. This *personalisation* parameter helps defeating the abuse of using a signed stream for different signers that share the same key by changing the digest computation result. See examples of this below and read more about it in the [hashlib docs](https://docs.python.org/3/library/hashlib.html#personalization).  \nInput data can always be arbitrarily long.\n\nA secure pseudorandom salt of the maximum allowed size for the hasher is generated for each signature internally and can\'t be manually set. Other packages usually refer to salt as something to add to the secret to prevent signer misuse, but here we have the *personalisation* parameter for that.\n\nAs a general rule of thumb if you have highly compressible data such as human readable text, then you should enable compression. Otherwise when dealing with somewhat random data compression won\'t help much (but probably won\'t hurt either unless you\'re dealing with a huge amount of random data).\n\n```python\n"""Many usage examples."""\n\nfrom datetime import timedelta\nfrom time import sleep\n\nfrom blake2signer import Blake2SerializerSigner\nfrom blake2signer import Blake2Signer\nfrom blake2signer import Blake2TimestampSigner\nfrom blake2signer import errors\n\nsecret = b\'ZnVja3RoZXBvbGljZQ\'\n\n# Serializing some data structure\ndata = [{\'a\': \'b\'}, 1] * 10000  # some big data structure\nprint(len(data))  # 20000\n\nsigner = Blake2SerializerSigner(secret)  # without timestamp (compression by default)\nsigned = signer.dumps(data)\nprint(len(signed))  # 405  # compression helped reducing size heavily\n\nunsigned = signer.loads(signed)\nprint(data == unsigned)  # True\n\nsigner = Blake2SerializerSigner(  # with timestamp\n    secret,\n    max_age=timedelta(weeks=1),\n)\nsigned = signer.dumps(data, use_compression=False)  # without compression\nprint(len(signed))  # 160048\n\n# You can also set the desired compression level where 1 is the fastest\n# and least compressed and 9 the slowest and most compressed.\nsigned = signer.dumps(data, compression_level=1)\nprint(len(signed))  # 880  # less size reduction\n# Since sample data is the same structure repeated many times it\'s highly\n# compressible so even the lowest compression level works excellent here.\n# That won\'t always be the case; the default value is a good balance.\n\nunsigned = signer.loads(signed)\nprint(data == unsigned)  # True\n\nsigner = Blake2SerializerSigner(  # with timestamp and personalisation\n    secret,\n    max_age=timedelta(weeks=1),\n    personalisation=b\'my-cookie-signer\',\n)\ntry:\n    signer.loads(signed)\nexcept errors.InvalidSignatureError as exc:\n    print(repr(exc))  # InvalidSignatureError(\'signature is not valid\')\n# Using the `personalisation` parameter made the sig to fail, thus protecting\n# signed data to be loaded incorrectly.\n\n# Signing some bytes value\ndata = b\'facundo castro presente\'\n\nsigner = Blake2Signer(  # without timestamp\n    secret,\n    hasher=Blake2Signer.Hashers.blake2s,  # Using Blake2s instead of Blake2b\n)\nsigned = signer.sign(data)\nprint(len(signed))  # 75\nunsigned = signer.unsign(signed)\nprint(data == unsigned)  # True\n\nsigner = Blake2Signer(secret)\nsigned = signer.sign(data)\nprint(len(signed))  # 126\nunsigned = signer.unsign(signed)\nprint(data == unsigned)  # True\n\nt_signer = Blake2TimestampSigner(secret)  # with timestamp\nsigned = t_signer.sign(data)\nprint(len(signed))  # 133\nunsigned = t_signer.unsign(signed, max_age=10)  # signature is valid if its not\n# older than this many seconds (10)\nprint(data == unsigned)  # True\n\n# The timestamp is checked when unsigning so that if that many seconds\n# since the data was signed passed then the signature is considered\n# expired. The signature is verified before checking the timestamp so it\n# must be valid too.\n# You can use both an integer or a float to represent seconds or a\n# timedelta with the time value you want.\nsigned = t_signer.sign(data)\nsleep(2)\ntry:\n    t_signer.unsign(signed, max_age=timedelta(seconds=2))\nexcept errors.ExpiredSignatureError as exc:\n    print(repr(exc))  # ExpiredSignatureError(\'signed data has expired\')\n\n# Preventing misuse of signed data\ntry:\n    t_signer.unsign(signer.sign(data), max_age=5.5)\nexcept errors.InvalidSignatureError as exc:\n    print(repr(exc))  # InvalidSignatureError(\'signature is not valid\')\ntry:\n    signer.unsign(t_signer.sign(data))\nexcept errors.InvalidSignatureError as exc:\n    print(repr(exc))  # InvalidSignatureError(\'signature is not valid\')\n# You can\'t mix and match signers, and that\'s on purpose. This is because\n# internally the personalisation parameter, which changes the computed\n# digest, is set to the class name. However you could find your way to\n# trick one class into accepting data generated by the other but you\n# really shouldn\'t!.\n```\n\nEven though both `Blake2Signer` and `Blake2TimestampSigner` accept data as string you should use bytes instead: both classes will try to convert any given string to bytes **assuming it\'s UTF-8 encoded** which might not be correct; if you are certain that the string given is UTF-8 then it\'s OK, otherwise ensure encoding the string correctly and using bytes instead.\n\n#### Using a custom JSON encoder or custom serializer\n\nYou can use a custom JSON encoder or even a custom serializer such as [msgpack](https://pypi.org/project/msgpack/) which is very efficient in size and performance, much better than JSON (half resulting size and more than twice as fast) so it is an excellent choice for a serializer. For keeping JSON as serializer a better choice than the standard library is [orjson](https://github.com/ijl/orjson) which is faster.\n\n```python\n"""Sample of custom JSON encoder and custom serializer."""\n\nimport typing\nfrom decimal import Decimal\nfrom json import JSONEncoder\n\nimport msgpack\n\nfrom blake2signer import Blake2SerializerSigner\nfrom blake2signer.serializers import JSONSerializer\nfrom blake2signer.serializers import SerializerInterface\n\n# Custom JSON encoder\nclass DecimalJSONEncoder(JSONEncoder):\n\n    def default(self, o):\n        if isinstance(o, Decimal):\n            return str(o)\n\n        return super().default(o)\n\nclass MyJSONSerializer(JSONSerializer):\n\n    def serialize(self, data: typing.Any, **kwargs: typing.Any) -> bytes:\n        return super().serialize(data, cls=DecimalJSONEncoder, **kwargs)\n\nsecret = b\'super-secret-value\'\nsigner = Blake2SerializerSigner(secret, serializer=MyJSONSerializer)\n\ndata = {\'points\': [1, 2, Decimal(\'3.4\')]}\nunsigned = signer.loads(signer.dumps(data))\nprint(unsigned)  # {\'points\': [1, 2, \'3.4\']}\n\n# Custom serializer with msgpack (same idea would be for orjson)\nclass MsgpackSerializer(SerializerInterface):\n\n    def serialize(self, data: typing.Any, **kwargs: typing.Any) -> bytes:\n        """Serialize given data as msgpack."""\n        return msgpack.packb(data)\n\n    def unserialize(self, data: bytes, **kwargs: typing.Any) -> typing.Any:\n        """Unserialize given msgpack data."""\n        return msgpack.unpackb(data)\n\nsigner = Blake2SerializerSigner(secret, serializer=MsgpackSerializer)\ndata = {\'points\': [1, 2, 3.4]}\nsigned = signer.dumps(data)\nprint(signed)  # ....gaZwb2ludHOTAQLLQAszMzMzMzM\nunsigned = signer.loads(signed)\nprint(unsigned)  # {\'points\': [1, 2, 3.4]}\n```\n\n`Blake2SerializerSigner` is quite flexible and can receive custom serializer, compressor or encoder. You could i.e. create a custom base62 encoder simply inheriting from `EncoderInterface`, or a custom bzip compressor inheriting from `CompressorInterface`. You don\'t need to handle or worry about exceptions: those are caught by the caller class.\n\nOn the other hand you can create your own *SerializerSigner* using provided `Blake2SerializerSignerBase` and/or any of the mixins: `SerializerMixin`, `CompressorMixin`, `EncoderMixin` or even creating your own mixin inheriting from `Mixin`.\n\n```python\n"""Custom serializer signer class example."""\n\nimport typing\n\nfrom blake2signer.serializers import EncoderMixin\n\n\nclass MySerializerSigner(EncoderMixin):\n\n    def dumps(self, data: typing.Any) -> str:\n        encoded = self._encode(self._force_bytes(data))\n\n        return self._dumps(encoded).decode()\n\n    def loads(self, signed_data: typing.AnyStr) -> typing.Any:\n        unsigned = self._loads(self._force_bytes(signed_data))\n\n        decoded = self._decode(unsigned)\n\n        return decoded.decode()\n\nsecret = b\'super-secret-value\'\nsigner = MySerializerSigner(secret)\ndata = \'memoria y justicia\'\nsigned = signer.dumps(data)\nprint(signed)  # ....bWVtb3JpYSB5IGp1c3RpY2lh\nprint(signer.loads(signed) == data)  # True\n```\n\n##### I need to work with raw bytes but I want compression and encoding\n\nUsually to work with bytes one can choose to use either `Blake2Signer` or `Blake2TimestampSigner`. However, if you also want to have compression and encoding, you need `Blake2SerializerSigner`. The problem now is that JSON doesn\'t support bytes so the class as-is won\'t work. There are two solutions:\n\n1. Use the `MsgpackSerializer` created above given that *msgpack* does handle bytes serialization.\n1. Create your custom serializer that doesn\'t actually serializes to use it with `Blake2SerializerSigner`.\n1. Create your custom class inheriting from `EncoderMixin` and `CompressorMixin`.\n\nHere are those examples:\n\n```python\n"""Sample of a custom encoder compressor signer class."""\n\nimport typing\n\nfrom blake2signer.serializers import CompressorMixin\nfrom blake2signer.serializers import EncoderMixin\n\n\nclass MyEncoderCompressorSigner(EncoderMixin, CompressorMixin):\n \n    def dumps(self, data: typing.AnyStr) -> str:\n        data_bytes = self._force_bytes(data)\n        compressed, _ = self._compress(data_bytes, level=6)\n        encoded = self._encode(compressed)\n        signed = self._dumps(encoded).decode()\n\n        return signed\n \n    def loads(self, signed_data: typing.AnyStr) -> bytes:\n        signed_bytes = self._force_bytes(signed_data)\n        unsigned = self._loads(signed_bytes)\n        decoded = self._decode(unsigned)\n        decompressed = self._decompress(decoded)\n \n        return decompressed\n\n\nsecret = b\'super-secret-value\'\nsigner = MyEncoderCompressorSigner(secret)\ndata = b\'acab\' * 100\nsigned = signer.dumps(data)\nprint(len(signed) < len(data))  # True\nprint(signer.loads(signed) == data)  # True\n```\n\n```python\n"""Sample of a custom serializer that doesn\'t serializes."""\n\nimport typing\n\nfrom blake2signer import Blake2SerializerSigner\nfrom blake2signer.serializers import SerializerInterface\nfrom blake2signer.utils import force_bytes\n\n\nclass MySerializer(SerializerInterface):\n\n    def serialize(self, data: typing.Any, **kwargs: typing.Any) -> bytes:\n        """Convert given data to bytes."""\n        return force_bytes(data)\n\n    def unserialize(self, data: bytes, **kwargs: typing.Any) -> typing.Any:\n        """Unserialize given serialized data."""\n        return data\n\n\nsecret = b\'super-secret-value\'\nsigner = Blake2SerializerSigner(secret, serializer=MySerializer)\ndata = b\'acab\' * 100\nsigned = signer.dumps(data)\nprint(len(signed) < len(data))  # True\nprint(signer.loads(signed) == data)  # True\n```\n\n#### Real use case example\n\nSign cookies in a FastAPI/Starlette middleware.\n\n```python\n"""Sample use case: sign cookies in a FastAPI/Starlette middleware."""\n\nfrom datetime import timedelta\n\nfrom fastapi import Request\nfrom fastapi import Response\nfrom starlette.middleware.base import BaseHTTPMiddleware\nfrom starlette.middleware.base import RequestResponseEndpoint\n\nfrom blake2signer import Blake2SerializerSigner\nfrom blake2signer.errors import SignedDataError\n\n# from .messages import Messages  # Some class that has the data we want to sign\nclass Messages:\n\n    def to_dict(self) -> dict:\n        return self.__dict__\n\n    @classmethod\n    def from_dict(cls, data: dict) -> \'Messages\':\n        return cls(**data)\n\n# In this example, that class can be converted to/read from dict.\n# It doesn\'t need to be exactly a dict but any Python type that\n# can be JSON encodable (string, number, list/tuple or dict).\n\nSECRET_KEY: bytes = b\'myverysecretsecret\'\nCOOKIE_TTL: timedelta = timedelta(days=5)\nCOOKIE_NAME: str = \'data_cookie\'\n\n\nclass CookieHTTPMiddleware(BaseHTTPMiddleware):\n\n    @property\n    def _signer(self) -> Blake2SerializerSigner:\n        return Blake2SerializerSigner(\n            SECRET_KEY,\n            max_age=COOKIE_TTL,\n            personalisation=b\'cookie_http_middleware\',\n        )\n\n    def get_cookie_data(self, request: Request) -> Messages:\n        signed_data = request.cookies.get(COOKIE_NAME, \'\')\n        messages_data = self._signer.loads(signed_data)  # may raise SignedDataError\n        messages = Messages.from_dict(messages_data)\n        return messages\n\n    def set_cookie_data(self, messages: Messages, response: Response) -> None:\n        data = messages.to_dict()\n        signed_data = self._signer.dumps(data)\n        response.set_cookie(\n            COOKIE_NAME,\n            value=signed_data,\n            max_age=int(COOKIE_TTL.total_seconds()),\n            secure=True,\n            httponly=True,\n            samesite=\'strict\',\n        )\n\n    async def dispatch(\n        self,\n        request: Request,\n        call_next: RequestResponseEndpoint,\n    ) -> Response:\n        try:\n            request.state.messages = self.get_cookie_data(request)\n        except SignedDataError:  # some tampering, maybe we changed the secret...\n            request.state.messages = Messages()\n\n        response = await call_next(request)\n\n        # You may want to implement some change detection mechanism to avoid\n        # writing cookies in every response.\n        # if changed(request.state.messages):\n        self.set_cookie_data(request.state.messages, response)\n\n        return response\n```\n\n## Comparison with other libs\n\nIt\'s easy to compare this lib to, say, [itsdangerous](https://itsdangerous.palletsprojects.com/en/1.1.x) and [django](https://www.djangoproject.com). Generally speaking its as fast or a bit faster than the other libs, so you should choose one or the other based on usability and fitting-your-needs rather than performance.\n\nRegarding **itsdangerous** (1.1.0), I found this lib to be *marginally faster* (~3%) when compared to it using blake2b or blake2s, *quite faster* regarding sha256 (~55%), 384 (~15%) and 512 (~15%) and *slower* regarding sha1 (~15%) (this is most likely due to CPU instructions optimization).\n\nRegarding **django** (3.1.2), I found this lib to be *quite faster* (~17%) when compared to it using blake2b or blake2s, *incredibly faster* regarding sha256 (~92%), 384 (~55%) and 512 (~55%) and *marginally faster* regarding sha1 (~4%). I have no idea what\'s going on with Django! It seems its doing too many additional operations. Additionally its Signer doesn\'t handle arbitrary bytes well (it breaks raising `BadSignature` if you use `datab` below, so it needs `datas`).\n\n```python\n"""Timing comparison."""\n\nimport json\nfrom hashlib import blake2b\nfrom hashlib import sha1\nfrom hashlib import sha256\nfrom hashlib import sha384\nfrom hashlib import sha512\n\nfrom django.core import signing\nfrom itsdangerous import Signer\nfrom itsdangerous import URLSafeSerializer\n\nfrom blake2signer import Blake2SerializerSigner\nfrom blake2signer import Blake2Signer\n\nsecret = b\'1\' * 16\ndata = [{\'a\': \'b\'}, 1] * 100000  # some big data structure\ndatas = json.dumps(data)\ndatab = datas.encode()\n\nb2s = Blake2Signer(secret)\nb2ss = Blake2SerializerSigner(secret)\nid_b2 = Signer(secret, digest_method=blake2b)\nid_s1 = Signer(secret, digest_method=sha1)\nid_s256 = Signer(secret, digest_method=sha256)\nid_s384 = Signer(secret, digest_method=sha384)\nid_s512 = Signer(secret, digest_method=sha512)\ndjs_b2 = signing.Signer(secret, algorithm=\'blake2b\')\ndjs_s1 = signing.Signer(secret, algorithm=\'sha1\')\ndjs_s256 = signing.Signer(secret, algorithm=\'sha256\')\ndjs_s384 = signing.Signer(secret, algorithm=\'sha384\')\ndjs_s512 = signing.Signer(secret, algorithm=\'sha512\')\nid_b2s = URLSafeSerializer(secret, signer_kwargs={\'digest_method\': blake2b})\n\n# Using ipython:\nprint(\'b2s\')\n%timeit b2s.unsign(b2s.sign(datab))\nprint(\'id_b2\')\n%timeit id_b2.unsign(id_b2.sign(datab))\nprint(\'id_s1\')\n%timeit id_s1.unsign(id_s1.sign(datab))\nprint(\'id_s256\')\n%timeit id_s256.unsign(id_s256.sign(datab))\nprint(\'id_s384\')\n%timeit id_s384.unsign(id_s384.sign(datab))\nprint(\'id_s512\')\n%timeit id_s512.unsign(id_s512.sign(datab))\nprint(\'djs_b2\')\n%timeit djs_b2.unsign(djs_b2.sign(datas))\nprint(\'djs_s1\')\n%timeit djs_s1.unsign(djs_s1.sign(datas))\nprint(\'djs_s256\')\n%timeit djs_s256.unsign(djs_s256.sign(datas))\nprint(\'djs_s384\')\n%timeit djs_s384.unsign(djs_s384.sign(datas))\nprint(\'djs_s512\')\n%timeit djs_s512.unsign(djs_s512.sign(datas))\nprint(\'b2ss\')\n%timeit b2ss.loads(b2ss.dumps(data))\nprint(\'id_b2s\')\n%timeit id_b2s.loads(id_b2s.dumps(data))\n```\n\n## Notice\n\nI\'m not a cryptoexpert, so there are some things that remain to be confirmed:\n\n* If an attacker can control some part (or all) of the input data, is it possible for them to guess the secret key or provoke a DoS given a huge amount of attempts? (assuming the key is long enough to prevent bruteforcing in the first place, which it should since I set the minimum key size to 128b).\n  > I think it is not possible but I would like an expert answer. I checked the code of different signers such as Itsdangerous, Django, etc. and they all do pretty much the same as I except they use the hmac lib.\n\n* I always assume that no attacker can influence the instantiation of the classes, thus they can\'t change any setting. If someone would break all of the given recommendations and somehow manage to get attacker-controlled data to class instantiation, which settings an attacker may change to break the security of this implementation and guess the secret key? This is more of an exercise but a fun one.\n  > I think that `Blake2SerializerSigner` class is the the best target that allows more room to play since it deals with many layers: serialization, compression, encoding...\n\n## License\n\n**Blake2Signer** is made by [HacKan](https://hackan.net) under MPL v2.0. You are free to use, share, modify and share modifications under the terms of that [license](LICENSE).  Derived works may link back to the canonical repository: https://gitlab.com/hackancuba/blake2signer.\n\n    Copyright (C) 2020 HacKan (https://hackan.net)\n    This Source Code Form is subject to the terms of the Mozilla Public\n    License, v. 2.0. If a copy of the MPL was not distributed with this\n    file, You can obtain one at https://mozilla.org/MPL/2.0/.\n',
    'author': 'HacKan',
    'author_email': 'hackan@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://gitlab.com/hackancuba/blake2signer',
    'packages': packages,
    'package_data': package_data,
    'python_requires': '>=3.7,<4.0',
}


setup(**setup_kwargs)
