# Easy Signer

Simple package for cryptographic signing. Features:
- String and Object signing.
- Support for all `hashlib` algorithms.
- Timestamp and ephemeral (expirable) signers.
- Helper functions for string and key generation.

This package uses portions of Django's `django.core.signing` source. Notable differences:
- Does not use django's secret key.
- New signer `EphemeralSigner` for ttl signing.
- Added support of object signing for `TimestampSigner`.
- Uses `orjson` for faster serialization and removing byte encoding.

Signer class constructors accepts these arguments:
- `key`: The key used for signing and securing the signed data, it is vital you keep this secure, or attackers could use it to generate their own signed values. If no key is provided, uses the library default `get_default_key()`.
- `sep`: Separator for the signed data and the signature. Defaults to `:`.
- `salt`: Extra value that will seed the signing hash function with both the salt and the key, typically used for putting different signatures into different namespaces. Optional.
- `algorithm`: The hashing algorithm used for the signing, currently supporting all `hashlib` algorithms. Defaults to `get_default_algorithm()`.

Signers may raise these exceptions:

- `BadSignature`: Signature is invalid or malformed.
- `SignatureExpired`: Signature has expired or older than max age.
- `InvalidAlgorithm`: Algorithm is invalid or cannot be found in `hashlib`.

## Configuration

Library import:
```python
import easy_signer
```

Library defaults (optional) can be set using:

```python
easy_signer.set_default_key("YOUR_KEY")
easy_signer.set_default_algorithm("sha256") # this is the default already
```

If you don't have a key, you can get one by running this script:

```python
print(easy_signer.get_random_key())
```

## Using Signer

Basic signer.

```python
# Valid instantiations:
signer = easy_signer.Signer() # if default key is set.
signer = easy_signer.Signer(key="YOUR_KEY") # provide key at runtime.
signer = easy_signer.Signer(salt="YOUR_SALT") # provide a salt.
signer = easy_signer.Signer(algorithm="sha...") # use a different algorithm.
```

To sign a string:
```python
signed_value = signer.sign("YOUR_STRING")
```

To sign an object:

```python
signed_object = signer.sign_object({"key": "value", "numbers": [0, 1, 2]})
```

To unsign a string:

```python
signer.unsign(signed_value)
```

To unsign an object:

```python
signer.unsign_object(signed_object)
```

## Using TimestampSigner

Timestamp signer. The current time when signing is added to the signature, can validate the signing age when unsigning.

```python
# Valid instantiations:
signer = easy_signer.TimestampSigner() # if default key is set.
signer = easy_signer.TimestampSigner(key="YOUR_KEY") # provide key at runtime.
signer = easy_signer.TimestampSigner(salt="YOUR_SALT") # provide a salt.
signer = easy_signer.TimestampSigner(algorithm="sha...") # use a different algorithm.
```

To sign a string:
```python
signed_value = signer.sign("YOUR_STRING")
```

To sign an object:

```python
signed_object = signer.sign_object({"key": "value", "numbers": [0, 1, 2]})
```

To unsign a string:

```python
max_age = 600 # 10 minutes
signer.unsign(signed_value, max_age)
```

To unsign an object:

```python
max_age = 600 # 10 minutes
signer.unsign_object(signed_object, max_age)
```

## Using EphemeralSigner

Ephemeral signer. The current time of signing plus a TTL is added to the signature, when unsigning it validates that the timestamp is still in the future (not expired).

```python
# Valid instantiations:
signer = easy_signer.EphemeralSigner() # if default key is set.
signer = easy_signer.EphemeralSigner(key="YOUR_KEY") # provide key at runtime.
signer = easy_signer.EphemeralSigner(salt="YOUR_SALT") # provide a salt.
signer = easy_signer.EphemeralSigner(algorithm="sha...") # use a different algorithm.
```

To sign a string:
```python
ttl = 600 # 10 minutes
signed_value = signer.sign("YOUR_STRING", ttl)
```

To sign an object:

```python
ttl = 600 # 10 minutes
signed_object = signer.sign_object({"key": "value", "numbers": [0, 1, 2]}, ttl)
```

To unsign a string:

```python
signer.unsign(signed_value)
```

To unsign an object:

```python
signer.unsign_object(signed_object)
```

## Benchmark

Using `sha256` algorithm, with `r=5000` and `n=10`. Benchmark script can be reviewed on `benchmark.py` or modified for local benchmarking.

Class                     | Mean                      | Median                    | Min                       | Max
------------------------- | ------------------------- | ------------------------- | ------------------------- | -------------------------
Signer (~256b)                   |   0.00022985016200000011s |   0.00022285999999996917s |   0.00021266000000004227s |    0.0012860500000000386s
Signer Object (~768b)            |    0.0005829096359999969s |    0.0005747900000001138s |    0.0005569999999998742s |    0.0010333499999997998s
Timestamp Signer (~256b)         |   0.00024130014600000108s |   0.00023708000000013384s |   0.00022681000000019936s |    0.0006035599999997032s
Timestamp Signer Object (~768b)  |    0.0005838345579999955s |    0.0005750949999999477s |    0.0005500100000006114s |    0.0010899600000001896s
Ephemeral Signer (~256b)         |   0.00024229529599998812s |   0.00023735000000044693s |   0.00022601000000008754s |     0.000627810000000295s
Ephemeral Signer Object (~768b)  |    0.0005835461620000055s |    0.0005760549999997977s |      0.00055435000000017s |    0.0010262900000000742s