dajiaji/pyseto

GitHub: dajiaji/pyseto

PySETO 是一个用 Python 实现的 PASETO/PASERK 库,为开发者提供比 JWT 更安全的令牌创建、编解码与密钥序列化能力。

Stars: 111 | Forks: 10

# PySETO - PASETO/PASERK 的 Python 实现 [![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://static.pigsec.cn/wp-content/uploads/repos/cas/ac/ac05029c959338349a9b7f2688ee1b56e4ede8360c567d0f63780d59e4cd0baa.svg) [![codecov](https://codecov.io/gh/dajiaji/pyseto/branch/main/graph/badge.svg?token=QN8GXEYEP3)](https://codecov.io/gh/dajiaji/pyseto) [![Known Vulnerabilities](https://snyk.io/test/github/dajiaji/pyseto/badge.svg?targetFile=requirements.txt)](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, 令牌, 加密, 密码学, 手动系统调用, 无后门, 漏洞扫描器, 逆向工具