Metadata-Version: 2.4
Name: easy_encryption_tool
Version: 3.2.1
Summary: 易加密 CLI 工具，支持 AES/SM4/ZUC/RSA/ECC/SM2 等加解密与签名验签
Home-page: https://cipherhub.cloud
Author: bowenerchen
Author-email: bowener.chen@gmail.com
License: MIT
Project-URL: Documentation, https://pypi.org/project/easy-encryption-tool/
Keywords: encryption cli tool security aes sm4 sm2 sm3 zuc hmac hash ecc rsa gmssl
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security :: Cryptography
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: click>=8.0
Requires-Dist: cryptography>=42.0
Requires-Dist: pyasn1>=0.4.8
Requires-Dist: pyyaml>=6.0
Requires-Dist: requests>=2.28
Requires-Dist: pyopenssl>=24.0
Requires-Dist: rich>=13.0
Requires-Dist: rich-click>=1.9.8
Requires-Dist: prompt-toolkit>=3.0
Provides-Extra: pqc
Requires-Dist: cryptography>=50.0; python_version >= "3.9" and extra == "pqc"
Requires-Dist: pyopenssl>=26.0; python_version >= "3.9" and extra == "pqc"
Provides-Extra: server
Requires-Dist: fastapi>=0.110; extra == "server"
Requires-Dist: uvicorn>=0.29; extra == "server"
Provides-Extra: kms
Requires-Dist: boto3>=1.26; extra == "kms"
Requires-Dist: tencentcloud-sdk-python-kms>=3.0.0; extra == "kms"
Requires-Dist: alibabacloud-kms20160120>=2.0.0; extra == "kms"
Requires-Dist: huaweicloudsdkkms>=3.1.0; extra == "kms"
Requires-Dist: google-cloud-kms>=2.0.0; extra == "kms"
Requires-Dist: hvac>=2.0.0; extra == "kms"
Requires-Dist: azure-keyvault-keys>=4.8.0; extra == "kms"
Requires-Dist: azure-identity>=1.15.0; extra == "kms"
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Requires-Dist: pytest-cov>=4.0; extra == "test"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: project-url
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# 易加密（easy_encryption_tool）

## 工具的安装

### 基础安装（仅国际算法）

以下命令安装后即可使用 AES、RSA、ECC、SHA 系列、HMAC（国际哈希）等算法：

```bash
pip install easy-encryption-tool
```

### 国密算法支持（可选）

3.0 起，国密算法（SM2、SM3、SM4、SM9、ZUC、SM3-KDF）改为直连 **GmSSL C 库**（版本 >= 3.1.1），不再依赖任何 Python 国密包。工具在运行时自动探测原生库：

- **默认候选路径**：`~/.gmssl_3.1.1_install/lib/` 下的 `libgmssl.so.3.1` / `libgmssl.3.1.dylib`（含不带版本号的软链接名），以及系统 `find_library("gmssl")` 能找到的位置；
- **显式指定**：设置环境变量 `EET_GMSSL_LIB_PATH` 指向 libgmssl 共享库文件（推荐做法，见下节环境变量表）。

自行编译 GmSSL 的方法见官方仓库 [guanzhi/GmSSL](https://github.com/guanzhi/GmSSL)；图文指南可参考 [easy-gmssl-sdk（博客站）](https://cipherhub.cloud/posts/easy-gmssl-sdk/)。

**依赖国密算法的命令**：`hash -a sm3`、`hmac -a sm3`、`sm2`、`sm4`、`sm9`、`zuc`、`kdf -a sm3`、`kdf -a pbkdf2-sm3`、`cert-parse -g`（`kdf -a hkdf` 走 cryptography，不在此列）。库缺失时执行这些命令会输出结构化错误并给出修复指引（构建 GmSSL 或设置 `EET_GMSSL_LIB_PATH`），不会静默回退。

### 环境变量

| 变量 | 作用 | 说明 |
|------|------|------|
| `EET_GMSSL_LIB_PATH` | 指定 libgmssl 共享库的文件路径 | 显式指定但加载失败时给出含路径的明确报错，**不回退**默认候选 |
| `EET_DEBUG_TIMING` | 输出命令耗时调试信息 | 仅排障用 |

```bash
# 示例：显式指定原生库后运行国密命令
export EET_GMSSL_LIB_PATH=~/.gmssl_3.1.1_install/lib/libgmssl.3.1.dylib
easy_encryption_tool sm2 --help
```

### 口令与机密数据的使用边界

- **命令行上的口令对同用户进程可见**：`-p/--password`、`-k/--key` 这类值经 argv 传入，同一台主机上能查看你进程列表的同一用户（或 root）可用 `ps` 读到明文。共享主机或高敏感环境请改用密钥文件输入，或只在独占环境中传口令。
- **`-i` 的值不做掩码**：`eet shell` 交互历史（`~/.eet_shell_history`）会自动把口令、密钥、IV、种子等敏感选项值掩成 `***`，但 `-i` 按普通数据处理、原文记录；在你自己的终端里，整条命令同样会进该 shell 的历史文件。机密明文请用文件输入（`-f`/输入文件参数），或事后清理历史。
- **密钥文件拒绝覆盖是刻意设计**：`-f` 前缀对应的公私钥文件已存在时命令直接失败，工具不提供 `--force`——旧私钥一旦被覆盖，用它加密过的数据将永久无法解开。脚本化场景请先显式移走旧文件，或换一个新的 `-f` 前缀。

### 后量子算法支持（可选）

如需使用 **后量子算法**（ML-KEM 密钥封装、ML-DSA 签名），需要 `cryptography >= 47`（ML-DSA ExternalMu 模式需 >= 49）：

```bash
# 推荐：通过 [pqc] extra 一次性安装并保证 cryptography 版本（>= 50）
pip install "easy-encryption-tool[pqc]"

# 或：单独升级 cryptography（若已安装 easy-encryption-tool）
pip install "cryptography>=47"
```

**依赖后量子的命令**：`pqc mlkem`、`pqc mldsa`。cryptography 版本过旧时，执行这些命令会提示升级；参数集（如 ML-KEM-512）是否可用取决于 cryptography 版本，`easy_encryption_tool version` 的 Dependencies 行会列出。

### Server 模式（可选，实验性）

如需以 HTTP 服务方式使用 CLI 能力，安装 `[server]` extra（FastAPI + uvicorn）：

```bash
pip install "easy-encryption-tool[server]"
```

未安装时执行 `easy_encryption_tool server` 会输出结构化错误并给出安装指引，不影响其他命令。用法见下文 [Server 模式](#server-模式http-服务) 一节。

> **实验性声明**：`server` 命令自 3.0.x 起**入口已注释停用**（`eet server` 返回 "No such command"）：并发请求下的进程内哨兵存在已知竞态（评审 R4-1），修复并稳定前不提供该命令。`server_command` 模块与 `create_app` 保留在代码库中，供测试与将来修复后重新启用。

项目地址：[easy-encryption-tool · PyPI](https://pypi.org/project/easy-encryption-tool/)

---

## 国密算法支持说明

| 算法 | 说明 | 对应命令 | 需 GmSSL 原生库 |
|------|------|----------|-----------------|
| SM2 | 国密非对称算法，对标 RSA | `sm2`（加解密、签名验签） | ✓ |
| SM3 | 国密哈希算法 | `hash -a sm3` | ✓ |
| SM4 | 国密对称算法，对标 AES | `sm4`（CBC/GCM/CTR 模式） | ✓ |
| SM9 | 国密标识密码（IBC） | `sm9`（主密钥生成、用户密钥派生、加解密、签名验签） | ✓ |
| ZUC | 祖冲之流密码 | `zuc` | ✓ |
| HKDF | RFC 5869 密钥派生（SHA-2 族，3.2.0 起默认） | `kdf -a hkdf` |  |
| SM3-KDF | SM3 密钥派生 | `kdf -a sm3` | ✓ |
| PBKDF2-HMAC-SM3 | 口令派生 | `kdf -a pbkdf2-sm3` | ✓ |
| SM2 证书 | 国密证书解析 | `cert-parse -g` | ✓ |

国际算法（AES、RSA、ECC、SHA、HMAC 等）**无需** GmSSL 原生库，仅安装 `easy-encryption-tool` 即可使用。

---

## 工具支持的命令

```python
❯ easy_encryption_tool --help

Usage: easy_encryption_tool [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  aes         aes加解密工具，默认支持 aes-cbc-256 和 aes-gcm-256
  cert-parse  解析 pem 或 der 格式的证书，支持国际算法及国密 SM2（-g）
  convert     字符串格式转换工具，支持 UTF-8、Base64、Hex 互转
  doctor      环境与可选能力体检（cryptography/PQC、GmSSL 原生库与 external-mu 能力、七家云 KMS）
  ecc         ecc签名验签和密钥交换验证工具
  envelope    信封加密：非对称密钥包裹数据密钥（DEK）
  hash        哈希摘要工具，支持 SM3、SHA256、SHA384、SHA512
  hmac        hmac消息验证码工具，支持 SM3 及国际哈希算法
  int-to-bytes 整数与大/小端十六进制字节互转
  kdf         密钥派生工具，支持 HKDF（RFC 5869，默认，无需原生库）、SM3-KDF 与 PBKDF2-HMAC-SM3（需 GmSSL 原生库）
  pqc         后量子命令组：ML-KEM 密钥封装与 ML-DSA 签名验签（需 cryptography>=47）
  random-bytes 随机字节流生成器（1-1024 字节，默认 Base64 输出）
  random-str  随机字符串生成器
  rsa         rsa加解密和签名验签工具
  server      以 HTTP 服务方式提供 CLI 能力（FastAPI，需 [server] extra；3.0.x 实验特性，入口已注释停用）
  sm2         国密 SM2 加解密和签名验签工具（多种密文/签名格式）
  sm4         国密 SM4 对称加解密工具，支持 cbc、gcm 和 ctr 模式
  sm9         国密 SM9 标识密码命令组（主密钥/用户密钥/加解密/签名验签）
  structured  结构化文件加密：JSON/YAML/INI 叶子级 AEAD 加密
  ts          时间戳转换工具（秒级，本地/UTC/毫秒互转）
  version     展示当前版本信息以及运行时信息
  zuc         ZUC 祖冲之流密码加解密工具
  install-completion  安装 Shell Tab 自动补全（bash/zsh/fish）
```

## Shell Tab 自动补全

支持对子命令、选项及选项值进行 Tab 补全，提升命令行使用效率。安装方式：

```bash
# 生成补全脚本并加载（zsh 示例）
easy_encryption_tool install-completion --shell zsh -p ~/.easy_encryption_tool_complete.sh
source ~/.easy_encryption_tool_complete.sh
```

补全能力包括：子命令补全、选项补全、固定可选值补全（如 `-A` encrypt/decrypt、`-m` cbc/gcm）、文件路径补全（如 `-i` 在指定 `-f` 时、`-o`、密钥文件路径等）。详见 [CLI 使用说明](../docs/CLI_USAGE_GUIDE.md#4-install-completion---shell-自动补全)。

## 环境与能力体检（eet doctor）

一行命令检查本机环境与全部可选能力，缺什么给什么修复命令：

```python
❯ easy_encryption_tool doctor
╭────────────────────────────────  eet doctor  ────────────────────────────────╮
│                                                                              │
│    python                ✓ ok              3.12.7                            │
│    eet                   ✓ ok              v3.0.0                            │
│                                                                              │
│    cryptography          ✓ ok              50.0.1                            │
│    pqc                   ✓ ok              available                         │
│                                            (ML-KEM-768/1024, ML-DSA-44/65/87) │
│                                            external-mu ok                   │
│    ── native libraries ──                                                    │
│    gmssl (native)        ✓ ok              GmSSL 3.1.1                       │
│                                            (/Users/cyx/.gmssl_3.1.1_in…      │
│    ML-DSA external-mu    ✓ ok              native                            │
│                                            (cryptography 50.0.1)             │
│                                                                              │
│    ── cloud KMS backends ──                                                  │
│    kms aws               ✓ ok              1.35.53 (boto3)                   │
│    kms tencentcloud      ✓ ok              3.1.87 (tencentcloud.kms.…        │
│    kms aliyun            ✓ ok              2.4.0 (alibabacloud_kms2…         │
│    kms huawei            ✓ ok              3.1.186 (huaweicloudsdkkms)       │
│    kms google            ✓ ok              3.11.0 (google.cloud.kms_v1)      │
│    kms vault             ✓ ok              2.4.0 (hvac)                      │
│    kms azure             ✗ missing         azure.keyvault.keys.crypt…        │
│    ── guides ──                                                              │
│                          azure-keyvault-keys https://pypi.org/projec…        │
╰──────────────────────────────────────────────────────────────────────────────╯
```

体检项分四组：核心运行时（python/eet）、密码栈（cryptography 及 PQC 参数集）、**原生库与能力**（GmSSL 国密依赖；ML-DSA external-mu 能力行——3.1 起走 cryptography 原生 `sign_mu`/`verify_mu`（>= 49），不再探测系统 OpenSSL）、七家云 KMS SDK。缺失项的 Fix 列给出修复方式：KMS SDK 缺失给 `pip install` 命令；GmSSL 缺失给构建指引与 `EET_GMSSL_LIB_PATH`；external-mu 缺失给 `[pqc]` extra 安装命令。表尾 **guides** 小节只为非 ok 的行列出官方与博客链接。`doctor` 默认输出表格适合直接阅读与截图排障；`--json` / `--pretty-json` 走统一输出契约，供脚本消费。

## 显示版本

缺省输出 rich 面板：版本号、功能清单、运行时环境、**依赖画像**（GmSSL 原生库版本与路径、external-mu 能力（cryptography 原生）、cryptography / rich / click、PQC 参数集、七家云 KMS SDK 安装状态）。

3.0 起 `version` 支持 `--json` / `--pretty-json`，输出与其他加密命令一致的标准输出契约，`result.dependencies` 携带完整依赖画像快照，便于脚本化采集：

```shell
❯ easy_encryption_tool version --json

{"metadata": {"operation": "version", "algorithm": "version", "input": {"type": "none", "size": 0}}, "result": {"version": "v3.2.1", "dependencies": {"gmssl_native_lib": {"available": true, "version": "GmSSL 3.1.1", "path": "/Users/cyx/.gmssl_3.1.1_install/lib/libgmssl.3.1.dylib"}, "external_mu": {"available": true, "via": "cryptography sign_mu/verify_mu", "cryptography": "50.0.1"}, "cryptography": "50.0.1", "rich": "15.0.0", "click": "8.0.4", "pqc": "available (ML-KEM-768/1024, ML-DSA-44/65/87); external-mu ok", "kms_sdks": {"kms aws": "not installed", "kms tencentcloud": "not installed", "kms aliyun": "not installed", "kms huawei": "not installed", "kms google": "not installed", "kms vault": "not installed", "kms azure": "not installed"}}}, "runtime": {"request_id": "e05f7c02-2d02-4dba-8f5b-f292cd502b6a", "timestamp": "2026-09-02T19:06:26.624378+08:00", "duration_ms": 2.784, "host": "MacM2Max-2.local", "version": "v3.2.1"}}
```

## 生成随机字符串

### 支持的参数

```python
❯ easy_encryption_tool random-str --help
Usage: main.py random-str [OPTIONS]

Options:
  -l, --length INTEGER RANGE  最小生成一个字节字符串，最大长度由系统最大整型值决定  [default: 32;
                              1<=x<=9223372036854775807]
  -o, --output-file TEXT      指定输出的文件，文件需要具有可写权限
  --help                      Show this message and exit.
```

### 直接输出到 stdout

```python
# -l指定随机字符串的长度为32字节
❯ easy_encryption_tool random-str -l 32

------ 632aebf88dfe8f93 begin@2024-04-04_15:01:23.987 ------
qBg@G%Tp((@2h81tg@9II7#0Su4`B06$
------ 632aebf88dfe8f93 took 0.049 milli-seconds to execute ------
```

### 输出到文件

```python
❯ easy_encryption_tool random-str -l 37 -o test_random

------ 71a2d32b0816349f begin@2024-04-04_15:24:22.476 ------
write to test_random success
------ 71a2d32b0816349f took 0.299 milli-seconds to execute ------

❯ cat test_random
_9@mL1`D2#NZz5m@!X7sdHKqQEowM6%o3E`bj
```

### 当指定的文件不可写时

```python
❯ easy_encryption_tool random-str -l 37 -o test_random

------ 0e4094ce6a4fe22c begin@2024-04-04_15:25:49.125 ------
try write to test_random failed
------ 0e4094ce6a4fe22c took 0.030 milli-seconds to execute ------

```

## AES对称加密算法

### 支持的命令参数

```python
❯ easy_encryption_tool aes --help
 Usage: eet aes [OPTIONS]                                                       
                                                                                
 AES encrypt/decrypt. Encrypt: each missing key/IV piece (-k/-v or b64          
 variants) is generated from full-entropy CSPRNG bytes and output as base64.    
 Decrypt: requires explicit key/IV (--key-b64/--iv-b64 or -k/-v). Input:        
 plaintext (-i), base64 (-e), or file (-f).                                     
                                                                                
╭─ Options ────────────────────────────────────────────────────────────────────╮
│    --mode               -m  [cbc|gcm]          AES mode: cbc or gcm          │
│                                                [default: cbc]                │
│    --key                -k  TEXT               Key string (normalized to 32  │
│                                                bytes / 256-bit,              │
│                                                CipherHUB-compatible).        │
│                                                Encrypt: a missing key is     │
│                                                randomly generated (base64 in │
│                                                output); decrypt requires it. │
│                                                The CipherHUB web preset key  │
│                                                applies only when passed      │
│                                                explicitly                    │
│    --iv-nonce           -v  TEXT               CBC uses IV (16 bytes); GCM   │
│                                                uses nonce (12 bytes).        │
│                                                Encrypt: a missing IV/nonce   │
│                                                is randomly generated (base64 │
│                                                in output); decrypt requires  │
│                                                it                            │
│    --aad                    TEXT               GCM additional authenticated  │
│                                                data (AAD), default:          │
│                                                "密码学人 CipherHUB 默认 AAD  │
│                                                数据" [default: 密码学人      │
│                                                CipherHUB 默认 AAD 数据]      │
│    --random-key-iv      -r                     Force both key and IV/nonce   │
│                                                random (encrypt only). On     │
│                                                encrypt any missing           │
│                                                -k/-v/--key-b64/--iv-b64      │
│                                                piece is randomly generated   │
│                                                anyway and shown as base64;   │
│                                                use --key-b64/--iv-b64 for    │
│                                                decrypt. Mutually exclusive   │
│                                                with                          │
│                                                -k/-v/--key-b64/--iv-b64.     │
│    --key-b64                TEXT               Base64 key bytes (must decode │
│                                                to 32 bytes). Use for decrypt │
│                                                with key from encrypt -r      │
│                                                output. Mutually exclusive    │
│                                                with -k                       │
│    --iv-b64                 TEXT               Base64 IV/nonce bytes (CBC    │
│                                                16, GCM 12). Use for decrypt  │
│                                                with IV from encrypt -r       │
│                                                output. Mutually exclusive    │
│                                                with -v                       │
│    --action             -A  [encrypt|decrypt]  Action: encrypt or decrypt    │
│                                                [default: encrypt]            │
│ *  --input-data         -i  TEXT               Input data. Encrypt:          │
│                                                text/base64/file. Decrypt:    │
│                                                base64/file [required]        │
│    --is-base64-encoded  -e                     Interpret -i as base64 data   │
│                                                (mutually exclusive with -f)  │
│    --is-a-file          -f                     Interpret -i as file path     │
│                                                (mutually exclusive with -e)  │
│    --input-limit        -l  INTEGER            Max non-file input size in MB │
│                                                [default: 1]                  │
│    --output-file        -o  TEXT               Output file path (required    │
│                                                with -f)                      │
│    --no-force                                  Do not overwrite an existing  │
│                                                output file                   │
│    --gcm-pad                                   [GCM only] Enable PKCS#7      │
│                                                padding before encrypt and    │
│                                                unpadding after decrypt.      │
│                                                Default is no padding;        │
│                                                encrypt and decrypt must both │
│                                                use or both omit --gcm-pad.   │
│                                                File decrypt with --gcm-pad   │
│                                                loads entire file into        │
│                                                memory; large files may cause │
│                                                OOM.                          │
│    --pretty-json                               Output colored pretty JSON    │
│                                                (default)                     │
│    --json                                      Output single-line JSON (use  │
│                                                with jq for field extraction) │
│    --help                                      Show this message and exit.   │
╰──────────────────────────────────────────────────────────────────────────────╯
```

### 关于密钥、IV和模式的预设

-   加密模式：仅支持 CBC 模式和 GCM 模式
-   **填充规则**：CBC 模式始终按 PKCS#7 填充；GCM 模式**默认不填充**（与 CipherHUB stream_cipher 兼容），若需填充可加 `--gcm-pad`，加密与解密需一致使用
-   密钥：长度必须恰好 32 字节（256 位）。加密时完全不给则随机生成并以 base64 输出；**给了但不足 32 字节会被拒绝**（2.8.1 起不再随机补齐——补齐过的密钥无法复现，密文将永远解不开）；超过 32 字节会截断并警告
-   IV：CBC 模式下时 IV 长度默认 16 字节，GCM 模式下 Nonce 长度默认 12 字节（其中 4 字节预留作为计数器，由算法自行处理）；与密钥同理——不给则加密时随机生成，给短了拒绝，给长了截断并警告

### 关于输入数据的预设

加密行为支持三种数据输入方式：

-   字符串如：hello,world
-   Base64 编码的字节流如：aGVsbG8sd29ybGQK（生成的 shell 命令：echo "hello,world"|base64）
-   文件名路径：~/data/test_plain.txt

解密行为支持两种数据输入方式：

-   Base64 编码的字节流如：/hEP3J5KHZgNnCeBD/W5MQ==
-   文件名路径：~/data/test_cipher.bin

### 指定密钥和 IV

#### 使用默认的密钥

```shell
# 加密hello,world，密钥和 iv 均为默认数据
❯ easy_encryption_tool aes -m cbc -a encrypt -i hello,world

------ 15ec713c1b8c0ef3 begin@2024-04-04_15:29:25.203 ------
plain size:11
key:kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
iv:vvvvvvvvvvvvvvvv
cipher size:16
cipher:PcgHm88aPtUjwVx+SDvMqw==
auth_tag_size:0
auth_tag:
------ 15ec713c1b8c0ef3 took 26.874 milli-seconds to execute ------

# 解密hello,world
❯ easy_encryption_tool aes -m cbc -a decrypt -i PcgHm88aPtUjwVx+SDvMqw== -e

------ fb11b7f46716698e begin@2024-04-04_15:29:40.648 ------
cipher size:16
plain size:11
str plain:hello,world
------ fb11b7f46716698e took 13.754 milli-seconds to execute ------
```

#### 使用随机生成的密钥

```python
# 加密 -r 表示随机生成密钥和 IV
❯ easy_encryption_tool aes -m cbc -a encrypt -i hello,world -r

------ d39dbe0c997a868b begin@2024-04-04_15:29:54.358 ------
plain size:11
key:Ta9M^p)+L1+_L^26!Xmcs6AR2^3p_5FY
iv:9*H`JW(dzpi5HBd0
cipher size:16
cipher:h7lMpOimKxO0zr7AMVsI9w==
auth_tag_size:0
auth_tag:
------ d39dbe0c997a868b took 14.258 milli-seconds to execute ------

# 解密
# -k 和 -v 的值使用引号是为了预防里面带有特殊 shell 命令的字符比如‘&’、‘!’等等
❯ easy_encryption_tool aes -m cbc -a decrypt -i h7lMpOimKxO0zr7AMVsI9w== -e -k 'Ta9M^p)+L1+_L^26!Xmcs6AR2^3p_5FY' -v '9*H`JW(dzpi5HBd0'

------ 1332e834884e2b0e begin@2024-04-04_15:31:06.666 ------
cipher size:16
plain size:11
str plain:hello,world
------ 1332e834884e2b0e took 15.691 milli-seconds to execute ------
```

#### 使用指定的密钥

##### 密钥或 iv 长度不够时会自动填充

```python
# 加密，此时 key(1234) 和 iv(1234) 长度都不足
❯ easy_encryption_tool aes -m cbc -a encrypt -i hello,world -k 1234 -v 4321

------ c5abaa3af64a5f6c begin@2024-04-04_15:31:34.231 ------
plain size:11
key:1234g6Z0GE$Z@ybb^IIb3FN5Ux%BE=00
iv:4321nJ4j*Nud(yH4
cipher size:16
cipher:dHJKRtSi8KsCe6ZFltF0kA==
auth_tag_size:0
auth_tag:
------ c5abaa3af64a5f6c took 14.648 milli-seconds to execute ------
                
# 解密
❯ easy_encryption_tool aes -m cbc -a decrypt -i dHJKRtSi8KsCe6ZFltF0kA== -e -k '1234g6Z0GE$Z@ybb^IIb3FN5Ux%BE=00' -v '4321nJ4j*Nud(yH4'

------ 7c2018bd08e58a63 begin@2024-04-04_15:32:16.014 ------
cipher size:16
plain size:11
str plain:hello,world
------ 7c2018bd08e58a63 took 14.343 milli-seconds to execute ------
```

##### 密钥或iv超长时会自动截取

```python
# 加密，此时密钥和 iv 的长度都超长
❯ easy_encryption_tool aes -m cbc -a encrypt -i hello,world -k 12345678901234567890123456789012abcde -v 1234567890123456abcde

------ 8ff4bd52df0a0865 begin@2024-04-04_15:32:31.104 ------
plain size:11
key:12345678901234567890123456789012
iv:1234567890123456
cipher size:16
cipher:wOXlD3Ie7xiQh81aR8N1tQ==
auth_tag_size:0
auth_tag:
------ 8ff4bd52df0a0865 took 13.849 milli-seconds to execute ------

# 解密
❯ easy_encryption_tool aes -m cbc -a decrypt -i wOXlD3Ie7xiQh81aR8N1tQ== -e -k 12345678901234567890123456789012 -v 1234567890123456

------ 50ea907cc74207ad begin@2024-04-04_15:32:46.937 ------
cipher size:16
plain size:11
str plain:hello,world
------ 50ea907cc74207ad took 13.690 milli-seconds to execute ------
```

### 指定明文

#### 输入字符串作为明文

```python
# 加密
❯ easy_encryption_tool aes -m cbc -a encrypt -i hello,world

------ e6dc33dc9ca747d0 begin@2024-04-04_15:33:05.505 ------
plain size:11
key:kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
iv:vvvvvvvvvvvvvvvv
cipher size:16
cipher:PcgHm88aPtUjwVx+SDvMqw==
auth_tag_size:0
auth_tag:
------ e6dc33dc9ca747d0 took 14.098 milli-seconds to execute ------
```

#### 输入base64编码的字节流作为明文

```python
# 加密 -e 表明输入的数据经过了 base64 编码，加密或解密时需要先将数据做 base64 解码
❯ easy_encryption_tool aes -m cbc -a encrypt -i 9H8InkmnUjgVHC8elQxThUSmzkO0tuGlP0Si4X1kmoK7azOIDoFnt8dXjeWNGb+dc7qiEBPi+jymax4i+24KBQ== -e

------ fc5b00c0a79ff88e begin@2024-04-04_15:33:17.585 ------
plain size:64
key:kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
iv:vvvvvvvvvvvvvvvv
cipher size:80
cipher:ZHq7uJQjkx/2Bm5ZmrcuS/5c/s/qayDVcuWZmvsTle1RAUKyv0dvGhOVYEINmL35eSMVoT3Bx/M6lU9NGCuiM5OxyJ2VcuB30dp8GVZg0oQ=
auth_tag_size:0
auth_tag:
------ fc5b00c0a79ff88e took 14.382 milli-seconds to execute ------
```

#### 输入文件作为明文

```python
# 加密
❯ easy_encryption_tool aes -m cbc -a encrypt -i ./test_data/test_plain.txt -f -o ./tmp_cipher.bin

------ 1d5fb25a63f1ed4d begin@2024-04-04_15:33:57.461 ------
input file size:64
cipher size:80
key:kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
iv:vvvvvvvvvvvvvvvv
auth_tag_size:0
auth_tag:
------ 1d5fb25a63f1ed4d took 14.859 milli-seconds to execute ------

# 查看文件大小，密文文件比明文文件多了16 字节，这是因为明文的最后一个 block 会做 PKCS#7 数据填充
❯ cat ./test_data/test_plain.txt
123456789012345612345678901234561234567890123456123456789012345
❯ ll ./test_data/test_plain.txt
-rw-r--r--  1 xxxx  staff  64 Apr  2 21:06 ./test_data/test_plain.txt
❯ ll ./tmp_cipher.bin
-rw-r--r--  1 xxxx  staff  80 Apr  4 15:33 ./tmp_cipher.bin
```

### 指定密文

#### 输入base64编码的字节流作为密文

##### 如果解密出来的明文直接可以以字符串方式打印

```python
# 明文本身为 hello,world
❯ easy_encryption_tool aes -m cbc -a decrypt -i PcgHm88aPtUjwVx+SDvMqw== -e

------ 2b6a86223a0ba102 begin@2024-04-04_15:35:26.995 ------
cipher size:16
plain size:11
str plain:hello,world
------ 2b6a86223a0ba102 took 13.676 milli-seconds to execute ------
```

##### 如果解密出来的密文不能以字符串方式打印

```python
# 明文本身是字节流
❯ easy_encryption_tool aes -m cbc -a decrypt -i ZHq7uJQjkx/2Bm5ZmrcuS/5c/s/qayDVcuWZmvsTle1RAUKyv0dvGhOVYEINmL35eSMVoT3Bx/M6lU9NGCuiM5OxyJ2VcuB30dp8GVZg0oQ= -e

------ d399aa241aa6b691 begin@2024-04-04_15:35:39.781 ------
cipher size:80
plain size:64
b64 encoded plain:9H8InkmnUjgVHC8elQxThUSmzkO0tuGlP0Si4X1kmoK7azOIDoFnt8dXjeWNGb+dc7qiEBPi+jymax4i+24KBQ==
------ d399aa241aa6b691 took 13.869 milli-seconds to execute ------
```

#### 输入文件作为密文

```python
❯ easy_encryption_tool aes -m cbc -a decrypt -i ./tmp_cipher.bin -f -o ./tmp_plain.txt

------ 1f27fb444d1139b2 begin@2024-04-04_15:36:03.267 ------
input file size:80
decrypt ./tmp_cipher.bin success
write to ./tmp_plain.txt
plain size:64
------ 1f27fb444d1139b2 took 14.259 milli-seconds to execute ------

# 文件大小一致、内容一致
❯ ll ./tmp_plain.txt ./test_data/test_plain.txt
-rw-r--r--  1 xxxx  staff  64 Apr  2 21:06 ./test_data/test_plain.txt
-rw-r--r--  1 xxxx  staff  64 Apr  3 10:58 ./tmp_plain.txt
❯ cat tmp_plain.txt ./test_data/test_plain.txt
123456789012345612345678901234561234567890123456123456789012345
123456789012345612345678901234561234567890123456123456789012345
```

### 使用GCM模式

**GCM 填充说明**：GCM 模式默认**不对**明文做 PKCS#7 填充，直接加密原始数据，与 CipherHUB stream_cipher 兼容。若需 PKCS#7 填充，请加 `--gcm-pad`，加密与解密必须一致使用。

#### 代码层面的预设

代码中，对于加密的明文默认使用固定的上下文数据作为验证数据

```python
        if mode == aes_gcm_mode:
            self.__auth_data = json.dumps({
                'mode': mode, # 值为 gcm
                'obj': 'aes_operator',
            }).encode(encoding = 'utf-8')
            if action == aes_encrypt_action:
                self.__aes_gcm_obj = Cipher(algorithms.AES(self.__key), modes.GCM(self.__iv), backend = default_backend())
                self.__aes_gcm_enc_op = self.__aes_gcm_obj.encryptor()
                self.__aes_gcm_enc_op.authenticate_additional_data(self.__auth_data)
```

#### 对字符串做加解密

```python
# gcm模式加密
❯ easy_encryption_tool aes -m gcm -a encrypt -i hello,world

------ b8e914a4634acde7 begin@2024-04-04_15:36:39.558 ------
plain size:11
key:kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
iv:vvvvvvvvvvvv
cipher size:16
cipher:TajM7IwxIZIoqHkU87dY7w==
auth_tag_size:16
auth_tag:df8z3ccRyGOQTluw26dIlA==
------ b8e914a4634acde7 took 14.280 milli-seconds to execute ------
```

#### 对 base64 编码的字节流做加解密

```python
# 加密
❯ easy_encryption_tool aes -m gcm -a encrypt -i 9H8InkmnUjgVHC8elQxThUSmzkO0tuGlP0Si4X1kmoK7azOIDoFnt8dXjeWNGb+dc7qiEBPi+jymax4i+24KBQ== -e

------ 7781b5bffdcef12b begin@2024-04-04_15:37:05.562 ------
plain size:64
key:kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
iv:vvvvvvvvvvvv
cipher size:80
cipher:0bKoHqq6BMVP2DIPY74Ob2tGi69gVzHSZREJT3DAeCsVU52ykLcfKZIq/GD2PEkCwLLE8o37nvPK9t/pr4LStVy5unAN/EVllIvvopq2pis=
auth_tag_size:16
auth_tag:B1Jp0FuxyNXAOVAvj9S+Ow==
------ 7781b5bffdcef12b took 13.915 milli-seconds to execute ------


# 解密
❯ easy_encryption_tool aes -m gcm -a decrypt -i 0bKoHqq6BMVP2DIPY74Ob2tGi69gVzHSZREJT3DAeCsVU52ykLcfKZIq/GD2PEkCwLLE8o37nvPK9t/pr4LStVy5unAN/EVllIvvopq2pis= -e -t B1Jp0FuxyNXAOVAvj9S+Ow==

------ 5bcc82c4235dcde4 begin@2024-04-04_15:37:17.397 ------
cipher size:80
plain size:64
b64 encoded plain:9H8InkmnUjgVHC8elQxThUSmzkO0tuGlP0Si4X1kmoK7azOIDoFnt8dXjeWNGb+dc7qiEBPi+jymax4i+24KBQ==
------ 5bcc82c4235dcde4 took 13.844 milli-seconds to execute ------
```

#### 对文件做加解密

```python
# 加密
❯ easy_encryption_tool aes -m gcm -a encrypt -i ./test_data/test_plain.txt -f -o ./tmp_gcm_cipher.bin

------ 0c4605fe37eb7e4b begin@2024-04-04_15:37:45.621 ------
input file size:64
cipher size:80
key:kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
iv:vvvvvvvvvvvv
auth_tag_size:16
auth_tag:krJchuyaDRYHnu5tsy8UzA==
------ 0c4605fe37eb7e4b took 14.347 milli-seconds to execute ------

# 解密
❯ easy_encryption_tool aes -m gcm -a decrypt -i ./tmp_gcm_cipher.bin -f -o tmp_gcm_plain.txt -t krJchuyaDRYHnu5tsy8UzA==

------ d181cab086ebaeaa begin@2024-04-04_15:38:00.709 ------
input file size:80
decrypt ./tmp_gcm_cipher.bin success
write to tmp_gcm_plain.txt
plain size:64
------ d181cab086ebaeaa took 14.397 milli-seconds to execute ------
```

#### tag值对解密很重要

```python
# gcm模式正常解密
❯ easy_encryption_tool aes -m gcm -a decrypt -i TajM7IwxIZIoqHkU87dY7w== -e -t df8z3ccRyGOQTluw26dIlA==

------ 86699527d1227e39 begin@2024-04-04_15:38:22.322 ------
cipher size:16
plain size:11
str plain:hello,world
------ 86699527d1227e39 took 13.987 milli-seconds to execute ------

# 不传 gcm tag 会报错
❯ easy_encryption_tool aes -m gcm -a decrypt -i TajM7IwxIZIoqHkU87dY7w== -e

------ 11c1531f0fd5b7a8 begin@2024-04-04_15:38:32.957 ------
expected a gcm tag(16 Bytes)
------ 11c1531f0fd5b7a8 took 0.030 milli-seconds to execute ------

# 传错误的 tag 会解密失败
❯ easy_encryption_tool aes -m gcm -a decrypt -i TajM7IwxIZIoqHkU87dY7w== -e -t H7n7OzKgQyHL86zbnQ0r+g==

------ 90580b5c3649a1ba begin@2024-04-04_15:38:46.823 ------
decrypt TajM7IwxIZIoqHkU87dY7w== failed:
------ 90580b5c3649a1ba took 14.030 milli-seconds to execute ------

```

### 常用的参数合法性检查

#### -m 模式参数

```python
easy_encryption_tool aes -m abc -a encrypt -i 1234
Usage: main.py aes [OPTIONS]
Try 'main.py aes --help' for help.

Error: Invalid value for '-m' / '--mode': 'abc' is not one of 'cbc', 'gcm'.
```

#### -a 动作参数

```python
easy_encryption_tool aes -m cbc -a abc -i 1234
Usage: main.py aes [OPTIONS]
Try 'main.py aes --help' for help.

Error: Invalid value for '-a' / '--action': 'abc' is not one of 'encrypt', 'decrypt'.
```

#### -i 输入参数

##### 字符串超限

```python
# 这里设置最大限制为0MBytes，也就是不允许加密，这里是故意预留的
❯ easy_encryption_tool aes -m cbc -a encrypt -i 1234 -l 0

------ 5ce766f36cc28968 begin@2024-04-04_15:39:42.675 ------
the data exceeds the maximum bytes limit, limited to:0Bytes, now:4Bytes
------ 5ce766f36cc28968 took 0.023 milli-seconds to execute ------
```

##### 非法的base64编码数据

```python
# 任意构造的字符串
❯ easy_encryption_tool aes -m cbc -a encrypt -i qwert -e

------ 4844fa0e0939482d begin@2024-04-04_15:39:53.597 ------
invalid b64 encoded data:qwert
------ 4844fa0e0939482d took 0.044 milli-seconds to execute ------

# base64数据缺少字符（正确的是：ZUD3MJT3ohiimrryNW7jBw==）
❯ easy_encryption_tool aes -m cbc -a encrypt -i ZUD3MJT3ohiimrryNW7jBw -e

------ 22301b388db43f9d begin@2024-04-04_15:40:05.092 ------
invalid b64 encoded data:ZUD3MJT3ohiimrryNW7jBw
------ 22301b388db43f9d took 0.036 milli-seconds to execute ------
```

##### 文件不可读

```python
# 创建文件并设置为只可root读
sudo touch test_plain
sudo chmod 400 test_plain
# 查看文件
ll test_plain
-r--------  1 root  staff  0 Apr  3 11:29 test_plain

# 使用其他用户运行命令访问
easy_encryption_tool aes -m cbc -a encrypt -i test_plain -f
test_plain may not exist or may be unreadable
------ aes_command took 0.076 milli-seconds to execute ------
```

##### 文件不可写

```python
# 文件写权限检查失败
easy_encryption_tool aes -m cbc -a encrypt -i  tmp_gcm_plain.txt -f -o test_plain

tmp_gcm_plain.txt opened in mode rb success
test_plain may not exist or may not writable
tmp_gcm_plain.txt closed success
------ aes_command took 0.126 milli-seconds to execute ------
```

##### -e 与 -f 参数互斥

```python
❯ easy_encryption_tool aes -m cbc -a encrypt -i test_plain -f -e

------ 75998f7a4a1364f6 begin@2024-04-04_15:40:30.038 ------
the input data cannot be used as both a file and base64 encoded data
------ 75998f7a4a1364f6 took 0.026 milli-seconds to execute ------
```

##### 对文件加密或解密时，必须指定输出的文件名

```python
# 加密不指定输出文件
❯ easy_encryption_tool aes -m gcm -a encrypt -i ./test_data/test_plain.txt -f

------ 3564874090cf12d5 begin@2024-04-04_15:40:55.522 ------
need a output file specified and writable
------ 3564874090cf12d5 took 0.074 milli-seconds to execute ------

# 解密不指定输出文件
❯ easy_encryption_tool aes -m gcm -a decrypt -i ./test_data/test_plain.txt -f -t df8z3ccRyGOQTluw26dIlA==

------ c3dee26a5649a077 begin@2024-04-04_15:41:07.541 ------
need a output file specified and writable
------ c3dee26a5649a077 took 0.084 milli-seconds to execute ------
```



## HMAC验证码

### 支持的命令参数

```python
❯ easy_encryption_tool hmac --help
                                                                                
 Usage: eet hmac [OPTIONS]                                                      
                                                                                
╭─ Options ────────────────────────────────────────────────────────────────────╮
│ *  --input-data         -i  TEXT                     Input data: text,       │
│                                                      base64, or file path    │
│                                                      [required]              │
│    --is-base64-encoded  -e                           Interpret -i as base64  │
│                                                      data (mutually          │
│                                                      exclusive with -f)      │
│    --is-a-file          -f                           Interpret -i as file    │
│                                                      path (mutually          │
│                                                      exclusive with -e)      │
│    --hash-alg           -a  [sha224|sha256|sha384|s  HMAC digest algorithm   │
│                             ha512|sha3_224|sha3_256  [default: sha256]       │
│                             |sha3_384|sha3_512|sm3]                          │
│    --key                -k  TEXT                     Key string. A missing   │
│                                                      key is randomly         │
│                                                      generated (length       │
│                                                      matches the hash digest │
│                                                      size, base64 in         │
│                                                      output); the CipherHUB  │
│                                                      web preset key applies  │
│                                                      only when passed        │
│                                                      explicitly              │
│    --random-key         -r                           Auto-generate random    │
│                                                      key (length matches     │
│                                                      digest size). Default   │
│                                                      when -k/--key-b64 not   │
│                                                      given. Mutually         │
│                                                      exclusive with          │
│                                                      -k/--key-b64.           │
│    --key-b64                TEXT                     Base64 key bytes (use   │
│                                                      for HMAC verify with    │
│                                                      key from random-key     │
│                                                      output). Mutually       │
│                                                      exclusive with -k       │
│    --input-limit        -l  INTEGER                  Max non-file input size │
│                                                      in MB [default: 1]      │
│    --pretty-json                                     Output colored pretty   │
│                                                      JSON (default)          │
│    --json                                            Output single-line JSON │
│                                                      (use with jq for field  │
│                                                      extraction)             │
│    --help                                            Show this message and   │
│                                                      exit.                   │
╰──────────────────────────────────────────────────────────────────────────────╯
```

### 关于输入数据和密钥的预设

-   输入数据支持三种方式：字符串明文、base64 编码的字节流（`-e`）、文件路径（`-f`）
-   **密钥没有默认值**：不给 `-k` 时自动生成随机密钥（长度对齐所选哈希算法的摘要长度），以 base64 回显在输出的 `result.key`，供验证方复用；显式指定 `-k` 时输出不回显密钥
-   要与 CipherHUB 网页工具的默认密钥对齐，需**显式传入**网页端默认密钥 `01234567890123456789012345678901`（CLI 不会在不给 `-k` 时使用它）
-   SM3 需 GmSSL 原生库，且密钥长度限制为 16-64 字节

### 指定密钥

#### 不指定密钥（自动随机）

```python
❯ easy_encryption_tool hmac -i hello,world

{
  "metadata": {
    "operation": "hmac",
    "algorithm": "sha256",
    "input": {
      "type": "text",
      "size": 11
    },
    "parameters": {
      "digest_size": 32,
      "key_size": 32
    }
  },
  "result": {
    "hmac": "493412f4bd78348692e9ab210427cb1c7b92b3e0125b268a67d05ed15570e9b3",
    "key": "UKAqp1XGjkPO+h/qAoogcxxSVHrkb/UOyVOhD8j8Dqk=",
    "formats": {
      "hmac": "hex",
      "key": "base64"
    }
  },
  "runtime": {
    "request_id": "86c4917d-53bf-48ff-8f57-ea21c4d8e43f",
    "timestamp": "2026-09-01T20:43:29.963649+08:00",
    "duration_ms": 0.027,
    "host": "MacM2Max-2.local",
    "version": "v3.0.1"
  }
}
```

`result.key` 是本次随机生成的密钥（base64），验证同一份数据时用 `--key-b64` 传入即可复现。

#### 显式指定密钥

```python
❯ easy_encryption_tool hmac -i hello,world -k 01234567890123456789012345678901

{
  "metadata": {
    "operation": "hmac",
    "algorithm": "sha256",
    "input": {
      "type": "text",
      "size": 11
    },
    "parameters": {
      "digest_size": 32,
      "key_size": 32
    }
  },
  "result": {
    "hmac": "a5e8ea8f0fbaf97fbc08841be49271b52b8c2ddb1c521e14b10608db68205220",
    "formats": {
      "hmac": "hex"
    }
  },
  "runtime": {
    "request_id": "3001aa9d-7a2c-4f91-83c1-affeea182d49",
    "timestamp": "2026-09-01T20:43:30.607722+08:00",
    "duration_ms": 0.032,
    "host": "MacM2Max-2.local",
    "version": "v3.0.1"
  }
}
```

显式给 `-k` 时密钥不回显，只输出 HMAC 值。

#### 强制随机密钥

```python
❯ easy_encryption_tool hmac -i hello,world -r

{
  "metadata": {
    "operation": "hmac",
    "algorithm": "sha256",
    "input": {
      "type": "text",
      "size": 11
    },
    "parameters": {
      "digest_size": 32,
      "key_size": 32
    }
  },
  "result": {
    "hmac": "35027f23283ebc4a67b03fd05c5609deacc2cd2565ddc36ed550e39a00c04bd3",
    "key": "5z+2SDM6PstT65SY1I/pCJit3ht3sDwCVDzwgBpMhi8=",
    "formats": {
      "hmac": "hex",
      "key": "base64"
    }
  },
  "runtime": {
    "request_id": "3e0723c0-d67d-445b-9324-7eadbc98c392",
    "timestamp": "2026-09-01T20:43:30.281768+08:00",
    "duration_ms": 0.029,
    "host": "MacM2Max-2.local",
    "version": "v3.0.1"
  }
}
```

`-r` 与 `-k`/`--key-b64` 互斥；不给任何密钥选项时默认走随机（同上文第一种）。

### 指定输入

#### 输入字符串

```python
❯ easy_encryption_tool hmac -i hello,world

{
  "metadata": {
    "operation": "hmac",
    "algorithm": "sha256",
    "input": {
      "type": "text",
      "size": 11
    },
    "parameters": {
      "digest_size": 32,
      "key_size": 32
    }
  },
  "result": {
    "hmac": "ebeb118a5fc3115c9485573f92ef7e42a33834164ea957d874989db9bd53fce5",
    "key": "SfLNkGNbfxngQdeA0UJE5W7zZB6cij1EerRo8quhWpc=",
    "formats": {
      "hmac": "hex",
      "key": "base64"
    }
  },
  "runtime": {
    "request_id": "97703ae4-b1c6-4504-9065-8b485e1d0223",
    "timestamp": "2026-09-01T20:45:50.508979+08:00",
    "duration_ms": 0.029,
    "host": "MacM2Max-2.local",
    "version": "v3.0.1"
  }
}
```

#### 输入 base64 编码的字节流

```python
❯ easy_encryption_tool hmac -i aGVsbG8sd29ybGQ= -e

{
  "metadata": {
    "operation": "hmac",
    "algorithm": "sha256",
    "input": {
      "type": "binary",
      "size": 11
    },
    "parameters": {
      "digest_size": 32,
      "key_size": 32
    }
  },
  "result": {
    "hmac": "1ff87ae95b2da86edade3f51fdb3f5eb777eaa84e3edbee6acf07861f2efdd5c",
    "key": "2fbc0j3mslVyQHNzNk/fk+K+iB9L/4Y0tiaAnnQFQoc=",
    "formats": {
      "hmac": "hex",
      "key": "base64"
    }
  },
  "runtime": {
    "request_id": "2946dc66-3945-4a6b-9e22-e5f704878c4a",
    "timestamp": "2026-09-01T20:43:31.590283+08:00",
    "duration_ms": 0.03,
    "host": "MacM2Max-2.local",
    "version": "v3.0.1"
  }
}
```

#### 输入文件

```python
❯ easy_encryption_tool hmac -i /tmp/eet_doc_plain.txt -f

{
  "metadata": {
    "operation": "hmac",
    "algorithm": "sha256",
    "input": {
      "type": "file",
      "size": 47
    },
    "parameters": {
      "digest_size": 32,
      "key_size": 32
    }
  },
  "result": {
    "hmac": "93eb116fa254e9729d1901cacc852ff926f53a51a059df7aa901492ebd4f3050",
    "key": "4iJ7Wlv/A/SrDP/vypJPicbA4jMljZFatjJxzSCUdco=",
    "formats": {
      "hmac": "hex",
      "key": "base64"
    }
  },
  "runtime": {
    "request_id": "e17cfec1-6d9e-46cd-8db5-513a9efbebf9",
    "timestamp": "2026-09-01T20:45:11.403918+08:00",
    "duration_ms": 0.249,
    "host": "MacM2Max-2.local",
    "version": "v3.0.1"
  }
}
```

### 指定哈希算法

```bash
# 支持的哈希列表：
# [sha224 | sha256 | sha384 | sha512 | sha3_224 | sha3_256 | sha3_384 | sha3_512 | sm3]

# 使用 sha512
❯ easy_encryption_tool hmac -i /tmp/eet_doc_plain.txt -f -a sha512

{
  "metadata": {
    "operation": "hmac",
    "algorithm": "sha512",
    "input": {
      "type": "file",
      "size": 47
    },
    "parameters": {
      "digest_size": 64,
      "key_size": 64
    }
  },
  "result": {
    "hmac": "8909cf54de94efc9961b967604bbad4bf28a5943fe41931724a5c378065c829490dcdf3a516a82ed1a075aec9ed8a9a4dcbe3e682a6fbd7c7a911c4c0d967f1f",
    "key": "zFJXjCegRtUvA9PfQ7clS6nwuVwvlCgCjtUpcuBPM6cM/xOudgOGbTg+R44bctQGSTmX61tzosY3pNNmswe3qQ==",
    "formats": {
      "hmac": "hex",
      "key": "base64"
    }
  },
  "runtime": {
    "request_id": "fabcf24e-43dd-4397-8d6d-93f168291063",
    "timestamp": "2026-09-01T20:43:30.938513+08:00",
    "duration_ms": 0.107,
    "host": "MacM2Max-2.local",
    "version": "v3.0.1"
  }
}

# 使用 sha3_256
❯ easy_encryption_tool hmac -i /tmp/eet_doc_plain.txt -f -a sha3_256

{
  "metadata": {
    "operation": "hmac",
    "algorithm": "sha3_256",
    "input": {
      "type": "file",
      "size": 47
    },
    "parameters": {
      "digest_size": 32,
      "key_size": 32
    }
  },
  "result": {
    "hmac": "76478f7367d32b2db8d7d2a0dcf04c01c609a3f85257808071c95f85017ce883",
    "key": "9Lt+8z2nqOLclPJIyUmWDmM5TXV0O9gwEYoKN3Qk1Hs=",
    "formats": {
      "hmac": "hex",
      "key": "base64"
    }
  },
  "runtime": {
    "request_id": "25cbedf7-7dc3-429f-9a3c-48a4d860425b",
    "timestamp": "2026-09-01T20:43:31.259750+08:00",
    "duration_ms": 0.102,
    "host": "MacM2Max-2.local",
    "version": "v3.0.1"
  }
}
```



## RSA非对称密钥

### 支持的命令

```python
❯ easy_encryption_tool rsa --help
Usage: main.py rsa [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  decrypt
  encrypt
  generate
  sign
  verify
```

### 生成密钥对

#### 支持的参数

```python
❯ easy_encryption_tool rsa generate --help
Usage: main.py rsa generate [OPTIONS]

Options:
  -s, --size [2048|3072|4096]  密钥位数  [default: 2048]
  -e, --encoding [pem|der]     密钥格式  [default: pem]
  -f, --file-name TEXT         输出密钥对的文件名前缀，最终写入数据时会创建文件并加上文件名后缀  [default:
                               demo; required]
  -p, --password TEXT          私钥密码，使用私钥时需要输入正确的密码
  -r, --random-password        是否生成私钥的随机密码，如果带上 -r 标识，则随机生成32字节的密码
  --help                       Show this message and exit.
```

#### 默认生成

``` python
# 密钥长度2048位，私钥不带密码
❯ easy_encryption_tool rsa generate -f test

------ 6b89fd023be2d70e begin@2024-04-04_15:48:44.313 ------
generate test_rsa_public.pem/test_rsa_private.pem success
------ 6b89fd023be2d70e took 134.487 milli-seconds to execute ------
```

#### 指定长度且指定密码

```python
# pem格式密钥，私钥不带密码
❯ easy_encryption_tool rsa generate -f test_no_pwd_pem -s 4096 -e pem

------ 7d68ecefd4536a1c begin@2024-04-04_15:50:00.393 ------
generate test_no_pwd_pem_rsa_public.pem/test_no_pwd_pem_rsa_private.pem success
------ 7d68ecefd4536a1c took 560.056 milli-seconds to execute ------

# pem格式密钥，私钥带密码
❯ easy_encryption_tool rsa generate -f test_pwd_pem -s 4096 -e pem -p 1234567890

------ f036eed08d4188e6 begin@2024-04-04_15:51:20.417 ------
private key password:1234567890
generate test_pwd_pem_rsa_public.pem/test_pwd_pem_rsa_private_cipher.pem success
------ f036eed08d4188e6 took 341.474 milli-seconds to execute ------

# der格式密钥，私钥不带密码
❯ easy_encryption_tool rsa generate -f test_no_pwd_der -s 4096 -e der

------ e152e62cc8ff4080 begin@2024-04-04_15:51:53.004 ------
generate test_no_pwd_der_rsa_public.der/test_no_pwd_der_rsa_private.der success
------ e152e62cc8ff4080 took 620.032 milli-seconds to execute ------

# der格式密钥，私钥带密码
❯ easy_encryption_tool rsa generate -f test_pwd_der -s 4096 -e der -p 1234567890

------ 9b08b9054b7642cd begin@2024-04-04_15:52:04.209 ------
private key password:1234567890
generate test_pwd_der_rsa_public.der/test_pwd_der_rsa_private_cipher.der success
------ 9b08b9054b7642cd took 1108.390 milli-seconds to execute ------

```

#### 指定长度且随机生成密码

```python
❯ easy_encryption_tool rsa generate -f test -s 4096 -r

------ e3eba04fda53c701 begin@2024-04-04_15:53:14.570 ------
private key password:4)H(iipM9=qnUV!!16LZ3)n&YGQE@v04
generate test_rsa_public.pem/test_rsa_private_cipher.pem success
------ e3eba04fda53c701 took 300.131 milli-seconds to execute ------ 
```

### 加密与解密

#### 支持的参数

```python
# 加密
❯ easy_encryption_tool rsa encrypt --help
Usage: main.py rsa encrypt [OPTIONS]

Options:
  -f, --public-key TEXT           公钥文件路径  [required]
  -i, --input-data TEXT           输入数据，可以直接为字符串，也可以为
                                  base64编码的数据，base64编码的数据需要带上标识 -c  [required]
  -e, --encoding [pem|der]        密钥格式  [default: pem]
  -c, --b64-encoded               输入数据是否被 base64 编码过
  -l, --input-limit INTEGER       输入内容最大长度，单位为 MB，默认为 1MB，非对称不适合直接加密过长的数据
                                  [default: 1]
  -m, --mode [oaep|pkcs1v15]      加密时的填充模式  [default: oaep; required]
  -h, --hash-mode [sha256|sha384|sha512]
                                  此参数仅在-m为 oaep 时生效  [default: sha256]
  --help                          Show this message and exit.

 # 解密
 ❯ easy_encryption_tool rsa decrypt --help
Usage: main.py rsa decrypt [OPTIONS]

Options:
  -f, --private-key TEXT          私钥文件路径  [required]
  -i, --input-data TEXT           输入的密文数据， 必须为base64编码的数据  [required]
  -e, --encoding [pem|der]        密钥格式  [default: pem]
  -m, --mode [oaep|pkcs1v15]      加密时的填充模式  [default: oaep; required]
  -h, --hash-mode [sha256|sha384|sha512]
                                  此参数仅在-m为 oaep 时生效  [default: sha256]
  -p, --password TEXT             私钥密码，使用私钥时需要输入正确的密码
  --help                          Show this message and exit.

```

#### 使用 PEM 密钥加解密

##### 私钥不需要密码

```python
# 加密
❯ easy_encryption_tool rsa encrypt -e pem -f ./test_data/test_no_pwd_pem_public.pem -i hello,world

------ 18869a5ba5f11a4f begin@2024-04-04_15:55:10.501 ------
pub key size:4096
padding mode:oaep-sha256
cipher:pQlqgAyKEdrjcdRPe90uWHIJv781VD1X0+wrVyzmf6GE1hMdEwcukflGsgkysN3jbR2btNAEfYwxmvk+b1Om/AUdtGrZNAMuCygY3Y2U6ikVRcOCdd0ZCz3Gp7NTEblifVxMR/UsK2VQ+/4Tysmslv2QFOV7Mz+uE6j/o+hTBYhR42r+tkKsAEQGB8LLfo5GC+Wjk5mU1Yt3d8bz3/55S7Wv3DhAlrD9AuiEhqv4E8JL0MgSIN26rzHnOOlkP5vRlH5lLITzier7W9inoQxpYpKdk3xa7gsRXKXVBNcaNdCn9AH/SkrfEe+bHpZcAWG7It2OyTlaAzSwmia+wYx3CgyVtzMHNU9jQrz8xnMcP1ZntxVNvoVnhn9li0H8XTCAy3p+YfYGybEqiSNeFL7cEsONO8x8y0bkqNQE9KFTba0yZNsME1JfJmQVG9IrLaIn1RAsGmPeVYCuHmwcZQCxFUc14von867Z4HewLNqnN5EzalTnVzIY4whZcwMnmp53tZKeNhh0QemuoEqWmkf4cwJHGR/KZJKi+dhB5vo0+LffXf4LzsJRAwbE9ylwEPEsjFx1BYw7jbVIb7hgZ0AZB0J2OMdov25xMWk2nYBmR6L/QFBCtw/J8t7198ZuHmHI247gl8zJ3tFEAPFw1eFOQOkIAKnSDVfnPBlTZQ8smtM=
------ 18869a5ba5f11a4f took 15.731 milli-seconds to execute ------

# 解密
❯ easy_encryption_tool rsa decrypt -e pem -f ./test_data/test_no_pwd_pem_private.pem -i pQlqgAyKEdrjcdRPe90uWHIJv781VD1X0+wrVyzmf6GE1hMdEwcukflGsgkysN3jbR2btNAEfYwxmvk+b1Om/AUdtGrZNAMuCygY3Y2U6ikVRcOCdd0ZCz3Gp7NTEblifVxMR/UsK2VQ+/4Tysmslv2QFOV7Mz+uE6j/o+hTBYhR42r+tkKsAEQGB8LLfo5GC+Wjk5mU1Yt3d8bz3/55S7Wv3DhAlrD9AuiEhqv4E8JL0MgSIN26rzHnOOlkP5vRlH5lLITzier7W9inoQxpYpKdk3xa7gsRXKXVBNcaNdCn9AH/SkrfEe+bHpZcAWG7It2OyTlaAzSwmia+wYx3CgyVtzMHNU9jQrz8xnMcP1ZntxVNvoVnhn9li0H8XTCAy3p+YfYGybEqiSNeFL7cEsONO8x8y0bkqNQE9KFTba0yZNsME1JfJmQVG9IrLaIn1RAsGmPeVYCuHmwcZQCxFUc14von867Z4HewLNqnN5EzalTnVzIY4whZcwMnmp53tZKeNhh0QemuoEqWmkf4cwJHGR/KZJKi+dhB5vo0+LffXf4LzsJRAwbE9ylwEPEsjFx1BYw7jbVIb7hgZ0AZB0J2OMdov25xMWk2nYBmR6L/QFBCtw/J8t7198ZuHmHI247gl8zJ3tFEAPFw1eFOQOkIAKnSDVfnPBlTZQ8smtM=

------ 197c89cd0b631ce0 begin@2024-04-04_15:55:40.536 ------
private key password:
key size:4096
padding mode:oaep-sha256
origin plain:hello,world
------ 197c89cd0b631ce0 took 338.602 milli-seconds to execute ------
```

##### 私钥需要密码

```python
# 加密
❯ easy_encryption_tool rsa encrypt -e pem -f ./test_data/test_pwd_pem_public.pem -i hello,world

------ e1cde686b573fb50 begin@2024-04-04_15:56:04.554 ------
pub key size:4096
padding mode:oaep-sha256
cipher:pF06oJgMvzJ8WUphoYqaccLhClQjeSiSQXbQpORXtzkAFKeSqAQwGCKLQlDeJft6bc4wxUe1hS5IM/21hOpx1HZKZyXurfeqHkOXx4ekiakqS+8MgW6x4vozQfTKUZHoDStA8chwibtWlDCAGESYj1drr1UA8cNc5I+ij+hM3voFA3zh8o6JaKLKmxvNedRk5ugJQE6lL3RHMAya5oQS5AYQTtfuLQl52G0loQIPoWWB8KgZD6iZ///I2MI8B4kEHS1O2eg897DNyHGRdf8nRjTJdecWFR7wXY0VQeV8lR2BEsPb7L15qg4lZvonpew9qII6gW5J39yLK73vbAAkpdAmpOGxvOVtztE0Tn4UFkIkOZDkH8nlj1JwhCJ5K9R+TwlkoUinFMasOUZFEvNHzbha69mVErxBQHwv6N6P4kTLOBFVDrqF1Y00ZAQ0ZjIr/s7OdJAyoHlzZkroSfkbvV2eOho3nJD8aoIYdfa1kwttJ7p027VSVMflO1jULRZ0EkT2ncgzOMjqm4fB8Se42/QGjGtKYKOOPp4uBbFuxi8ra4LWY0l0h+FYJ6wXeIcODMLuxHWK4drfJrj5IpaTYNuysmeDEDfMQZQV1WYmyfFsJtIqXFiKrQatgFtGsfYXPCNBTrXa4HVW/Ohm7vE1PKGh+e2K7VpSZ6F4nW+YmQc=
------ e1cde686b573fb50 took 15.492 milli-seconds to execute ------

# 解密 -p 指定密码
❯ easy_encryption_tool rsa decrypt -e pem -f ./test_data/test_pwd_pem_private_cipher.pem -i pF06oJgMvzJ8WUphoYqaccLhClQjeSiSQXbQpORXtzkAFKeSqAQwGCKLQlDeJft6bc4wxUe1hS5IM/21hOpx1HZKZyXurfeqHkOXx4ekiakqS+8MgW6x4vozQfTKUZHoDStA8chwibtWlDCAGESYj1drr1UA8cNc5I+ij+hM3voFA3zh8o6JaKLKmxvNedRk5ugJQE6lL3RHMAya5oQS5AYQTtfuLQl52G0loQIPoWWB8KgZD6iZ///I2MI8B4kEHS1O2eg897DNyHGRdf8nRjTJdecWFR7wXY0VQeV8lR2BEsPb7L15qg4lZvonpew9qII6gW5J39yLK73vbAAkpdAmpOGxvOVtztE0Tn4UFkIkOZDkH8nlj1JwhCJ5K9R+TwlkoUinFMasOUZFEvNHzbha69mVErxBQHwv6N6P4kTLOBFVDrqF1Y00ZAQ0ZjIr/s7OdJAyoHlzZkroSfkbvV2eOho3nJD8aoIYdfa1kwttJ7p027VSVMflO1jULRZ0EkT2ncgzOMjqm4fB8Se42/QGjGtKYKOOPp4uBbFuxi8ra4LWY0l0h+FYJ6wXeIcODMLuxHWK4drfJrj5IpaTYNuysmeDEDfMQZQV1WYmyfFsJtIqXFiKrQatgFtGsfYXPCNBTrXa4HVW/Ohm7vE1PKGh+e2K7VpSZ6F4nW+YmQc= -p 1234567890

------ cb0ebbe7b572b665 begin@2024-04-04_15:56:25.496 ------
private key password:1234567890
key size:4096
padding mode:oaep-sha256
origin plain:hello,world
------ cb0ebbe7b572b665 took 338.788 milli-seconds to execute ------
```

#### 使用 DER 密钥加解密

##### 私钥不需要密码

```python
# 加密
❯ easy_encryption_tool rsa encrypt -e der -f ./test_data/test_no_pwd_der_public.der -i hello,world

------ 10e4568e22050ecc begin@2024-04-04_15:56:47.705 ------
pub key size:4096
padding mode:oaep-sha256
cipher:V0g9TwUetAKZOl6xwe9SL7ra1P3K2JGwTZ2NMKdZiP4zNaPxxjPPv8Me3g9qMWLNBcfU6dd+7Ia7xGb0c5Ou1/uf7D3xSoV6hU0PV/0i8feJYATgkWFO1NOt1TpIHlcYHtA9NdHEaNXR9qbY8pHyAokVRf83hyQIZMTPgpGo2GH0lJkFAOjxWOiGyPKF7GgdHjz+8rfu4R9VBUg0Wy0O1zyvTKA+b4iE6MS4zJBbzPe0H43w9OLp+TQFykrhLWXsFX+AEhdhxa7N0ebaorlQNtPnY8KuXFx0cqIzWigBcfWNTYgcjbFGLm+mo0Btin0UqDFhbC8EwdpVGnVr6ZBLCvEmyqDAuJN5UCEBQ7Jrakgot/qZ4QHPL5HdU+tNXb8KULH75fyu0A11zzHjpw2E2KRKmg1Fg9aExaim4r15T2VU1eYjZKaPV/YiYMPlqZM9udUQFTmrLRIhCUUp+fc+MJu3zR6chz6d0eSx/RdV8ik8ilKILZl7dAfRS3hC5QG0pPh54Z+MqgAZbHfTxCbjnxqoPJzMOcC+JOPEpjC2PhS6MYE70+Ub8RS1cGZmZ2z32UnanqfT9kLbR626CWUzPzZWsnMheoX5bAABDfp7AkC9BXv+ca3REAyvR8HchVkVMiIRC4dTlY4p4+uFVtOnkhUG5mzSOVGebAWOJ+ftTFY=
------ 10e4568e22050ecc took 14.792 milli-seconds to execute ------


# 解密
❯ easy_encryption_tool rsa decrypt -e der -f ./test_data/test_no_pwd_der_private.der -i V0g9TwUetAKZOl6xwe9SL7ra1P3K2JGwTZ2NMKdZiP4zNaPxxjPPv8Me3g9qMWLNBcfU6dd+7Ia7xGb0c5Ou1/uf7D3xSoV6hU0PV/0i8feJYATgkWFO1NOt1TpIHlcYHtA9NdHEaNXR9qbY8pHyAokVRf83hyQIZMTPgpGo2GH0lJkFAOjxWOiGyPKF7GgdHjz+8rfu4R9VBUg0Wy0O1zyvTKA+b4iE6MS4zJBbzPe0H43w9OLp+TQFykrhLWXsFX+AEhdhxa7N0ebaorlQNtPnY8KuXFx0cqIzWigBcfWNTYgcjbFGLm+mo0Btin0UqDFhbC8EwdpVGnVr6ZBLCvEmyqDAuJN5UCEBQ7Jrakgot/qZ4QHPL5HdU+tNXb8KULH75fyu0A11zzHjpw2E2KRKmg1Fg9aExaim4r15T2VU1eYjZKaPV/YiYMPlqZM9udUQFTmrLRIhCUUp+fc+MJu3zR6chz6d0eSx/RdV8ik8ilKILZl7dAfRS3hC5QG0pPh54Z+MqgAZbHfTxCbjnxqoPJzMOcC+JOPEpjC2PhS6MYE70+Ub8RS1cGZmZ2z32UnanqfT9kLbR626CWUzPzZWsnMheoX5bAABDfp7AkC9BXv+ca3REAyvR8HchVkVMiIRC4dTlY4p4+uFVtOnkhUG5mzSOVGebAWOJ+ftTFY=

------ ff1efcc52f4fc05e begin@2024-04-04_15:57:10.634 ------
private key password:
key size:4096
padding mode:oaep-sha256
origin plain:hello,world
------ ff1efcc52f4fc05e took 348.368 milli-seconds to execute ------
```

##### 私钥需要密码

```python
# 加密
❯ easy_encryption_tool rsa encrypt -e der -f ./test_data/test_pwd_der_public.der -i hello,world

------ d59dc4bec2be5592 begin@2024-04-04_15:57:28.114 ------
pub key size:4096
padding mode:oaep-sha256
cipher:XNNpZfpu7ZjnI1HnH/KN9BdO+/rxrtt0K4z/KRQjsAZEYZV4uMtT0o45ZHrDfr6mHNrrIlTRrt6wghIeQUojEo0uQA7auwhqJXXl3ghwTqGhKH4Lkf6q+d0X/Pn1MgRoNb3dIvWsZcpTlnqmffphOe2DzWP4By9a3yZe9rb8S/ddml7/+4BIXVqxwWcAMsAg3lpLnNHBQ853XYeDZXxKjvx4J8f2RUbp7c/xsH6eUjxZfDehcoZL7te6OrY2N342UzYKBqTQV4zbqVTm0c6V1Q7XkjFK3esgcxicitIP2UsdQjpQf9xMtOTQzErdSQk/Pd6tLxLNyKQcxDaqXR9TXA84koIfGETx434im+zhOgsUuSwS6zBARI3AlpQi14LVAbr3/6ABIEJG5QvVVVG32aVzOMrPtViRqZzcDgkyIsBOXLAQv7c5UkP+nePtnjs31IXmcO87p5zW6rweW/6Y5z1emI6RIHgLjqGKFKkaWXua0N+ZqHTEuqks2y27mFFT/g2DJN3zp5corIcjgSEqyuQbQg/hFaurrqzu+djQ1Pevjzy8rUOM7k97UYUHwjv0ITeIB/m2Rknbwsu3WH3jW6TV8Ta+Bw05ZKYT6hoFPttfno+iDVRmzRlY2QuBxHzEALtdsANzxKnpUr/vr5mEmU8Wmi87QSjp1ULMJ5lTU64=
------ d59dc4bec2be5592 took 14.696 milli-seconds to execute ------

# 解密 -p 指定密码
❯ easy_encryption_tool rsa decrypt -e der -f ./test_data/test_pwd_der_private_cipher.der -i XNNpZfpu7ZjnI1HnH/KN9BdO+/rxrtt0K4z/KRQjsAZEYZV4uMtT0o45ZHrDfr6mHNrrIlTRrt6wghIeQUojEo0uQA7auwhqJXXl3ghwTqGhKH4Lkf6q+d0X/Pn1MgRoNb3dIvWsZcpTlnqmffphOe2DzWP4By9a3yZe9rb8S/ddml7/+4BIXVqxwWcAMsAg3lpLnNHBQ853XYeDZXxKjvx4J8f2RUbp7c/xsH6eUjxZfDehcoZL7te6OrY2N342UzYKBqTQV4zbqVTm0c6V1Q7XkjFK3esgcxicitIP2UsdQjpQf9xMtOTQzErdSQk/Pd6tLxLNyKQcxDaqXR9TXA84koIfGETx434im+zhOgsUuSwS6zBARI3AlpQi14LVAbr3/6ABIEJG5QvVVVG32aVzOMrPtViRqZzcDgkyIsBOXLAQv7c5UkP+nePtnjs31IXmcO87p5zW6rweW/6Y5z1emI6RIHgLjqGKFKkaWXua0N+ZqHTEuqks2y27mFFT/g2DJN3zp5corIcjgSEqyuQbQg/hFaurrqzu+djQ1Pevjzy8rUOM7k97UYUHwjv0ITeIB/m2Rknbwsu3WH3jW6TV8Ta+Bw05ZKYT6hoFPttfno+iDVRmzRlY2QuBxHzEALtdsANzxKnpUr/vr5mEmU8Wmi87QSjp1ULMJ5lTU64= -p 1234567890

------ 806b307f230908a4 begin@2024-04-04_15:57:47.873 ------
private key password:1234567890
key size:4096
padding mode:oaep-sha256
origin plain:hello,world
------ 806b307f230908a4 took 343.988 milli-seconds to execute ------
```

#### 对明文为 base64 编码的字节流做加解密

```python
# 加密
❯ easy_encryption_tool rsa encrypt -e pem -f ./test_data/test_pwd_pem_public.pem -i krJchuyaDRYHnu5tsy8UzA== -c

------ 29fa5af7e9f84f15 begin@2024-04-04_15:58:07.029 ------
pub key size:4096
padding mode:oaep-sha256
cipher:QiztiJ21bVsNf4DyPhzDAR/E24Xqe94P398JLDakFl3LDhOA3I/XHY3v3R0GBNRrrMPtUorGHxCVmJNqc06kLCtQ4ljd015rYFRgbAtNLsaNgjsdv1Q7kHQSVa7L1BHdhNDK47svEWMQTS4jKLiKVYbCnz4ARYqe3n+vf5UtVDlrpmMNjowbdfnhnEFibCICBwnGUpssFs9X546/BzCTlqgEGdy1SpFhfX2LaqXkTQwsd3YxU9ynHZ7oVe78z/xJlEDQbPGJSfGbUtoCgQAn975hzCceC+CeDK9E7N8vu6HTv9K3vMNRHTOweRWuGxJKKjzVLSXRcDjkXynJPPe6yuyJdFnsoSHWuOsyqPrjJJC+2RvrkW24RlIEMnVEqLReD8/OnZEEw4CZF9xqplS7yRN/khaYM4MxD9qcxAIYgpg2wq6QngtabU+nGtQSfjKIkXlXnbvqMcLI1pS0TtG5vUeNSzc3Ll8g9CxvMrA/T/vUjz9d60fsGpHbjZezpsQdI8Us60BMxUWgFZ6x/ME5HkMmtXYfvi0/aNKWXymgHtVxqeM9hJaQ6MX1zNZRPe4MMHu3xJgATAbVc7GldgV+d2gZm2cLzZaTTB4ARcAARSKPgbskmIQ2buNRs2/ogjvtG72jilOCmSPKZom0VPHkrLzL4csxgxSd2M5N4cu6+Po=
------ 29fa5af7e9f84f15 took 15.584 milli-seconds to execute ------

# 解密 -p 指定密码
❯ easy_encryption_tool rsa decrypt -e pem -f ./test_data/test_pwd_pem_private_cipher.pem -i QiztiJ21bVsNf4DyPhzDAR/E24Xqe94P398JLDakFl3LDhOA3I/XHY3v3R0GBNRrrMPtUorGHxCVmJNqc06kLCtQ4ljd015rYFRgbAtNLsaNgjsdv1Q7kHQSVa7L1BHdhNDK47svEWMQTS4jKLiKVYbCnz4ARYqe3n+vf5UtVDlrpmMNjowbdfnhnEFibCICBwnGUpssFs9X546/BzCTlqgEGdy1SpFhfX2LaqXkTQwsd3YxU9ynHZ7oVe78z/xJlEDQbPGJSfGbUtoCgQAn975hzCceC+CeDK9E7N8vu6HTv9K3vMNRHTOweRWuGxJKKjzVLSXRcDjkXynJPPe6yuyJdFnsoSHWuOsyqPrjJJC+2RvrkW24RlIEMnVEqLReD8/OnZEEw4CZF9xqplS7yRN/khaYM4MxD9qcxAIYgpg2wq6QngtabU+nGtQSfjKIkXlXnbvqMcLI1pS0TtG5vUeNSzc3Ll8g9CxvMrA/T/vUjz9d60fsGpHbjZezpsQdI8Us60BMxUWgFZ6x/ME5HkMmtXYfvi0/aNKWXymgHtVxqeM9hJaQ6MX1zNZRPe4MMHu3xJgATAbVc7GldgV+d2gZm2cLzZaTTB4ARcAARSKPgbskmIQ2buNRs2/ogjvtG72jilOCmSPKZom0VPHkrLzL4csxgxSd2M5N4cu6+Po= -p 1234567890

------ d1f6350d34acf14e begin@2024-04-04_15:58:25.431 ------
private key password:1234567890
key size:4096
padding mode:oaep-sha256
b64 encoded plain:krJchuyaDRYHnu5tsy8UzA==
------ d1f6350d34acf14e took 344.681 milli-seconds to execute ------
```

#### 加密模式设置

##### OAEP模式

```python
# 加密
❯ easy_encryption_tool rsa encrypt -e pem -f ./test_data/test_no_pwd_pem_public.pem -i hello,world -m oaep

------ 5d4c86b12cac7641 begin@2024-04-04_15:58:41.153 ------
pub key size:4096
padding mode:oaep-sha256
cipher:LmYOuAO+zP1qCjivWUH7/EM81/2pDNK5t7JmRXW1GvhElRCa0uKTvEYa/Ncsoqv+zpbxo3O6j9K3YkGbO6rbi2DasYSKCX6DGvMNd348onbEiLZEpRMCGG03PpTpoargFZJhsnUQ6MvFPEpML9Om8EbjAwMG+xukuKV2Vak/6GmO8XQfsr6C+1hzhXqBggorJPyjpGZfypQO3Cx38puIpGC+TAewXZNMcBiVU3MTFvVfPk/vqlzYU68TXHGIIsKFKQG35iOSJA3bXr/FLwor3yAGdk7VS2e1kAYnuceUW9WN4FKb+ThRGRiDepJmFPM3TN9ZEEbdDH+wIZO7MkmKD4RBUgBO0RSDMIP8x8LxKN05xKqRxcrFbx5UKfurf1vAi4Hu+AP+e3kL/2UlvmHfLwmVYMT4Quzy414T/R43cNe9eH8KCypNjV5bFqdUyxJ4u7mC0f+S7PD1oTB6vhwsMOXH6tadhd7LpzpUrEyH6DLhukRIuo72IIdGnXNlB9d4yklxCPfvg9lj/irmdJKaq0MSbm1mZCp3J6R+sd0iKXakZnkPU7YLjwz92S7a1gSwfEpzBfsQZcuqs2BSN/iOEWMEB1XVGZrsbv5Tpp8APo/scGLBHmyk7Q/mDgsaNXrrGiQw0NfU4ZPzaFlvL2AC6HVE0YK4Nm4CIAXy+m2hItM=
------ 5d4c86b12cac7641 took 14.201 milli-seconds to execute ------

# 解密
❯ easy_encryption_tool rsa decrypt -e pem -f test_data/test_no_pwd_pem_private.pem -m oaep -i LmYOuAO+zP1qCjivWUH7/EM81/2pDNK5t7JmRXW1GvhElRCa0uKTvEYa/Ncsoqv+zpbxo3O6j9K3YkGbO6rbi2DasYSKCX6DGvMNd348onbEiLZEpRMCGG03PpTpoargFZJhsnUQ6MvFPEpML9Om8EbjAwMG+xukuKV2Vak/6GmO8XQfsr6C+1hzhXqBggorJPyjpGZfypQO3Cx38puIpGC+TAewXZNMcBiVU3MTFvVfPk/vqlzYU68TXHGIIsKFKQG35iOSJA3bXr/FLwor3yAGdk7VS2e1kAYnuceUW9WN4FKb+ThRGRiDepJmFPM3TN9ZEEbdDH+wIZO7MkmKD4RBUgBO0RSDMIP8x8LxKN05xKqRxcrFbx5UKfurf1vAi4Hu+AP+e3kL/2UlvmHfLwmVYMT4Quzy414T/R43cNe9eH8KCypNjV5bFqdUyxJ4u7mC0f+S7PD1oTB6vhwsMOXH6tadhd7LpzpUrEyH6DLhukRIuo72IIdGnXNlB9d4yklxCPfvg9lj/irmdJKaq0MSbm1mZCp3J6R+sd0iKXakZnkPU7YLjwz92S7a1gSwfEpzBfsQZcuqs2BSN/iOEWMEB1XVGZrsbv5Tpp8APo/scGLBHmyk7Q/mDgsaNXrrGiQw0NfU4ZPzaFlvL2AC6HVE0YK4Nm4CIAXy+m2hItM=

------ 9f151c4c9f56af86 begin@2024-04-04_15:59:05.700 ------
private key password:
key size:4096
padding mode:oaep-sha256
origin plain:hello,world
------ 9f151c4c9f56af86 took 346.121 milli-seconds to execute ------
```

##### PKCS1v15 模式

```python
# 加密
❯ easy_encryption_tool rsa encrypt -e pem -f ./test_data/test_no_pwd_pem_public.pem -i hello,world -m pkcs1v15

------ 875086be5059242c begin@2024-04-04_15:59:19.282 ------
pub key size:4096
padding mode:pkcs1v15
cipher:i1hVnoQsWAQK4sftuvg08Fq1dzm3o5BRNb78K6gBGVwsu1a/gSFGwIqSOa22ONlZ0cnPu7v/mZAVOi8/tXa1++JrmpnyoZTQ3vATkXK8WG6Q6Dw/dLKUFvsRv04hxMI2ttSMNqFsiRRnCy1qx2m4PFPPuZFniuT4i9hutGn4br+d5e1DtyLxsSmefKk4BJn0Rzgzv3ImeBmT0znTJ5VN2SI/WRHlvc6KmtnQ1xD2/2kkMycPzLguL+XXc+ie/sNg3Y0CelQcXOxonAnkQcXKpzQL7+pONVDHIJvRRXjv0nUWhTay+KWPApfB+BZ0MkI03oW0divZXohI5pWa7n4Gvw1qN+Kvt3BAmBK64LvlKFzZF553re5fMKodhrK/yhIsCtWYjsrgEz+5UAmavwH6dgTrIpssfSQDaUlWwVQRG/pG+jbxkKZ1Y7SX1SAznS4Jb3xKFeWN7ulPLJIe3Zro4VxihW0KgChDFrnVtpZLuXq98mVR+v/L2HaCzNRCY39soYZZywt3IhP2ZDVoCGpVQWQ8y69pbCKfCfiOheRf1j7VYtIOATT2kNGJrVnBwZvXBfQ2Mp3y9UPIud+WAkS2s6bha1QBF/YX72xtZoiICLZZmrmXN3DdGWy1y8SM3mI75s2GsqjF6JeN2iNoLtDy2tpREH78MujjnSjMSCP78wY=
------ 875086be5059242c took 14.024 milli-seconds to execute ------

# 解密
❯ easy_encryption_tool rsa decrypt -e pem -f test_data/test_no_pwd_pem_private.pem -m pkcs1v15 -i i1hVnoQsWAQK4sftuvg08Fq1dzm3o5BRNb78K6gBGVwsu1a/gSFGwIqSOa22ONlZ0cnPu7v/mZAVOi8/tXa1++JrmpnyoZTQ3vATkXK8WG6Q6Dw/dLKUFvsRv04hxMI2ttSMNqFsiRRnCy1qx2m4PFPPuZFniuT4i9hutGn4br+d5e1DtyLxsSmefKk4BJn0Rzgzv3ImeBmT0znTJ5VN2SI/WRHlvc6KmtnQ1xD2/2kkMycPzLguL+XXc+ie/sNg3Y0CelQcXOxonAnkQcXKpzQL7+pONVDHIJvRRXjv0nUWhTay+KWPApfB+BZ0MkI03oW0divZXohI5pWa7n4Gvw1qN+Kvt3BAmBK64LvlKFzZF553re5fMKodhrK/yhIsCtWYjsrgEz+5UAmavwH6dgTrIpssfSQDaUlWwVQRG/pG+jbxkKZ1Y7SX1SAznS4Jb3xKFeWN7ulPLJIe3Zro4VxihW0KgChDFrnVtpZLuXq98mVR+v/L2HaCzNRCY39soYZZywt3IhP2ZDVoCGpVQWQ8y69pbCKfCfiOheRf1j7VYtIOATT2kNGJrVnBwZvXBfQ2Mp3y9UPIud+WAkS2s6bha1QBF/YX72xtZoiICLZZmrmXN3DdGWy1y8SM3mI75s2GsqjF6JeN2iNoLtDy2tpREH78MujjnSjMSCP78wY=

------ d5096f43a1dd5d65 begin@2024-04-04_15:59:37.216 ------
private key password:
key size:4096
padding mode:pkcs1v15
origin plain:hello,world
------ d5096f43a1dd5d65 took 348.221 milli-seconds to execute ------
```



### 签名与验签

#### 支持的参数

```python
# 签名
❯ easy_encryption_tool rsa sign --help
Usage: main.py rsa sign [OPTIONS]

Options:
  -f, --private-key TEXT          私钥文件路径  [required]
  -e, --encoding [pem|der]        密钥格式  [default: pem]
  -m, --mode [pss|pkcs1v15]       签名时的填充模式  [default: pss; required]
  -h, --hash-mode [sha256|sha384|sha512]
                                  签名时的哈希算法  [default: sha256]
  -p, --password TEXT             私钥密码，使用私钥时需要输入正确的密码
  -i, --input-data TEXT           需要被签名的数据  [required]
  -c, --b64-encoded               输入数据是否被 base64 编码过
  --help                          Show this message and exit.

# 验签
❯ easy_encryption_tool rsa verify --help
Usage: main.py rsa verify [OPTIONS]

Options:
  -f, --public-key TEXT           公钥文件路径  [required]
  -e, --encoding [pem|der]        密钥格式  [default: pem]
  -m, --mode [pss|pkcs1v15]       签名时的填充模式  [default: pss; required]
  -h, --hash-mode [sha256|sha384|sha512]
                                  签名时的哈希算法  [default: sha256]
  -i, --input-data TEXT           需要被签名的数据  [required]
  -c, --b64-encoded               输入数据是否被 base64 编码过
  -s, --signature TEXT            base64 编码过的签名值
  --help                          Show this message and exit.
```

#### PSS模式

```python
# 签名 -p 指定密码
❯ easy_encryption_tool rsa sign -e der -f ./test_data/test_pwd_der_private_cipher.der -m pss -i hello,world -p 1234567890

------ 9238d47e0e2636f1 begin@2024-04-04_16:01:35.674 ------
key size:4096
signature:74bd0807319b4b7c58dc40eaf58efee853eb5d506079ebe8aa3e76350d42ea88c4560c4e732911cca4cd101da6b5b712fae4005bd207d88fcc87761c5c0942f932b9f5773814b91c68c91b23fd623681a069fd29d86c1c89b080b35cf1f45024bf92fdfade2b9536603f5ae40ee0d1350eae686ba1b02954efd8f2b43226228247ce8d5d9c1bbebb97aa77eb52c5a62fbf8bd46e531abf3683487ce5f18cb48f2f175398861f579ea3f38281fc74c7d26942a890fbf3f7c089c553fb632058e7d553c9fc3f4126433cd126067df935008227eaa96b4b7fcb01621d1d553d58070b656aaf2e6d28eb062e5643c639261d908bfd57542ed06e18bfd1419f300582d571c8f461422f684ffd9a68584fd164667f94e9775024cfe635a207297c8a3f1e078652c45c92c7d5ac2a10b6541b2aaaa30e926496caae06effe280bc275ea6b34cf83f3f2ce141acfbce1e03d11071cd9e177be28855fe108ef25ae0ba42db67e9992e3fa0072f92dd8843c666707685f49a10fba0f5719b752025893b272f8171951e007f1527b0320c62e1e9d6814e8de43084e51f917d52047a856576e6cc608576f4d99f4a8e9b237be7c576cf30cb8f97b69e85be2a6e5911f1105555778d122351691d434d40e3742278c54e71c8eac4bbb59918e3ffd3c162d25f0d1dff36e17c3ced4af449f5d06195f9a3328f1c24bd7b52aa49d8727e1894ad2
----------------
base64 encoded:dL0IBzGbS3xY3EDq9Y7+6FPrXVBgeevoqj52NQ1C6ojEVgxOcykRzKTNEB2mtbcS+uQAW9IH2I/Mh3YcXAlC+TK59Xc4FLkcaMkbI/1iNoGgaf0p2GwcibCAs1zx9FAkv5L9+t4rlTZgP1rkDuDRNQ6uaGuhsClU79jytDImIoJHzo1dnBu+u5eqd+tSxaYvv4vUblMavzaDSHzl8Yy0jy8XU5iGH1eeo/OCgfx0x9JpQqiQ+/P3wInFU/tjIFjn1VPJ/D9BJkM80SYGffk1AIIn6qlrS3/LAWIdHVU9WAcLZWqvLm0o6wYuVkPGOSYdkIv9V1Qu0G4Yv9FBnzAFgtVxyPRhQi9oT/2aaFhP0WRmf5Tpd1Akz+Y1ogcpfIo/HgeGUsRcksfVrCoQtlQbKqqjDpJklsquBu/+KAvCdeprNM+D8/LOFBrPvOHgPREHHNnhd74ohV/hCO8lrgukLbZ+mZLj+gBy+S3YhDxmZwdoX0mhD7oPVxm3UgJYk7Jy+BcZUeAH8VJ7AyDGLh6daBTo3kMITlH5F9UgR6hWV25sxghXb02Z9Kjpsje+fFds8wy4+Xtp6FvipuWRHxEFVVd40SI1FpHUNNQON0InjFTnHI6sS7tZkY4//TwWLSXw0d/zbhfDztSvRJ9dBhlfmjMo8cJL17UqpJ2HJ+GJStI=
mode:pss-sha256
------ 9238d47e0e2636f1 took 348.185 milli-seconds to execute ------

# 验签
❯ easy_encryption_tool rsa verify -e der -f ./test_data/test_pwd_der_public.der -m pss -s dL0IBzGbS3xY3EDq9Y7+6FPrXVBgeevoqj52NQ1C6ojEVgxOcykRzKTNEB2mtbcS+uQAW9IH2I/Mh3YcXAlC+TK59Xc4FLkcaMkbI/1iNoGgaf0p2GwcibCAs1zx9FAkv5L9+t4rlTZgP1rkDuDRNQ6uaGuhsClU79jytDImIoJHzo1dnBu+u5eqd+tSxaYvv4vUblMavzaDSHzl8Yy0jy8XU5iGH1eeo/OCgfx0x9JpQqiQ+/P3wInFU/tjIFjn1VPJ/D9BJkM80SYGffk1AIIn6qlrS3/LAWIdHVU9WAcLZWqvLm0o6wYuVkPGOSYdkIv9V1Qu0G4Yv9FBnzAFgtVxyPRhQi9oT/2aaFhP0WRmf5Tpd1Akz+Y1ogcpfIo/HgeGUsRcksfVrCoQtlQbKqqjDpJklsquBu/+KAvCdeprNM+D8/LOFBrPvOHgPREHHNnhd74ohV/hCO8lrgukLbZ+mZLj+gBy+S3YhDxmZwdoX0mhD7oPVxm3UgJYk7Jy+BcZUeAH8VJ7AyDGLh6daBTo3kMITlH5F9UgR6hWV25sxghXb02Z9Kjpsje+fFds8wy4+Xtp6FvipuWRHxEFVVd40SI1FpHUNNQON0InjFTnHI6sS7tZkY4//TwWLSXw0d/zbhfDztSvRJ9dBhlfmjMo8cJL17UqpJ2HJ+GJStI= -i hello,world

------ 8e8b995bbc527e67 begin@2024-04-04_16:02:01.136 ------
verify success
key size:4096
mode:pss-sha256
------ 8e8b995bbc527e67 took 14.193 milli-seconds to execute ------
```

#### PKCS1v15模式

```python
# 签名 -p 指定密码
❯ easy_encryption_tool rsa sign -e pem -f ./test_data/test_pwd_pem_private_cipher.pem -i hello,world -p 1234567890

------ c12c59d278e72bb6 begin@2024-04-04_16:02:15.070 ------
key size:4096
signature:8590c16fd7349f7da4b845ab56c0fd786cd0f5bd4c60b8508d571f9c1da8a6f647c6b283a593ce3a8d77bf052f8eef2752058cb653faf434b7e04e4b49248d2d55815616c0e9e4e4196186bc749831616ab66a815f82bc6414d9469790aabb987cdf76b2594e586c65512042aa70f04c6c5ddf9d2038cd01971d36829e3342710e3221eb83f0299baa876df8fa43f76b6bad7ec0a28e30d77f5bdbae132f880ee2234bd5de82ae1e7c00fcb20d04f5c65e5d79a9b64f3776868139581b1ba3cf8167ca2e893330b4a49f2a5c0cd9bd5f331d270dbd3aacc6fba492ab7cce3fe78ec2f7b82d339b9ab90b89bbed8fe5f123f26709a4777dee8df500bf6d4d7178c25889c90b4d0826a47a85556958041aeb5e3609abe315d286d2804eacda85af2bbdfab943206150bd651fd0cb5106ff9b03636dcd2b5ab4d2186fe48241802ad0c8a4313d8bc29ff5c36fa0cffad5e566f3b47b3564b2739685e5ca881c3729fc300911f62b95c36eced3ebb6260997ba16b614599d809df71010a94ada31fa1db0342b632826b57599a8227ce18a44565de61e23e96b5d413702648173ada70f7c3d46599357edb4b2926f5824eef4a5960eae06c236c7cf1a8890ff67365b1aaf778423b99e641ffd56553d8196b4ed40b11ae6e804f8623fc9e3842f248394b61d1886c550d5e20c4e9d3c8b071da6f55569740f3b633cfd99167442a9f5
----------------
base64 encoded:hZDBb9c0n32kuEWrVsD9eGzQ9b1MYLhQjVcfnB2opvZHxrKDpZPOOo13vwUvju8nUgWMtlP69DS34E5LSSSNLVWBVhbA6eTkGWGGvHSYMWFqtmqBX4K8ZBTZRpeQqruYfN92sllOWGxlUSBCqnDwTGxd350gOM0Blx02gp4zQnEOMiHrg/Apm6qHbfj6Q/dra61+wKKOMNd/W9uuEy+IDuIjS9Xegq4efAD8sg0E9cZeXXmptk83doaBOVgbG6PPgWfKLokzMLSknypcDNm9XzMdJw29OqzG+6SSq3zOP+eOwve4LTObmrkLibvtj+XxI/JnCaR3fe6N9QC/bU1xeMJYickLTQgmpHqFVWlYBBrrXjYJq+MV0obSgE6s2oWvK736uUMgYVC9ZR/Qy1EG/5sDY23NK1q00hhv5IJBgCrQyKQxPYvCn/XDb6DP+tXlZvO0ezVksnOWheXKiBw3KfwwCRH2K5XDbs7T67YmCZe6FrYUWZ2AnfcQEKlK2jH6HbA0K2MoJrV1magifOGKRFZd5h4j6WtdQTcCZIFzracPfD1GWZNX7bSykm9YJO70pZYOrgbCNsfPGoiQ/2c2Wxqvd4QjuZ5kH/1WVT2BlrTtQLEa5ugE+GI/yeOELySDlLYdGIbFUNXiDE6dPIsHHab1VWl0DztjPP2ZFnRCqfU=
mode:pss-sha256
------ c12c59d278e72bb6 took 343.034 milli-seconds to execute ------

# 验签
❯ easy_encryption_tool rsa verify -e pem -f ./test_data/test_pwd_pem_public.pem -i hello,world -s hZDBb9c0n32kuEWrVsD9eGzQ9b1MYLhQjVcfnB2opvZHxrKDpZPOOo13vwUvju8nUgWMtlP69DS34E5LSSSNLVWBVhbA6eTkGWGGvHSYMWFqtmqBX4K8ZBTZRpeQqruYfN92sllOWGxlUSBCqnDwTGxd350gOM0Blx02gp4zQnEOMiHrg/Apm6qHbfj6Q/dra61+wKKOMNd/W9uuEy+IDuIjS9Xegq4efAD8sg0E9cZeXXmptk83doaBOVgbG6PPgWfKLokzMLSknypcDNm9XzMdJw29OqzG+6SSq3zOP+eOwve4LTObmrkLibvtj+XxI/JnCaR3fe6N9QC/bU1xeMJYickLTQgmpHqFVWlYBBrrXjYJq+MV0obSgE6s2oWvK736uUMgYVC9ZR/Qy1EG/5sDY23NK1q00hhv5IJBgCrQyKQxPYvCn/XDb6DP+tXlZvO0ezVksnOWheXKiBw3KfwwCRH2K5XDbs7T67YmCZe6FrYUWZ2AnfcQEKlK2jH6HbA0K2MoJrV1magifOGKRFZd5h4j6WtdQTcCZIFzracPfD1GWZNX7bSykm9YJO70pZYOrgbCNsfPGoiQ/2c2Wxqvd4QjuZ5kH/1WVT2BlrTtQLEa5ugE+GI/yeOELySDlLYdGIbFUNXiDE6dPIsHHab1VWl0DztjPP2ZFnRCqfU=

------ 8151e7568131a1c6 begin@2024-04-04_16:02:36.431 ------
verify success
key size:4096
mode:pss-sha256
------ 8151e7568131a1c6 took 13.708 milli-seconds to execute ------
```

## ECC椭圆曲线

### 支持的命令

```
❯ easy_encryption_tool ecc --help
Usage: main.py ecc [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  ecdh
  generate
  sign
  verify
```

### 生成密钥对

```python
❯ easy_encryption_tool ecc generate --help
Usage: main.py ecc generate [OPTIONS]

Options:
  -c, --curve [secp256r1|secp384r1|secp521r1|secp256k1]
                                  ecc 椭圆曲线类型  [default: secp256k1]
  -e, --encoding [pem|der]        密钥格式  [default: pem]
  -f, --file-name TEXT            输出密钥对的文件名前缀，最终写入数据时会创建文件并加上文件名后缀  [default:
                                  demo; required]
  -p, --password TEXT             私钥密码，使用私钥时需要输入正确的密码
  -r, --random-password           是否生成私钥的随机密码，如果带上 -r 标识，则随机生成32字节的密码
  --help                          Show this message and exit.
```

#### 默认生成

```python
❯ easy_encryption_tool ecc generate

------ d6b0cebd74d64b57 begin@2024-04-04_16:07:59.720 ------
generate demo_ecc_public.pem/demo_ecc_private.pem success
------ d6b0cebd74d64b57 took 17.039 milli-seconds to execute ------
```

#### 指定椭圆曲线且指定密码

```python
❯ easy_encryption_tool ecc generate -c secp384r1 -p 1234567890

------ e852fd0a2d84d39f begin@2024-04-04_16:08:46.706 ------
private key password:1234567890
generate demo_ecc_public.pem/demo_ecc_private_cipher.pem success
------ e852fd0a2d84d39f took 16.710 milli-seconds to execute ------
```

#### 指定椭圆曲线且随机生成密码

```python
❯ easy_encryption_tool ecc generate -c secp384r1 -r

------ 073bd5585937e6fd begin@2024-04-04_16:09:28.102 ------
private key password:)N)y&4dq=ODg`339uE`7*@A9Gl0eVs3Z
generate demo_ecc_public.pem/demo_ecc_private_cipher.pem success
------ 073bd5585937e6fd took 16.721 milli-seconds to execute ------
```

### ECDH密钥交换

#### 支持的参数

```python
❯ easy_encryption_tool ecc ecdh --help
Usage: main.py ecc ecdh [OPTIONS]

Options:
  -a, --alice-pub-key TEXT    你自己的公钥文件的路径如: ./alice_public.pem  [required]
  -k, --alice-pri-key TEXT    你自己的私钥文件的路径如: ./alice_private.pem  [required]
  -p, --password TEXT         你自己的私钥的密码，如果创建时设置了密码，那么在使用私钥时需要输入正确的密码
  -b, --bob-pub-key TEXT      对方的公钥文件的路径如: ./bob_public.pem  [required]
  -e, --encoding [pem|der]    密钥格式  [default: pem]
  -l, --length INTEGER RANGE  派生密钥的长度，默认 32 字节，长度范围[16 -- 64]  [default: 32;
                              16<=x<=64]
  -s, --salt TEXT             用于增加派生密钥安全性的盐值，两边必须提供一样的盐值  [default:
                              hello,world1234567890!@#$%^&*()_+{}:";<>?/;
                              required]
  -c, --context TEXT          用于增加派生密钥安全性的上下文信息，两边必须提供一样的上下文数据  [default: ecc
                              handshake context data; required]
  --help                      Show this message and exit.

```

#### 生成 alice 与 bob 的密钥对

```python
❯ easy_encryption_tool ecc generate -f alice -p 1234567890

------ f4815ee66aa727b2 begin@2024-04-04_16:11:03.966 ------
private key password:1234567890
generate alice_ecc_public.pem/alice_ecc_private_cipher.pem success
------ f4815ee66aa727b2 took 17.763 milli-seconds to execute ------

❯ easy_encryption_tool ecc generate -f bob -p 1234567890

------ 76e72cdd07cb5c32 begin@2024-04-04_16:11:26.201 ------
private key password:1234567890
generate bob_ecc_public.pem/bob_ecc_private_cipher.pem success
------ 76e72cdd07cb5c32 took 16.373 milli-seconds to execute ------
```

#### alice派生和 bob 共享的对称密钥

```python
❯ easy_encryption_tool ecc ecdh -a ./alice_ecc_public.pem -k ./alice_ecc_private_cipher.pem -p 1234567890 -b ./bob_ecc_public.pem -l 64 -s alice-bob -c key-alice-bob

------ 8235537a02e647d4 begin@2024-04-04_16:14:48.686 ------
curve name:secp256k1
derived key:u+UNGIzrPbLRVlTSixl8fgd3SgLuGeQrwSI4Irs1tpSVivmTxYLTOUm/o1pvqPLuOGVA8D3iLdUGLEE72Wo1QQ==
length:64
------ 8235537a02e647d4 took 18.166 milli-seconds to execute ------
```

#### bob派生和alice共享的对称密钥

```python
❯ easy_encryption_tool ecc ecdh -a ./bob_ecc_public.pem -k ./bob_ecc_private_cipher.pem -p 1234567890 -b ./alice_ecc_public.pem -l 64 -s alice-bob -c key-alice-bob

------ d50d7d254d02104c begin@2024-04-04_16:15:39.570 ------
curve name:secp256k1
derived key:u+UNGIzrPbLRVlTSixl8fgd3SgLuGeQrwSI4Irs1tpSVivmTxYLTOUm/o1pvqPLuOGVA8D3iLdUGLEE72Wo1QQ==
length:64
------ d50d7d254d02104c took 16.998 milli-seconds to execute ------
```

### 签名与验签

#### 支持的参数

```python
# 签名
❯ easy_encryption_tool ecc sign --help
Usage: main.py ecc sign [OPTIONS]

Options:
  -f, --private-key TEXT          私钥文件路径  [required]
  -e, --encoding [pem|der]        密钥格式  [default: pem]
  -h, --hash-mode [sha256|sha384|sha512|sha3-224|sha3-256|sha3-384|sha3-512]
                                  签名时的哈希算法  [default: sha256]
  -p, --password TEXT             私钥密码，如果生成时设置了密码那么在使用私钥时需要输入正确的密码
  -i, --input-data TEXT           需要被签名的数据  [required]
  -c, --b64-encoded               输入数据是否被 base64 编码过
  --help                          Show this message and exit.

# 验签
❯ easy_encryption_tool ecc verify --help
Usage: main.py ecc verify [OPTIONS]

Options:
  -f, --public-key TEXT           公钥文件路径  [required]
  -e, --encoding [pem|der]        密钥格式  [default: pem]
  -h, --hash-mode [sha256|sha384|sha512|sha3-224|sha3-256|sha3-384|sha3-512]
                                  签名时的哈希算法  [default: sha256]
  -i, --input-data TEXT           需要被签名的数据  [required]
  -c, --b64-encoded               输入数据是否被 base64 编码过
  -s, --signature TEXT            base64 编码过的签名值
  --help                          Show this message and exit.
```

#### 生成密钥对

```python
❯ easy_encryption_tool ecc generate -c secp384r1 -p 1234567890 -e der

------ 3a40beeed28cffa4 begin@2024-04-04_16:17:49.172 ------
private key password:1234567890
generate demo_ecc_public.der/demo_ecc_private_cipher.der success
------ 3a40beeed28cffa4 took 16.821 milli-seconds to execute ------
```

#### 签名

```python
❯ easy_encryption_tool ecc sign -f ./demo_ecc_private_cipher.der -e der -h sha3-512 -p 1234567890 -i aGVsbG8sd29ybGQK -c

------ dbc12fab8422ba0f begin@2024-04-04_16:18:57.578 ------
curve name:secp384r1
key size:384
signature:30640230243bba7ec0a95f7aef4868673282b70217285a667ae52ce5e43c6af5b33e8adbda86bbe2a7d9f995b934d038eae5624c02306c47daed069e51bb12c274c13219c173ed4b59c6f76caab04f50b4359f3f25c0fa4dab4c17cb88888767f37e4c8cf993
base64 encoded:MGQCMCQ7un7AqV9670hoZzKCtwIXKFpmeuUs5eQ8avWzPorb2oa74qfZ+ZW5NNA46uViTAIwbEfa7QaeUbsSwnTBMhnBc+1LWcb3bKqwT1C0NZ8/JcD6TatMF8uIiIdn835MjPmT
mode:ECDSA
------ dbc12fab8422ba0f took 17.265 milli-seconds to execute ------
```

#### 验签

```python
❯ easy_encryption_tool ecc verify -f ./demo_ecc_public.der -e der -h sha3-512 -i aGVsbG8sd29ybGQK -c -s MGQCMCQ7un7AqV9670hoZzKCtwIXKFpmeuUs5eQ8avWzPorb2oa74qfZ+ZW5NNA46uViTAIwbEfa7QaeUbsSwnTBMhnBc+1LWcb3bKqwT1C0NZ8/JcD6TatMF8uIiIdn835MjPmT

------ d1c74d3acc46413b begin@2024-04-04_16:20:09.177 ------
curve name:secp384r1
verify success
key size:384
mode:ECDSA
------ d1c74d3acc46413b took 15.220 milli-seconds to execute ------
```

## SM9 标识密码（IBC）

SM9 是国密标识密码（Identity-Based Cryptography）：**身份字符串（如邮箱）本身就是公钥**，无需事先交换证书或公钥。密钥体系分两层——KGC 持有主密钥（master key），按身份为用户派生用户私钥；加密者只需主公钥 + 接收方身份即可加密，签名者持自己的用户签名私钥签名，验签者用主公钥 + 签名者身份验签。命令组为 `sm9`，需要 GmSSL 原生库。

```shell
❯ easy_encryption_tool sm9 --help

Commands:
  master-generate  生成 SM9 主密钥对（加密型或签名型）
  extract          从主私钥为指定身份派生用户私钥
  encrypt          用主公钥为指定身份加密（标识加密）
  decrypt          用用户私钥解密
  sign             用用户签名私钥签名
  verify           用主公钥为指定身份验签
```

### 生成主密钥并派生用户私钥

`-k` 选主密钥类型：`enc`（加密型）或 `sign`（签名型）。主私钥文件以口令加密落盘（GmSSL 原生加密 PEM），`password` 与 sm2/rsa/pqc 相同约定：仅 `-r` 随机口令时在 result 回显（随机口令不回显则无人能再用这把主密钥），`-p` 自给口令不回显，均不进 metadata：

```shell
❯ easy_encryption_tool sm9 master-generate -k enc -f demo -p 1234567890 --json

{"metadata": {"operation": "master-generate", "algorithm": "sm9-enc", "parameters": {"encoding": "pem", "kind": "enc"}}, "result": {"public_file": "demo_sm9encmaster_public.pem", "private_file": "demo_sm9encmaster_private_cipher.pem"}, "runtime": {...}, "messages": [{"status": "hint", "message": {"code": "EET_HINT", "text": "next: eet sm9 extract -k enc -f demo_sm9encmaster_private_cipher.pem -p <password> --id <identity>"}}]}

# 为 bob@example.com 派生加密用户私钥（解密方持有，身份即公钥）
❯ easy_encryption_tool sm9 extract -k enc -f demo_sm9encmaster_private_cipher.pem -p 1234567890 --id bob@example.com -o demo_bob_user.pem --json

{"metadata": {"operation": "extract", "algorithm": "sm9-enc", "parameters": {"kind": "enc", "id": "bob@example.com"}}, "result": {"key_file": "demo_bob_user.pem", "id": "bob@example.com"}, "runtime": {...}}
```

用户私钥同样以口令加密存储。身份串最长 63 字节（SM9_MAX_ID_SIZE）。

### 标识加密与解密

加密只需主公钥 + 接收方身份（`--to`），接收方用自己的用户私钥解密：

```shell
❯ easy_encryption_tool sm9 encrypt -f demo_sm9encmaster_public.pem --to bob@example.com -i "hello bob, this is sm9 encrypted 中文" --json

{"metadata": {"operation": "encrypt", "algorithm": "sm9-enc", "input": {"type": "text", "size": 39}, "parameters": {"to": "bob@example.com", "cipher_size": 149}}, "result": {"cipher": "MIGSAgEAA0IABJXfGqJuTtFMLMdtmutukXmnPsBN9nDdrd/T13EOsTeaQbvr4c0ggomnav72wSGmO9wTnirjpW2354hmqhSO3WIEIMeXQd3g0kskZgUx0qNQ+xqH7T3URlDms0cvKChLwVluBCcwvhKLi9sCKzrl3JQKLEysASQTgs52PyWvpACxOwHE6R5nKdjAX44=", "cipher_in_hex": "3081920201000342000495df1aa26e4ed14c2cc76d9aeb6e9179a73ec04df670ddaddfd3d7710eb1379a41bbebe1cd208289a76afef6c121a63bdc139e2ae3a56db7e78866aa148edd620420c79741dde0d24b24660531d2a350fb1a87ed3dd44650e6b3472f28284bc1596e042730be128b8bdb022b3ae5dc940a2c4cac01241382ce763f25afa400b13b01c4e91e6729d8c05f8e", "cipher_bytes": 149}, "runtime": {...}}

# 解密：-e 表明 -i 是 base64 密文（取上一步输出的 cipher 字段）
❯ easy_encryption_tool sm9 decrypt -f demo_bob_user.pem -p 1234567890 --id bob@example.com -i "<cipher>" -e --json

{"metadata": {"operation": "decrypt", "algorithm": "sm9-enc", "input": {"type": "binary", "size": 149}, "parameters": {"id": "bob@example.com"}}, "result": {"plain": "hello bob, this is sm9 encrypted 中文"}, "runtime": {...}}
```

身份不匹配（用 alice 的用户私钥解给 bob 的密文）解密失败并输出错误文档。

### 标识签名与验签

签名者持签名型用户私钥，验签方用签名主公钥 + 签名者身份（`--id`）：

```shell
❯ easy_encryption_tool sm9 sign -f demo_alice_sign_user.pem -p 1234567890 -i "sm9 signed message from alice" --json

{"metadata": {"operation": "sign", "algorithm": "sm9-sign", "input": {"type": "text", "size": 29}}, "result": {"signature": "MGYEIFVPXeRmEhbqQ/gUUUlhnn6bmsXO/BTHLignsjNW3s2mA0IABCyjMZQa5yXSm/D4piAJv6PcAUCh8WXqzStsAT020nALL8lYGukcLL53SgwSwKeTXO+cKDvchuG7kit9l1LIsOE=", "signature_in_hex": "30660420554f5de4661216ea43f8145149619e7e9b9ac5cefc14c72e2827b23356decda6034200042ca331941ae725d29bf0f8a62009bfa3dc0140a1f165eacd2b6c013d36d2700b2fc9581ae91c2cbe774a0c12c0a7935cef9c283bdc86e1bb922b7d9752c8b0e1", "signature_bytes": 104}, "runtime": {...}}

# 验签：-s 传入签名输出的 signature（base64），--id 必须与签名者身份一致
❯ easy_encryption_tool sm9 verify -f demo_sm9signmaster_public.pem --id alice@example.com -i "sm9 signed message from alice" -s "<signature>" --json

{"metadata": {"operation": "verify", "algorithm": "sm9-sign", "input": {"type": "text", "size": 29}, "parameters": {"id": "alice@example.com"}}, "result": {"valid": true}, "runtime": {...}}
```

身份不符或消息被篡改时 `valid` 为 false 或输出错误文档。SM9 签名随机化：同一私钥对同一消息多次签名结果不同，属正常现象。

## 密钥派生（HKDF / SM3-KDF / PBKDF2-HMAC-SM3）

`kdf` 命令提供三种派生构造：`hkdf`（RFC 5869，走 cryptography，无需原生库，**3.2.0 起为默认**）与两种基于 SM3 的派生方式（需 GmSSL 原生库）：

```shell
# HKDF：默认算法（sha256、盐未提供按 RFC 5869 补零），二进制 IKM 用 -i <base64> -e
❯ easy_encryption_tool kdf -i ikm-material -l 32 --json

{"metadata": {"operation": "kdf", "algorithm": "hkdf", "input": {"type": "text", "size": 12}, "parameters": {"algorithm": "hkdf", "key_len": 32, "hash": "sha256", "salt_source": "none", "salt_bytes": 0, "info_bytes": 0}}, "result": {"key": "vBmgrK8IqUxy+NSYQ7JrLhVEJtNsL0zKKHlmEFCtUlg=", "key_in_hex": "bc19a0acaf08a94c72f8d49843b26b2e154426d36c2f4cca2879661050ad5258", "key_bytes": 32, "formats": {"key": "base64", "key_in_hex": "hex"}}, "runtime": {...}}

# HKDF：指定哈希与盐/上下文（--info/--info-b64 提供上下文信息）
❯ easy_encryption_tool kdf -a hkdf -H sha512 -i ikm-material --salt eet-salt-01 --info derive-ctx -l 64 --json

{"metadata": {"operation": "kdf", "algorithm": "hkdf", "input": {"type": "text", "size": 12}, "parameters": {"algorithm": "hkdf", "key_len": 64, "hash": "sha512", "salt_source": "cli", "salt_bytes": 11, "info_bytes": 10}}, "result": {"key": "8R5n8jXyB5rRnwrJvTTiGb1Sg9SivldQ/X2GqKhA8fMmABhzV+dQFGvP7wwVU0ZzgxKj+Ihmta+5V2/XFflzEA==", "key_in_hex": "f11e67f235f2079ad19f0ac9bd34e219bd5283d4a2be5750fd7d86a8a840f1f32600187357e750146bcfef0c155346738312a3f88866b5afb9576fd715f97310", "key_bytes": 64, "formats": {"key": "base64", "key_in_hex": "hex"}}, "runtime": {...}}

# SM3-KDF：从共享秘密派生密钥材料（ECDH 共享点后派生对称密钥的典型用法）
❯ easy_encryption_tool kdf -a sm3 -i kat-secret -l 32 --json

{"metadata": {"operation": "kdf", "algorithm": "sm3-kdf", "input": {"type": "text", "size": 10}, "parameters": {"algorithm": "sm3", "key_len": 32}}, "result": {"key": "rfR8C/wIpbbg2PPzkHuCYho96xmf7po4EY5zzdb0OQs=", "key_in_hex": "adf47c0bfc08a5b6e0d8f3f3907b82621a3deb199fee9a38118e73cdd6f4390b", "key_bytes": 32, "formats": {"key": "base64", "key_in_hex": "hex"}}, "runtime": {...}}

# PBKDF2-HMAC-SM3：从口令派生，--salt/--salt-b64/--iter 可选
❯ easy_encryption_tool kdf -a pbkdf2-sm3 -i kat-password --salt eet-salt-01 --iter 10000 -l 32 --json

{"metadata": {"operation": "kdf", "algorithm": "sm3-pbkdf2", "input": {"type": "text", "size": 12}, "parameters": {"algorithm": "pbkdf2-sm3", "key_len": 32, "iterations": 10000, "salt_bytes": 11, "salt_source": "cli"}}, "result": {"key": "Mqm1kNfz58iWtbKMx4mUw6CayE2qituhTNZJztcq/GM=", "key_in_hex": "32a9b590d7f3e7c896b5b28cc78994c3a09ac84daa8adba14cd649ced72afc63", "key_bytes": 32, "formats": {"key": "base64", "key_in_hex": "hex"}}, "runtime": {...}}
```

要点：

- `-a hkdf`（默认，3.2.0 起；原默认 `sm3`，隐式依赖默认值的脚本需显式写 `-a sm3`）：`-H` 选 sha1/224/256/384/512，长度 1–1024 字节。盐是可选的上下文绑定值，**缺省不随机生成**（未提供按 RFC 补 HashLen 零），`--salt`（文本）/`--salt-b64`（字节）显式给；`--info`/`--info-b64` 提供上下文信息。RFC 5869 A.1–A.4 官方向量在 CLI 上逐位复现。
- `-a sm3`（SM3-KDF）不接受盐参数，派生是确定性的；长度范围 1–1024 字节。
- `-a pbkdf2-sm3` 缺省生成 16 字节随机盐，**随机盐会以 base64 回显在 result 的 `salt` 字段**（否则派生不可复现）；用 `--salt`（文本）或 `--salt-b64`（字节）显式给盐时 result 不再回显。迭代次数范围 [10000, 16777216]，长度范围 1–256 字节。
- 二进制秘密用 `-i <base64> -e` 传入（hkdf 与 sm3 均支持）。

## SM4 对称加解密（CBC / GCM / CTR）

`sm4` 命令用法与 `aes` 一致（密钥/IV 规范化、`-r` 随机密钥、文件输入输出），模式经 `-m` 选择。3.0 起新增 **CTR 计数器模式**：

```shell
❯ easy_encryption_tool sm4 -A encrypt -m ctr -k 0123456789abcdef -v fedcba9876543210 -i "ctr mode payload" --json

{"metadata": {"operation": "encrypt", "algorithm": "sm4-ctr", "input": {"type": "text", "size": 16}, "parameters": {"mode": "ctr"}}, "result": {"cipher": "9wc1NPqEZ8jTrkvEQmXQ+g==", "formats": {"cipher": "base64"}}, "runtime": {...}}

❯ easy_encryption_tool sm4 -A decrypt -m ctr -k 0123456789abcdef -v fedcba9876543210 -i "<cipher>" -e --json

{"metadata": {"operation": "decrypt", "algorithm": "sm4-ctr", "input": {"type": "binary", "size": 16}, "parameters": {"mode": "ctr"}}, "result": {"plain": "ctr mode payload", "formats": {"plain": "utf-8"}}, "runtime": {...}}
```

CTR 是流模式：**不做填充，密文长度恒等于明文长度**（上例 16 字节进 16 字节出），因此 `--gcm-pad` 类填充选项与 ctr 互斥。CBC（PKCS#7 填充）与 GCM（12 字节 nonce + 16 字节 tag）行为不变。

## PQC 后量子（ML-KEM / ML-DSA）

后量子命令组 `pqc`，提供 **ML-KEM**（FIPS 203 密钥封装）与 **ML-DSA**（FIPS 204 签名）能力，需要 `cryptography >= 47`（ExternalMu 模式需 >= 49）。参数集可用性取决于所装 cryptography 版本（`easy_encryption_tool version` 的 Dependencies 行会列出当前可用参数集）。

与 [CipherHUB 工具站](https://cipherhub.cloud) 的互通关系：**公钥可互换**（双方均为 NIST 标准 OID 的 SPKI/PKCS#8，工具站后端 liboqs 生成的公钥可直接喂给本工具 encap/verify）；**私钥不可互换**（本工具私钥为 PKCS#8 种子形态加密存储，工具站为展开形态，二者格式不同）。

### 生成 ML-KEM 密钥对

```shell
# 私钥强制密码保护：-p 指定密码，或 -r 随机生成（两者都不传默认随机）
# -r 随机口令回显在 result.password；-p 自给口令不回显
❯ easy_encryption_tool pqc mlkem generate -f demo -p 1234567890 --json

{"metadata": {"operation": "generate", "algorithm": "ML-KEM-768", "parameters": {"encoding": "pem", "seed_derived": false}}, "result": {"public_file": "demo_mlkem768_public.pem", "private_file": "demo_mlkem768_private_cipher.pem"}, "runtime": {"request_id": "a2bac659-2d54-47f2-be3f-aab0fa28174c", "timestamp": "2026-08-28T15:07:32.353623+08:00", "duration_ms": 9.809, "host": "MacM2Max-2.local", "version": "v2.7.0"}}
```

`-a/--algorithm` 可选 `ML-KEM-512`、`ML-KEM-768`（默认）、`ML-KEM-1024`；`--seed-in-hex` 接受 64 字节 hex（128 个十六进制字符）做确定性派生，仅供教学与向量复现，生产环境留空即随机生成。

### ML-KEM 封装与解封装

发送方持有公钥做封装，得到密文与共享秘密；接收方持私钥解封装还原同一个共享秘密（典型用法：共享秘密作为对称加密的密钥材料）。

```shell
# 封装：输出 ciphertext（交给孩子信道传输）与 shared_secret（发送方本地留存）
❯ easy_encryption_tool pqc mlkem encap -f demo_mlkem768_public.pem --json

{"metadata": {"operation": "encap", "algorithm": "ML-KEM-768", "input": {"type": "key"}, "parameters": {"ciphertext_bytes": 1088}}, "result": {"ciphertext_in_hex": "cbf390d5c3a98de7ece00606...（共 2176 个 hex 字符）", "ciphertext_in_base64": "y/OQ1cOpjefs4AYGeHQe/Cf2knCg...（共 1452 个字符）", "ciphertext_bytes": 1088, "shared_secret_in_hex": "ca4954152db0dd42d6e50ff35ecc51a7590bc487cf7339ee352610cc4b7a56c3", "shared_secret_in_base64": "yklUFS2w3ULW5Q/zXsxRp1kLxIfPcznuNSYQzEt6VsM=", "shared_secret_sha256": "b71c5ae5a9132597df7a96250af8b029b24ec1e91ce92cc18de699dd8f35b8cc", "shared_secret_bytes": 32}, "runtime": {...}}

# 解封装：-i 传入封装输出的 ciphertext_in_base64
❯ easy_encryption_tool pqc mlkem decap -f demo_mlkem768_private_cipher.pem -p 1234567890 -i "<ciphertext_in_base64>" --json

{"metadata": {"operation": "decap", "algorithm": "ML-KEM-768", "input": {"type": "binary", "size": 1088}}, "result": {"shared_secret_in_hex": "9b907dd7844ce9dd906e645ecaaa95936b18b43d7f66b21ae2ef265496ef1f7a", "shared_secret_in_base64": "m5B914RM6d2QbmReyqqVk2sYtD1/ZrIa4u8mVJbvH3o=", "shared_secret_sha256": "c8f5e74ba6ca40c5cef8d72de727c67183b256359c2639fb489e54abcc167697", "shared_secret_bytes": 32}, "runtime": {...}}
```

注意两次 `shared_secret` 不同是正常的：ML-KEM 封装本身随机化，同一条 ciphertext 的 encap/decap 结果必然一致（上例两段输出来自两次独立的封装）。按 FIPS 203 的隐式拒绝语义，篡改后的 ciphertext 解封装不报错，但得到的共享秘密必然不同——比对 `shared_secret_sha256` 即可发现。

### 生成 ML-DSA 密钥对

```shell
❯ easy_encryption_tool pqc mldsa generate -f demo -p 1234567890 --json

{"metadata": {"operation": "generate", "algorithm": "ML-DSA-65", "parameters": {"encoding": "pem", "seed_derived": false}}, "result": {"public_file": "demo_mldsa65_public.pem", "private_file": "demo_mldsa65_private_cipher.pem"}, "runtime": {...}}
```

`-a/--algorithm` 可选 `ML-DSA-44`、`ML-DSA-65`（默认）、`ML-DSA-87`；`--seed-in-hex` 接受 32 字节 hex（64 个十六进制字符）做确定性派生，仅供教学与向量复现。

### ML-DSA 签名与验签

```shell
# 签名：--context 为可选域分隔字符串（最长 255 字节），验签时必须传同一个值
❯ easy_encryption_tool pqc mldsa sign -f demo_mldsa65_private_cipher.pem -p 1234567890 -i "hello,post-quantum" --context eet-demo --json

{"metadata": {"operation": "sign", "algorithm": "ML-DSA-65", "input": {"type": "text", "size": 18}, "parameters": {"context": "eet-demo"}}, "result": {"signature_in_hex": "b2de9bf26b6685e2b64d8c348ccf726f...（共 6618 个 hex 字符）", "signature_in_base64": "st6b8mtmheK2TYw0jM9ybwQwSnPvYWp2M+Yk...（共 4412 个字符）", "signature_bytes": 3309}, "runtime": {...}}

# 验签：-s 传入签名输出的 signature_in_base64，--context 必须与签名时一致
❯ easy_encryption_tool pqc mldsa verify -f demo_mldsa65_public.pem -i "hello,post-quantum" --context eet-demo -s "<signature_in_base64>" --json

{"metadata": {"operation": "verify", "algorithm": "ML-DSA-65", "input": {"type": "text", "size": 18}, "parameters": {"context": "eet-demo"}}, "result": {"valid": true}, "runtime": {...}}
```

ML-DSA 签名是随机化的：同一私钥对同一消息多次签名结果不同，属正常现象。验签失败（密钥、消息、context 不匹配或签名被篡改）时输出错误文档并以退出码 1 结束。

### ML-DSA ExternalMu 模式（cryptography >= 49）

FIPS 204 的签名输入实际是 64 字节中间值 **mu**（mu = SHAKE-256(tr‖M', 64)，tr 是公钥的哈希、M' 是 context 前缀化的消息）。ExternalMu 模式把「算 mu」与「对 mu 签名」拆开，与 AWS KMS ML-DSA `EXTERNAL_MU` 语义对齐——**密钥留在 KMS/本机，消息摘要可在别处计算**。3.1 起此模式走 cryptography 原生 `sign_mu`/`verify_mu`（>= 49），**不再依赖系统 OpenSSL**（cryptography wheel 静态自带），可用性在 `doctor` 的 external-mu 行与 `version --json` 的 `external_mu` 段展示。

三个命令形态：

```shell
# 1) compute-mu：从公钥（或私钥，会派生公钥）计算 mu，纯 hashlib 实现，零额外依赖
❯ easy_encryption_tool pqc mldsa compute-mu -f demo_mldsa65_public.pem -i "external mu demo message" --json

{"metadata": {"operation": "compute-mu", "algorithm": "ML-DSA-65", "input": {"type": "text", "size": 24}}, "result": {"mu_in_hex": "4960e833ab4cd4835874423b0dc78880fc76e2b04e0cef784c1320359e81e885ec99e84ed2970d34839c17fa2003e8b9751499c6fd831524f37650efb49833e4", "mu_in_base64": "SWDoM6tM1INYdEI7DceIgPx24rBODO94TBMgNZ6B6IXsmehO0pcNNIOcF/ogA+i5dRSZxv2DFSTzdlDvtJgz5A==", "mu_bytes": 64, "tr_in_hex": "0da3bf875d68bf5e2ec526af9065e24bf6d279c18b8e6907dd09e349267ab154373d0dd5584dd1e8904f6bc06f590f9a30c1f64368f3bd362b28b1d5232eb24e"}, "runtime": {...}, "messages": [{"status": "hint", "message": {"code": "EET_HINT", "text": "next: eet pqc mldsa sign --external-mu --raw-mu -f <private_key> -i <mu_in_hex>"}}]}

# 2) sign --external-mu --raw-mu：-i 直接是 64 字节 mu（hex），不再内部哈希
❯ easy_encryption_tool pqc mldsa sign --external-mu --raw-mu -f demo_mldsa65_private_cipher.pem -p 1234567890 -i "<mu_in_hex>" --json

{"metadata": {"operation": "sign", "algorithm": "ML-DSA-65", "input": {"type": "text", "size": 64}, "parameters": {"mode": "external-mu-raw"}}, "result": {"signature_in_hex": "f9b53026...（共 6618 个 hex 字符）", "signature_in_base64": "+bUwJpwhBPJ1...（共 4412 个字符）", "signature_bytes": 3309}, "runtime": {...}}

# 3) verify --external-mu --raw-mu：对称验签，-s 传 base64 签名
❯ easy_encryption_tool pqc mldsa verify --external-mu --raw-mu -f demo_mldsa65_public.pem -i "<mu_in_hex>" -s "<signature_in_base64>" --json

{"metadata": {"operation": "verify", "algorithm": "ML-DSA-65", "input": {"type": "text", "size": 64}, "parameters": {"mode": "external-mu-raw"}}, "result": {"valid": true}, "runtime": {...}}
```

说明：

- `sign --external-mu`（不带 `--raw-mu`）：消息照常传 `-i`，eet 内部算 mu 后走 cryptography 原生 mu 签名——产物与标准模式签名互通，`pqc mldsa verify`（标准模式）可直接验。
- `--raw-mu`：`-i` 必须恰为 64 字节（hex 128 字符或 base64），且与 `--context` 互斥（context 已在算 mu 时消费）。长度不符报「raw mu must be 64 bytes」。
- `compute-mu` 与 `--raw-mu` 串联即完整 ExternalMu 流程：摘要计算与密钥使用可以发生在不同机器。

## 证书解析与验证

### 解析证书并验证签名

```python
 ❯ easy_encryption_tool cert-parse -f test_certs/baidu.com.cer

------ 27f6aa95a2461345 begin@2024-04-06_15:02:26.887 ------
------- verify signature: -------
verify cert signature success
ca issuer:http://secure.globalsign.com/cacert/gsrsaovsslca2018.crt

------- basic info: -------
version: v3-2
serial_number: 26585094245224241434632730821
signature_algorithm: PKCS #1 RSA Encryption
signature_hash_algorithm: sha256
issuer: GlobalSign nv-sa|GlobalSign RSA OV SSL CA 2018|BE
valid_before: 20230706015106Z
valid_after: 20240806015105Z
subject: Beijing Baidu Netcom Science Technology Co., Ltd|CN|baidu.com
public_key_bits: 2048
public_key_type: 6(6:RSA|116:DSA|408:EC|28:DH)
------ 27f6aa95a2461345 took 83.634 milli-seconds to execute ------

```

### 解析完整证书内容

```python
❯ easy_encryption_tool cert-parse -f baidu.com.cer -v

------ 247b96f11927fe0f begin@2024-04-06_15:04:26.023 ------
------- verify signature: -------
verify cert signature success
ca issuer:http://secure.globalsign.com/cacert/gsrsaovsslca2018.crt

------- basic info: -------
version: v3-2
serial_number: 26585094245224241434632730821
signature_algorithm: PKCS #1 RSA Encryption
signature_hash_algorithm: sha256
issuer: GlobalSign nv-sa|GlobalSign RSA OV SSL CA 2018|BE
valid_before: 20230706015106Z
valid_after: 20240806015105Z
subject: Beijing Baidu Netcom Science Technology Co., Ltd|CN|baidu.com
public_key_bits: 2048
public_key_type: 6(6:RSA|116:DSA|408:EC|28:DH)

------- verbose info: -------
public_key_exponent: ('10001',)
public_key_modules: ('BB04BB84765807B45A8854E06A56BCE5D48D3E1EB928E0D7018F382B41B2597DF0AC27B426241438FE4CEA3B4951F7E95B40F73FA6C8DA0F026E258B4791B82E9E0021191D1800FCDE04FD2679395DF290BC809DA87CB2918989D8402FE5D2A7F35E6D482BC51F0AB1E08E8C76FFBCD1670AD249D609EE260302F3CCCDEA8AD531A82D8F03FD5EFCE43AC68967994CCE986DFA840D0E538BE66352C59B4AA9ABA32235990DEE19FF9B2DF5A477F2EC1080F4AB82B9D17E361F0E9F9B19A0F5C357DD88BBCEE1909C3F4BBADD3AA941B3DD864DC2C2B7E8FF3713C0048943443811E6A396F70922212F2C4E0E7EE5D85CBB00445BAFDEE4B3B0F03CB63845495D',)
public_key_fingerprints: 43A4C24005AA3F8B7E7AAE35C6729A52025DACF5AAC86E4BF8D8B6633321A9D6
signature: 0X195A675043B1AC7A93A86818728B407EA675DEAC21FCC94116204BF38C0BB94745AEF85D79F64335260198F0B9863E2901F1DFB072B5AE78D2DF61B678678AC9779ADEE0E4412F9C1EE53B7C973F422FADE3497F9D2B0288906925030114B9B5CB0F593D2D973D02D55190690C811022DAC651EF480CD24FDE61F26A8715A56D718E3702A2850F1E1975A3802E6A1AA2028C2FECBD3D81033F8AC0A0E6B40E0857CB001C8AB71B8F38719A8DC0710C3FBCD4BE569DF718C1AABEE4DF1A86E2626F23863054782D471FB4AD052973249814A019C002FD90904E625CE84D3189C3E88B9E73593B9891CA47A5055BC51E8F85390ECEB5260A804E9F084A11491363
certificate_fingerprints: D8:AA:2D:80:6C:57:1F:B6:2E:D4:87:48:41:90:92:3F:93:24:F0:31:9C:FF:FE:DF:7B:62:1F:13:4E:6B:C1:00
extension_count: 10
extensions_detail: {
    "x509.AuthorityInformationAccess": {
        "oid": "1.3.6.1.5.5.7.1.1",
        "critical": false,
        "value": {
            "caIssuers": "http://secure.globalsign.com/cacert/gsrsaovsslca2018.crt",
            "OCSP": "http://ocsp.globalsign.com/gsrsaovsslca2018"
        }
    },
    "x509.BasicConstraints": {
        "oid": "2.5.29.19",
        "critical": false,
        "value": "<BasicConstraints(ca=False, path_length=None)>"
    },
    "x509.KeyUsage": {
        "oid": "2.5.29.15",
        "critical": true,
        "value": "<KeyUsage(digital_signature=True, content_commitment=False, key_encipherment=True, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False)>"
    },
    "x509.SubjectKeyIdentifier": {
        "oid": "2.5.29.14",
        "critical": false,
        "value": "<SubjectKeyIdentifier(digest=b'\\xeds\\xab\\xf9 \\xbez\\x19\\x9fY\\x1f\\xb2\\x9f\\xf2?/?\\x91\\x84\\x12')>"
    },
    "x509.SubjectAlternativeName": {
        "oid": "2.5.29.17",
        "critical": false,
        "value": "<SubjectAlternativeName(<GeneralNames([<DNSName(value='baidu.com')>, <DNSName(value='baifubao.com')>, <DNSName(value='www.baidu.cn')>, <DNSName(value='www.baidu.com.cn')>, <DNSName(value='mct.y.nuomi.com')>, <DNSName(value='apollo.auto')>, <DNSName(value='dwz.cn')>, <DNSName(value='*.baidu.com')>, <DNSName(value='*.baifubao.com')>, <DNSName(value='*.baidustatic.com')>, <DNSName(value='*.bdstatic.com')>, <DNSName(value='*.bdimg.com')>, <DNSName(value='*.hao123.com')>, <DNSName(value='*.nuomi.com')>, <DNSName(value='*.chuanke.com')>, <DNSName(value='*.trustgo.com')>, <DNSName(value='*.bce.baidu.com')>, <DNSName(value='*.eyun.baidu.com')>, <DNSName(value='*.map.baidu.com')>, <DNSName(value='*.mbd.baidu.com')>, <DNSName(value='*.fanyi.baidu.com')>, <DNSName(value='*.baidubce.com')>, <DNSName(value='*.mipcdn.com')>, <DNSName(value='*.news.baidu.com')>, <DNSName(value='*.baidupcs.com')>, <DNSName(value='*.aipage.com')>, <DNSName(value='*.aipage.cn')>, <DNSName(value='*.bcehost.com')>, <DNSName(value='*.safe.baidu.com')>, <DNSName(value='*.im.baidu.com')>, <DNSName(value='*.baiducontent.com')>, <DNSName(value='*.dlnel.com')>, <DNSName(value='*.dlnel.org')>, <DNSName(value='*.dueros.baidu.com')>, <DNSName(value='*.su.baidu.com')>, <DNSName(value='*.91.com')>, <DNSName(value='*.hao123.baidu.com')>, <DNSName(value='*.apollo.auto')>, <DNSName(value='*.xueshu.baidu.com')>, <DNSName(value='*.bj.baidubce.com')>, <DNSName(value='*.gz.baidubce.com')>, <DNSName(value='*.smartapps.cn')>, <DNSName(value='*.bdtjrcv.com')>, <DNSName(value='*.hao222.com')>, <DNSName(value='*.haokan.com')>, <DNSName(value='*.pae.baidu.com')>, <DNSName(value='*.vd.bdstatic.com')>, <DNSName(value='*.cloud.baidu.com')>, <DNSName(value='click.hm.baidu.com')>, <DNSName(value='log.hm.baidu.com')>, <DNSName(value='cm.pos.baidu.com')>, <DNSName(value='wn.pos.baidu.com')>, <DNSName(value='update.pan.baidu.com')>])>)>"
    },
    "x509.CRLDistributionPoints": {
        "oid": "2.5.29.31",
        "critical": false,
        "value": "<CRLDistributionPoints([<DistributionPoint(full_name=[<UniformResourceIdentifier(value='http://crl.globalsign.com/gsrsaovsslca2018.crl')>], relative_name=None, reasons=None, crl_issuer=None)>])>"
    },
    "x509.ExtendedKeyUsage": {
        "oid": "2.5.29.37",
        "critical": false,
        "value": "<ExtendedKeyUsage([<ObjectIdentifier(oid=1.3.6.1.5.5.7.3.1, name=serverAuth)>, <ObjectIdentifier(oid=1.3.6.1.5.5.7.3.2, name=clientAuth)>])>"
    },
    "x509.FreshestCRL": {
        "ERROR": "x509.ExtensionNotFound",
        "oid": "",
        "value": {},
        "critical": false
    },
    "x509.NameConstraints": {
        "ERROR": "x509.ExtensionNotFound",
        "oid": "",
        "value": {},
        "critical": false
    },
    "x509.PolicyConstraints": {
        "ERROR": "x509.ExtensionNotFound",
        "oid": "",
        "value": {},
        "critical": false
    }
}
------ 247b96f11927fe0f took 76.036 milli-seconds to execute ------
```

### 解析非法证书内容

```python
# pem格式
❯ easy_encryption_tool cert-parse -f ./test.crt

------ 34ce7a7ed108f599 begin@2024-04-06_15:05:43.194 ------
loading cert file:./upload.sh as format:pem failed:[('PEM routines', '', 'no start line')]
------ 34ce7a7ed108f599 took 0.140 milli-seconds to execute ------

# der格式
❯ easy_encryption_tool cert-parse -f ./test.crt -e der

------ 041f017f1492c8ce begin@2024-04-06_15:06:04.418 ------
loading cert file:./upload.sh as format:der failed:[('asn1 encoding routines', '', 'wrong tag'), ('asn1 encoding routines', '', 'nested asn1 error')]
------ 041f017f1492c8ce took 0.170 milli-seconds to execute ------
```

## Server 模式（HTTP 服务，实验性）

`server` 命令把整棵 CLI 命令树以 HTTP 服务暴露：**请求体里的 `args` 数组就是命令行参数**，服务在进程内调用与 CLI 完全相同的实现，响应体是与 `--json` 输出完全一致的标准输出文档。需要 `[server]` extra（`pip install "easy-encryption-tool[server]"`）。**3.0.x 入口已注释停用**（见上文实验性声明），下文用法为将来重新启用后的参考。

```shell
# 启动：端口必填；--token 开启 Bearer 鉴权；默认只绑定 127.0.0.1
❯ easy_encryption_tool server --port 8000 --token my-secret-token

INFO:     Uvicorn running on http://127.0.0.1:8000
```

两个端点：

- `GET /api/v1/health` —— 探活，不做鉴权，返回 `{"status": "ok", "service": "eet-server", "version": "..."}`；
- `POST /api/v1/cli` —— 通用 argv 端点，body `{"args": ["hash", "-i", "hello", "-a", "sm3"]}`。

```shell
# 像用命令行一样发请求：args 就是 CLI 参数（--json 由服务自动补上）
❯ curl -s http://127.0.0.1:8000/api/v1/health
{"status":"ok","service":"eet-server","version":"3.0.0"}

❯ curl -s -H "Authorization: Bearer my-secret-token" \
       -H "Content-Type: application/json" \
       -d '{"args":["hash","-i","hello from server","-a","sm3"]}' \
       http://127.0.0.1:8000/api/v1/cli

{"metadata":{"operation":"hash","algorithm":"sm3","input":{"type":"text","size":17},"parameters":{"digest_size":32}},"result":{"digest":"0404a15d0ceee521bec8b0f8c91cac471c2213628c258cac2e0a8ab5d5988cd2","formats":{"digest":"hex"}},"runtime":{"request_id":"ae044e1d-764a-485c-a223-57ab533318a5","timestamp":"2026-08-31T16:23:46.676712+08:00","duration_ms":0.07,"host":"MacM2Max-2.local","version":"v3.0.0"}}
```

行为约定：

| 方面 | 行为 |
|------|------|
| 状态码映射 | CLI 退出码 0 → 200；2（参数错误）→ 400；1（运行错误）→ 422；真实退出码另附 `X-EET-Exit-Code` 响应头 |
| 错误响应体 | 与 CLI 相同的结构化错误文档（`status: "error"`），非 FastAPI 默认格式 |
| 鉴权 | `--token` 给出后要求 `Authorization: Bearer <token>` 或 `X-EET-Token: <token>`，缺失/不符返回 401（body 同样是结构化错误文档，code `EET_UNAUTHORIZED`）；不给 `--token` 则不鉴权 |
| stdout 回退 | 命令不支持 `--json` 时（如 `--version`）回退捕获 stdout，包成 `{"output": "...", "exit_code": 0}` |
| 绑定地址 | 默认 `127.0.0.1`，`--host` 显式放开（对外开放务必配合 `--token`，并置于反向代理/TLS 之后） |

FastAPI 未安装时 `server` 命令输出结构化错误与安装指引（`pip install "easy-encryption-tool[server]"`），其余命令不受影响。

