Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,23 @@ jobs:
name: Test
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
otp: ['27']
elixir: ['1.18']
include:
# Minimum versions declared in README.md and mix.exs.
- os: ubuntu-latest
otp: '24.3'
elixir: '1.14.5'
# Current primary toolchain on every supported runner.
- os: ubuntu-latest
otp: '27'
elixir: '1.18'
- os: windows-latest
otp: '27'
elixir: '1.18'
- os: macos-latest
otp: '27'
elixir: '1.18'

steps:
- name: Preserve LF line endings
Expand All @@ -39,9 +52,9 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-mix-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-otp-${{ matrix.otp }}-elixir-${{ matrix.elixir }}-mix-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-mix-
${{ runner.os }}-otp-${{ matrix.otp }}-elixir-${{ matrix.elixir }}-mix-

- name: Install Hex and Rebar
run: |
Expand All @@ -52,6 +65,7 @@ jobs:
run: mix deps.get

- name: Check formatting
if: matrix.os == 'ubuntu-latest' && matrix.elixir == '1.18'
run: mix format --check-formatted

- name: Compile with warnings as errors
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Verify tag matches package version
shell: bash
run: |
tag_version="${GITHUB_REF_NAME#v}"
mix_version="$(sed -n 's/.*@version "\([^"]*\)".*/\1/p' mix.exs)"

if [ -z "$mix_version" ] || [ "$tag_version" != "$mix_version" ]; then
echo "Release tag version '$tag_version' does not match mix.exs version '$mix_version'." >&2
exit 1
fi

- name: Set up Erlang/OTP and Elixir
uses: erlef/setup-beam@v1
Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,27 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
### Added

- Added `bench/bench.exs` micro-benchmark for the pure-Elixir SM2, SM3, and SM4 cores.
- Benchmark output now records runtime/OS/architecture parameters and reports
min/median/max across repeated samples.
- Added large-input SM4 CTR coverage and fixed SM2 scalar-multiplication vectors.
- Added explicit standard SM2 APIs for ZA-aware raw signatures and C1/C3/C2
encryption using the counter-based SM3 KDF; legacy APIs retain their format.

### Changed

- CI now verifies the documented minimum Elixir 1.14/OTP 24 toolchain in addition
to the current cross-platform toolchain.
- Release jobs now reject tags whose version does not match `mix.exs`.
- Removed the reserved, behaviorless SM2 CLI `--hex` option; SM2 keys,
signatures, and ciphertext remain hex-encoded where documented.
- Added public API documentation for SM2, SM3, and SM4, and report malformed
SM2 message iodata as `:invalid_input` instead of `:invalid_key`.
- CLI positional arguments now consistently mean message text; file input uses
the explicit `--file <path>` option for SM2, SM3, and SM4.
- Added SM4-CTR CLI encryption/decryption with an explicit required 16-byte
`--counter`; padding options are rejected in CTR mode.
- Added doctests for the documented facade and SM3 examples; CLI examples remain
covered by subprocess integration tests.
- Optimized SM3 compression with a rolling 16-word message schedule,
precomputed round constants, and specialized rotations.
- Optimized SM2 scalar multiplication with Jacobian coordinates and mixed
Expand All @@ -25,6 +42,12 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht

### Documentation

- Added an implementation-oriented SM2 standards and migration design covering
ZA/user IDs, signature encoding, KDF, ciphertext layout, and legacy handling.
- Added a security policy covering SM2 migration, SM4 integrity composition,
key/randomness handling, side-channel limits, and unsupported use cases.
- Documented reproducible benchmark comparison rules and scoped future streaming,
certificate parsing, and SM9 work with explicit implementation prerequisites.
- Corrected the README SM3 CLI example and removed unverified performance claims.
- Clarified API return values, binary formats, SM2 interoperability limits, and
SM2 long-message confidentiality limits and SM4 confidentiality-only mode caveats.
Expand Down
27 changes: 20 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@

国密算法纯 Elixir 实现(GM/T 0002-2012, GM/T 0003-2012, GM/T 0004-2012),无需外部依赖。

> 当前 SM2 签名与加密接口有明确的兼容性限制,不应直接视为完整的跨实现标准 SM2 接口;其中加密接口不得用于保护敏感数据。详见[兼容性与安全边界](#兼容性与安全边界)。
> SM2 标准接口已有公开向量和 OpenSSL 互操作测试,但尚未完成独立安全审查;旧兼容加密接口不得用于保护敏感数据。详见[兼容性与安全边界](#兼容性与安全边界)。

## 文档导航

- [库 API 快速入门](#使用)
- [命令行工具完整说明](cli.md)
- [SM2 标准兼容与迁移设计](sm2_migration.md)
- [安全策略与适用边界](SECURITY.md)
- [流式 API、证书与 SM9 范围](future_work.md)
- [版本变更记录](CHANGELOG.md)
- [开发路线图](todo.md)
- [发布维护指南](hex.pm.md)
Expand All @@ -39,8 +42,8 @@
| SM3 | 纯 Elixir 实现(GM/T 0004-2012 压缩函数),32 字节摘要 |
| SM4 ECB/CBC | 纯 Elixir 实现(GM/T 0002-2012 S-box 与密钥扩展),支持 `:pkcs7` 与 `:none` 填充 |
| SM4 CTR | 纯 Elixir 实现,16 字节初始计数器块按大端 128 位整数递增,不提供认证 |
| SM2 密钥/签名 | 纯 Elixir 椭圆曲线运算,SM3 预哈希,64 字节 raw `r || s` 签名 |
| SM2 加密/解密 | Guomi 内部 `C1 || C2 || C3` 格式,仅保留用于兼容和测试;不得用于敏感数据 |
| SM2 密钥/签名 | 标准接口计算 ZA,签名为 64 字节 raw `r || s`;另保留不计算 ZA 的旧兼容接口 |
| SM2 加密/解密 | 标准接口使用 `C1 || C3 || C2` 和 SM3 KDF;旧 `C1 || C2 || C3` 接口仅用于兼容 |

## API 约定

Expand Down Expand Up @@ -161,6 +164,16 @@ Guomi.SM4.supported?()
# 解密
{:ok, plaintext} = Guomi.SM2.decrypt(ciphertext, private_key)

# 标准签名 API 要求显式 user ID,并计算 SM3(ZA || message)
user_id = "1234567812345678"
{:ok, standard_signature} = Guomi.SM2.sign_standard("message", private_key, user_id)
{:ok, true} =
Guomi.SM2.verify_standard("message", standard_signature, public_key, user_id)

# 标准加密使用 C1 || C3 || C2 与可扩展 SM3 KDF(当前拒绝空消息)
{:ok, standard_ciphertext} = Guomi.SM2.encrypt_standard("secret message", public_key)
{:ok, "secret message"} = Guomi.SM2.decrypt_standard(standard_ciphertext, private_key)

# 检查运行时支持(始终为 true)
Guomi.SM2.supported?()
#=> true
Expand All @@ -176,7 +189,7 @@ Guomi.SM2.verify("message", <<0>>, <<0>>)
#=> {:error, :invalid_key}
```

SM2 可能返回 `:invalid_key`、`:invalid_signature`、`:invalid_ciphertext` 或 `:decryption_failed`。SM4 可能返回 `:invalid_key_size`、`:invalid_iv_size`、`:invalid_block_size` 或 `:invalid_padding`。
SM2 可能返回 `:invalid_input`、`:invalid_key`、`:invalid_signature`、`:invalid_ciphertext` 或 `:decryption_failed`。SM4 可能返回 `:invalid_key_size`、`:invalid_iv_size`、`:invalid_block_size` 或 `:invalid_padding`。

## CLI 工具

Expand Down Expand Up @@ -222,7 +235,7 @@ guomi sm2 --generate
echo "message" | guomi sm2 --sign --private-key <hex-key>

# SM2 验签
guomi sm2 --verify --public-key <hex-key> --signature <hex-sig> message.txt
guomi sm2 --verify --public-key <hex-key> --signature <hex-sig> --file message.txt
```

### 完整文档
Expand Down Expand Up @@ -341,8 +354,8 @@ See [CHANGELOG.md](CHANGELOG.md) for a detailed list of changes.

## 兼容性与安全边界

- **SM2 签名**:使用 SM3 预哈希与 raw `r || s` 签名格式,未实现用户 ID/ZA 参数,不应假定与 OpenSSL 或其他 SM2 实现互通。
- **SM2 加密**:采用本库内部的 ECDH + SM3 派生密钥 + XOR + SM3 MAC 构造及 `C1 || C2 || C3` 格式。当前实现会对超过 32 字节的消息重复使用同一段 XOR 掩码,使相隔 32 字节的密文泄露对应明文的 XOR 关系;即使 MAC 能检测篡改,也不能修复该机密性缺陷。此接口仅保留用于兼容和测试,不得用于保护敏感数据或生产协议。
- **SM2 签名**:`sign_standard/3`、`verify_standard/4` 提供显式用户 ID/ZA 的 raw `r || s` 接口;旧 `sign/2`、`verify/3` 只对消息做 SM3 预哈希,仅用于兼容。
- **SM2 加密**:`encrypt_standard/2`、`decrypt_standard/2` 使用 `C1 || C3 || C2` 与可扩展 SM3 KDF,但仍未经独立安全审查。旧 `encrypt/2`、`decrypt/2` 采用内部 `C1 || C2 || C3` 格式并对超过 32 字节的消息重复 XOR 掩码,仅用于兼容和测试,不得用于保护敏感数据或生产协议。
- **ECB**:会泄露明文模式,不要用于新系统中的敏感数据保护。
- **CBC**:每次加密必须使用不可预测且不复用的 16 字节 IV;当前接口不提供认证。
- **CTR**:同一密钥下不得复用初始计数器块;当前接口只提供机密性,不提供认证。
Expand Down
56 changes: 56 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Security Policy and Usage Boundaries

Guomi 是纯 Elixir 的 SM2、SM3、SM4 实现。除非发布说明另有明确记录,本项目没有密码产品认证,也没有完成独立第三方安全审计。

## 漏洞报告

请不要在公开 Issue 中披露尚未修复的安全漏洞。通过 GitHub Security Advisory 私下报告,并附上受影响版本、复现方式、潜在影响和建议修复方向。

## API 安全边界

### SM2

- 新代码应使用 `sign_standard/3`、`verify_standard/4`,并由协议显式规定相同的 user ID。
- 新代码应使用 `encrypt_standard/2`、`decrypt_standard/2`;它们采用标准 SM3 KDF 和 `C1 || C3 || C2` 裸格式。
- `sign/2`、`verify/3` 不计算 ZA,只为旧 Guomi 数据和脚本保留。
- `encrypt/2`、`decrypt/2` 会对长消息重复 32 字节掩码,只为旧数据迁移保留,不得用于敏感数据。
- 标准与旧解密入口不会互相猜测或回退。调用方必须根据可信的格式元数据选择入口。
- SM2 适合加密短密钥材料,不适合直接加密大文件。大数据应使用经过审查的混合加密协议。
- 标准 API 已覆盖公开向量和 OpenSSL 互操作,但这不等同于独立安全审计。

### SM4

- ECB 只适合标准向量测试或严格受控的遗留兼容,不适合一般消息。
- CBC 和 CTR 只提供机密性,不检测篡改。不得把成功解密或成功去除 padding 当作消息可信。
- CBC 的 IV 必须不可预测且不得在同一密钥下复用。
- CTR 的初始 counter 在同一密钥下必须唯一;复用会泄露明文之间的 XOR 关系。
- 需要完整性时,优先使用协议已经定义并经审查的认证加密方案。如果协议必须使用裸 SM4 CBC/CTR,应在协议层采用独立密钥的 encrypt-then-MAC,认证算法及标签长度必须由协议固定,并覆盖版本、算法标识、IV/counter、关联元数据和完整密文。
- 本库目前不提供通用 SM4 encrypt-then-MAC 高层 API,避免在没有协议上下文时规定密钥派生、标签编码或重放语义。应用不应自行拼接未经评审的组合。

### SM3

- SM3 是无密钥哈希函数,不等同于 MAC、密码散列或密钥派生函数。
- 需要消息认证时使用协议指定的 MAC;需要存储密码时使用专用、带盐且成本可调的密码哈希方案。

## 密钥、随机数与侧信道

- 私钥和对称密钥必须由受信任的密钥管理系统生成、存储和轮换,不应写入日志、命令历史或错误信息。
- SM2 临时标量和密钥使用 Erlang `:crypto.strong_rand_bytes/1`。部署方仍需保证操作系统随机源正常。
- 纯 Elixir 大整数和曲线运算没有声明为常量时间。对本地攻击者、共享主机或高价值密钥场景,不应假定可抵抗缓存、调度和计时侧信道。
- `secure_compare` 只减少摘要比较中的早退差异,不能使整个 SM2 解密路径成为常量时间。
- 解密端应限制输入大小、调用频率和错误可见性,防止资源消耗与错误预言机被协议放大。

## 明确不适用场景

- 需要经过国家密码管理认证、FIPS 验证或硬件密钥隔离的系统。
- 无法接受纯 Elixir 大整数侧信道风险的多租户或敌对共址环境。
- 直接加密大文件、数据库备份或无限长度网络流量。
- 仅依赖裸 SM4 CBC/CTR 提供身份认证、完整性或防重放的协议。
- 无可靠格式版本信息却需要自动区分旧 Guomi 密文和标准 SM2 密文的系统。

## 发布前安全检查

- 标准向量、负面测试和 OpenSSL 互操作测试通过。
- 新公开 API 的输入上限、错误分类、二进制格式和迁移影响已记录。
- release tag 与包版本一致,最低支持的 Elixir/OTP 组合通过 CI。
- 涉及密码学构造、随机数或解析边界的重大变更应在发布前接受独立审查。
20 changes: 20 additions & 0 deletions bench/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Benchmark Recording

Run benchmarks from a clean checkout with no other CPU-intensive workload:

```bash
mix run bench/bench.exs | tee bench/results/guomi-<version>-<date>-<host>.txt
```

The script records Elixir, OTP, ERTS, operating system, architecture, scheduler count, message sizes, warmups, samples and SM2 iteration count. Results report min/median/max; use the median for routine comparison and retain the range as noise context.

## Comparison rules

- Compare results only when architecture, scheduler count, runtime versions and message sizes match.
- Run both the baseline commit and candidate commit on the same host and power profile.
- Keep raw output under `bench/results/` in release or performance PR artifacts; do not commit machine-specific results by default.
- Treat a repeatable median regression above 10% as investigation-worthy, not automatically as a release failure.
- Re-run at least twice before drawing a conclusion. Single wall-clock samples are not performance evidence.
- Record the compared Git commits and any VM/container/CPU-frequency constraints beside the raw output.

`bench/results/` is a result exchange convention, not a CI gate. A future gate should use dedicated, stable hardware and a statistically justified threshold.
Loading
Loading