pyseto


Namepyseto JSON
Version 1.8.3 PyPI version JSON
download
home_pagehttps://github.com/dajiaji/pyseto
SummaryA Python implementation of PASETO/PASERK.
upload_time2025-03-02 23:47:09
maintainerNone
docs_urlNone
authorAjitomi Daisuke
requires_python<4.0,>=3.9.2
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # PySETO - A Python implementation of PASETO/PASERK

[![PyPI version](https://badge.fury.io/py/pyseto.svg)](https://badge.fury.io/py/pyseto)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pyseto)
[![Documentation Status](https://readthedocs.org/projects/pyseto/badge/?version=latest)](https://pyseto.readthedocs.io/en/latest/?badge=latest)
![Github CI](https://github.com/dajiaji/pyseto/actions/workflows/ci.yml/badge.svg)
[![codecov](https://codecov.io/gh/dajiaji/pyseto/branch/main/graph/badge.svg?token=QN8GXEYEP3)](https://codecov.io/gh/dajiaji/pyseto)


PySETO is a [PASETO (Platform-Agnostic SEcurity TOkens)](https://paseto.io/)/[PASERK (Platform-Agnostic Serialized Keys)](https://github.com/paseto-standard/paserk) implementation written in Python
which supports all of the versions ([v1](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version1.md),
[v2](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version2.md),
[v3](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version3.md) and
[v4](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version4.md)) and purposes (`public` and `local`)
and has passed all of [the official tests](https://github.com/paseto-standard/test-vectors).

You can install PySETO with pip:

```sh
$ pip install pyseto
```

PySETO can be used in ease as follows (in case of `v4.public`):

```py
import pyseto
from pyseto import Key

private_key_pem = b"-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----"
public_key_pem = b"-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----"


# Create a PASETO token.
private_key = Key.new(version=4, purpose="public", key=private_key_pem)
token = pyseto.encode(
    private_key,
    b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}',
)

# Decode and verify a PASETO token.
public_key = Key.new(version=4, purpose="public", key=public_key_pem)
decoded = pyseto.decode(public_key, token)

assert (
    token
    == b"v4.public.eyJkYXRhIjogInRoaXMgaXMgYSBzaWduZWQgbWVzc2FnZSIsICJleHAiOiAiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9l1YiKei2FESvHBSGPkn70eFO1hv3tXH0jph1IfZyEfgm3t1DjkYqD5r4aHWZm1eZs_3_bZ9pBQlZGp0DPSdzDg"
)
assert (
    decoded.payload
    == b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}'
)
```

See following contents or [Documentation](https://pyseto.readthedocs.io/en/stable/) for details.

## Index

- [Installation](#installation)
- [Supported PASETO Versions](#supported-paseto-versions)
- [Supported PASERK Types](#supported-paserk-types)
- [PASETO Usage](#paseto-usage)
    - [Basic usage: v4.public](#basic-usage-v4public)
    - [Basic usage: v4.local](#basic-usage-v4local)
    - [Using serializer/deserializer for payload and footer](#using-serializerdeserializer-for-payload-and-footer)
    - [Using Paseto class for handling registered claims](#using-paseto-class-for-handling-registered-claims)
- [PASERK Usage](#paserk-usage)
    - [Serializing/Deserializing PASERK](#serializingdeserializing-paserk)
    - [Serializing PASERK ID](#serializing-paserk-id)
    - [Key Wrapping](#key-wrapping)
    - [Password-based Key Encryption](#password-based-key-encryption)
    - [Asymmetric Encryption](#asymmetric-encryption)
- [API Reference](#api-reference)
- [Tests](#tests)
- [Contributing](#contributing)

## Installation

You can install PySETO with pip:

```sh
$ pip install pyseto
```

## Supported PASETO Versions

PySETO supports all of PASETO versions and purposes below:


|          |  v4  |  v3  |  v2  |  v1  |
| ---------| ---- | ---- | ---- | ---- |
| `local`  |  ✅  |  ✅  |  ✅  |  ✅  |
| `public` |  ✅  |  ✅  |  ✅  |  ✅  |


## Supported PASERK Types

PySETO also supports [PASERK (Platform-Agnostic Serialized Keys)](https://github.com/paseto-standard/paserk).
Currently, following PASERK types are supported:


|               |  v4  |  v3  |  v2  |  v1  |
| ------------- | ---- | ---- | ---- | ---- |
| `lid`         |  ✅  |  ✅  |  ✅  |  ✅  |
| `sid`         |  ✅  |  ✅  |  ✅  |  ✅  |
| `pid`         |  ✅  |  ✅  |  ✅  |  ✅  |
| `local`       |  ✅  |  ✅  |  ✅  |  ✅  |
| `secret`      |  ✅  |  ✅  |  ✅  |  ✅  |
| `public`      |  ✅  |  ✅  |  ✅  |  ✅  |
| `seal`        |  ✅  |      |  ✅  |      |
| `local-wrap`  |  ✅  |  ✅  |  ✅  |  ✅  |
| `secret-wrap` |  ✅  |  ✅  |  ✅  |  ✅  |
| `local-pw`    |  ✅  |  ✅  |  ✅  |  ✅  |
| `secret-pw`   |  ✅  |  ✅  |  ✅  |  ✅  |


## PASETO Usage

By using this PySETO, you can easily create, decode and verify PASETO tokens. Here are sample codes that handle version 4 PySETO tokens.

Please refer to [the Documentation](https://pyseto.readthedocs.io/en/stable/) for all usage examples including other versions.

### Basic usage: v4.public

`v4.public` is one of current PASETO versions to be used for asymmetric authentication (public key signatures).

```py
import pyseto
from pyseto import Key

private_key_pem = b"-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----"
public_key_pem = b"-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----"

private_key = Key.new(version=4, purpose="public", key=private_key_pem)
token = pyseto.encode(
    private_key,
    b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}',
)

public_key = Key.new(version=4, purpose="public", key=public_key_pem)
decoded = pyseto.decode(public_key, token)

assert (
    token
    == b"v4.public.eyJkYXRhIjogInRoaXMgaXMgYSBzaWduZWQgbWVzc2FnZSIsICJleHAiOiAiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9l1YiKei2FESvHBSGPkn70eFO1hv3tXH0jph1IfZyEfgm3t1DjkYqD5r4aHWZm1eZs_3_bZ9pBQlZGp0DPSdzDg"
)
assert (
    decoded.payload
    == b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}'
)
```

### Basic usage: v4.local

`v4.local` is one of current PASETO versions to be used for symmetric authenticated encryption.

```py
import pyseto
from pyseto import Key

key = Key.new(version=4, purpose="local", key=b"our-secret")
token = pyseto.encode(
    key, b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}'
)

decoded = pyseto.decode(key, token)

assert (
    decoded.payload
    == b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}'
)
```

### Using serializer/deserializer for payload and footer

By using `serializer` and `deserializer`, you can encode/decode a dict-typed payload and footer included in PASETO tokens into an arbitrary format.
The following example shows that the payload and the footer in a PASETO token are encoded/decoded as JSON formatted data.
When specifing dict-typed payload, exp parameter can be used to set the expiration time (seconds) of the token.

```py
import json
import pyseto
from pyseto import Key

private_key_pem = b"-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----"
public_key_pem = b"-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----"

private_key = Key.new(version=4, purpose="public", key=private_key_pem)
public_key = Key.new(version=4, purpose="public", key=public_key_pem)
token = pyseto.encode(
    private_key,
    {"data": "this is a signed message"},
    footer={"kid": public_key.to_paserk_id()},
    serializer=json,
    exp=3600,
)

decoded = pyseto.decode(public_key, token, deserializer=json)

assert decoded.payload["data"] == "this is a signed message"
assert decoded.payload["exp"] == "2021-11-11T00:00:00+00:00"
assert decoded.footer["kid"] == "k4.pid.yh4-bJYjOYAG6CWy0zsfPmpKylxS7uAWrxqVmBN2KAiJ"
```

### Using `Paseto` class for handling registered claims

By using `Paseto` class, you can change the default value of `exp` (the expiration date ot tokens), whether to include an `iat` claim, and other settings.

Note that `pyseto.encode()` and `pyseto.decode()` are aliases to the `encode()` and `decode()` of the global "Paseto" class instance created with the default settings.

```py
import json
import pyseto
from pyseto import Key, Paseto

private_key_pem = b"-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----"
public_key_pem = b"-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----"

private_key = Key.new(version=4, purpose="public", key=private_key_pem)
paseto = Paseto.new(
    exp=3600, include_iat=True
)  # Default values are exp=0(not specified) and including_iat=False
token = paseto.encode(
    private_key,
    {"data": "this is a signed message"},
    serializer=json,
)

public_key = Key.new(version=4, purpose="public", key=public_key_pem)
decoded = pyseto.decode(public_key, token, deserializer=json)

assert decoded.payload["data"] == "this is a signed message"
assert decoded.payload["iat"] == "2021-11-11T00:00:00+00:00"
assert decoded.payload["exp"] == "2021-11-11T01:00:00+00:00"
```

## PASERK Usage

[PASERK (Platform-Agnostic Serialized Keys)](https://github.com/paseto-standard/paserk) is an extension to PASETO that provides key-wrapping and serialization.

### Serializing/Deserializing PASERK

As shown in the examples above, the `pyseto.Key` used for encryption and signature can be generated from PASERK or converted to PASERK as follows:

```py
import pyseto
from pyseto import Key

# pyseto.Key can be generated from PASERK.
symmetric_key = Key.new(version=4, purpose="local", key=b"our-secret")
private_key = Key.from_paserk(
    "k4.secret.tMv7Q99M4hByfZU-SnEzB_oZu32fhQQUONnhG5QqN3Qeudu7vAR8A_1wYE4AcfCYfhayi3VyJcEfAEFdDiCxog"
)
public_key = Key.from_paserk("k4.public.Hrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI")

token = pyseto.encode(
    private_key,
    b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}',
)
decoded = pyseto.decode(public_key, token)

assert (
    decoded.payload
    == b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}'
)

# PASERK can be derived from pyseto.Key.
assert symmetric_key.to_paserk() == "k4.local.b3VyLXNlY3JldA"
assert (
    private_key.to_paserk()
    == "k4.secret.tMv7Q99M4hByfZU-SnEzB_oZu32fhQQUONnhG5QqN3Qeudu7vAR8A_1wYE4AcfCYfhayi3VyJcEfAEFdDiCxog"
)
assert public_key.to_paserk() == "k4.public.Hrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI"
```

### Serializing PASERK ID

`pyseto.Key` can also be converted to PASERK ID as follows:

```py
import pyseto
from pyseto import Key

# pyseto.Key can be generated from PASERK.
symmetric_key = Key.new(version=4, purpose="local", key=b"our-secret")
private_key = Key.from_paserk(
    "k4.secret.tMv7Q99M4hByfZU-SnEzB_oZu32fhQQUONnhG5QqN3Qeudu7vAR8A_1wYE4AcfCYfhayi3VyJcEfAEFdDiCxog"
)
public_key = Key.from_paserk("k4.public.Hrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI")

# PASERK ID can be derived from pyseto.Key.
assert (
    symmetric_key.to_paserk_id()
    == "k4.lid._D6kgTzxgiPGk35gMj9bukgj4En2H94u22wVX9zaoh05"
)
assert (
    private_key.to_paserk()
    == "k4.secret.tMv7Q99M4hByfZU-SnEzB_oZu32fhQQUONnhG5QqN3Qeudu7vAR8A_1wYE4AcfCYfhayi3VyJcEfAEFdDiCxog"
)
assert (
    public_key.to_paserk_id() == "k4.pid.yh4-bJYjOYAG6CWy0zsfPmpKylxS7uAWrxqVmBN2KAiJ"
)
```

### Key Wrapping

If you call `to_paserk` with `wrapping_key`, you can get a wrapped (encrypted) PASERK with the wrapping key.
The wrapped PASERK can be decrypted by calling `from_paserk` with `wrapping key`.

In case of `local-wrap.pie`:

```py
import pyseto
from pyseto import Key

raw_key = Key.new(version=4, purpose="local", key=b"our-secret")
wrapping_key = token_bytes(32)
wpk = raw_key.to_paserk(wrapping_key=wrapping_key)
token = pyseto.encode(
    raw_key, b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}'
)

unwrapped_key = Key.from_paserk(wpk, wrapping_key=wrapping_key)
decoded = pyseto.decode(unwrapped_key, token)

# assert wpk == "k4.local-wrap.pie.TNKEwC4K1xBcgJ_GiwWAoRlQFE33HJO3oN9DHEZ05pieSCd-W7bgAL64VG9TZ_pBkuNBFHNrfOGHtnfnhYGdbz5-x3CxShhPJxg"
assert (
    decoded.payload
    == b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}'
)
```

In case of `secret-wrap.pie`:

```py
import pyseto
from pyseto import Key

raw_private_key = Key.from_paserk(
    "k4.secret.tMv7Q99M4hByfZU-SnEzB_oZu32fhQQUONnhG5QqN3Qeudu7vAR8A_1wYE4AcfCYfhayi3VyJcEfAEFdDiCxog"
)
wrapping_key = token_bytes(32)
wpk = raw_private_key.to_paserk(wrapping_key=wrapping_key)
unwrapped_private_key = Key.from_paserk(wpk, wrapping_key=wrapping_key)
token = pyseto.encode(
    unwrapped_private_key,
    b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}',
)

public_key = Key.from_paserk("k4.public.Hrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI")
decoded = pyseto.decode(public_key, token)

# assert wpk == "k4.secret-wrap.pie.excv7V4-NaECy5hpji-tkSkMvyjsAgNxA-mGALgdjyvGNyDlTb89bJ35R1e3tILgbMpEW5WXMXzySe2T-sBz-ZAcs1j7rbD3ZWvsBTM6K5N9wWfAxbR4ppCXH_H5__9yY-kBaF2NimyAJyduhOhSmqLm6TTSucpAOakEJOXePW8"
assert (
    decoded.payload
    == b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}'
)
```

### Password-based Key Encryption

If you call `to_paserk` with `password`, you can get a wrapped (encrypted) PASERK with the password.
The wrapped PASERK can be decrypted by calling `from_paserk` with `passwrod`.

In case of `local-pw`:

```py
import pyseto
from pyseto import Key

raw_key = Key.new(version=4, purpose="local", key=b"our-secret")
token = pyseto.encode(
    raw_key, b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}'
)

wpk = raw_key.to_paserk(password="our-secret")
unwrapped_key = Key.from_paserk(wpk, password="our-secret")
decoded = pyseto.decode(unwrapped_key, token)

# assert wpk == "k4.local-pw.HrCs9Pu-2LB0l7jkHB-x2gAAAAAA8AAAAAAAAgAAAAGttW0IHZjQCHJdg-Vc3tqO_GSLR4vzLl-yrKk2I-l8YHj6jWpC0lQB2Z7uzTtVyV1rd_EZQPzHdw5VOtyucP0FkCU"
assert (
    decoded.payload
    == b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}'
)
```

In case of `secret-pw`:

```py
import pyseto
from pyseto import Key

raw_private_key = Key.from_paserk(
    "k4.secret.tMv7Q99M4hByfZU-SnEzB_oZu32fhQQUONnhG5QqN3Qeudu7vAR8A_1wYE4AcfCYfhayi3VyJcEfAEFdDiCxog"
)
wpk = raw_private_key.to_paserk(password="our-secret")
unwrapped_private_key = Key.from_paserk(wpk, password="our-secret")
token = pyseto.encode(
    unwrapped_private_key,
    b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}',
)

public_key = Key.from_paserk("k4.public.Hrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI")
decoded = pyseto.decode(public_key, token)

# assert wpk == "k4.secret-pw.MEMW4K1MaD5nWigCLyEyFAAAAAAA8AAAAAAAAgAAAAFU-tArtryNVjS2n2hCYiM11V6tOyuIog69Bjb0yNZanrLJ3afGclb3kPzQ6IhK8ob9E4QgRdEALGWCizZ0RCPFF_M95IQDfmdYKC0Er656UgKUK4UKG9JlxP4o81UwoJoZYz_D1zTlltipEa5RiNvUtNU8vLKoGSY"
assert (
    decoded.payload
    == b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}'
)
```

### Asymmetric Encryption

At this time, PySETO supports asymmetric encryption (key sealing) for `v2` and `v4`.

```py
import pyseto
from pyseto import Key

private_key_pem = b"-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VuBCIEIFAF7jSCZHFgWvC8hUkXr55Az6Pot2g4zOAUxck0/6x8\n-----END PRIVATE KEY-----"
public_key_pem = b"-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VuAyEAFv8IXsICYj0paznDK/99GyCsFOIGnfY87ayyNSIvSB4=\n-----END PUBLIC KEY-----"

raw_key = Key.new(version=4, purpose="local", key=b"our-secret")
token = pyseto.encode(
    raw_key,
    b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}',
)

sealed_key = raw_key.to_paserk(sealing_key=public_key_pem)
unsealed_key = Key.from_paserk(sealed_key, unsealing_key=private_key_pem)
decoded = pyseto.decode(unsealed_key, token)

assert (
    decoded.payload
    == b'{"data": "this is a signed message", "exp": "2022-01-01T00:00:00+00:00"}'
)
```

Key searing for `v1` and `v3` have not been supported yet.


## API Reference

See [Documentation](https://pyseto.readthedocs.io/en/stable/api.html).

## Tests

You can run tests from the project root after cloning with:

```sh
$ tox
```

## Contributing

We welcome all kind of contributions, filing issues, suggesting new features or sending PRs.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/dajiaji/pyseto",
    "name": "pyseto",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.9.2",
    "maintainer_email": null,
    "keywords": null,
    "author": "Ajitomi Daisuke",
    "author_email": "dajiaji@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/ac/67/024ffe5177b23eb5f3f84fb3773e539fba8e4e32734b0309b92e831083ba/pyseto-1.8.3.tar.gz",
    "platform": null,
    "description": "# PySETO - A Python implementation of PASETO/PASERK\n\n[![PyPI version](https://badge.fury.io/py/pyseto.svg)](https://badge.fury.io/py/pyseto)\n![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pyseto)\n[![Documentation Status](https://readthedocs.org/projects/pyseto/badge/?version=latest)](https://pyseto.readthedocs.io/en/latest/?badge=latest)\n![Github CI](https://github.com/dajiaji/pyseto/actions/workflows/ci.yml/badge.svg)\n[![codecov](https://codecov.io/gh/dajiaji/pyseto/branch/main/graph/badge.svg?token=QN8GXEYEP3)](https://codecov.io/gh/dajiaji/pyseto)\n\n\nPySETO is a [PASETO (Platform-Agnostic SEcurity TOkens)](https://paseto.io/)/[PASERK (Platform-Agnostic Serialized Keys)](https://github.com/paseto-standard/paserk) implementation written in Python\nwhich supports all of the versions ([v1](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version1.md),\n[v2](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version2.md),\n[v3](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version3.md) and\n[v4](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version4.md)) and purposes (`public` and `local`)\nand has passed all of [the official tests](https://github.com/paseto-standard/test-vectors).\n\nYou can install PySETO with pip:\n\n```sh\n$ pip install pyseto\n```\n\nPySETO can be used in ease as follows (in case of `v4.public`):\n\n```py\nimport pyseto\nfrom pyseto import Key\n\nprivate_key_pem = b\"-----BEGIN PRIVATE KEY-----\\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\\n-----END PRIVATE KEY-----\"\npublic_key_pem = b\"-----BEGIN PUBLIC KEY-----\\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\\n-----END PUBLIC KEY-----\"\n\n\n# Create a PASETO token.\nprivate_key = Key.new(version=4, purpose=\"public\", key=private_key_pem)\ntoken = pyseto.encode(\n    private_key,\n    b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}',\n)\n\n# Decode and verify a PASETO token.\npublic_key = Key.new(version=4, purpose=\"public\", key=public_key_pem)\ndecoded = pyseto.decode(public_key, token)\n\nassert (\n    token\n    == b\"v4.public.eyJkYXRhIjogInRoaXMgaXMgYSBzaWduZWQgbWVzc2FnZSIsICJleHAiOiAiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9l1YiKei2FESvHBSGPkn70eFO1hv3tXH0jph1IfZyEfgm3t1DjkYqD5r4aHWZm1eZs_3_bZ9pBQlZGp0DPSdzDg\"\n)\nassert (\n    decoded.payload\n    == b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}'\n)\n```\n\nSee following contents or [Documentation](https://pyseto.readthedocs.io/en/stable/) for details.\n\n## Index\n\n- [Installation](#installation)\n- [Supported PASETO Versions](#supported-paseto-versions)\n- [Supported PASERK Types](#supported-paserk-types)\n- [PASETO Usage](#paseto-usage)\n    - [Basic usage: v4.public](#basic-usage-v4public)\n    - [Basic usage: v4.local](#basic-usage-v4local)\n    - [Using serializer/deserializer for payload and footer](#using-serializerdeserializer-for-payload-and-footer)\n    - [Using Paseto class for handling registered claims](#using-paseto-class-for-handling-registered-claims)\n- [PASERK Usage](#paserk-usage)\n    - [Serializing/Deserializing PASERK](#serializingdeserializing-paserk)\n    - [Serializing PASERK ID](#serializing-paserk-id)\n    - [Key Wrapping](#key-wrapping)\n    - [Password-based Key Encryption](#password-based-key-encryption)\n    - [Asymmetric Encryption](#asymmetric-encryption)\n- [API Reference](#api-reference)\n- [Tests](#tests)\n- [Contributing](#contributing)\n\n## Installation\n\nYou can install PySETO with pip:\n\n```sh\n$ pip install pyseto\n```\n\n## Supported PASETO Versions\n\nPySETO supports all of PASETO versions and purposes below:\n\n\n|          |  v4  |  v3  |  v2  |  v1  |\n| ---------| ---- | ---- | ---- | ---- |\n| `local`  |  \u2705  |  \u2705  |  \u2705  |  \u2705  |\n| `public` |  \u2705  |  \u2705  |  \u2705  |  \u2705  |\n\n\n## Supported PASERK Types\n\nPySETO also supports [PASERK (Platform-Agnostic Serialized Keys)](https://github.com/paseto-standard/paserk).\nCurrently, following PASERK types are supported:\n\n\n|               |  v4  |  v3  |  v2  |  v1  |\n| ------------- | ---- | ---- | ---- | ---- |\n| `lid`         |  \u2705  |  \u2705  |  \u2705  |  \u2705  |\n| `sid`         |  \u2705  |  \u2705  |  \u2705  |  \u2705  |\n| `pid`         |  \u2705  |  \u2705  |  \u2705  |  \u2705  |\n| `local`       |  \u2705  |  \u2705  |  \u2705  |  \u2705  |\n| `secret`      |  \u2705  |  \u2705  |  \u2705  |  \u2705  |\n| `public`      |  \u2705  |  \u2705  |  \u2705  |  \u2705  |\n| `seal`        |  \u2705  |      |  \u2705  |      |\n| `local-wrap`  |  \u2705  |  \u2705  |  \u2705  |  \u2705  |\n| `secret-wrap` |  \u2705  |  \u2705  |  \u2705  |  \u2705  |\n| `local-pw`    |  \u2705  |  \u2705  |  \u2705  |  \u2705  |\n| `secret-pw`   |  \u2705  |  \u2705  |  \u2705  |  \u2705  |\n\n\n## PASETO Usage\n\nBy using this PySETO, you can easily create, decode and verify PASETO tokens. Here are sample codes that handle version 4 PySETO tokens.\n\nPlease refer to [the Documentation](https://pyseto.readthedocs.io/en/stable/) for all usage examples including other versions.\n\n### Basic usage: v4.public\n\n`v4.public` is one of current PASETO versions to be used for asymmetric authentication (public key signatures).\n\n```py\nimport pyseto\nfrom pyseto import Key\n\nprivate_key_pem = b\"-----BEGIN PRIVATE KEY-----\\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\\n-----END PRIVATE KEY-----\"\npublic_key_pem = b\"-----BEGIN PUBLIC KEY-----\\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\\n-----END PUBLIC KEY-----\"\n\nprivate_key = Key.new(version=4, purpose=\"public\", key=private_key_pem)\ntoken = pyseto.encode(\n    private_key,\n    b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}',\n)\n\npublic_key = Key.new(version=4, purpose=\"public\", key=public_key_pem)\ndecoded = pyseto.decode(public_key, token)\n\nassert (\n    token\n    == b\"v4.public.eyJkYXRhIjogInRoaXMgaXMgYSBzaWduZWQgbWVzc2FnZSIsICJleHAiOiAiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9l1YiKei2FESvHBSGPkn70eFO1hv3tXH0jph1IfZyEfgm3t1DjkYqD5r4aHWZm1eZs_3_bZ9pBQlZGp0DPSdzDg\"\n)\nassert (\n    decoded.payload\n    == b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}'\n)\n```\n\n### Basic usage: v4.local\n\n`v4.local` is one of current PASETO versions to be used for symmetric authenticated encryption.\n\n```py\nimport pyseto\nfrom pyseto import Key\n\nkey = Key.new(version=4, purpose=\"local\", key=b\"our-secret\")\ntoken = pyseto.encode(\n    key, b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}'\n)\n\ndecoded = pyseto.decode(key, token)\n\nassert (\n    decoded.payload\n    == b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}'\n)\n```\n\n### Using serializer/deserializer for payload and footer\n\nBy using `serializer` and `deserializer`, you can encode/decode a dict-typed payload and footer included in PASETO tokens into an arbitrary format.\nThe following example shows that the payload and the footer in a PASETO token are encoded/decoded as JSON formatted data.\nWhen specifing dict-typed payload, exp parameter can be used to set the expiration time (seconds) of the token.\n\n```py\nimport json\nimport pyseto\nfrom pyseto import Key\n\nprivate_key_pem = b\"-----BEGIN PRIVATE KEY-----\\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\\n-----END PRIVATE KEY-----\"\npublic_key_pem = b\"-----BEGIN PUBLIC KEY-----\\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\\n-----END PUBLIC KEY-----\"\n\nprivate_key = Key.new(version=4, purpose=\"public\", key=private_key_pem)\npublic_key = Key.new(version=4, purpose=\"public\", key=public_key_pem)\ntoken = pyseto.encode(\n    private_key,\n    {\"data\": \"this is a signed message\"},\n    footer={\"kid\": public_key.to_paserk_id()},\n    serializer=json,\n    exp=3600,\n)\n\ndecoded = pyseto.decode(public_key, token, deserializer=json)\n\nassert decoded.payload[\"data\"] == \"this is a signed message\"\nassert decoded.payload[\"exp\"] == \"2021-11-11T00:00:00+00:00\"\nassert decoded.footer[\"kid\"] == \"k4.pid.yh4-bJYjOYAG6CWy0zsfPmpKylxS7uAWrxqVmBN2KAiJ\"\n```\n\n### Using `Paseto` class for handling registered claims\n\nBy using `Paseto` class, you can change the default value of `exp` (the expiration date ot tokens), whether to include an `iat` claim, and other settings.\n\nNote that `pyseto.encode()` and `pyseto.decode()` are aliases to the `encode()` and `decode()` of the global \"Paseto\" class instance created with the default settings.\n\n```py\nimport json\nimport pyseto\nfrom pyseto import Key, Paseto\n\nprivate_key_pem = b\"-----BEGIN PRIVATE KEY-----\\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\\n-----END PRIVATE KEY-----\"\npublic_key_pem = b\"-----BEGIN PUBLIC KEY-----\\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\\n-----END PUBLIC KEY-----\"\n\nprivate_key = Key.new(version=4, purpose=\"public\", key=private_key_pem)\npaseto = Paseto.new(\n    exp=3600, include_iat=True\n)  # Default values are exp=0(not specified) and including_iat=False\ntoken = paseto.encode(\n    private_key,\n    {\"data\": \"this is a signed message\"},\n    serializer=json,\n)\n\npublic_key = Key.new(version=4, purpose=\"public\", key=public_key_pem)\ndecoded = pyseto.decode(public_key, token, deserializer=json)\n\nassert decoded.payload[\"data\"] == \"this is a signed message\"\nassert decoded.payload[\"iat\"] == \"2021-11-11T00:00:00+00:00\"\nassert decoded.payload[\"exp\"] == \"2021-11-11T01:00:00+00:00\"\n```\n\n## PASERK Usage\n\n[PASERK (Platform-Agnostic Serialized Keys)](https://github.com/paseto-standard/paserk) is an extension to PASETO that provides key-wrapping and serialization.\n\n### Serializing/Deserializing PASERK\n\nAs shown in the examples above, the `pyseto.Key` used for encryption and signature can be generated from PASERK or converted to PASERK as follows:\n\n```py\nimport pyseto\nfrom pyseto import Key\n\n# pyseto.Key can be generated from PASERK.\nsymmetric_key = Key.new(version=4, purpose=\"local\", key=b\"our-secret\")\nprivate_key = Key.from_paserk(\n    \"k4.secret.tMv7Q99M4hByfZU-SnEzB_oZu32fhQQUONnhG5QqN3Qeudu7vAR8A_1wYE4AcfCYfhayi3VyJcEfAEFdDiCxog\"\n)\npublic_key = Key.from_paserk(\"k4.public.Hrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI\")\n\ntoken = pyseto.encode(\n    private_key,\n    b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}',\n)\ndecoded = pyseto.decode(public_key, token)\n\nassert (\n    decoded.payload\n    == b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}'\n)\n\n# PASERK can be derived from pyseto.Key.\nassert symmetric_key.to_paserk() == \"k4.local.b3VyLXNlY3JldA\"\nassert (\n    private_key.to_paserk()\n    == \"k4.secret.tMv7Q99M4hByfZU-SnEzB_oZu32fhQQUONnhG5QqN3Qeudu7vAR8A_1wYE4AcfCYfhayi3VyJcEfAEFdDiCxog\"\n)\nassert public_key.to_paserk() == \"k4.public.Hrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI\"\n```\n\n### Serializing PASERK ID\n\n`pyseto.Key` can also be converted to PASERK ID as follows:\n\n```py\nimport pyseto\nfrom pyseto import Key\n\n# pyseto.Key can be generated from PASERK.\nsymmetric_key = Key.new(version=4, purpose=\"local\", key=b\"our-secret\")\nprivate_key = Key.from_paserk(\n    \"k4.secret.tMv7Q99M4hByfZU-SnEzB_oZu32fhQQUONnhG5QqN3Qeudu7vAR8A_1wYE4AcfCYfhayi3VyJcEfAEFdDiCxog\"\n)\npublic_key = Key.from_paserk(\"k4.public.Hrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI\")\n\n# PASERK ID can be derived from pyseto.Key.\nassert (\n    symmetric_key.to_paserk_id()\n    == \"k4.lid._D6kgTzxgiPGk35gMj9bukgj4En2H94u22wVX9zaoh05\"\n)\nassert (\n    private_key.to_paserk()\n    == \"k4.secret.tMv7Q99M4hByfZU-SnEzB_oZu32fhQQUONnhG5QqN3Qeudu7vAR8A_1wYE4AcfCYfhayi3VyJcEfAEFdDiCxog\"\n)\nassert (\n    public_key.to_paserk_id() == \"k4.pid.yh4-bJYjOYAG6CWy0zsfPmpKylxS7uAWrxqVmBN2KAiJ\"\n)\n```\n\n### Key Wrapping\n\nIf you call `to_paserk` with `wrapping_key`, you can get a wrapped (encrypted) PASERK with the wrapping key.\nThe wrapped PASERK can be decrypted by calling `from_paserk` with `wrapping key`.\n\nIn case of `local-wrap.pie`:\n\n```py\nimport pyseto\nfrom pyseto import Key\n\nraw_key = Key.new(version=4, purpose=\"local\", key=b\"our-secret\")\nwrapping_key = token_bytes(32)\nwpk = raw_key.to_paserk(wrapping_key=wrapping_key)\ntoken = pyseto.encode(\n    raw_key, b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}'\n)\n\nunwrapped_key = Key.from_paserk(wpk, wrapping_key=wrapping_key)\ndecoded = pyseto.decode(unwrapped_key, token)\n\n# assert wpk == \"k4.local-wrap.pie.TNKEwC4K1xBcgJ_GiwWAoRlQFE33HJO3oN9DHEZ05pieSCd-W7bgAL64VG9TZ_pBkuNBFHNrfOGHtnfnhYGdbz5-x3CxShhPJxg\"\nassert (\n    decoded.payload\n    == b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}'\n)\n```\n\nIn case of `secret-wrap.pie`:\n\n```py\nimport pyseto\nfrom pyseto import Key\n\nraw_private_key = Key.from_paserk(\n    \"k4.secret.tMv7Q99M4hByfZU-SnEzB_oZu32fhQQUONnhG5QqN3Qeudu7vAR8A_1wYE4AcfCYfhayi3VyJcEfAEFdDiCxog\"\n)\nwrapping_key = token_bytes(32)\nwpk = raw_private_key.to_paserk(wrapping_key=wrapping_key)\nunwrapped_private_key = Key.from_paserk(wpk, wrapping_key=wrapping_key)\ntoken = pyseto.encode(\n    unwrapped_private_key,\n    b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}',\n)\n\npublic_key = Key.from_paserk(\"k4.public.Hrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI\")\ndecoded = pyseto.decode(public_key, token)\n\n# assert wpk == \"k4.secret-wrap.pie.excv7V4-NaECy5hpji-tkSkMvyjsAgNxA-mGALgdjyvGNyDlTb89bJ35R1e3tILgbMpEW5WXMXzySe2T-sBz-ZAcs1j7rbD3ZWvsBTM6K5N9wWfAxbR4ppCXH_H5__9yY-kBaF2NimyAJyduhOhSmqLm6TTSucpAOakEJOXePW8\"\nassert (\n    decoded.payload\n    == b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}'\n)\n```\n\n### Password-based Key Encryption\n\nIf you call `to_paserk` with `password`, you can get a wrapped (encrypted) PASERK with the password.\nThe wrapped PASERK can be decrypted by calling `from_paserk` with `passwrod`.\n\nIn case of `local-pw`:\n\n```py\nimport pyseto\nfrom pyseto import Key\n\nraw_key = Key.new(version=4, purpose=\"local\", key=b\"our-secret\")\ntoken = pyseto.encode(\n    raw_key, b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}'\n)\n\nwpk = raw_key.to_paserk(password=\"our-secret\")\nunwrapped_key = Key.from_paserk(wpk, password=\"our-secret\")\ndecoded = pyseto.decode(unwrapped_key, token)\n\n# assert wpk == \"k4.local-pw.HrCs9Pu-2LB0l7jkHB-x2gAAAAAA8AAAAAAAAgAAAAGttW0IHZjQCHJdg-Vc3tqO_GSLR4vzLl-yrKk2I-l8YHj6jWpC0lQB2Z7uzTtVyV1rd_EZQPzHdw5VOtyucP0FkCU\"\nassert (\n    decoded.payload\n    == b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}'\n)\n```\n\nIn case of `secret-pw`:\n\n```py\nimport pyseto\nfrom pyseto import Key\n\nraw_private_key = Key.from_paserk(\n    \"k4.secret.tMv7Q99M4hByfZU-SnEzB_oZu32fhQQUONnhG5QqN3Qeudu7vAR8A_1wYE4AcfCYfhayi3VyJcEfAEFdDiCxog\"\n)\nwpk = raw_private_key.to_paserk(password=\"our-secret\")\nunwrapped_private_key = Key.from_paserk(wpk, password=\"our-secret\")\ntoken = pyseto.encode(\n    unwrapped_private_key,\n    b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}',\n)\n\npublic_key = Key.from_paserk(\"k4.public.Hrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI\")\ndecoded = pyseto.decode(public_key, token)\n\n# assert wpk == \"k4.secret-pw.MEMW4K1MaD5nWigCLyEyFAAAAAAA8AAAAAAAAgAAAAFU-tArtryNVjS2n2hCYiM11V6tOyuIog69Bjb0yNZanrLJ3afGclb3kPzQ6IhK8ob9E4QgRdEALGWCizZ0RCPFF_M95IQDfmdYKC0Er656UgKUK4UKG9JlxP4o81UwoJoZYz_D1zTlltipEa5RiNvUtNU8vLKoGSY\"\nassert (\n    decoded.payload\n    == b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}'\n)\n```\n\n### Asymmetric Encryption\n\nAt this time, PySETO supports asymmetric encryption (key sealing) for `v2` and `v4`.\n\n```py\nimport pyseto\nfrom pyseto import Key\n\nprivate_key_pem = b\"-----BEGIN PRIVATE KEY-----\\nMC4CAQAwBQYDK2VuBCIEIFAF7jSCZHFgWvC8hUkXr55Az6Pot2g4zOAUxck0/6x8\\n-----END PRIVATE KEY-----\"\npublic_key_pem = b\"-----BEGIN PUBLIC KEY-----\\nMCowBQYDK2VuAyEAFv8IXsICYj0paznDK/99GyCsFOIGnfY87ayyNSIvSB4=\\n-----END PUBLIC KEY-----\"\n\nraw_key = Key.new(version=4, purpose=\"local\", key=b\"our-secret\")\ntoken = pyseto.encode(\n    raw_key,\n    b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}',\n)\n\nsealed_key = raw_key.to_paserk(sealing_key=public_key_pem)\nunsealed_key = Key.from_paserk(sealed_key, unsealing_key=private_key_pem)\ndecoded = pyseto.decode(unsealed_key, token)\n\nassert (\n    decoded.payload\n    == b'{\"data\": \"this is a signed message\", \"exp\": \"2022-01-01T00:00:00+00:00\"}'\n)\n```\n\nKey searing for `v1` and `v3` have not been supported yet.\n\n\n## API Reference\n\nSee [Documentation](https://pyseto.readthedocs.io/en/stable/api.html).\n\n## Tests\n\nYou can run tests from the project root after cloning with:\n\n```sh\n$ tox\n```\n\n## Contributing\n\nWe welcome all kind of contributions, filing issues, suggesting new features or sending PRs.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A Python implementation of PASETO/PASERK.",
    "version": "1.8.3",
    "project_urls": {
        "Homepage": "https://github.com/dajiaji/pyseto",
        "Repository": "https://github.com/dajiaji/pyseto"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "0d66f4744bf8ca902c697d1852338b5fc7f875b77431595a2b1d59ab47b4b24a",
                "md5": "935fe4731908d71c224998cd9bfbe1e1",
                "sha256": "ed2d0ce7eca954528b0ea843dd1d3cf817e85f199ab826bcb2a82360bc09a672"
            },
            "downloads": -1,
            "filename": "pyseto-1.8.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "935fe4731908d71c224998cd9bfbe1e1",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.9.2",
            "size": 29685,
            "upload_time": "2025-03-02T23:47:06",
            "upload_time_iso_8601": "2025-03-02T23:47:06.579576Z",
            "url": "https://files.pythonhosted.org/packages/0d/66/f4744bf8ca902c697d1852338b5fc7f875b77431595a2b1d59ab47b4b24a/pyseto-1.8.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ac67024ffe5177b23eb5f3f84fb3773e539fba8e4e32734b0309b92e831083ba",
                "md5": "067bfe148fa92bc3c4cf4457f4f742a0",
                "sha256": "755066171d2feddc865705006fa3127786c59b83a223551a82fc78af600eb055"
            },
            "downloads": -1,
            "filename": "pyseto-1.8.3.tar.gz",
            "has_sig": false,
            "md5_digest": "067bfe148fa92bc3c4cf4457f4f742a0",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.9.2",
            "size": 113269,
            "upload_time": "2025-03-02T23:47:09",
            "upload_time_iso_8601": "2025-03-02T23:47:09.114905Z",
            "url": "https://files.pythonhosted.org/packages/ac/67/024ffe5177b23eb5f3f84fb3773e539fba8e4e32734b0309b92e831083ba/pyseto-1.8.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-03-02 23:47:09",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "dajiaji",
    "github_project": "pyseto",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "pyseto"
}
        
Elapsed time: 0.89174s