dajiaji/pyseto
GitHub: dajiaji/pyseto
PySETO 是一个用 Python 实现的 PASETO/PASERK 库,为开发者提供比 JWT 更安全的令牌创建、编解码与密钥序列化能力。
Stars: 111 | Forks: 10
# PySETO - PASETO/PASERK 的 Python 实现
[](https://badge.fury.io/py/pyseto)

[](https://pyseto.readthedocs.io/en/latest/?badge=latest)

[](https://codecov.io/gh/dajiaji/pyseto)
[](https://snyk.io/test/github/dajiaji/pyseto?targetFile=requirements.txt)
PySETO 是一个用 Python 编写的 [PASETO (Platform-Agnostic SEcurity TOkens)](https://paseto.io/)/[PASERK (Platform-Agnostic Serialized Keys)](https://github.com/paseto-standard/paserk) 实现
它支持所有的版本([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) 和
[v4](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version4.md))和用途(`public` 和 `local`)
并且已经通过了所有的[官方测试](https://github.com/paseto-standard/test-vectors)。
你可以使用 pip 安装 PySETO:
```
$ pip install pyseto
```
PySETO 可以像下面这样轻松使用(以 `v4.public` 为例):
```
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-----"
# 创建 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"}',
)
# 解码并验证 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"}'
)
```
详情请参阅以下内容或[文档](https://pyseto.readthedocs.io/en/stable/)。
## 目录
- [安装](#installation)
- [支持的 PASETO 版本](#supported-paseto-versions)
- [支持的 PASERK 类型](#supported-paserk-types)
- [PASETO 用法](#paseto-usage)
- [基本用法:v4.public](#basic-usage-v4public)
- [基本用法:v4.local](#basic-usage-v4local)
- [为 payload 和 footer 使用序列化器/反序列化器](#using-serializerdeserializer-for-payload-and-footer)
- [使用 Paseto 类处理已注册声明](#using-paseto-class-for-handling-registered-claims)
- [PASERK 用法](#paserk-usage)
- [序列化/反序列化 PASERK](#serializingdeserializing-paserk)
- [序列化 PASERK ID](#serializing-paserk-id)
- [密钥封装](#key-wrapping)
- [基于密码的密钥加密](#password-based-key-encryption)
- [非对称加密](#asymmetric-encryption)
- [API 参考](#api-reference)
- [测试](#tests)
- [安全性](#security)
- [贡献](#contributing)
## 安装
你可以使用 pip 安装 PySETO:
```
$ pip install pyseto
```
## 支持的 PASETO 版本
PySETO 支持以下所有的 PASETO 版本和用途:
| | v4 | v3 | v2 | v1 |
| ---------| ---- | ---- | ---- | ---- |
| `local` | ✅ | ✅ | ✅ | ✅ |
| `public` | ✅ | ✅ | ✅ | ✅ |
## 支持的 PASERK 类型
PySETO 还支持 [PASERK (Platform-Agnostic Serialized Keys)](https://github.com/paseto-standard/paserk)。
目前,支持以下 PASERK 类型:
| | v4 | v3 | v2 | v1 |
| ------------- | ---- | ---- | ---- | ---- |
| `lid` | ✅ | ✅ | ✅ | ✅ |
| `sid` | ✅ | ✅ | ✅ | ✅ |
| `pid` | ✅ | ✅ | ✅ | ✅ |
| `local` | ✅ | ✅ | ✅ | ✅ |
| `secret` | ✅ | ✅ | ✅ | ✅ |
| `public` | ✅ | ✅ | ✅ | ✅ |
| `seal` | ✅ | | ✅ | |
| `local-wrap` | ✅ | ✅ | ✅ | ✅ |
| `secret-wrap` | ✅ | ✅ | ✅ | ✅ |
| `local-pw` | ✅ | ✅ | ✅ | ✅ |
| `secret-pw` | ✅ | ✅ | ✅ | ✅ |
## PASETO 用法
通过使用 PySETO,你可以轻松地创建、解码和验证 PASETO token。以下是处理版本 4 PySETO token 的示例代码。
有关包括其他版本在内的所有用法示例,请参阅[文档](https://pyseto.readthedocs.io/en/stable/)。
### 基本用法:v4.public
`v4.public` 是当前的 PASETO 版本之一,用于非对称身份验证(公钥签名)。
```
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"}'
)
```
### 基本用法:v4.local
`v4.local` 是当前的 PASETO 版本之一,用于对称认证加密。
```
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"}'
)
```
### 为 payload 和 footer 使用序列化器/反序列化器
通过使用 `serializer` 和 `deserializer`,你可以将 PASETO token 中包含的 dict 类型的 payload 和 footer 编码/解码为任意格式。
以下示例展示了 PASETO token 中的 payload 和 footer 是如何被编码/解码为 JSON 格式数据的。
当指定 dict 类型的 payload 时,可以使用 exp 参数来设置 token 的过期时间(秒)。
```
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"
```
### 使用 `Paseto` 类处理已注册声明
通过使用 `Paseto` 类,你可以更改 `exp`(token 的过期日期)的默认值、是否包含 `iat` 声明以及其他设置。
请注意,`pyseto.encode()` 和 `pyseto.decode()` 是使用默认设置创建的全局 "Paseto" 类实例的 `encode()` 和 `decode()` 的别名。
```
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 用法
[PASERK (Platform-Agnostic Serialized Keys)](https://github.com/paseto-standard/paserk) 是 PASETO 的一个扩展,提供了密钥封装和序列化功能。
### 序列化/反序列化 PASERK
如上面的示例所示,用于加密和签名的 `pyseto.Key` 可以从 PASERK 生成,也可以转换为 PASERK,如下所示:
```
import pyseto
from pyseto import Key
# pyseto.Key 可以从 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 可以从 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"
```
### 序列化 PASERK ID
`pyseto.Key` 也可以转换为 PASERK ID,如下所示:
```
import pyseto
from pyseto import Key
# pyseto.Key 可以从 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 可以从 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"
)
```
### 密钥封装
如果你带上 `wrapping_key` 调用 `to_paserk`,你将获得一个使用封装密钥封装(加密)过的 PASERK。
被封装的 PASERK 可以通过带上 `wrapping key` 调用 `from_paserk` 来解密。
以 `local-wrap.pie` 为例:
```
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"}'
)
```
以 `secret-wrap.pie` 为例:
```
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` 调用 `to_paserk`,你将获得一个使用密码封装(加密)过的 PASERK。
被封装的 PASERK 可以通过带上 `passwrod` 调用 `from_paserk` 来解密。
以 `local-pw` 为例:
```
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"}'
)
```
以 `secret-pw` 为例:
```
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"}'
)
```
### 非对称加密
目前,PySETO 支持 `v2` 和 `v4` 的非对称加密(密钥密封)。
```
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"}'
)
```
`v1` 和 `v3` 的密钥密封尚不支持。
## API 参考
请参阅[文档](https://pyseto.readthedocs.io/en/stable/api.html)。
## 测试
克隆项目后,你可以从项目根目录运行测试:
```
$ tox
```
## 安全性
如果你发现安全问题,请通过 GitHub issue 报告,并避免在我们回复之前公开披露漏洞详情。
此项目尚未经过正式审计;请根据你的风险评估来使用它。
Snyk 徽章报告的结果基于从 `uv.lock` 导出的 `requirements.txt`,仅反映依赖项扫描。
有关我们的安全策略和报告指南,请参阅 [SECURITY.md](SECURITY.md)。
### 开发环境
```
$ uv sync --frozen
$ uv run pre-commit install
```
### 依赖更新
`pyproject.toml` 中的 `[tool.uv]` 固定了 `exclude-newer = "2 days"`,因此解析时仅
考虑两天前上传的包。这为最新发布的
版本提供了一个验证窗口,然后我们才会依赖它们,并且它会自动
应用于每个 `uv` 命令 —— 无需额外的 flags。
更改依赖项后,请重新导出 `requirements.txt`,这是依赖
扫描器读取的文件:
```
$ uv add # or: uv lock --upgrade
$ uv export --format requirements.txt --all-groups --locked \
--no-hashes --no-editable --no-emit-project -o requirements.txt
```
### 扫描
```
$ uv run pip-audit
$ uv run pip-licenses
$ trivy fs --severity HIGH,CRITICAL .
```
标签:PASERK, PASETO, Python, 令牌, 加密, 密码学, 手动系统调用, 无后门, 漏洞扫描器, 逆向工具