Compare commits

..

4 Commits

75 changed files with 10375 additions and 1928 deletions

35
.gitignore vendored Normal file
View File

@ -0,0 +1,35 @@
# IDE / editor
.idea/
.vscode/
# Local Go module/cache sandboxes used in this repo
.gopath/
.gomodcache/
.gocache/
# Build artifacts
*.exe
*.dll
*.so
*.dylib
*.test
*.out
*.prof
bin/
dist/
# Coverage
coverage.out
*.coverprofile
# OS files
.DS_Store
Thumbs.db
# Temp/test artifacts
sm3/ifile
# Agent workflow artifacts
.sentrux/
agent_readme.md
target.md

85
CHANGELOG.md Normal file
View File

@ -0,0 +1,85 @@
# Changelog
## 2026-03-17
---
### Added
- Added AES-XTS and SM4-XTS APIs in `symm` and root wrappers:
- bytes APIs: `Encrypt*/Decrypt*XTS`
- data-unit indexed APIs: `Encrypt*/Decrypt*XTSAt`
- stream APIs: `Encrypt*/Decrypt*XTSStream`
- stream + data-unit indexed APIs: `Encrypt*/Decrypt*XTSStreamAt`
- Added XTS master-key split helpers:
- `SplitXTSMasterKey`
- `SplitAesXTSMasterKey`
- `SplitSM4XTSMasterKey`
- Added XTS parameter validation and explicit no-CTS behavior:
- dual keys must be non-empty and equal length
- `dataUnitSize` must be a positive multiple of 16
- non-stream input must be 16-byte aligned
- stream tail must be 16-byte aligned
- Added key-derivation APIs in `hashx` and root wrappers:
- `DerivePBKDF2SHA256Key` (`crypto/pbkdf2`, stdlib)
- `DerivePBKDF2SHA512Key` (`crypto/pbkdf2`, stdlib)
- `DeriveArgon2idKey` / `DeriveArgon2iKey` (`golang.org/x/crypto/argon2`)
- `Argon2Params` + `DefaultArgon2idParams`
- Added benchmark coverage for symmetric hot paths:
- AES/SM4 `GCM`, `CCM`, `XTS`
- AES/SM4 `XTS` stream path
- Added file random fill APIs:
- `FillWithRandom` (math/rand pseudo-random)
- `FillWithCryptoRandom` (crypto/rand secure random, may be slower)
- Added HMAC verify APIs (bytes + hex wrappers) in `macx` and root package.
- Added XTS standard-vector tests (IEEE P1619 subset) and XTS fuzz tests.
- Added CCM/XTS related test coverage for root wrappers and `symm`.
### Changed
- Refactored XTS internals to reduce duplication via shared factory/path.
- Switched AEAD options behavior to require explicit `Nonce` (no `IV` fallback in GCM/CCM paths).
- Reworked CFB-8 register update to ring-buffer state handling.
- Refined `README.md` structure and added:
- XTS usage/constraints documentation
- AEAD nonce non-reuse requirement
- AEAD `CipherOptions` nonce-only behavior note
- legacy GCM/CCM stream decryption memory warning
- `FillWithRandom` vs `FillWithCryptoRandom` guidance
- XTS `dataUnitIndex` mapping consistency note
## Unreleased
### Added
- Introduced subpackages and root wrappers:
- `asymm`, `symm`, `hashx`, `encodingx`, `paddingx`, `filex`, `legacy`, `macx`.
- Added Chinese `README.md` and Apache-2.0 `LICENSE`.
- Added `SM9` support in asymmetric APIs.
- Added `ChaCha20` and `ChaCha20-Poly1305` APIs (memory + stream wrappers where applicable).
- Added unified symmetric cipher options API:
- `CipherOptions{Mode, Padding, IV, Nonce, AAD}`.
- Added AEAD APIs and wrappers:
- `AES-GCM`, `SM4-GCM`, `AES-CCM`, `SM4-CCM` (bytes + chunk + stream helper APIs).
- Added more symmetric mode coverage for SM4:
- `ECB/CBC/CFB/OFB/CTR` (bytes + stream derived APIs).
- Added comprehensive tests across packages and root wrappers.
- Added fuzz tests for `paddingx`, `encodingx`, and `symm` round-trip invariants.
### Changed
- Refactored monolithic implementation to subpackage architecture while preserving root-package convenience APIs.
- AES mode APIs now support generic mode selection and derived mode helpers.
- Stream APIs expanded across AES/SM4/DES/3DES and ChaCha20.
- Updated docs to include a security-first recommendation and algorithm capability matrix.
- Updated dependencies and modules for current code paths (`gmsm`, `x/crypto`).
- Refactored duplicated AES/SM4 symmetric code paths by extracting shared dispatch/helpers in `symm`.
- Unified hash method validation semantics: `hashx.SumAll` and `hashx.FileSumAll` now return errors for unknown methods (aligned with `FileSum`).
- Updated `hashx` tests for unsupported-method behavior.
### Fixed
- Fixed Base128 encode/decode round-trip bug in `encodingx`.
- Corrected CRC32A test expectations and clarified CRC32A variant comments.
- Corrected default padding behavior for AES-CBC to PKCS7.
### Notes
- Legacy/insecure algorithms and modes remain available for compatibility.
- Production recommendations now explicitly prefer AEAD schemes.

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

140
README.md Normal file
View File

@ -0,0 +1,140 @@
# starcrypto
`starcrypto` 是一个 Go 单包聚合风格的密码学工具库:根包可直接调用,同时内部拆分为子包实现,兼顾易用性与可维护性。
## 安装
```bash
go get b612.me/starcrypto
```
## 特性
- 根包直调 + 子包分层(`asymm` / `symm` / `hashx` / `encodingx` / `paddingx` 等)
- 对称算法AES、SM4、DES、3DES、ChaCha20、ChaCha20-Poly1305
- AEADAES-GCM/AES-CCM、SM4-GCM/SM4-CCM、ChaCha20-Poly1305
- 存储加密AES-XTS、SM4-XTS双 key支持 data unit 索引与流式)
- 非对称算法RSA、ECDSA、SM2、SM9
- 哈希与消息认证SHA 系列、MD5/MD4、RIPEMD160、SM3、CRC32/CRC32A、HMAC
- 支持内存 `[]byte` 与流式 `io.Reader/io.Writer`
## 推荐用法(安全优先)
- 通用数据传输优先使用 AEAD
- `EncryptAesGCM/DecryptAesGCM`
- `EncryptAesCCM/DecryptAesCCM`
- `EncryptSM4GCM/DecryptSM4GCM`
- `EncryptSM4CCM/DecryptSM4CCM`
- `EncryptChaCha20Poly1305/DecryptChaCha20Poly1305`
- 磁盘/扇区类场景可用 XTS
- `EncryptAesXTS/DecryptAesXTS`
- `EncryptSM4XTS/DecryptSM4XTS`
- 统一选项接口(默认 `GCM`
- `EncryptAesWithOptions/DecryptAesWithOptions`
- `EncryptSM4WithOptions/DecryptSM4WithOptions`
> `CBC/CFB/OFB/CTR/ECB/XTS` 不提供完整性校验,不能替代 AEAD 的篡改检测能力。
> AEAD (`GCM/CCM/ChaCha20-Poly1305`) 下,同一把 key 绝不能重复使用同一个 nonce。
> 使用 `CipherOptions`AEAD 模式仅读取 `Nonce` 字段,不再回退 `IV`
> GCM/CCM 流解密在 legacy 兼容分支会缓冲完整密文到内存;大文件建议使用分块流格式(带 `SCG1/SCC1` 头)。
## XTS 约束(重要)
- 两个密钥分开传入:`key1`, `key2`,且长度必须相同且非空。
- `dataUnitSize`(数据单元大小)由调用方自定义,但必须是正整数且为 `16` 的倍数。
- `dataUnitIndex` 必须与存储层的逻辑块映射保持稳定一致;同一数据单元在加密和解密时索引必须一致。
- 当前实现不做 CTSciphertext stealing
- 非流式 API输入长度必须满足 `len(data) % 16 == 0`,否则直接返回错误。
- 流式 API最终尾块若不是 `16` 字节对齐会返回错误。
## 文件随机填充
- `FillWithRandom` 使用 `math/rand` 伪随机,速度更高,但不适合安全用途。
- `FillWithCryptoRandom` 使用 `crypto/rand`,安全性更高,但速度可能受限。
## 快速示例
### 统一 Options默认 GCM
```go
package main
import (
"fmt"
"log"
"b612.me/starcrypto"
)
func main() {
key := []byte("0123456789abcdef")
nonce := []byte("123456789012")
plain := []byte("hello starcrypto")
opts := &starcrypto.CipherOptions{Nonce: nonce}
enc, err := starcrypto.EncryptAesWithOptions(plain, key, opts)
if err != nil {
log.Fatal(err)
}
dec, err := starcrypto.DecryptAesWithOptions(enc, key, opts)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(dec))
}
```
### AES-XTS按 data unit
```go
package main
import (
"bytes"
"log"
"b612.me/starcrypto"
)
func main() {
k1 := []byte("0123456789abcdef")
k2 := []byte("fedcba9876543210")
plain := bytes.Repeat([]byte("0123456789abcdef"), 8) // 128 bytes, 16-byte aligned
// 每个 data unit 64 字节
enc, err := starcrypto.EncryptAesXTS(plain, k1, k2, 64)
if err != nil {
log.Fatal(err)
}
dec, err := starcrypto.DecryptAesXTS(enc, k1, k2, 64)
if err != nil {
log.Fatal(err)
}
_ = dec
}
```
## 算法能力矩阵
| 算法 | 模式 / 方案 | AEAD | Stream | 建议 |
|---|---|---:|---:|---|
| AES | ECB/CBC/CFB/OFB/CTR/GCM/CCM/XTS | GCM/CCM: 是XTS: 否 | 是 | 生产优先 GCM/CCM存储场景可用 XTS |
| SM4 | ECB/CBC/CFB/OFB/CTR/GCM/CCM/XTS | GCM/CCM: 是XTS: 否 | 是 | 生产优先 GCM/CCM存储场景可用 XTS |
| ChaCha20 | ChaCha20 / ChaCha20-Poly1305 | Poly1305: 是 | ChaCha20: 是 | 生产优先 ChaCha20-Poly1305 |
| DES | CBC | 否 | 是 | 仅兼容历史系统 |
| 3DES | CBC | 否 | 是 | 仅兼容历史系统 |
## 默认填充策略
- AES/SM4 的 CBC/ECB 默认:`PKCS7`
- DES/3DES 的 CBC 默认:`PKCS5`
- CFB/OFB/CTR/GCM/CCM/XTS/ChaCha20 不使用填充
## 兼容性说明
库中保留了部分历史/兼容用途算法与接口(例如 `ECB``DES/3DES`)。如无兼容要求,建议优先使用 AEAD 方案。
- `hashx``SumAll` / `FileSumAll` / `FileSum` 对未知算法名会返回错误(不再静默忽略)。
## 许可证
本项目使用 Apache-2.0 许可证,详见 `LICENSE`

34
THIRD_PARTY_NOTICES.md Normal file
View File

@ -0,0 +1,34 @@
# Third-Party Notices
This repository is primarily licensed under Apache-2.0. Some files are copied from third-party projects and remain under their original licenses.
## Pion DTLS CCM implementation
- Upstream project: https://github.com/pion/dtls
- Upstream path: `pkg/crypto/ccm`
- Local files:
- `ccm/ccm.go`
- `ccm/ccm_test.go`
- License: MIT
### MIT License (Pion DTLS)
Copyright (c) 2026 The Pion community <https://pion.ly>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

353
aes.go
View File

@ -1,122 +1,283 @@
package starcrypto
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"io"
"b612.me/starcrypto/symm"
)
type CipherOptions = symm.CipherOptions
const (
PKCS5PADDING = "PKCS5"
PKCS5PADDING = symm.PKCS5PADDING
PKCS7PADDING = symm.PKCS7PADDING
ZEROPADDING = symm.ZEROPADDING
ANSIX923PADDING = symm.ANSIX923PADDING
MODEECB = symm.MODEECB
MODECBC = symm.MODECBC
MODECFB = symm.MODECFB
MODEOFB = symm.MODEOFB
MODECTR = symm.MODECTR
MODEGCM = symm.MODEGCM
MODECCM = symm.MODECCM
)
func CustomEncryptAesCFB(origData []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
encrypted := make([]byte, aes.BlockSize+len(origData))
iv := encrypted[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(encrypted[aes.BlockSize:], origData)
return encrypted, nil
func EncryptAes(data, key, iv []byte, mode, paddingType string) ([]byte, error) {
return symm.EncryptAes(data, key, iv, mode, paddingType)
}
func CustomDecryptAesCFB(encrypted []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(encrypted) < aes.BlockSize {
return nil, errors.New("ciphertext too short")
}
iv := encrypted[:aes.BlockSize]
encrypted = encrypted[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(encrypted, encrypted)
return encrypted, nil
func DecryptAes(src, key, iv []byte, mode, paddingType string) ([]byte, error) {
return symm.DecryptAes(src, key, iv, mode, paddingType)
}
func CustomEncryptAesCFBNoBlock(origData []byte, key []byte, iv []byte) ([]byte, error) {
if len(iv) != 16 {
return nil, errors.New("iv length must be 16")
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
encrypted := make([]byte, len(origData))
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(encrypted, origData)
return encrypted, err
func EncryptAesStream(dst io.Writer, src io.Reader, key, iv []byte, mode, paddingType string) error {
return symm.EncryptAesStream(dst, src, key, iv, mode, paddingType)
}
func CustomDecryptAesCFBNoBlock(encrypted []byte, key []byte, iv []byte) ([]byte, error) {
if len(iv) != 16 {
return nil, errors.New("iv length must be 16")
func DecryptAesStream(dst io.Writer, src io.Reader, key, iv []byte, mode, paddingType string) error {
return symm.DecryptAesStream(dst, src, key, iv, mode, paddingType)
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
func EncryptAesWithOptions(data, key []byte, opts *CipherOptions) ([]byte, error) {
return symm.EncryptAesWithOptions(data, key, opts)
}
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(encrypted, encrypted)
return encrypted, err
func DecryptAesWithOptions(src, key []byte, opts *CipherOptions) ([]byte, error) {
return symm.DecryptAesWithOptions(src, key, opts)
}
func EncryptAesStreamWithOptions(dst io.Writer, src io.Reader, key []byte, opts *CipherOptions) error {
return symm.EncryptAesStreamWithOptions(dst, src, key, opts)
}
func DecryptAesStreamWithOptions(dst io.Writer, src io.Reader, key []byte, opts *CipherOptions) error {
return symm.DecryptAesStreamWithOptions(dst, src, key, opts)
}
func EncryptAesGCM(plain, key, nonce, aad []byte) ([]byte, error) {
return symm.EncryptAesGCM(plain, key, nonce, aad)
}
func DecryptAesGCM(ciphertext, key, nonce, aad []byte) ([]byte, error) {
return symm.DecryptAesGCM(ciphertext, key, nonce, aad)
}
func EncryptAesGCMChunk(plain, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return symm.EncryptAesGCMChunk(plain, key, nonce, aad, chunkIndex)
}
func DecryptAesGCMChunk(ciphertext, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return symm.DecryptAesGCMChunk(ciphertext, key, nonce, aad, chunkIndex)
}
func EncryptAesGCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return symm.EncryptAesGCMStream(dst, src, key, nonce, aad)
}
func DecryptAesGCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return symm.DecryptAesGCMStream(dst, src, key, nonce, aad)
}
func EncryptAesCCM(plain, key, nonce, aad []byte) ([]byte, error) {
return symm.EncryptAesCCM(plain, key, nonce, aad)
}
func DecryptAesCCM(ciphertext, key, nonce, aad []byte) ([]byte, error) {
return symm.DecryptAesCCM(ciphertext, key, nonce, aad)
}
func EncryptAesCCMChunk(plain, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return symm.EncryptAesCCMChunk(plain, key, nonce, aad, chunkIndex)
}
func DecryptAesCCMChunk(ciphertext, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return symm.DecryptAesCCMChunk(ciphertext, key, nonce, aad, chunkIndex)
}
func EncryptAesCCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return symm.EncryptAesCCMStream(dst, src, key, nonce, aad)
}
func DecryptAesCCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return symm.DecryptAesCCMStream(dst, src, key, nonce, aad)
}
func EncryptAesECB(data, key []byte, paddingType string) ([]byte, error) {
return symm.EncryptAesECB(data, key, paddingType)
}
func DecryptAesECB(src, key []byte, paddingType string) ([]byte, error) {
return symm.DecryptAesECB(src, key, paddingType)
}
func EncryptAesCBC(data, key []byte, iv []byte, paddingType string) ([]byte, error) {
var content []byte
aesBlockEncrypter, err := aes.NewCipher(key)
switch paddingType {
case PKCS5PADDING:
content = PKCS5Padding(data, aesBlockEncrypter.BlockSize())
default:
return nil, errors.New("padding type not supported")
}
encrypted := make([]byte, len(content))
if err != nil {
return nil, err
}
aesEncrypter := cipher.NewCBCEncrypter(aesBlockEncrypter, iv)
aesEncrypter.CryptBlocks(encrypted, content)
return encrypted, nil
}
func PKCS5Padding(cipherText []byte, blockSize int) []byte {
padding := blockSize - len(cipherText)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(cipherText, padText...)
}
func PKCS5Trimming(encrypt []byte) []byte {
padding := encrypt[len(encrypt)-1]
if len(encrypt)-int(padding) < 0 {
return nil
}
return encrypt[:len(encrypt)-int(padding)]
return symm.EncryptAesCBC(data, key, iv, paddingType)
}
func DecryptAesCBC(src, key []byte, iv []byte, paddingType string) (data []byte, err error) {
decrypted := make([]byte, len(src))
var aesBlockDecrypter cipher.Block
aesBlockDecrypter, err = aes.NewCipher(key)
if err != nil {
println(err.Error())
return nil, err
return symm.DecryptAesCBC(src, key, iv, paddingType)
}
aesDecrypter := cipher.NewCBCDecrypter(aesBlockDecrypter, iv)
aesDecrypter.CryptBlocks(decrypted, src)
switch paddingType {
case PKCS5PADDING:
return PKCS5Trimming(decrypted), nil
default:
return nil, errors.New("padding type not supported")
func EncryptAesCFB(data, key, iv []byte) ([]byte, error) {
return symm.EncryptAesCFB(data, key, iv)
}
func DecryptAesCFB(src, key, iv []byte) ([]byte, error) {
return symm.DecryptAesCFB(src, key, iv)
}
func EncryptAesOFB(data, key, iv []byte) ([]byte, error) {
return symm.EncryptAesOFB(data, key, iv)
}
func DecryptAesOFB(src, key, iv []byte) ([]byte, error) {
return symm.DecryptAesOFB(src, key, iv)
}
func EncryptAesCTR(data, key, iv []byte) ([]byte, error) {
return symm.EncryptAesCTR(data, key, iv)
}
func DecryptAesCTR(src, key, iv []byte) ([]byte, error) {
return symm.DecryptAesCTR(src, key, iv)
}
func EncryptAesCTRAt(data, key, iv []byte, offset int64) ([]byte, error) {
return symm.EncryptAesCTRAt(data, key, iv, offset)
}
func DecryptAesCTRAt(src, key, iv []byte, offset int64) ([]byte, error) {
return symm.DecryptAesCTRAt(src, key, iv, offset)
}
func EncryptAesECBStream(dst io.Writer, src io.Reader, key []byte, paddingType string) error {
return symm.EncryptAesECBStream(dst, src, key, paddingType)
}
func DecryptAesECBStream(dst io.Writer, src io.Reader, key []byte, paddingType string) error {
return symm.DecryptAesECBStream(dst, src, key, paddingType)
}
func EncryptAesCBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
return symm.EncryptAesCBCStream(dst, src, key, iv, paddingType)
}
func DecryptAesCBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
return symm.DecryptAesCBCStream(dst, src, key, iv, paddingType)
}
func EncryptAesCFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.EncryptAesCFBStream(dst, src, key, iv)
}
func DecryptAesCFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.DecryptAesCFBStream(dst, src, key, iv)
}
func EncryptAesOFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.EncryptAesOFBStream(dst, src, key, iv)
}
func DecryptAesOFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.DecryptAesOFBStream(dst, src, key, iv)
}
func EncryptAesCTRStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.EncryptAesCTRStream(dst, src, key, iv)
}
func DecryptAesCTRStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.DecryptAesCTRStream(dst, src, key, iv)
}
func CustomEncryptAesCFB(origData []byte, key []byte) ([]byte, error) {
return symm.CustomEncryptAesCFB(origData, key)
}
func CustomDecryptAesCFB(encrypted []byte, key []byte) ([]byte, error) {
return symm.CustomDecryptAesCFB(encrypted, key)
}
func CustomEncryptAesCFBNoBlock(origData []byte, key []byte, iv []byte) ([]byte, error) {
return symm.CustomEncryptAesCFBNoBlock(origData, key, iv)
}
func CustomDecryptAesCFBNoBlock(encrypted []byte, key []byte, iv []byte) ([]byte, error) {
return symm.CustomDecryptAesCFBNoBlock(encrypted, key, iv)
}
func PKCS5Padding(cipherText []byte, blockSize int) []byte {
return symm.PKCS5Padding(cipherText, blockSize)
}
func PKCS5Trimming(encrypt []byte) []byte {
return symm.PKCS5Trimming(encrypt)
}
func PKCS7Padding(cipherText []byte, blockSize int) []byte {
return symm.PKCS7Padding(cipherText, blockSize)
}
func PKCS7Trimming(encrypt []byte, blockSize int) []byte {
return symm.PKCS7Trimming(encrypt, blockSize)
}
func EncryptAesCFB8(data, key, iv []byte) ([]byte, error) {
return symm.EncryptAesCFB8(data, key, iv)
}
func DecryptAesCFB8(src, key, iv []byte) ([]byte, error) {
return symm.DecryptAesCFB8(src, key, iv)
}
func EncryptAesCFB8Stream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.EncryptAesCFB8Stream(dst, src, key, iv)
}
func DecryptAesCFB8Stream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.DecryptAesCFB8Stream(dst, src, key, iv)
}
func DecryptAesECBBlocks(src, key []byte) ([]byte, error) {
return symm.DecryptAesECBBlocks(src, key)
}
func DecryptAesCBCFromSecondBlock(src, key, prevCipherBlock []byte) ([]byte, error) {
return symm.DecryptAesCBCFromSecondBlock(src, key, prevCipherBlock)
}
func DecryptAesCFBFromSecondBlock(src, key, prevCipherBlock []byte) ([]byte, error) {
return symm.DecryptAesCFBFromSecondBlock(src, key, prevCipherBlock)
}
func EncryptAesXTS(plain, key1, key2 []byte, dataUnitSize int) ([]byte, error) {
return symm.EncryptAesXTS(plain, key1, key2, dataUnitSize)
}
func DecryptAesXTS(ciphertext, key1, key2 []byte, dataUnitSize int) ([]byte, error) {
return symm.DecryptAesXTS(ciphertext, key1, key2, dataUnitSize)
}
func EncryptAesXTSAt(plain, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) ([]byte, error) {
return symm.EncryptAesXTSAt(plain, key1, key2, dataUnitSize, dataUnitIndex)
}
func DecryptAesXTSAt(ciphertext, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) ([]byte, error) {
return symm.DecryptAesXTSAt(ciphertext, key1, key2, dataUnitSize, dataUnitIndex)
}
func EncryptAesXTSStream(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int) error {
return symm.EncryptAesXTSStream(dst, src, key1, key2, dataUnitSize)
}
func DecryptAesXTSStream(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int) error {
return symm.DecryptAesXTSStream(dst, src, key1, key2, dataUnitSize)
}
func EncryptAesXTSStreamAt(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) error {
return symm.EncryptAesXTSStreamAt(dst, src, key1, key2, dataUnitSize, dataUnitIndex)
}
func DecryptAesXTSStreamAt(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) error {
return symm.DecryptAesXTSStreamAt(dst, src, key1, key2, dataUnitSize, dataUnitIndex)
}

48
api_security_test.go Normal file
View File

@ -0,0 +1,48 @@
package starcrypto
import (
"os"
"path/filepath"
"testing"
)
func TestRootHmacVerifyWrappers(t *testing.T) {
msg := []byte("hmac-verify-message")
key := []byte("hmac-verify-key")
sum := HmacSHA256(msg, key)
if !VerifyHmacSHA256(msg, key, sum) {
t.Fatalf("VerifyHmacSHA256 should pass for correct digest")
}
bad := make([]byte, len(sum))
copy(bad, sum)
bad[0] ^= 0xff
if VerifyHmacSHA256(msg, key, bad) {
t.Fatalf("VerifyHmacSHA256 should fail for wrong digest")
}
hexSum := HmacSHA256Str(msg, key)
if !VerifyHmacSHA256Str(msg, key, hexSum) {
t.Fatalf("VerifyHmacSHA256Str should pass for correct digest")
}
if VerifyHmacSHA256Str(msg, key, "not-hex") {
t.Fatalf("VerifyHmacSHA256Str should fail for invalid hex")
}
}
func TestRootFillWithCryptoRandomWrapper(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "secure.bin")
if err := FillWithCryptoRandom(path, 1024, 128, nil); err != nil {
t.Fatalf("FillWithCryptoRandom failed: %v", err)
}
info, err := os.Stat(path)
if err != nil {
t.Fatalf("stat file failed: %v", err)
}
if info.Size() != 1024 {
t.Fatalf("unexpected file size: %d", info.Size())
}
}

615
api_test.go Normal file
View File

@ -0,0 +1,615 @@
package starcrypto
import (
"bytes"
"testing"
)
func TestRootSymmetricWrappers(t *testing.T) {
aesKey := []byte("0123456789abcdef")
aesIV := []byte("abcdef9876543210")
plain := []byte("root-wrapper-aes")
aesEnc, err := EncryptAesCBC(plain, aesKey, aesIV, "")
if err != nil {
t.Fatalf("EncryptAesCBC failed: %v", err)
}
aesDec, err := DecryptAesCBC(aesEnc, aesKey, aesIV, "")
if err != nil {
t.Fatalf("DecryptAesCBC failed: %v", err)
}
if !bytes.Equal(aesDec, plain) {
t.Fatalf("aes wrapper mismatch")
}
aesCFBEnc, err := EncryptAes(plain, aesKey, aesIV, MODECFB, "")
if err != nil {
t.Fatalf("EncryptAes failed: %v", err)
}
aesCFBDec, err := DecryptAes(aesCFBEnc, aesKey, aesIV, MODECFB, "")
if err != nil {
t.Fatalf("DecryptAes failed: %v", err)
}
if !bytes.Equal(aesCFBDec, plain) {
t.Fatalf("aes generic wrapper mismatch")
}
aesStreamEnc := &bytes.Buffer{}
if err := EncryptAesCBCStream(aesStreamEnc, bytes.NewReader(plain), aesKey, aesIV, ""); err != nil {
t.Fatalf("EncryptAesCBCStream failed: %v", err)
}
aesStreamDec := &bytes.Buffer{}
if err := DecryptAesCBCStream(aesStreamDec, bytes.NewReader(aesStreamEnc.Bytes()), aesKey, aesIV, ""); err != nil {
t.Fatalf("DecryptAesCBCStream failed: %v", err)
}
if !bytes.Equal(aesStreamDec.Bytes(), plain) {
t.Fatalf("aes stream wrapper mismatch")
}
sm4Enc, err := EncryptSM4CBC(plain, aesKey, aesIV, "")
if err != nil {
t.Fatalf("EncryptSM4CBC failed: %v", err)
}
sm4Dec, err := DecryptSM4CBC(sm4Enc, aesKey, aesIV, "")
if err != nil {
t.Fatalf("DecryptSM4CBC failed: %v", err)
}
if !bytes.Equal(sm4Dec, plain) {
t.Fatalf("sm4 wrapper mismatch")
}
sm4StreamEnc := &bytes.Buffer{}
if err := EncryptSM4CBCStream(sm4StreamEnc, bytes.NewReader(plain), aesKey, aesIV, ""); err != nil {
t.Fatalf("EncryptSM4CBCStream failed: %v", err)
}
sm4StreamDec := &bytes.Buffer{}
if err := DecryptSM4CBCStream(sm4StreamDec, bytes.NewReader(sm4StreamEnc.Bytes()), aesKey, aesIV, ""); err != nil {
t.Fatalf("DecryptSM4CBCStream failed: %v", err)
}
if !bytes.Equal(sm4StreamDec.Bytes(), plain) {
t.Fatalf("sm4 stream wrapper mismatch")
}
desKey := []byte("12345678")
desIV := []byte("abcdefgh")
desEnc, err := EncryptDESCBC(plain, desKey, desIV, "")
if err != nil {
t.Fatalf("EncryptDESCBC failed: %v", err)
}
desDec, err := DecryptDESCBC(desEnc, desKey, desIV, "")
if err != nil {
t.Fatalf("DecryptDESCBC failed: %v", err)
}
if !bytes.Equal(desDec, plain) {
t.Fatalf("des wrapper mismatch")
}
desStreamEnc := &bytes.Buffer{}
if err := EncryptDESCBCStream(desStreamEnc, bytes.NewReader(plain), desKey, desIV, ""); err != nil {
t.Fatalf("EncryptDESCBCStream failed: %v", err)
}
desStreamDec := &bytes.Buffer{}
if err := DecryptDESCBCStream(desStreamDec, bytes.NewReader(desStreamEnc.Bytes()), desKey, desIV, ""); err != nil {
t.Fatalf("DecryptDESCBCStream failed: %v", err)
}
if !bytes.Equal(desStreamDec.Bytes(), plain) {
t.Fatalf("des stream wrapper mismatch")
}
}
func TestRootSM2AndSM9Wrappers(t *testing.T) {
sm2Priv, sm2Pub, err := GenerateSM2Key()
if err != nil {
t.Fatalf("GenerateSM2Key failed: %v", err)
}
msg := []byte("root-sm")
sig, err := SM2Sign(sm2Priv, msg, nil)
if err != nil {
t.Fatalf("SM2Sign failed: %v", err)
}
if !SM2Verify(sm2Pub, msg, sig, nil) {
t.Fatalf("SM2Verify failed")
}
signMaster, signPub, err := GenerateSM9SignMasterKey()
if err != nil {
t.Fatalf("GenerateSM9SignMasterKey failed: %v", err)
}
encryptMaster, encryptPub, err := GenerateSM9EncryptMasterKey()
if err != nil {
t.Fatalf("GenerateSM9EncryptMasterKey failed: %v", err)
}
uid := []byte("root@example.com")
signUser, err := GenerateSM9SignUserKey(signMaster, uid, 0)
if err != nil {
t.Fatalf("GenerateSM9SignUserKey failed: %v", err)
}
encryptUser, err := GenerateSM9EncryptUserKey(encryptMaster, uid, 0)
if err != nil {
t.Fatalf("GenerateSM9EncryptUserKey failed: %v", err)
}
sm9Sig, err := SM9SignASN1(signUser, msg)
if err != nil {
t.Fatalf("SM9SignASN1 failed: %v", err)
}
if !SM9VerifyASN1(signPub, uid, 0, msg, sm9Sig) {
t.Fatalf("SM9VerifyASN1 failed")
}
cipher, err := SM9Encrypt(encryptPub, uid, 0, msg)
if err != nil {
t.Fatalf("SM9Encrypt failed: %v", err)
}
dec, err := SM9Decrypt(encryptUser, uid, cipher)
if err != nil {
t.Fatalf("SM9Decrypt failed: %v", err)
}
if !bytes.Equal(dec, msg) {
t.Fatalf("SM9 wrapper decrypt mismatch")
}
}
func TestRootChaChaAndSM4DerivedWrappers(t *testing.T) {
key := []byte("0123456789abcdef0123456789abcdef")
nonce := []byte("123456789012")
aad := []byte("aad")
plain := []byte("root-chacha-wrapper")
chachaEnc, err := EncryptChaCha20(plain, key, nonce)
if err != nil {
t.Fatalf("EncryptChaCha20 failed: %v", err)
}
chachaDec, err := DecryptChaCha20(chachaEnc, key, nonce)
if err != nil {
t.Fatalf("DecryptChaCha20 failed: %v", err)
}
if !bytes.Equal(chachaDec, plain) {
t.Fatalf("chacha wrapper mismatch")
}
polyEnc, err := EncryptChaCha20Poly1305(plain, key, nonce, aad)
if err != nil {
t.Fatalf("EncryptChaCha20Poly1305 failed: %v", err)
}
polyDec, err := DecryptChaCha20Poly1305(polyEnc, key, nonce, aad)
if err != nil {
t.Fatalf("DecryptChaCha20Poly1305 failed: %v", err)
}
if !bytes.Equal(polyDec, plain) {
t.Fatalf("chacha20-poly1305 wrapper mismatch")
}
sm4Key := []byte("0123456789abcdef")
sm4IV := []byte("abcdef9876543210")
sm4Plain := []byte("root-sm4-derived")
ecbEnc, err := EncryptSM4ECB(sm4Plain, sm4Key, "")
if err != nil {
t.Fatalf("EncryptSM4ECB failed: %v", err)
}
ecbDec, err := DecryptSM4ECB(ecbEnc, sm4Key, "")
if err != nil {
t.Fatalf("DecryptSM4ECB failed: %v", err)
}
if !bytes.Equal(ecbDec, sm4Plain) {
t.Fatalf("sm4 ecb wrapper mismatch")
}
ofbEnc, err := EncryptSM4OFB(sm4Plain, sm4Key, sm4IV)
if err != nil {
t.Fatalf("EncryptSM4OFB failed: %v", err)
}
ofbDec, err := DecryptSM4OFB(ofbEnc, sm4Key, sm4IV)
if err != nil {
t.Fatalf("DecryptSM4OFB failed: %v", err)
}
if !bytes.Equal(ofbDec, sm4Plain) {
t.Fatalf("sm4 ofb wrapper mismatch")
}
ctrEnc, err := EncryptSM4CTR(sm4Plain, sm4Key, sm4IV)
if err != nil {
t.Fatalf("EncryptSM4CTR failed: %v", err)
}
ctrDec, err := DecryptSM4CTR(ctrEnc, sm4Key, sm4IV)
if err != nil {
t.Fatalf("DecryptSM4CTR failed: %v", err)
}
if !bytes.Equal(ctrDec, sm4Plain) {
t.Fatalf("sm4 ctr wrapper mismatch")
}
ecbStreamEnc := &bytes.Buffer{}
if err := EncryptSM4ECBStream(ecbStreamEnc, bytes.NewReader(sm4Plain), sm4Key, ""); err != nil {
t.Fatalf("EncryptSM4ECBStream failed: %v", err)
}
ecbStreamDec := &bytes.Buffer{}
if err := DecryptSM4ECBStream(ecbStreamDec, bytes.NewReader(ecbStreamEnc.Bytes()), sm4Key, ""); err != nil {
t.Fatalf("DecryptSM4ECBStream failed: %v", err)
}
if !bytes.Equal(ecbStreamDec.Bytes(), sm4Plain) {
t.Fatalf("sm4 ecb stream wrapper mismatch")
}
}
func TestRootOptionsAndGCMWrappers(t *testing.T) {
aesKey := []byte("0123456789abcdef")
sm4Key := []byte("0123456789abcdef")
nonce := []byte("123456789012")
plain := []byte("root-options-gcm")
aesEnc, err := EncryptAesGCM(plain, aesKey, nonce, []byte("aad"))
if err != nil {
t.Fatalf("EncryptAesGCM failed: %v", err)
}
aesDec, err := DecryptAesGCM(aesEnc, aesKey, nonce, []byte("aad"))
if err != nil {
t.Fatalf("DecryptAesGCM failed: %v", err)
}
if !bytes.Equal(aesDec, plain) {
t.Fatalf("aes gcm wrapper mismatch")
}
sm4Enc, err := EncryptSM4WithOptions(plain, sm4Key, &CipherOptions{Nonce: nonce})
if err != nil {
t.Fatalf("EncryptSM4WithOptions failed: %v", err)
}
sm4Dec, err := DecryptSM4WithOptions(sm4Enc, sm4Key, &CipherOptions{Nonce: nonce})
if err != nil {
t.Fatalf("DecryptSM4WithOptions failed: %v", err)
}
if !bytes.Equal(sm4Dec, plain) {
t.Fatalf("sm4 options wrapper mismatch")
}
}
func TestRootCTRAtAndGCMChunkWrappers(t *testing.T) {
aesKey := []byte("0123456789abcdef")
aesIV := []byte("abcdef9876543210")
aesNonce := []byte("123456789012")
plain := bytes.Repeat([]byte("root-ctr-offset-"), 64)
aesFull, err := EncryptAesCTR(plain, aesKey, aesIV)
if err != nil {
t.Fatalf("EncryptAesCTR failed: %v", err)
}
offset := 33
seg := aesFull[offset : offset+100]
decSeg, err := DecryptAesCTRAt(seg, aesKey, aesIV, int64(offset))
if err != nil {
t.Fatalf("DecryptAesCTRAt failed: %v", err)
}
if !bytes.Equal(decSeg, plain[offset:offset+100]) {
t.Fatalf("root aes ctr at mismatch")
}
chunkCipher, err := EncryptAesGCMChunk([]byte("root-aes-gcm-chunk"), aesKey, aesNonce, []byte("aad"), 2)
if err != nil {
t.Fatalf("EncryptAesGCMChunk failed: %v", err)
}
chunkPlain, err := DecryptAesGCMChunk(chunkCipher, aesKey, aesNonce, []byte("aad"), 2)
if err != nil {
t.Fatalf("DecryptAesGCMChunk failed: %v", err)
}
if !bytes.Equal(chunkPlain, []byte("root-aes-gcm-chunk")) {
t.Fatalf("root aes gcm chunk mismatch")
}
sm4Key := []byte("0123456789abcdef")
sm4IV := []byte("abcdef9876543210")
sm4Nonce := []byte("123456789012")
sm4Full, err := EncryptSM4CTR(plain, sm4Key, sm4IV)
if err != nil {
t.Fatalf("EncryptSM4CTR failed: %v", err)
}
sm4Seg := sm4Full[offset : offset+100]
sm4DecSeg, err := DecryptSM4CTRAt(sm4Seg, sm4Key, sm4IV, int64(offset))
if err != nil {
t.Fatalf("DecryptSM4CTRAt failed: %v", err)
}
if !bytes.Equal(sm4DecSeg, plain[offset:offset+100]) {
t.Fatalf("root sm4 ctr at mismatch")
}
sm4ChunkCipher, err := EncryptSM4GCMChunk([]byte("root-sm4-gcm-chunk"), sm4Key, sm4Nonce, []byte("aad"), 3)
if err != nil {
t.Fatalf("EncryptSM4GCMChunk failed: %v", err)
}
sm4ChunkPlain, err := DecryptSM4GCMChunk(sm4ChunkCipher, sm4Key, sm4Nonce, []byte("aad"), 3)
if err != nil {
t.Fatalf("DecryptSM4GCMChunk failed: %v", err)
}
if !bytes.Equal(sm4ChunkPlain, []byte("root-sm4-gcm-chunk")) {
t.Fatalf("root sm4 gcm chunk mismatch")
}
}
func TestRootCFB8AndSegmentDecryptWrappers(t *testing.T) {
key := []byte("0123456789abcdef")
iv := []byte("abcdef9876543210")
plain := bytes.Repeat([]byte("0123456789abcdef"), 4)
aesCFB8, err := EncryptAesCFB8(plain, key, iv)
if err != nil {
t.Fatalf("EncryptAesCFB8 failed: %v", err)
}
aesCFB8Dec, err := DecryptAesCFB8(aesCFB8, key, iv)
if err != nil {
t.Fatalf("DecryptAesCFB8 failed: %v", err)
}
if !bytes.Equal(aesCFB8Dec, plain) {
t.Fatalf("root aes cfb8 mismatch")
}
aesCBC, err := EncryptAesCBC(plain, key, iv, ZEROPADDING)
if err != nil {
t.Fatalf("EncryptAesCBC failed: %v", err)
}
aesCBCSegDec, err := DecryptAesCBCFromSecondBlock(aesCBC[len(iv):], key, aesCBC[:len(iv)])
if err != nil {
t.Fatalf("DecryptAesCBCFromSecondBlock failed: %v", err)
}
if !bytes.Equal(aesCBCSegDec, plain[len(iv):]) {
t.Fatalf("root aes cbc from-second-block mismatch")
}
aesCFB, err := EncryptAesCFB(plain, key, iv)
if err != nil {
t.Fatalf("EncryptAesCFB failed: %v", err)
}
aesCFBSegDec, err := DecryptAesCFBFromSecondBlock(aesCFB[len(iv):], key, aesCFB[:len(iv)])
if err != nil {
t.Fatalf("DecryptAesCFBFromSecondBlock failed: %v", err)
}
if !bytes.Equal(aesCFBSegDec, plain[len(iv):]) {
t.Fatalf("root aes cfb from-second-block mismatch")
}
sm4CFB8, err := EncryptSM4CFB8(plain, key, iv)
if err != nil {
t.Fatalf("EncryptSM4CFB8 failed: %v", err)
}
sm4CFB8Dec, err := DecryptSM4CFB8(sm4CFB8, key, iv)
if err != nil {
t.Fatalf("DecryptSM4CFB8 failed: %v", err)
}
if !bytes.Equal(sm4CFB8Dec, plain) {
t.Fatalf("root sm4 cfb8 mismatch")
}
sm4CBC, err := EncryptSM4CBC(plain, key, iv, ZEROPADDING)
if err != nil {
t.Fatalf("EncryptSM4CBC failed: %v", err)
}
sm4CBCSegDec, err := DecryptSM4CBCFromSecondBlock(sm4CBC[len(iv):], key, sm4CBC[:len(iv)])
if err != nil {
t.Fatalf("DecryptSM4CBCFromSecondBlock failed: %v", err)
}
if !bytes.Equal(sm4CBCSegDec, plain[len(iv):]) {
t.Fatalf("root sm4 cbc from-second-block mismatch")
}
sm4CFB, err := EncryptSM4CFBNoBlock(plain, key, iv)
if err != nil {
t.Fatalf("EncryptSM4CFB failed: %v", err)
}
sm4CFBSegDec, err := DecryptSM4CFBFromSecondBlock(sm4CFB[len(iv):], key, sm4CFB[:len(iv)])
if err != nil {
t.Fatalf("DecryptSM4CFBFromSecondBlock failed: %v", err)
}
if !bytes.Equal(sm4CFBSegDec, plain[len(iv):]) {
t.Fatalf("root sm4 cfb from-second-block mismatch")
}
}
func TestRootOptionsAndCCMWrappers(t *testing.T) {
aesKey := []byte("0123456789abcdef")
sm4Key := []byte("0123456789abcdef")
nonce := []byte("123456789012")
aad := []byte("aad")
plain := []byte("root-options-ccm")
aesEnc, err := EncryptAesCCM(plain, aesKey, nonce, aad)
if err != nil {
t.Fatalf("EncryptAesCCM failed: %v", err)
}
aesDec, err := DecryptAesCCM(aesEnc, aesKey, nonce, aad)
if err != nil {
t.Fatalf("DecryptAesCCM failed: %v", err)
}
if !bytes.Equal(aesDec, plain) {
t.Fatalf("aes ccm wrapper mismatch")
}
aesOpts := &CipherOptions{Mode: MODECCM, Nonce: nonce, AAD: aad}
aesOptEnc, err := EncryptAesWithOptions(plain, aesKey, aesOpts)
if err != nil {
t.Fatalf("EncryptAesWithOptions CCM failed: %v", err)
}
aesOptDec, err := DecryptAesWithOptions(aesOptEnc, aesKey, aesOpts)
if err != nil {
t.Fatalf("DecryptAesWithOptions CCM failed: %v", err)
}
if !bytes.Equal(aesOptDec, plain) {
t.Fatalf("aes options ccm wrapper mismatch")
}
aesChunkCipher, err := EncryptAesCCMChunk([]byte("root-aes-ccm-chunk"), aesKey, nonce, aad, 4)
if err != nil {
t.Fatalf("EncryptAesCCMChunk failed: %v", err)
}
aesChunkPlain, err := DecryptAesCCMChunk(aesChunkCipher, aesKey, nonce, aad, 4)
if err != nil {
t.Fatalf("DecryptAesCCMChunk failed: %v", err)
}
if !bytes.Equal(aesChunkPlain, []byte("root-aes-ccm-chunk")) {
t.Fatalf("root aes ccm chunk mismatch")
}
aesStreamEnc := &bytes.Buffer{}
if err := EncryptAesCCMStream(aesStreamEnc, bytes.NewReader(plain), aesKey, nonce, aad); err != nil {
t.Fatalf("EncryptAesCCMStream failed: %v", err)
}
aesStreamDec := &bytes.Buffer{}
if err := DecryptAesCCMStream(aesStreamDec, bytes.NewReader(aesStreamEnc.Bytes()), aesKey, nonce, aad); err != nil {
t.Fatalf("DecryptAesCCMStream failed: %v", err)
}
if !bytes.Equal(aesStreamDec.Bytes(), plain) {
t.Fatalf("aes ccm stream wrapper mismatch")
}
sm4Enc, err := EncryptSM4CCM(plain, sm4Key, nonce, aad)
if err != nil {
t.Fatalf("EncryptSM4CCM failed: %v", err)
}
sm4Dec, err := DecryptSM4CCM(sm4Enc, sm4Key, nonce, aad)
if err != nil {
t.Fatalf("DecryptSM4CCM failed: %v", err)
}
if !bytes.Equal(sm4Dec, plain) {
t.Fatalf("sm4 ccm wrapper mismatch")
}
sm4Opts := &CipherOptions{Mode: MODECCM, Nonce: nonce, AAD: aad}
sm4OptEnc, err := EncryptSM4WithOptions(plain, sm4Key, sm4Opts)
if err != nil {
t.Fatalf("EncryptSM4WithOptions CCM failed: %v", err)
}
sm4OptDec, err := DecryptSM4WithOptions(sm4OptEnc, sm4Key, sm4Opts)
if err != nil {
t.Fatalf("DecryptSM4WithOptions CCM failed: %v", err)
}
if !bytes.Equal(sm4OptDec, plain) {
t.Fatalf("sm4 options ccm wrapper mismatch")
}
sm4ChunkCipher, err := EncryptSM4CCMChunk([]byte("root-sm4-ccm-chunk"), sm4Key, nonce, aad, 6)
if err != nil {
t.Fatalf("EncryptSM4CCMChunk failed: %v", err)
}
sm4ChunkPlain, err := DecryptSM4CCMChunk(sm4ChunkCipher, sm4Key, nonce, aad, 6)
if err != nil {
t.Fatalf("DecryptSM4CCMChunk failed: %v", err)
}
if !bytes.Equal(sm4ChunkPlain, []byte("root-sm4-ccm-chunk")) {
t.Fatalf("root sm4 ccm chunk mismatch")
}
sm4StreamEnc := &bytes.Buffer{}
if err := EncryptSM4CCMStream(sm4StreamEnc, bytes.NewReader(plain), sm4Key, nonce, aad); err != nil {
t.Fatalf("EncryptSM4CCMStream failed: %v", err)
}
sm4StreamDec := &bytes.Buffer{}
if err := DecryptSM4CCMStream(sm4StreamDec, bytes.NewReader(sm4StreamEnc.Bytes()), sm4Key, nonce, aad); err != nil {
t.Fatalf("DecryptSM4CCMStream failed: %v", err)
}
if !bytes.Equal(sm4StreamDec.Bytes(), plain) {
t.Fatalf("sm4 ccm stream wrapper mismatch")
}
}
func TestRootXTSWrappers(t *testing.T) {
k1 := []byte("0123456789abcdef")
k2 := []byte("fedcba9876543210")
plain := bytes.Repeat([]byte("0123456789abcdef"), 16)
aesEnc, err := EncryptAesXTS(plain, k1, k2, 64)
if err != nil {
t.Fatalf("EncryptAesXTS failed: %v", err)
}
aesDec, err := DecryptAesXTS(aesEnc, k1, k2, 64)
if err != nil {
t.Fatalf("DecryptAesXTS failed: %v", err)
}
if !bytes.Equal(aesDec, plain) {
t.Fatalf("root aes xts mismatch")
}
aesSegEnc, err := EncryptAesXTSAt(plain[:64], k1, k2, 64, 1)
if err != nil {
t.Fatalf("EncryptAesXTSAt failed: %v", err)
}
aesSegDec, err := DecryptAesXTSAt(aesSegEnc, k1, k2, 64, 1)
if err != nil {
t.Fatalf("DecryptAesXTSAt failed: %v", err)
}
if !bytes.Equal(aesSegDec, plain[:64]) {
t.Fatalf("root aes xts at mismatch")
}
aesStreamEnc := &bytes.Buffer{}
if err := EncryptAesXTSStream(aesStreamEnc, bytes.NewReader(plain), k1, k2, 64); err != nil {
t.Fatalf("EncryptAesXTSStream failed: %v", err)
}
aesStreamDec := &bytes.Buffer{}
if err := DecryptAesXTSStream(aesStreamDec, bytes.NewReader(aesStreamEnc.Bytes()), k1, k2, 64); err != nil {
t.Fatalf("DecryptAesXTSStream failed: %v", err)
}
if !bytes.Equal(aesStreamDec.Bytes(), plain) {
t.Fatalf("root aes xts stream mismatch")
}
sm4Enc, err := EncryptSM4XTS(plain, k1, k2, 64)
if err != nil {
t.Fatalf("EncryptSM4XTS failed: %v", err)
}
sm4Dec, err := DecryptSM4XTS(sm4Enc, k1, k2, 64)
if err != nil {
t.Fatalf("DecryptSM4XTS failed: %v", err)
}
if !bytes.Equal(sm4Dec, plain) {
t.Fatalf("root sm4 xts mismatch")
}
sm4SegEnc, err := EncryptSM4XTSAt(plain[:64], k1, k2, 64, 2)
if err != nil {
t.Fatalf("EncryptSM4XTSAt failed: %v", err)
}
sm4SegDec, err := DecryptSM4XTSAt(sm4SegEnc, k1, k2, 64, 2)
if err != nil {
t.Fatalf("DecryptSM4XTSAt failed: %v", err)
}
if !bytes.Equal(sm4SegDec, plain[:64]) {
t.Fatalf("root sm4 xts at mismatch")
}
sm4StreamEnc := &bytes.Buffer{}
if err := EncryptSM4XTSStream(sm4StreamEnc, bytes.NewReader(plain), k1, k2, 64); err != nil {
t.Fatalf("EncryptSM4XTSStream failed: %v", err)
}
sm4StreamDec := &bytes.Buffer{}
if err := DecryptSM4XTSStream(sm4StreamDec, bytes.NewReader(sm4StreamEnc.Bytes()), k1, k2, 64); err != nil {
t.Fatalf("DecryptSM4XTSStream failed: %v", err)
}
if !bytes.Equal(sm4StreamDec.Bytes(), plain) {
t.Fatalf("root sm4 xts stream mismatch")
}
}
func TestRootKDFAndXTSKeySplitWrappers(t *testing.T) {
pbk, err := DerivePBKDF2SHA256Key("password", []byte("salt"), 1, 32)
if err != nil {
t.Fatalf("DerivePBKDF2SHA256Key failed: %v", err)
}
if len(pbk) != 32 {
t.Fatalf("pbkdf2 key length mismatch")
}
argonParams := DefaultArgon2idParams()
argonParams.Memory = 32 * 1024
argonParams.Threads = 1
argon, err := DeriveArgon2idKey("password", []byte("salt-salt"), argonParams)
if err != nil {
t.Fatalf("DeriveArgon2idKey failed: %v", err)
}
if len(argon) != int(argonParams.KeyLen) {
t.Fatalf("argon2 key length mismatch")
}
master := []byte("0123456789abcdef0123456789abcdef")
k1, k2, err := SplitXTSMasterKey(master)
if err != nil {
t.Fatalf("SplitXTSMasterKey failed: %v", err)
}
if len(k1) != 16 || len(k2) != 16 {
t.Fatalf("split xts wrapper key lengths mismatch")
}
}

103
asy.go
View File

@ -1,117 +1,34 @@
package starcrypto
import (
"b612.me/starcrypto/asymm"
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"golang.org/x/crypto/ssh"
)
func EncodePrivateKey(private crypto.PrivateKey, secret string) ([]byte, error) {
switch private.(type) {
case *rsa.PrivateKey:
return EncodeRsaPrivateKey(private.(*rsa.PrivateKey), secret)
case *ecdsa.PrivateKey:
return EncodeEcdsaPrivateKey(private.(*ecdsa.PrivateKey), secret)
default:
b, err := x509.MarshalPKCS8PrivateKey(private)
if err != nil {
return nil, err
}
if secret == "" {
return pem.EncodeToMemory(&pem.Block{
Bytes: b,
Type: "PRIVATE KEY",
}), err
}
chiper := x509.PEMCipherAES256
blk, err := x509.EncryptPEMBlock(rand.Reader, "PRIVATE KEY", b, []byte(secret), chiper)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(blk), err
}
return asymm.EncodePrivateKey(private, secret)
}
func EncodePublicKey(public crypto.PublicKey) ([]byte, error) {
switch public.(type) {
case *rsa.PublicKey:
return EncodeRsaPublicKey(public.(*rsa.PublicKey))
case *ecdsa.PublicKey:
return EncodeEcdsaPublicKey(public.(*ecdsa.PublicKey))
default:
publicBytes, err := x509.MarshalPKIXPublicKey(public)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{
Bytes: publicBytes,
Type: "PUBLIC KEY",
}), nil
}
return asymm.EncodePublicKey(public)
}
func DecodePrivateKey(private []byte, password string) (crypto.PrivateKey, error) {
blk, _ := pem.Decode(private)
if blk == nil {
return nil, errors.New("private key error")
}
switch blk.Type {
case "RSA PRIVATE KEY":
return DecodeRsaPrivateKey(private, password)
case "EC PRIVATE KEY":
return DecodeEcdsaPrivateKey(private, password)
case "PRIVATE KEY":
var prikey crypto.PrivateKey
var err error
var bytes []byte
blk, _ := pem.Decode(private)
if blk == nil {
return nil, errors.New("private key error")
}
if password != "" {
tmp, err := x509.DecryptPEMBlock(blk, []byte(password))
if err != nil {
return nil, err
}
bytes = tmp
} else {
bytes = blk.Bytes
}
prikey, err = x509.ParsePKCS8PrivateKey(bytes)
if err != nil {
return nil, err
}
return prikey, err
default:
return nil, errors.New("private key type error")
return asymm.DecodePrivateKey(private, password)
}
func EncodeOpenSSHPrivateKey(private crypto.PrivateKey, secret string) ([]byte, error) {
return asymm.EncodeOpenSSHPrivateKey(private, secret)
}
func DecodePublicKey(pubStr []byte) (crypto.PublicKey, error) {
blk, _ := pem.Decode(pubStr)
if blk == nil {
return nil, errors.New("public key error")
}
pub, err := x509.ParsePKIXPublicKey(blk.Bytes)
if err != nil {
return nil, err
}
return pub, nil
return asymm.DecodePublicKey(pubStr)
}
func EncodeSSHPublicKey(public crypto.PublicKey) ([]byte, error) {
publicKey, err := ssh.NewPublicKey(public)
if err != nil {
return nil, err
}
return ssh.MarshalAuthorizedKey(publicKey), nil
return asymm.EncodeSSHPublicKey(public)
}
func DecodeSSHPublicKey(pubStr []byte) (crypto.PublicKey, error) {
return ssh.ParsePublicKey(pubStr)
return asymm.DecodeSSHPublicKey(pubStr)
}

138
asymm/asymm_sm_test.go Normal file
View File

@ -0,0 +1,138 @@
package asymm
import (
"bytes"
"testing"
)
func TestSM2SignVerifyAndEncryptDecrypt(t *testing.T) {
priv, pub, err := GenerateSM2Key()
if err != nil {
t.Fatalf("GenerateSM2Key failed: %v", err)
}
msg := []byte("sm2-message")
uid := []byte("user123")
sig, err := SM2Sign(priv, msg, uid)
if err != nil {
t.Fatalf("SM2Sign failed: %v", err)
}
if !SM2Verify(pub, msg, sig, uid) {
t.Fatalf("SM2Verify failed")
}
cipher, err := SM2EncryptASN1(pub, msg)
if err != nil {
t.Fatalf("SM2EncryptASN1 failed: %v", err)
}
plain, err := SM2DecryptASN1(priv, cipher)
if err != nil {
t.Fatalf("SM2DecryptASN1 failed: %v", err)
}
if !bytes.Equal(plain, msg) {
t.Fatalf("SM2 decrypt mismatch")
}
}
func TestSM2PEMEncodeDecode(t *testing.T) {
priv, pub, err := GenerateSM2Key()
if err != nil {
t.Fatalf("GenerateSM2Key failed: %v", err)
}
privPEM, err := EncodeSM2PrivateKey(priv, "pwd")
if err != nil {
t.Fatalf("EncodeSM2PrivateKey failed: %v", err)
}
pubPEM, err := EncodeSM2PublicKey(pub)
if err != nil {
t.Fatalf("EncodeSM2PublicKey failed: %v", err)
}
decodedPriv, err := DecodeSM2PrivateKey(privPEM, "pwd")
if err != nil {
t.Fatalf("DecodeSM2PrivateKey failed: %v", err)
}
decodedPub, err := DecodeSM2PublicKey(pubPEM)
if err != nil {
t.Fatalf("DecodeSM2PublicKey failed: %v", err)
}
msg := []byte("sm2-pem")
sig, err := SM2Sign(decodedPriv, msg, nil)
if err != nil {
t.Fatalf("SM2Sign failed: %v", err)
}
if !SM2Verify(decodedPub, msg, sig, nil) {
t.Fatalf("SM2 verify with decoded keys failed")
}
}
func TestSM9SignVerifyAndEncryptDecrypt(t *testing.T) {
signMasterPriv, signMasterPub, err := GenerateSM9SignMasterKey()
if err != nil {
t.Fatalf("GenerateSM9SignMasterKey failed: %v", err)
}
encryptMasterPriv, encryptMasterPub, err := GenerateSM9EncryptMasterKey()
if err != nil {
t.Fatalf("GenerateSM9EncryptMasterKey failed: %v", err)
}
uid := []byte("alice@example.com")
signUserKey, err := GenerateSM9SignUserKey(signMasterPriv, uid, 0)
if err != nil {
t.Fatalf("GenerateSM9SignUserKey failed: %v", err)
}
encryptUserKey, err := GenerateSM9EncryptUserKey(encryptMasterPriv, uid, 0)
if err != nil {
t.Fatalf("GenerateSM9EncryptUserKey failed: %v", err)
}
msg := []byte("sm9-message")
sig, err := SM9SignASN1(signUserKey, msg)
if err != nil {
t.Fatalf("SM9SignASN1 failed: %v", err)
}
if !SM9VerifyASN1(signMasterPub, uid, 0, msg, sig) {
t.Fatalf("SM9VerifyASN1 failed")
}
cipher, err := SM9Encrypt(encryptMasterPub, uid, 0, msg)
if err != nil {
t.Fatalf("SM9Encrypt failed: %v", err)
}
plain, err := SM9Decrypt(encryptUserKey, uid, cipher)
if err != nil {
t.Fatalf("SM9Decrypt failed: %v", err)
}
if !bytes.Equal(plain, msg) {
t.Fatalf("SM9 decrypt mismatch")
}
}
func TestSM9PEMEncodeDecode(t *testing.T) {
signMasterPriv, _, err := GenerateSM9SignMasterKey()
if err != nil {
t.Fatalf("GenerateSM9SignMasterKey failed: %v", err)
}
encryptMasterPriv, _, err := GenerateSM9EncryptMasterKey()
if err != nil {
t.Fatalf("GenerateSM9EncryptMasterKey failed: %v", err)
}
signPrivPEM, err := EncodeSM9SignMasterPrivateKey(signMasterPriv)
if err != nil {
t.Fatalf("EncodeSM9SignMasterPrivateKey failed: %v", err)
}
encPrivPEM, err := EncodeSM9EncryptMasterPrivateKey(encryptMasterPriv)
if err != nil {
t.Fatalf("EncodeSM9EncryptMasterPrivateKey failed: %v", err)
}
if _, err := DecodeSM9SignMasterPrivateKey(signPrivPEM); err != nil {
t.Fatalf("DecodeSM9SignMasterPrivateKey failed: %v", err)
}
if _, err := DecodeSM9EncryptMasterPrivateKey(encPrivPEM); err != nil {
t.Fatalf("DecodeSM9EncryptMasterPrivateKey failed: %v", err)
}
}

113
asymm/ecdsa.go Normal file
View File

@ -0,0 +1,113 @@
package asymm
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"errors"
"golang.org/x/crypto/ssh"
)
func GenerateEcdsaKey(pubkeyCurve elliptic.Curve) (*ecdsa.PrivateKey, *ecdsa.PublicKey, error) {
priv, err := ecdsa.GenerateKey(pubkeyCurve, rand.Reader)
if err != nil {
return nil, nil, err
}
return priv, &priv.PublicKey, nil
}
func EncodeEcdsaPrivateKey(private *ecdsa.PrivateKey, secret string) ([]byte, error) {
b, err := x509.MarshalECPrivateKey(private)
if err != nil {
return nil, err
}
if secret == "" {
return pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: b,
}), nil
}
blk, err := x509.EncryptPEMBlock(rand.Reader, "EC PRIVATE KEY", b, []byte(secret), x509.PEMCipherAES256)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(blk), nil
}
func EncodeEcdsaPublicKey(public *ecdsa.PublicKey) ([]byte, error) {
publicBytes, err := x509.MarshalPKIXPublicKey(public)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicBytes,
}), nil
}
func DecodeEcdsaPrivateKey(private []byte, password string) (*ecdsa.PrivateKey, error) {
blk, _ := pem.Decode(private)
if blk == nil {
return nil, errors.New("private key error")
}
bytes, err := decodePEMBlockBytes(blk, password)
if err != nil {
return nil, err
}
if key, err := x509.ParseECPrivateKey(bytes); err == nil {
return key, nil
}
pkcs8, err := x509.ParsePKCS8PrivateKey(bytes)
if err != nil {
return nil, err
}
key, ok := pkcs8.(*ecdsa.PrivateKey)
if !ok {
return nil, errors.New("private key is not ECDSA")
}
return key, nil
}
func DecodeEcdsaPublicKey(pubStr []byte) (*ecdsa.PublicKey, error) {
blk, _ := pem.Decode(pubStr)
if blk == nil {
return nil, errors.New("public key error")
}
pub, err := x509.ParsePKIXPublicKey(blk.Bytes)
if err != nil {
return nil, err
}
key, ok := pub.(*ecdsa.PublicKey)
if !ok {
return nil, errors.New("public key is not ECDSA")
}
return key, nil
}
func EncodeEcdsaSSHPublicKey(public *ecdsa.PublicKey) ([]byte, error) {
publicKey, err := ssh.NewPublicKey(public)
if err != nil {
return nil, err
}
return ssh.MarshalAuthorizedKey(publicKey), nil
}
func GenerateEcdsaSSHKeyPair(pubkeyCurve elliptic.Curve, secret string) (string, string, error) {
pkey, pubkey, err := GenerateEcdsaKey(pubkeyCurve)
if err != nil {
return "", "", err
}
pub, err := EncodeEcdsaSSHPublicKey(pubkey)
if err != nil {
return "", "", err
}
priv, err := EncodeEcdsaPrivateKey(pkey, secret)
if err != nil {
return "", "", err
}
return string(priv), string(pub), nil
}

167
asymm/key.go Normal file
View File

@ -0,0 +1,167 @@
package asymm
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"github.com/emmansun/gmsm/pkcs8"
"github.com/emmansun/gmsm/sm2"
"github.com/emmansun/gmsm/smx509"
"golang.org/x/crypto/ssh"
)
func EncodePrivateKey(private crypto.PrivateKey, secret string) ([]byte, error) {
switch key := private.(type) {
case *rsa.PrivateKey:
return EncodeRsaPrivateKey(key, secret)
case *ecdsa.PrivateKey:
return EncodeEcdsaPrivateKey(key, secret)
case *sm2.PrivateKey:
return EncodeSM2PrivateKey(key, secret)
default:
b, err := x509.MarshalPKCS8PrivateKey(private)
if err != nil {
return nil, err
}
if secret == "" {
return pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Bytes: b,
}), nil
}
blk, err := x509.EncryptPEMBlock(rand.Reader, "PRIVATE KEY", b, []byte(secret), x509.PEMCipherAES256)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(blk), nil
}
}
func EncodePublicKey(public crypto.PublicKey) ([]byte, error) {
switch key := public.(type) {
case *rsa.PublicKey:
return EncodeRsaPublicKey(key)
case *ecdsa.PublicKey:
if sm2.IsSM2PublicKey(key) {
return EncodeSM2PublicKey(key)
}
return EncodeEcdsaPublicKey(key)
default:
publicBytes, err := x509.MarshalPKIXPublicKey(public)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicBytes,
}), nil
}
}
func DecodePrivateKey(private []byte, password string) (crypto.PrivateKey, error) {
blk, _ := pem.Decode(private)
if blk == nil {
return nil, errors.New("private key error")
}
switch blk.Type {
case "RSA PRIVATE KEY":
return DecodeRsaPrivateKey(private, password)
case "EC PRIVATE KEY":
return DecodeEcdsaPrivateKey(private, password)
case "SM2 PRIVATE KEY":
return DecodeSM2PrivateKey(private, password)
case "PRIVATE KEY":
bytes, err := decodePEMBlockBytes(blk, password)
if err != nil {
return nil, err
}
key, err := x509.ParsePKCS8PrivateKey(bytes)
if err == nil {
return key, nil
}
return smx509.ParsePKCS8PrivateKey(bytes)
case "ENCRYPTED PRIVATE KEY":
if password == "" {
return nil, errors.New("private key is encrypted but password is empty")
}
return pkcs8.ParsePKCS8PrivateKey(blk.Bytes, []byte(password))
case "OPENSSH PRIVATE KEY":
if password == "" {
return ssh.ParseRawPrivateKey(private)
}
return ssh.ParseRawPrivateKeyWithPassphrase(private, []byte(password))
default:
return nil, errors.New("private key type error")
}
}
func EncodeOpenSSHPrivateKey(private crypto.PrivateKey, secret string) ([]byte, error) {
key := interface{}(private)
if k, ok := private.(*ed25519.PrivateKey); ok {
key = *k
}
var (
block *pem.Block
err error
)
if secret == "" {
block, err = ssh.MarshalPrivateKey(key, "")
} else {
block, err = ssh.MarshalPrivateKeyWithPassphrase(key, "", []byte(secret))
}
if err != nil {
return nil, err
}
return pem.EncodeToMemory(block), nil
}
func DecodePublicKey(pubStr []byte) (crypto.PublicKey, error) {
blk, _ := pem.Decode(pubStr)
if blk == nil {
return nil, errors.New("public key error")
}
key, err := x509.ParsePKIXPublicKey(blk.Bytes)
if err == nil {
return key, nil
}
return smx509.ParsePKIXPublicKey(blk.Bytes)
}
func EncodeSSHPublicKey(public crypto.PublicKey) ([]byte, error) {
publicKey, err := ssh.NewPublicKey(public)
if err != nil {
return nil, err
}
return ssh.MarshalAuthorizedKey(publicKey), nil
}
func DecodeSSHPublicKey(pubStr []byte) (crypto.PublicKey, error) {
return ssh.ParsePublicKey(pubStr)
}
func decodePEMBlockBytes(blk *pem.Block, password string) ([]byte, error) {
if password == "" {
if x509.IsEncryptedPEMBlock(blk) || smx509.IsEncryptedPEMBlock(blk) {
return nil, errors.New("private key is encrypted but password is empty")
}
return blk.Bytes, nil
}
if x509.IsEncryptedPEMBlock(blk) {
if b, err := x509.DecryptPEMBlock(blk, []byte(password)); err == nil {
return b, nil
}
}
if smx509.IsEncryptedPEMBlock(blk) {
return smx509.DecryptPEMBlock(blk, []byte(password))
}
return blk.Bytes, nil
}

338
asymm/rsa.go Normal file
View File

@ -0,0 +1,338 @@
package asymm
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"math/big"
"github.com/emmansun/gmsm/pkcs8"
"golang.org/x/crypto/ssh"
)
func GenerateRsaKey(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error) {
private, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, nil, err
}
return private, &private.PublicKey, nil
}
func EncodeRsaPrivateKey(private *rsa.PrivateKey, secret string) ([]byte, error) {
return EncodeRsaPrivateKeyWithLegacy(private, secret, true)
}
func EncodeRsaPrivateKeyWithLegacy(private *rsa.PrivateKey, secret string, legacy bool) ([]byte, error) {
if legacy {
return encodeRsaPrivateKeyLegacy(private, secret)
}
return EncodeRsaPrivateKeyPKCS8(private, secret)
}
func EncodeRsaPrivateKeyPKCS8(private *rsa.PrivateKey, secret string) ([]byte, error) {
password := []byte(secret)
var (
der []byte
blockType = "PRIVATE KEY"
err error
)
if secret == "" {
der, err = pkcs8.MarshalPrivateKey(private, nil, nil)
} else {
der, err = pkcs8.MarshalPrivateKey(private, password, pkcs8.DefaultOpts)
blockType = "ENCRYPTED PRIVATE KEY"
}
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: blockType, Bytes: der}), nil
}
func encodeRsaPrivateKeyLegacy(private *rsa.PrivateKey, secret string) ([]byte, error) {
der := x509.MarshalPKCS1PrivateKey(private)
if secret == "" {
return pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: der,
}), nil
}
blk, err := x509.EncryptPEMBlock(rand.Reader, "RSA PRIVATE KEY", der, []byte(secret), x509.PEMCipherAES256)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(blk), nil
}
func EncodeRsaPublicKey(public *rsa.PublicKey) ([]byte, error) {
publicBytes, err := x509.MarshalPKIXPublicKey(public)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicBytes,
}), nil
}
func DecodeRsaPrivateKey(private []byte, password string) (*rsa.PrivateKey, error) {
blk, _ := pem.Decode(private)
if blk == nil {
return nil, errors.New("private key error")
}
switch blk.Type {
case "PRIVATE KEY", "ENCRYPTED PRIVATE KEY":
return DecodeRsaPrivateKeyPKCS8(private, password)
default:
return decodeRsaPrivateKeyLegacy(private, password)
}
}
func DecodeRsaPrivateKeyWithLegacy(private []byte, password string, legacy bool) (*rsa.PrivateKey, error) {
if legacy {
return decodeRsaPrivateKeyLegacy(private, password)
}
return DecodeRsaPrivateKeyPKCS8(private, password)
}
func DecodeRsaPrivateKeyPKCS8(private []byte, password string) (*rsa.PrivateKey, error) {
blk, _ := pem.Decode(private)
if blk == nil {
return nil, errors.New("private key error")
}
switch blk.Type {
case "PRIVATE KEY":
return pkcs8.ParsePKCS8PrivateKeyRSA(blk.Bytes)
case "ENCRYPTED PRIVATE KEY":
if password == "" {
return nil, errors.New("private key is encrypted but password is empty")
}
return pkcs8.ParsePKCS8PrivateKeyRSA(blk.Bytes, []byte(password))
default:
return nil, errors.New("private key is not PKCS#8")
}
}
func decodeRsaPrivateKeyLegacy(private []byte, password string) (*rsa.PrivateKey, error) {
blk, _ := pem.Decode(private)
if blk == nil {
return nil, errors.New("private key error")
}
bytes, err := decodePEMBlockBytes(blk, password)
if err != nil {
return nil, err
}
if prikey, err := x509.ParsePKCS1PrivateKey(bytes); err == nil {
return prikey, nil
}
pkcs8key, err := x509.ParsePKCS8PrivateKey(bytes)
if err != nil {
return nil, err
}
prikey, ok := pkcs8key.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("private key is not RSA")
}
return prikey, nil
}
func DecodeRsaPublicKey(pubStr []byte) (*rsa.PublicKey, error) {
blk, _ := pem.Decode(pubStr)
if blk == nil {
return nil, errors.New("public key error")
}
pub, err := x509.ParsePKIXPublicKey(blk.Bytes)
if err != nil {
return nil, err
}
rsapub, ok := pub.(*rsa.PublicKey)
if !ok {
return nil, errors.New("public key is not RSA")
}
return rsapub, nil
}
func EncodeRsaSSHPublicKey(public *rsa.PublicKey) ([]byte, error) {
publicKey, err := ssh.NewPublicKey(public)
if err != nil {
return nil, err
}
return ssh.MarshalAuthorizedKey(publicKey), nil
}
func GenerateRsaSSHKeyPair(bits int, secret string) (string, string, error) {
return GenerateRsaSSHKeyPairWithLegacy(bits, secret, true)
}
func GenerateRsaSSHKeyPairWithLegacy(bits int, secret string, legacy bool) (string, string, error) {
pkey, pubkey, err := GenerateRsaKey(bits)
if err != nil {
return "", "", err
}
pub, err := EncodeRsaSSHPublicKey(pubkey)
if err != nil {
return "", "", err
}
priv, err := EncodeRsaPrivateKeyWithLegacy(pkey, secret, legacy)
if err != nil {
return "", "", err
}
return string(priv), string(pub), nil
}
func RSAEncrypt(pub *rsa.PublicKey, data []byte) ([]byte, error) {
return rsa.EncryptPKCS1v15(rand.Reader, pub, data)
}
func RSADecrypt(prikey *rsa.PrivateKey, data []byte) ([]byte, error) {
return rsa.DecryptPKCS1v15(rand.Reader, prikey, data)
}
func RSAEncryptOAEP(pub *rsa.PublicKey, data, label []byte, hashType crypto.Hash) ([]byte, error) {
hashType, err := normalizeModernRSAHash(hashType)
if err != nil {
return nil, err
}
return rsa.EncryptOAEP(hashType.New(), rand.Reader, pub, data, label)
}
func RSADecryptOAEP(prikey *rsa.PrivateKey, data, label []byte, hashType crypto.Hash) ([]byte, error) {
hashType, err := normalizeModernRSAHash(hashType)
if err != nil {
return nil, err
}
return rsa.DecryptOAEP(hashType.New(), rand.Reader, prikey, data, label)
}
func RSASign(msg, priKey []byte, password string, hashType crypto.Hash) ([]byte, error) {
prikey, err := DecodeRsaPrivateKey(priKey, password)
if err != nil {
return nil, err
}
hashed, err := hashMessage(msg, hashType)
if err != nil {
return nil, err
}
return rsa.SignPKCS1v15(rand.Reader, prikey, hashType, hashed)
}
func RSAVerify(sig, msg, pubKey []byte, hashType crypto.Hash) error {
pubkey, err := DecodeRsaPublicKey(pubKey)
if err != nil {
return err
}
hashed, err := hashMessage(msg, hashType)
if err != nil {
return err
}
return rsa.VerifyPKCS1v15(pubkey, hashType, hashed, sig)
}
func RSASignPSS(msg, priKey []byte, password string, hashType crypto.Hash, opts *rsa.PSSOptions) ([]byte, error) {
prikey, err := DecodeRsaPrivateKey(priKey, password)
if err != nil {
return nil, err
}
hashType, err = normalizeModernRSAHash(hashType)
if err != nil {
return nil, err
}
hashed, err := hashMessage(msg, hashType)
if err != nil {
return nil, err
}
return rsa.SignPSS(rand.Reader, prikey, hashType, hashed, opts)
}
func RSAVerifyPSS(sig, msg, pubKey []byte, hashType crypto.Hash, opts *rsa.PSSOptions) error {
pubkey, err := DecodeRsaPublicKey(pubKey)
if err != nil {
return err
}
hashType, err = normalizeModernRSAHash(hashType)
if err != nil {
return err
}
hashed, err := hashMessage(msg, hashType)
if err != nil {
return err
}
return rsa.VerifyPSS(pubkey, hashType, hashed, sig, opts)
}
func RSAEncryptByPrivkey(priv *rsa.PrivateKey, data []byte) ([]byte, error) {
return rsa.SignPKCS1v15(nil, priv, crypto.Hash(0), data)
}
func RSADecryptByPubkey(pub *rsa.PublicKey, data []byte) ([]byte, error) {
c := new(big.Int).SetBytes(data)
m := new(big.Int).Exp(c, big.NewInt(int64(pub.E)), pub.N)
em := leftPad(m.Bytes(), (pub.N.BitLen()+7)/8)
return unLeftPad(em)
}
func normalizeModernRSAHash(hashType crypto.Hash) (crypto.Hash, error) {
if hashType == 0 {
hashType = crypto.SHA256
}
if !hashType.Available() {
return 0, errors.New("hash function is not available")
}
return hashType, nil
}
func hashMessage(msg []byte, hashType crypto.Hash) ([]byte, error) {
if hashType == 0 {
return msg, nil
}
if !hashType.Available() {
return nil, errors.New("hash function is not available")
}
h := hashType.New()
_, err := h.Write(msg)
if err != nil {
return nil, err
}
return h.Sum(nil), nil
}
func leftPad(input []byte, size int) []byte {
n := len(input)
if n > size {
n = size
}
out := make([]byte, size)
copy(out[len(out)-n:], input)
return out
}
func unLeftPad(input []byte) ([]byte, error) {
// PKCS#1 v1.5 block format: 0x00 || 0x01 || PS(0xff...) || 0x00 || M
if len(input) < 3 {
return nil, errors.New("invalid RSA block")
}
if input[0] != 0x00 || input[1] != 0x01 {
return nil, errors.New("invalid RSA block header")
}
i := 2
for i < len(input) && input[i] == 0xff {
i++
}
if i >= len(input) || input[i] != 0x00 {
return nil, errors.New("invalid RSA block padding")
}
i++
if i > len(input) {
return nil, errors.New("invalid RSA block payload")
}
out := make([]byte, len(input)-i)
copy(out, input[i:])
return out, nil
}

134
asymm/rsa_test.go Normal file
View File

@ -0,0 +1,134 @@
package asymm
import (
"bytes"
"crypto/rsa"
"testing"
)
func TestRsaPrivateKeyEncodeDecodeWithLegacyFlag(t *testing.T) {
priv, pub, err := GenerateRsaKey(1024)
if err != nil {
t.Fatalf("GenerateRsaKey failed: %v", err)
}
modernPEM, err := EncodeRsaPrivateKeyWithLegacy(priv, "pwd", false)
if err != nil {
t.Fatalf("EncodeRsaPrivateKeyWithLegacy modern failed: %v", err)
}
if !bytes.Contains(modernPEM, []byte("ENCRYPTED PRIVATE KEY")) {
t.Fatalf("modern PEM should be ENCRYPTED PRIVATE KEY")
}
modernPriv, err := DecodeRsaPrivateKeyWithLegacy(modernPEM, "pwd", false)
if err != nil {
t.Fatalf("DecodeRsaPrivateKeyWithLegacy modern failed: %v", err)
}
if modernPriv.D.Cmp(priv.D) != 0 {
t.Fatalf("modern decoded private key mismatch")
}
autoPriv, err := DecodeRsaPrivateKey(modernPEM, "pwd")
if err != nil {
t.Fatalf("DecodeRsaPrivateKey auto failed: %v", err)
}
if autoPriv.D.Cmp(priv.D) != 0 {
t.Fatalf("auto decoded private key mismatch")
}
if _, err := DecodePrivateKey(modernPEM, "pwd"); err != nil {
t.Fatalf("DecodePrivateKey for encrypted PKCS8 failed: %v", err)
}
legacyPEM, err := EncodeRsaPrivateKeyWithLegacy(priv, "pwd", true)
if err != nil {
t.Fatalf("EncodeRsaPrivateKeyWithLegacy legacy failed: %v", err)
}
if !bytes.Contains(legacyPEM, []byte("RSA PRIVATE KEY")) {
t.Fatalf("legacy PEM should be RSA PRIVATE KEY")
}
legacyPriv, err := DecodeRsaPrivateKeyWithLegacy(legacyPEM, "pwd", true)
if err != nil {
t.Fatalf("DecodeRsaPrivateKeyWithLegacy legacy failed: %v", err)
}
if legacyPriv.D.Cmp(priv.D) != 0 {
t.Fatalf("legacy decoded private key mismatch")
}
pubPEM, err := EncodeRsaPublicKey(pub)
if err != nil {
t.Fatalf("EncodeRsaPublicKey failed: %v", err)
}
decodedPub, err := DecodeRsaPublicKey(pubPEM)
if err != nil {
t.Fatalf("DecodeRsaPublicKey failed: %v", err)
}
if decodedPub.N.Cmp(pub.N) != 0 || decodedPub.E != pub.E {
t.Fatalf("decoded public key mismatch")
}
}
func TestRSAOAEPEncryptDecrypt(t *testing.T) {
priv, pub, err := GenerateRsaKey(1024)
if err != nil {
t.Fatalf("GenerateRsaKey failed: %v", err)
}
msg := []byte("rsa-oaep-message")
label := []byte("label")
enc, err := RSAEncryptOAEP(pub, msg, label, 0)
if err != nil {
t.Fatalf("RSAEncryptOAEP failed: %v", err)
}
dec, err := RSADecryptOAEP(priv, enc, label, 0)
if err != nil {
t.Fatalf("RSADecryptOAEP failed: %v", err)
}
if !bytes.Equal(dec, msg) {
t.Fatalf("oaep decrypt mismatch")
}
}
func TestRSAPSSSignVerify(t *testing.T) {
priv, pub, err := GenerateRsaKey(1024)
if err != nil {
t.Fatalf("GenerateRsaKey failed: %v", err)
}
privPEM, err := EncodeRsaPrivateKeyWithLegacy(priv, "pwd", false)
if err != nil {
t.Fatalf("EncodeRsaPrivateKeyWithLegacy failed: %v", err)
}
pubPEM, err := EncodeRsaPublicKey(pub)
if err != nil {
t.Fatalf("EncodeRsaPublicKey failed: %v", err)
}
msg := []byte("rsa-pss-message")
sig, err := RSASignPSS(msg, privPEM, "pwd", 0, &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash})
if err != nil {
t.Fatalf("RSASignPSS failed: %v", err)
}
if err := RSAVerifyPSS(sig, msg, pubPEM, 0, &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}); err != nil {
t.Fatalf("RSAVerifyPSS failed: %v", err)
}
}
func TestRSAPKCS1v15EncryptDecryptCompatibility(t *testing.T) {
priv, pub, err := GenerateRsaKey(1024)
if err != nil {
t.Fatalf("GenerateRsaKey failed: %v", err)
}
msg := []byte("rsa-pkcs1v15-message")
enc, err := RSAEncrypt(pub, msg)
if err != nil {
t.Fatalf("RSAEncrypt failed: %v", err)
}
dec, err := RSADecrypt(priv, enc)
if err != nil {
t.Fatalf("RSADecrypt failed: %v", err)
}
if !bytes.Equal(dec, msg) {
t.Fatalf("pkcs1v15 decrypt mismatch")
}
}

136
asymm/sm2.go Normal file
View File

@ -0,0 +1,136 @@
package asymm
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"encoding/pem"
"errors"
"github.com/emmansun/gmsm/sm2"
"github.com/emmansun/gmsm/smx509"
)
func GenerateSM2Key() (*sm2.PrivateKey, *ecdsa.PublicKey, error) {
priv, err := sm2.GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
return priv, &priv.PublicKey, nil
}
func EncodeSM2PrivateKey(private *sm2.PrivateKey, secret string) ([]byte, error) {
der, err := smx509.MarshalSM2PrivateKey(private)
if err != nil {
return nil, err
}
if secret == "" {
return pem.EncodeToMemory(&pem.Block{Type: "SM2 PRIVATE KEY", Bytes: der}), nil
}
blk, err := smx509.EncryptPEMBlock(rand.Reader, "SM2 PRIVATE KEY", der, []byte(secret), smx509.PEMCipherAES256)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(blk), nil
}
func EncodeSM2PublicKey(public *ecdsa.PublicKey) ([]byte, error) {
der, err := smx509.MarshalPKIXPublicKey(public)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der}), nil
}
func DecodeSM2PrivateKey(private []byte, password string) (*sm2.PrivateKey, error) {
blk, _ := pem.Decode(private)
if blk == nil {
return nil, errors.New("private key error")
}
bytes := blk.Bytes
if smx509.IsEncryptedPEMBlock(blk) {
if password == "" {
return nil, errors.New("private key is encrypted but password is empty")
}
var err error
bytes, err = smx509.DecryptPEMBlock(blk, []byte(password))
if err != nil {
return nil, err
}
}
if key, err := smx509.ParseSM2PrivateKey(bytes); err == nil {
return key, nil
}
pkcs8, err := smx509.ParsePKCS8PrivateKey(bytes)
if err != nil {
return nil, err
}
key, ok := pkcs8.(*sm2.PrivateKey)
if !ok {
return nil, errors.New("private key is not SM2")
}
return key, nil
}
func DecodeSM2PublicKey(pubStr []byte) (*ecdsa.PublicKey, error) {
blk, _ := pem.Decode(pubStr)
if blk == nil {
return nil, errors.New("public key error")
}
pubAny, err := smx509.ParsePKIXPublicKey(blk.Bytes)
if err != nil {
return nil, err
}
pub, ok := pubAny.(*ecdsa.PublicKey)
if !ok {
return nil, errors.New("public key is not ECDSA/SM2")
}
if !sm2.IsSM2PublicKey(pub) {
return nil, errors.New("public key is not SM2")
}
return pub, nil
}
func SM2EncryptASN1(pub *ecdsa.PublicKey, data []byte) ([]byte, error) {
return sm2.EncryptASN1(rand.Reader, pub, data)
}
func SM2DecryptASN1(priv *sm2.PrivateKey, data []byte) ([]byte, error) {
return priv.Decrypt(nil, data, sm2.ASN1DecrypterOpts)
}
func SM2Sign(priv *sm2.PrivateKey, msg, uid []byte) ([]byte, error) {
if len(uid) == 0 {
uid = nil
}
return sm2.SignASN1(rand.Reader, priv, msg, sm2.NewSM2SignerOption(true, uid))
}
func SM2Verify(pub *ecdsa.PublicKey, msg, sig, uid []byte) bool {
if len(uid) == 0 {
uid = nil
}
return sm2.VerifyASN1WithSM2(pub, uid, msg, sig)
}
func SM2SignByPEM(msg, priKey []byte, password string, uid []byte) ([]byte, error) {
priv, err := DecodeSM2PrivateKey(priKey, password)
if err != nil {
return nil, err
}
return SM2Sign(priv, msg, uid)
}
func SM2VerifyByPEM(sig, msg, pubKey []byte, uid []byte) (bool, error) {
pub, err := DecodeSM2PublicKey(pubKey)
if err != nil {
return false, err
}
return SM2Verify(pub, msg, sig, uid), nil
}
func IsSM2PublicKey(public crypto.PublicKey) bool {
return sm2.IsSM2PublicKey(public)
}

236
asymm/sm9.go Normal file
View File

@ -0,0 +1,236 @@
package asymm
import (
"crypto/rand"
"encoding/pem"
"errors"
gmsm3 "github.com/emmansun/gmsm/sm3"
gmsm9 "github.com/emmansun/gmsm/sm9"
)
const (
SM9SignHID byte = 0x01
SM9EncryptHID byte = 0x03
)
func GenerateSM9SignMasterKey() (*gmsm9.SignMasterPrivateKey, *gmsm9.SignMasterPublicKey, error) {
priv, err := gmsm9.GenerateSignMasterKey(rand.Reader)
if err != nil {
return nil, nil, err
}
return priv, priv.PublicKey(), nil
}
func GenerateSM9EncryptMasterKey() (*gmsm9.EncryptMasterPrivateKey, *gmsm9.EncryptMasterPublicKey, error) {
priv, err := gmsm9.GenerateEncryptMasterKey(rand.Reader)
if err != nil {
return nil, nil, err
}
return priv, priv.PublicKey(), nil
}
func GenerateSM9SignUserKey(master *gmsm9.SignMasterPrivateKey, uid []byte, hid byte) (*gmsm9.SignPrivateKey, error) {
if master == nil {
return nil, errors.New("sm9 sign master key is nil")
}
if hid == 0 {
hid = SM9SignHID
}
return master.GenerateUserKey(uid, hid)
}
func GenerateSM9EncryptUserKey(master *gmsm9.EncryptMasterPrivateKey, uid []byte, hid byte) (*gmsm9.EncryptPrivateKey, error) {
if master == nil {
return nil, errors.New("sm9 encrypt master key is nil")
}
if hid == 0 {
hid = SM9EncryptHID
}
return master.GenerateUserKey(uid, hid)
}
func EncodeSM9SignMasterPrivateKey(key *gmsm9.SignMasterPrivateKey) ([]byte, error) {
if key == nil {
return nil, errors.New("sm9 sign master private key is nil")
}
der, err := key.MarshalASN1()
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: "SM9 SIGN MASTER PRIVATE KEY", Bytes: der}), nil
}
func DecodeSM9SignMasterPrivateKey(data []byte) (*gmsm9.SignMasterPrivateKey, error) {
der, err := pemOrDER(data)
if err != nil {
return nil, err
}
return gmsm9.UnmarshalSignMasterPrivateKeyASN1(der)
}
func EncodeSM9SignMasterPublicKey(key *gmsm9.SignMasterPublicKey) ([]byte, error) {
if key == nil {
return nil, errors.New("sm9 sign master public key is nil")
}
der, err := key.MarshalASN1()
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: "SM9 SIGN MASTER PUBLIC KEY", Bytes: der}), nil
}
func DecodeSM9SignMasterPublicKey(data []byte) (*gmsm9.SignMasterPublicKey, error) {
der, err := pemOrDER(data)
if err != nil {
return nil, err
}
return gmsm9.UnmarshalSignMasterPublicKeyASN1(der)
}
func EncodeSM9SignPrivateKey(key *gmsm9.SignPrivateKey) ([]byte, error) {
if key == nil {
return nil, errors.New("sm9 sign private key is nil")
}
der, err := key.MarshalASN1()
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: "SM9 SIGN PRIVATE KEY", Bytes: der}), nil
}
func DecodeSM9SignPrivateKey(data []byte) (*gmsm9.SignPrivateKey, error) {
der, err := pemOrDER(data)
if err != nil {
return nil, err
}
return gmsm9.UnmarshalSignPrivateKeyASN1(der)
}
func EncodeSM9EncryptMasterPrivateKey(key *gmsm9.EncryptMasterPrivateKey) ([]byte, error) {
if key == nil {
return nil, errors.New("sm9 encrypt master private key is nil")
}
der, err := key.MarshalASN1()
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: "SM9 ENCRYPT MASTER PRIVATE KEY", Bytes: der}), nil
}
func DecodeSM9EncryptMasterPrivateKey(data []byte) (*gmsm9.EncryptMasterPrivateKey, error) {
der, err := pemOrDER(data)
if err != nil {
return nil, err
}
return gmsm9.UnmarshalEncryptMasterPrivateKeyASN1(der)
}
func EncodeSM9EncryptMasterPublicKey(key *gmsm9.EncryptMasterPublicKey) ([]byte, error) {
if key == nil {
return nil, errors.New("sm9 encrypt master public key is nil")
}
der, err := key.MarshalASN1()
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: "SM9 ENCRYPT MASTER PUBLIC KEY", Bytes: der}), nil
}
func DecodeSM9EncryptMasterPublicKey(data []byte) (*gmsm9.EncryptMasterPublicKey, error) {
der, err := pemOrDER(data)
if err != nil {
return nil, err
}
return gmsm9.UnmarshalEncryptMasterPublicKeyASN1(der)
}
func EncodeSM9EncryptPrivateKey(key *gmsm9.EncryptPrivateKey) ([]byte, error) {
if key == nil {
return nil, errors.New("sm9 encrypt private key is nil")
}
der, err := key.MarshalASN1()
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: "SM9 ENCRYPT PRIVATE KEY", Bytes: der}), nil
}
func DecodeSM9EncryptPrivateKey(data []byte) (*gmsm9.EncryptPrivateKey, error) {
der, err := pemOrDER(data)
if err != nil {
return nil, err
}
return gmsm9.UnmarshalEncryptPrivateKeyASN1(der)
}
func SM9SignHashASN1(priv *gmsm9.SignPrivateKey, hash []byte) ([]byte, error) {
if priv == nil {
return nil, errors.New("sm9 sign private key is nil")
}
return gmsm9.SignASN1(rand.Reader, priv, hash)
}
func SM9SignASN1(priv *gmsm9.SignPrivateKey, message []byte) ([]byte, error) {
sum := gmsm3.Sum(message)
return SM9SignHashASN1(priv, sum[:])
}
func SM9VerifyHashASN1(pub *gmsm9.SignMasterPublicKey, uid []byte, hid byte, hash, sig []byte) bool {
if pub == nil {
return false
}
if hid == 0 {
hid = SM9SignHID
}
return gmsm9.VerifyASN1(pub, uid, hid, hash, sig)
}
func SM9VerifyASN1(pub *gmsm9.SignMasterPublicKey, uid []byte, hid byte, message, sig []byte) bool {
sum := gmsm3.Sum(message)
return SM9VerifyHashASN1(pub, uid, hid, sum[:], sig)
}
func SM9Encrypt(pub *gmsm9.EncryptMasterPublicKey, uid []byte, hid byte, plaintext []byte) ([]byte, error) {
if pub == nil {
return nil, errors.New("sm9 encrypt master public key is nil")
}
if hid == 0 {
hid = SM9EncryptHID
}
return gmsm9.Encrypt(rand.Reader, pub, uid, hid, plaintext, gmsm9.SM4CBCEncrypterOpts)
}
func SM9Decrypt(priv *gmsm9.EncryptPrivateKey, uid, ciphertext []byte) ([]byte, error) {
if priv == nil {
return nil, errors.New("sm9 encrypt private key is nil")
}
return gmsm9.Decrypt(priv, uid, ciphertext, gmsm9.SM4CBCEncrypterOpts)
}
func SM9EncryptASN1(pub *gmsm9.EncryptMasterPublicKey, uid []byte, hid byte, plaintext []byte) ([]byte, error) {
if pub == nil {
return nil, errors.New("sm9 encrypt master public key is nil")
}
if hid == 0 {
hid = SM9EncryptHID
}
return gmsm9.EncryptASN1(rand.Reader, pub, uid, hid, plaintext, gmsm9.SM4CBCEncrypterOpts)
}
func SM9DecryptASN1(priv *gmsm9.EncryptPrivateKey, uid, ciphertext []byte) ([]byte, error) {
if priv == nil {
return nil, errors.New("sm9 encrypt private key is nil")
}
return gmsm9.DecryptASN1(priv, uid, ciphertext)
}
func pemOrDER(data []byte) ([]byte, error) {
if len(data) == 0 {
return nil, errors.New("empty key data")
}
if blk, _ := pem.Decode(data); blk != nil {
return blk.Bytes, nil
}
return data, nil
}

View File

@ -1,368 +1,80 @@
package starcrypto
import (
"encoding/ascii85"
"encoding/base64"
"errors"
"io"
"os"
)
import "b612.me/starcrypto/encodingx"
var (
// ErrLength is returned from the Decode* methods if the input has an
// impossible length.
ErrLength = errors.New("base128: invalid length base128 string")
// ErrBit is returned from the Decode* methods if the input has a byte with
// the high-bit set (e.g. 0x80). This will never be the case for strings
// produced from the Encode* methods in this package.
ErrBit = errors.New("base128: high bit set in base128 string")
ErrLength = encodingx.ErrLength
ErrBit = encodingx.ErrBit
)
// Encoding table holds all the characters for base91 encoding
var enctab = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~'")
// Decoding table maps all the characters back to their integer values
var dectab = map[byte]byte{
'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7,
'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15,
'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23,
'Y': 24, 'Z': 25, 'a': 26, 'b': 27, 'c': 28, 'd': 29, 'e': 30, 'f': 31,
'g': 32, 'h': 33, 'i': 34, 'j': 35, 'k': 36, 'l': 37, 'm': 38, 'n': 39,
'o': 40, 'p': 41, 'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47,
'w': 48, 'x': 49, 'y': 50, 'z': 51, '0': 52, '1': 53, '2': 54, '3': 55,
'4': 56, '5': 57, '6': 58, '7': 59, '8': 60, '9': 61, '!': 62, '#': 63,
'$': 64, '%': 65, '&': 66, '(': 67, ')': 68, '*': 69, '+': 70, ',': 71,
'.': 72, '/': 73, ':': 74, ';': 75, '<': 76, '=': 77, '>': 78, '?': 79,
'@': 80, '[': 81, ']': 82, '^': 83, '_': 84, '`': 85, '{': 86, '|': 87,
'}': 88, '~': 89, '\'': 90,
}
// Base91EncodeToString encodes the given byte array and returns a string
func Base91EncodeToString(d []byte) string {
return string(Base91Encode(d))
return encodingx.Base91EncodeToString(d)
}
// Base91Encode returns the base91 encoded string
func Base91Encode(d []byte) []byte {
var n, b uint
var o []byte
for i := 0; i < len(d); i++ {
b |= uint(d[i]) << n
n += 8
if n > 13 {
v := b & 8191
if v > 88 {
b >>= 13
n -= 13
} else {
v = b & 16383
b >>= 14
n -= 14
}
o = append(o, enctab[v%91], enctab[v/91])
}
}
if n > 0 {
o = append(o, enctab[b%91])
if n > 7 || b > 90 {
o = append(o, enctab[b/91])
}
}
return o
return encodingx.Base91Encode(d)
}
// Base91DecodeToString decodes a given byte array are returns a string
func Base91DecodeString(d string) []byte {
return Base91Decode([]byte(d))
return encodingx.Base91DecodeString(d)
}
// Base91Decode decodes a base91 encoded string and returns the result
func Base91Decode(d []byte) []byte {
var b, n uint
var o []byte
v := -1
for i := 0; i < len(d); i++ {
c, ok := dectab[d[i]]
if !ok {
continue
}
if v < 0 {
v = int(c)
} else {
v += int(c) * 91
b |= uint(v) << n
if v&8191 > 88 {
n += 13
} else {
n += 14
}
o = append(o, byte(b&255))
b >>= 8
n -= 8
for n > 7 {
o = append(o, byte(b&255))
b >>= 8
n -= 8
}
v = -1
}
}
if v+1 > 0 {
o = append(o, byte((b|uint(v)<<n)&255))
}
return o
return encodingx.Base91Decode(d)
}
// Base128Encode encodes src into EncodedLen(len(src)) bytes of dst. As a convenience,
// it returns the number of bytes written to dst, but this value is always
// EncodedLen(len(src)).
//
// Encode implements base128 encoding.
func Base128Encode(dst, src []byte) int {
ret := Base128EncodedLen(len(src))
if len(dst) < ret {
panic("dst has insufficient length")
}
dst = dst[:0]
whichByte := uint(1)
bufByte := byte(0)
for _, v := range src {
dst = append(dst, bufByte|(v>>whichByte))
bufByte = (v&(1<<whichByte) - 1) << (7 - whichByte)
if whichByte == 7 {
dst = append(dst, bufByte)
bufByte = 0
whichByte = 0
}
whichByte++
}
dst = append(dst, bufByte)
return ret
return encodingx.Base128Encode(dst, src)
}
// Base128Decode decodes src into DecodedLen(len(src)) bytes, returning the actual
// number of bytes written to dst.
//
// If Decode encounters invalid input, it returns an error describing the
// failure.
func Base128Decode(dst, src []byte) (int, error) {
dLen := Base128DecodedLen(len(src))
if Base128EncodedLen(dLen) != len(src) {
return 0, ErrLength
}
if len(dst) < dLen {
panic("dst has insufficient length")
}
dst = dst[:0]
whichByte := uint(1)
bufByte := byte(0)
for _, v := range src {
if (v & 0x80) != 0 {
return len(dst), ErrBit
}
if whichByte > 1 {
dst = append(dst, bufByte|(v>>(8-whichByte)))
}
bufByte = v << whichByte
if whichByte == 8 {
whichByte = 0
}
whichByte++
}
return len(dst), nil
return encodingx.Base128Decode(dst, src)
}
// Base128DecodeString returns the bytes represented by the base128 string s.
func Base128DecodeString(s string) ([]byte, error) {
src := []byte(s)
dst := make([]byte, Base128DecodedLen(len(src)))
if _, err := Base128Decode(dst, src); err != nil {
return nil, err
}
return dst, nil
return encodingx.Base128DecodeString(s)
}
// Base128DecodedLen returns the number of bytes `encLen` encoded bytes decodes to.
func Base128DecodedLen(encLen int) int {
return (encLen * 7 / 8)
return encodingx.Base128DecodedLen(encLen)
}
// Base128EncodedLen returns the number of bytes that `dataLen` bytes will encode to.
func Base128EncodedLen(dataLen int) int {
return (((dataLen * 8) + 6) / 7)
return encodingx.Base128EncodedLen(dataLen)
}
// Base128EncodeToString returns the base128 encoding of src.
func Base128EncodeToString(src []byte) string {
dst := make([]byte, Base128EncodedLen(len(src)))
Base128Encode(dst, src)
return string(dst)
return encodingx.Base128EncodeToString(src)
}
// Base64Encode 输出格式化后的Base64字符串
func Base64Encode(bstr []byte) string {
return base64.StdEncoding.EncodeToString(bstr)
return encodingx.Base64Encode(bstr)
}
// Base64Decode 输出解密前的Base64数据
func Base64Decode(str string) ([]byte, error) {
return base64.StdEncoding.DecodeString(str)
return encodingx.Base64Decode(str)
}
// Base85Encode 输出格式化后的Base85字符串
func Base85Encode(bstr []byte) string {
var rtn []byte
rtn = make([]byte, ascii85.MaxEncodedLen(len(bstr)))
ascii85.Encode(rtn, bstr)
return string(rtn)
return encodingx.Base85Encode(bstr)
}
// Base85Decode 输出解密前的Base85数据
func Base85Decode(str string) ([]byte, error) {
var rtn []byte
rtn = make([]byte, len(str))
_, _, err := ascii85.Decode(rtn, []byte(str), true)
return rtn, err
return encodingx.Base85Decode(str)
}
// Base85EncodeFile 用base85方法编码src文件到dst文件中去shell传入当前进度
func Base85EncodeFile(src, dst string, shell func(float64)) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
stat, _ := os.Stat(src)
filebig := float64(stat.Size())
var sum int64
fpdst, err := os.Create(dst)
if err != nil {
return err
}
defer fpdst.Close()
b85 := ascii85.NewEncoder(fpdst)
defer b85.Close()
for {
buf := make([]byte, 1024000)
n, err := fpsrc.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return err
}
sum += int64(n)
go shell(float64(sum) / filebig * 100)
b85.Write(buf[0:n])
}
return nil
return encodingx.Base85EncodeFile(src, dst, shell)
}
// Base85DecodeFile 用base85方法解码src文件到dst文件中去shell传入当前进度
func Base85DecodeFile(src, dst string, shell func(float64)) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
stat, _ := os.Stat(src)
filebig := float64(stat.Size())
var sum int64
defer fpsrc.Close()
fpdst, err := os.Create(dst)
if err != nil {
return err
}
defer fpdst.Close()
b85 := ascii85.NewDecoder(fpsrc)
for {
buf := make([]byte, 1280000)
n, err := b85.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return err
}
sum += int64(n)
per := float64(sum) / filebig * 100 / 4.0 * 5.0
if per >= 100 {
per = 100
}
go shell(per)
fpdst.Write(buf[0:n])
}
return nil
return encodingx.Base85DecodeFile(src, dst, shell)
}
// Base64EncodeFile 用base64方法编码src文件到dst文件中去shell传入当前进度
func Base64EncodeFile(src, dst string, shell func(float64)) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
stat, _ := os.Stat(src)
filebig := float64(stat.Size())
var sum int64 = 0
fpdst, err := os.Create(dst)
if err != nil {
return err
}
defer fpdst.Close()
b64 := base64.NewEncoder(base64.StdEncoding, fpdst)
defer b64.Close()
for {
buf := make([]byte, 1048575)
n, err := fpsrc.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return err
}
sum += int64(n)
go shell(float64(sum) / filebig * 100)
b64.Write(buf[0:n])
}
return nil
return encodingx.Base64EncodeFile(src, dst, shell)
}
// Base64DecodeFile 用base64方法解码src文件到dst文件中去shell传入当前进度
func Base64DecodeFile(src, dst string, shell func(float64)) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
stat, _ := os.Stat(src)
filebig := float64(stat.Size())
var sum int64 = 0
defer fpsrc.Close()
fpdst, err := os.Create(dst)
if err != nil {
return err
}
defer fpdst.Close()
b64 := base64.NewDecoder(base64.StdEncoding, fpsrc)
for {
buf := make([]byte, 1048576)
n, err := b64.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return err
}
sum += int64(n)
per := float64(sum) / filebig * 100 / 3.0 * 4.0
if per >= 100 {
per = 100
}
go shell(per)
fpdst.Write(buf[0:n])
}
return nil
return encodingx.Base64DecodeFile(src, dst, shell)
}

259
ccm/ccm.go Normal file
View File

@ -0,0 +1,259 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package ccm implements a CCM, Counter with CBC-MAC
// as per RFC 3610.
//
// See https://tools.ietf.org/html/rfc3610
//
// This code is derived from https://github.com/pion/dtls (pkg/crypto/ccm).
// The original upstream license is MIT and is preserved in this repository.
//
// A request for including CCM into the Go standard library
// can be found as issue #27484 on the https://github.com/golang/go/
// repository.
package ccm
import (
"crypto/cipher"
"crypto/subtle"
"encoding/binary"
"errors"
"math"
)
// ccm represents a Counter with CBC-MAC with a specific key.
type ccm struct {
b cipher.Block
M uint8
L uint8
}
const ccmBlockSize = 16
// CCM is a block cipher in Counter with CBC-MAC mode.
// Providing authenticated encryption with associated data via the cipher.AEAD interface.
type CCM interface {
cipher.AEAD
// MaxLength returns the maxium length of plaintext in calls to Seal.
// The maximum length of ciphertext in calls to Open is MaxLength()+Overhead().
// The maximum length is related to CCM's `L` parameter (15-noncesize) and
// is 1<<(8*L) - 1 (but also limited by the maxium size of an int).
MaxLength() int
}
var (
errInvalidBlockSize = errors.New("ccm: NewCCM requires 128-bit block cipher")
errInvalidTagSize = errors.New("ccm: tagsize must be 4, 6, 8, 10, 12, 14, or 16")
errInvalidNonceSize = errors.New("ccm: invalid nonce size")
)
// NewCCM returns the given 128-bit block cipher wrapped in CCM.
// The tagsize must be an even integer between 4 and 16 inclusive
// and is used as CCM's `M` parameter.
// The noncesize must be an integer between 7 and 13 inclusive,
// 15-noncesize is used as CCM's `L` parameter.
func NewCCM(b cipher.Block, tagsize, noncesize int) (CCM, error) {
if b.BlockSize() != ccmBlockSize {
return nil, errInvalidBlockSize
}
if tagsize < 4 || tagsize > 16 || tagsize&1 != 0 {
return nil, errInvalidTagSize
}
lensize := 15 - noncesize
if lensize < 2 || lensize > 8 {
return nil, errInvalidNonceSize
}
c := &ccm{b: b, M: uint8(tagsize), L: uint8(lensize)} //nolint:gosec // G114
return c, nil
}
func (c *ccm) NonceSize() int { return 15 - int(c.L) }
func (c *ccm) Overhead() int { return int(c.M) }
func (c *ccm) MaxLength() int { return maxlen(c.L, c.Overhead()) }
func maxlen(l uint8, tagsize int) int {
mLen := (uint64(1) << (8 * l)) - 1
if m64 := uint64(math.MaxInt64) - uint64(tagsize); l > 8 || mLen > m64 { //nolint:gosec // G114
mLen = m64 // The maximum lentgh on a 64bit arch
}
if mLen != uint64(int(mLen)) { //nolint:gosec // G114
return math.MaxInt32 - tagsize // We have only 32bit int's
}
return int(mLen) //nolint:gosec // G114
}
// MaxNonceLength returns the maximum nonce length for a given plaintext length.
// A return value <= 0 indicates that plaintext length is too large for
// any nonce length.
func MaxNonceLength(pdatalen int) int {
const tagsize = 16
for L := 2; L <= 8; L++ {
if maxlen(uint8(L), tagsize) >= pdatalen { //nolint:gosec // G115
return 15 - L
}
}
return 0
}
func (c *ccm) cbcRound(mac, data []byte) {
for i := range ccmBlockSize {
mac[i] ^= data[i]
}
c.b.Encrypt(mac, mac)
}
func (c *ccm) cbcData(mac, data []byte) {
for len(data) >= ccmBlockSize {
c.cbcRound(mac, data[:ccmBlockSize])
data = data[ccmBlockSize:]
}
if len(data) > 0 {
var block [ccmBlockSize]byte
copy(block[:], data)
c.cbcRound(mac, block[:])
}
}
var errPlaintextTooLong = errors.New("ccm: plaintext too large")
func (c *ccm) tag(nonce, plaintext, adata []byte) ([]byte, error) {
var mac [ccmBlockSize]byte
if len(adata) > 0 {
mac[0] |= 1 << 6
}
mac[0] |= (c.M - 2) << 2
mac[0] |= c.L - 1
if len(nonce) != c.NonceSize() {
return nil, errInvalidNonceSize
}
if len(plaintext) > c.MaxLength() {
return nil, errPlaintextTooLong
}
binary.BigEndian.PutUint64(mac[ccmBlockSize-8:], uint64(len(plaintext)))
copy(mac[1:ccmBlockSize-c.L], nonce)
c.b.Encrypt(mac[:], mac[:])
var block [ccmBlockSize]byte
if adataLength := uint64(len(adata)); adataLength > 0 { //nolint:nestif
// First adata block includes adata length
i := 2
if adataLength <= 0xfeff {
binary.BigEndian.PutUint16(block[:i], uint16(adataLength))
} else {
binary.BigEndian.PutUint16(block[0:2], 0xfeff)
if adataLength < uint64(1<<32) {
i = 2 + 4
binary.BigEndian.PutUint32(block[2:i], uint32(adataLength)) //nolint:gosec // G115
} else {
i = 2 + 8
binary.BigEndian.PutUint64(block[2:i], adataLength)
}
}
i = copy(block[i:], adata)
c.cbcRound(mac[:], block[:])
c.cbcData(mac[:], adata[i:])
}
if len(plaintext) > 0 {
c.cbcData(mac[:], plaintext)
}
return mac[:c.M], nil
}
// sliceForAppend takes a slice and a requested number of bytes. It returns a
// slice with the contents of the given slice followed by that many bytes and a
// second slice that aliases into it and contains only the extra bytes. If the
// original slice has sufficient capacity then no allocation is performed.
// From crypto/cipher/gcm.go
// .
func sliceForAppend(in []byte, n int) (head, tail []byte) {
if total := len(in) + n; cap(in) >= total {
head = in[:total]
} else {
head = make([]byte, total)
copy(head, in)
}
tail = head[len(in):]
return
}
// Seal encrypts and authenticates plaintext, authenticates the
// additional data and appends the result to dst, returning the updated
// slice. The nonce must be NonceSize() bytes long and unique for all
// time, for a given key.
// The plaintext must be no longer than MaxLength() bytes long.
//
// The plaintext and dst may alias exactly or not at all.
func (c *ccm) Seal(dst, nonce, plaintext, adata []byte) []byte {
tag, err := c.tag(nonce, plaintext, adata)
if err != nil {
// The cipher.AEAD interface doesn't allow for an error return.
panic(err) // nolint
}
var iv, s0 [ccmBlockSize]byte
iv[0] = c.L - 1
copy(iv[1:ccmBlockSize-c.L], nonce)
c.b.Encrypt(s0[:], iv[:])
for i := 0; i < int(c.M); i++ {
tag[i] ^= s0[i]
}
iv[len(iv)-1] |= 1
stream := cipher.NewCTR(c.b, iv[:])
ret, out := sliceForAppend(dst, len(plaintext)+int(c.M))
stream.XORKeyStream(out, plaintext)
copy(out[len(plaintext):], tag)
return ret
}
var (
errOpen = errors.New("ccm: message authentication failed")
errCiphertextTooShort = errors.New("ccm: ciphertext too short")
errCiphertextTooLong = errors.New("ccm: ciphertext too long")
)
func (c *ccm) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) {
if len(ciphertext) < int(c.M) {
return nil, errCiphertextTooShort
}
if len(ciphertext) > c.MaxLength()+c.Overhead() {
return nil, errCiphertextTooLong
}
tag := make([]byte, int(c.M))
copy(tag, ciphertext[len(ciphertext)-int(c.M):])
ciphertextWithoutTag := ciphertext[:len(ciphertext)-int(c.M)]
var iv, s0 [ccmBlockSize]byte
iv[0] = c.L - 1
copy(iv[1:ccmBlockSize-c.L], nonce)
c.b.Encrypt(s0[:], iv[:])
for i := 0; i < int(c.M); i++ {
tag[i] ^= s0[i]
}
iv[len(iv)-1] |= 1
stream := cipher.NewCTR(c.b, iv[:])
// Cannot decrypt directly to dst since we're not supposed to
// reveal the plaintext to the caller if authentication fails.
plaintext := make([]byte, len(ciphertextWithoutTag))
stream.XORKeyStream(plaintext, ciphertextWithoutTag)
expectedTag, err := c.tag(nonce, plaintext, adata)
if err != nil {
return nil, err
}
if subtle.ConstantTimeCompare(tag, expectedTag) != 1 {
return nil, errOpen
}
return append(dst, plaintext...), nil
}

451
ccm/ccm_test.go Normal file
View File

@ -0,0 +1,451 @@
// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package ccm
// Refer to RFC 3610 section 8 for the vectors.
import (
"crypto/aes"
"encoding/hex"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func mustHexDecode(t *testing.T, s string) []byte {
t.Helper()
r, err := hex.DecodeString(s)
assert.NoError(t, err)
return r
}
func aesKey1to12(t *testing.T) []byte {
t.Helper()
return mustHexDecode(t, "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf")
}
func aesKey13to24(t *testing.T) []byte {
t.Helper()
return mustHexDecode(t, "d7828d13b2b0bdc325a76236df93cc6b")
}
// AESKey: AES Key
// CipherText: Authenticated and encrypted output
// ClearHeaderOctets: Input with X cleartext header octets
// Data: Input with X cleartext header octets
// M: length(CBC-MAC)
// Nonce: Nonce.
type vector struct {
AESKey []byte
CipherText []byte
ClearHeaderOctets int
Data []byte
M int
Nonce []byte
}
func TestRFC3610Vectors(t *testing.T) { //nolint:maintidx
cases := []vector{
// Vectors 1-12
{
AESKey: aesKey1to12(t),
CipherText: mustHexDecode(t,
"0001020304050607588c979a61c663d2f066d0c2c0f989806d5f6b61dac38417e8d12cfdf926e0"),
ClearHeaderOctets: 8,
Data: mustHexDecode(t, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e"),
M: 8,
Nonce: mustHexDecode(t, "00000003020100a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12(t),
CipherText: mustHexDecode(t,
"000102030405060772c91a36e135f8cf291ca894085c87e3cc15c439c9e43a3ba091d56e10400916"),
ClearHeaderOctets: 8,
Data: mustHexDecode(t, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"),
M: 8,
Nonce: mustHexDecode(t, "00000004030201a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12(t),
CipherText: mustHexDecode(t,
"000102030405060751b1e5f44a197d1da46b0f8e2d282ae871e838bb64da8596574adaa76fbd9fb0c5",
),
ClearHeaderOctets: 8,
Data: mustHexDecode(t, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"),
M: 8,
Nonce: mustHexDecode(t, "00000005040302a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12(t),
CipherText: mustHexDecode(t,
"000102030405060708090a0ba28c6865939a9a79faaa5c4c2a9d4a91cdac8c96c861b9c9e61ef1"),
ClearHeaderOctets: 12,
Data: mustHexDecode(t, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e"),
M: 8,
Nonce: mustHexDecode(t, "00000006050403a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12(t),
CipherText: mustHexDecode(t,
"000102030405060708090a0bdcf1fb7b5d9e23fb9d4e131253658ad86ebdca3e51e83f077d9c2d93"),
ClearHeaderOctets: 12,
Data: mustHexDecode(t, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"),
M: 8,
Nonce: mustHexDecode(t, "00000007060504a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12(t),
CipherText: mustHexDecode(t,
"000102030405060708090a0b6fc1b011f006568b5171a42d953d469b2570a4bd87405a0443ac91cb94",
),
ClearHeaderOctets: 12,
Data: mustHexDecode(t, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"),
M: 8,
Nonce: mustHexDecode(t, "00000008070605a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12(t),
CipherText: mustHexDecode(t,
"00010203040506070135d1b2c95f41d5d1d4fec185d166b8094e999dfed96c048c56602c97acbb7490",
),
ClearHeaderOctets: 8,
Data: mustHexDecode(t, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e"),
M: 10,
Nonce: mustHexDecode(t, "00000009080706a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12(t),
CipherText: mustHexDecode(t,
"00010203040506077b75399ac0831dd2f0bbd75879a2fd8f6cae6b6cd9b7db24c17b4433f434963f34b4",
),
ClearHeaderOctets: 8,
Data: mustHexDecode(t, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"),
M: 10,
Nonce: mustHexDecode(t, "0000000a090807a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12(t),
CipherText: mustHexDecode(t,
"000102030405060782531a60cc24945a4b8279181ab5c84df21ce7f9b73f42e197ea9c07e56b5eb17e5f4e",
),
ClearHeaderOctets: 8,
Data: mustHexDecode(t, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"),
M: 10,
Nonce: mustHexDecode(t, "0000000b0a0908a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12(t),
CipherText: mustHexDecode(t,
"000102030405060708090a0b07342594157785152b074098330abb141b947b566aa9406b4d999988dd",
),
ClearHeaderOctets: 12,
Data: mustHexDecode(t, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e"),
M: 10,
Nonce: mustHexDecode(t, "0000000c0b0a09a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12(t),
CipherText: mustHexDecode(t,
"000102030405060708090a0b676bb20380b0e301e8ab79590a396da78b834934f53aa2e9107a8b6c022c",
),
ClearHeaderOctets: 12,
Data: mustHexDecode(t, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"),
M: 10,
Nonce: mustHexDecode(t, "0000000d0c0b0aa0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12(t),
CipherText: mustHexDecode(t,
"000102030405060708090a0bc0ffa0d6f05bdb67f24d43a4338d2aa4bed7b20e43cd1aa31662e7ad65d6db",
),
ClearHeaderOctets: 12,
Data: mustHexDecode(t, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"),
M: 10,
Nonce: mustHexDecode(t, "0000000e0d0c0ba0a1a2a3a4a5"),
},
// Vectors 13-24
{
AESKey: aesKey13to24(t),
CipherText: mustHexDecode(t,
"0be1a88bace018b14cb97f86a2a4689a877947ab8091ef5386a6ffbdd080f8e78cf7cb0cddd7b3"),
ClearHeaderOctets: 8,
Data: mustHexDecode(t, "0be1a88bace018b108e8cf97d820ea258460e96ad9cf5289054d895ceac47c"),
M: 8,
Nonce: mustHexDecode(t, "00412b4ea9cdbe3c9696766cfa"),
},
{
AESKey: aesKey13to24(t),
CipherText: mustHexDecode(t,
"63018f76dc8a1bcb4ccb1e7ca981befaa0726c55d378061298c85c92814abc33c52ee81d7d77c08a"),
ClearHeaderOctets: 8,
Data: mustHexDecode(t, "63018f76dc8a1bcb9020ea6f91bdd85afa0039ba4baff9bfb79c7028949cd0ec"),
M: 8,
Nonce: mustHexDecode(t, "0033568ef7b2633c9696766cfa"),
},
{
AESKey: aesKey13to24(t),
CipherText: mustHexDecode(t,
"aa6cfa36cae86b40b1d23a2220ddc0ac900d9aa03c61fcf4a559a4417767089708a776796edb723506",
),
ClearHeaderOctets: 8,
Data: mustHexDecode(t, "aa6cfa36cae86b40b916e0eacc1c00d7dcec68ec0b3bbb1a02de8a2d1aa346132e"),
M: 8,
Nonce: mustHexDecode(t, "00103fe41336713c9696766cfa"),
},
{
AESKey: aesKey13to24(t),
CipherText: mustHexDecode(t,
"d0d0735c531e1becf049c24414d253c3967b70609b7cbb7c499160283245269a6f49975bcadeaf"),
ClearHeaderOctets: 12,
Data: mustHexDecode(t, "d0d0735c531e1becf049c24412daac5630efa5396f770ce1a66b21f7b2101c"),
M: 8,
Nonce: mustHexDecode(t, "00764c63b8058e3c9696766cfa"),
},
{
AESKey: aesKey13to24(t),
CipherText: mustHexDecode(t,
"77b60f011c03e1525899bcae5545ff1a085ee2efbf52b2e04bee1e2336c73e3f762c0c7744fe7e3c"),
ClearHeaderOctets: 12,
Data: mustHexDecode(t, "77b60f011c03e1525899bcaee88b6a46c78d63e52eb8c546efb5de6f75e9cc0d"),
M: 8,
Nonce: mustHexDecode(t, "00f8b678094e3b3c9696766cfa"),
},
{
AESKey: aesKey13to24(t),
CipherText: mustHexDecode(t,
"cd9044d2b71fdb8120ea60c0009769ecabdf48625594c59251e6035722675e04c847099e5ae0704551",
),
ClearHeaderOctets: 12,
Data: mustHexDecode(t, "cd9044d2b71fdb8120ea60c06435acbafb11a82e2f071d7ca4a5ebd93a803ba87f"),
M: 8,
Nonce: mustHexDecode(t, "00d560912d3f703c9696766cfa"),
},
{
AESKey: aesKey13to24(t),
CipherText: mustHexDecode(t,
"d85bc7e69f944fb8bc218daa947427b6db386a99ac1aef23ade0b52939cb6a637cf9bec2408897c6ba",
),
ClearHeaderOctets: 8,
Data: mustHexDecode(t, "d85bc7e69f944fb88a19b950bcf71a018e5e6701c91787659809d67dbedd18"),
M: 10,
Nonce: mustHexDecode(t, "0042fff8f1951c3c9696766cfa"),
},
{
AESKey: aesKey13to24(t),
CipherText: mustHexDecode(t,
"74a0ebc9069f5b375810e6fd25874022e80361a478e3e9cf484ab04f447efff6f0a477cc2fc9bf548944",
),
ClearHeaderOctets: 8,
Data: mustHexDecode(t, "74a0ebc9069f5b371761433c37c5a35fc1f39f406302eb907c6163be38c98437"),
M: 10,
Nonce: mustHexDecode(t, "00920f40e56cdc3c9696766cfa"),
},
{
AESKey: aesKey13to24(t),
CipherText: mustHexDecode(t,
"44a3aa3aae6475caf2beed7bc5098e83feb5b31608f8e29c38819a89c8e776f1544d4151a4ed3a8b87b9ce",
),
ClearHeaderOctets: 8,
Data: mustHexDecode(t, "44a3aa3aae6475caa434a8e58500c6e41530538862d686ea9e81301b5ae4226bfa"),
M: 10,
Nonce: mustHexDecode(t, "0027ca0c7120bc3c9696766cfa"),
},
{
AESKey: aesKey13to24(t),
CipherText: mustHexDecode(t,
"ec46bb63b02520c33c49fd7031d750a09da3ed7fddd49a2032aabf17ec8ebf7d22c8088c666be5c197",
),
ClearHeaderOctets: 12,
Data: mustHexDecode(t, "ec46bb63b02520c33c49fd70b96b49e21d621741632875db7f6c9243d2d7c2"),
M: 10,
Nonce: mustHexDecode(t, "005b8ccbcd9af83c9696766cfa"),
},
{
AESKey: aesKey13to24(t),
CipherText: mustHexDecode(t,
"47a65ac78b3d594227e85e71e882f1dbd38ce3eda7c23f04dd65071eb41342acdf7e00dccec7ae52987d",
),
ClearHeaderOctets: 12,
Data: mustHexDecode(t, "47a65ac78b3d594227e85e71e2fcfbb880442c731bf95167c8ffd7895e337076"),
M: 10,
Nonce: mustHexDecode(t, "003ebe94044b9a3c9696766cfa"),
},
{
AESKey: aesKey13to24(t),
CipherText: mustHexDecode(t,
"6e37a6ef546d955d34ab6059f32905b88a641b04b9c9ffb58cc390900f3da12ab16dce9e82efa16da62059",
),
ClearHeaderOctets: 12,
Data: mustHexDecode(t, "6e37a6ef546d955d34ab6059abf21c0b02feb88f856df4a37381bce3cc128517d4"),
M: 10,
Nonce: mustHexDecode(t, "008d493b30ae8b3c9696766cfa"),
},
}
assert.Equal(t, 24, len(cases))
for idx, testCase := range cases {
t.Run(fmt.Sprintf("packet vector #%d", idx+1), func(t *testing.T) {
blk, err := aes.NewCipher(testCase.AESKey)
assert.NoError(t, err, "could not initialize AES block cipher from key")
lccm, err := NewCCM(blk, testCase.M, len(testCase.Nonce))
assert.NoError(t, err, "could not create CCM")
t.Run("seal", func(t *testing.T) {
var dst []byte
dst = lccm.Seal(
dst,
testCase.Nonce,
testCase.Data[testCase.ClearHeaderOctets:],
testCase.Data[:testCase.ClearHeaderOctets],
)
assert.Equal(t, testCase.CipherText[testCase.ClearHeaderOctets:], dst)
})
t.Run("open", func(t *testing.T) {
var dst []byte
dst, err = lccm.Open(
dst,
testCase.Nonce,
testCase.CipherText[testCase.ClearHeaderOctets:],
testCase.CipherText[:testCase.ClearHeaderOctets],
)
assert.NoError(t, err)
assert.Equal(t, testCase.Data[testCase.ClearHeaderOctets:], dst)
})
})
}
}
func TestNewCCMError(t *testing.T) {
cases := map[string]struct {
vector
err error
}{
"ShortNonceLength": {
vector{
AESKey: aesKey1to12(t),
M: 8,
Nonce: mustHexDecode(t, "a0a1a2a3a4a5"),
}, errInvalidNonceSize,
},
"LongNonceLength": {
vector{
AESKey: aesKey1to12(t),
M: 8,
Nonce: mustHexDecode(t, "0001020304050607080910111213"),
}, errInvalidNonceSize,
},
"ShortTag": {
vector{
AESKey: aesKey1to12(t),
M: 3,
Nonce: mustHexDecode(t, "00010203040506070809101112"),
}, errInvalidTagSize,
},
"LongTag": {
vector{
AESKey: aesKey1to12(t),
M: 17,
Nonce: mustHexDecode(t, "00010203040506070809101112"),
}, errInvalidTagSize,
},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
blk, err := aes.NewCipher(c.AESKey)
assert.NoError(t, err, "could not initialize AES block cipher from key")
_, err = NewCCM(blk, c.M, len(c.Nonce))
assert.ErrorIs(t, err, c.err)
})
}
}
func TestSealError(t *testing.T) {
cases := map[string]struct {
vector
err error
}{
"InvalidNonceLength": {
vector{
Data: mustHexDecode(t, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e"),
M: 8,
Nonce: mustHexDecode(t, "00000003020100a0a1a2a3a4"), // short
}, errInvalidNonceSize,
},
"PlaintextTooLong": {
vector{
Data: make([]byte, 100000),
M: 8,
Nonce: mustHexDecode(t, "00000003020100a0a1a2a3a4a5"),
}, errPlaintextTooLong,
},
}
blk, err := aes.NewCipher(aesKey1to12(t))
assert.NoError(t, err)
lccm, err := NewCCM(blk, 8, 13)
assert.NoError(t, err)
for name, testCase := range cases {
t.Run(name, func(t *testing.T) {
defer func() {
err, ok := recover().(error)
assert.True(t, ok)
assert.ErrorIs(t, err, testCase.err)
}()
var dst []byte
_ = lccm.Seal(
dst,
testCase.Nonce,
testCase.Data[testCase.ClearHeaderOctets:],
testCase.Data[:testCase.ClearHeaderOctets],
)
})
}
}
func TestOpenError(t *testing.T) {
cases := map[string]struct {
vector
err error
}{
"CiphertextTooShort": {
vector{
CipherText: make([]byte, 10),
ClearHeaderOctets: 8,
Nonce: mustHexDecode(t, "00000003020100a0a1a2a3a4a5"),
}, errCiphertextTooShort,
},
"CiphertextTooLong": {
vector{
CipherText: make([]byte, 100000),
ClearHeaderOctets: 8,
Nonce: mustHexDecode(t, "00000003020100a0a1a2a3a4a5"),
}, errCiphertextTooLong,
},
}
blk, err := aes.NewCipher(aesKey1to12(t))
assert.NoError(t, err, "could not initialize AES block cipher from key")
lccm, err := NewCCM(blk, 8, 13)
assert.NoError(t, err, "could not create CCM")
for name, c := range cases {
t.Run(name, func(t *testing.T) {
var dst []byte
_, err = lccm.Open(dst, c.Nonce, c.CipherText[c.ClearHeaderOctets:], c.CipherText[:c.ClearHeaderOctets])
assert.ErrorIs(t, err, c.err)
})
}
}

31
chacha20.go Normal file
View File

@ -0,0 +1,31 @@
package starcrypto
import (
"io"
"b612.me/starcrypto/symm"
)
func EncryptChaCha20(data, key, nonce []byte) ([]byte, error) {
return symm.EncryptChaCha20(data, key, nonce)
}
func DecryptChaCha20(src, key, nonce []byte) ([]byte, error) {
return symm.DecryptChaCha20(src, key, nonce)
}
func EncryptChaCha20Stream(dst io.Writer, src io.Reader, key, nonce []byte) error {
return symm.EncryptChaCha20Stream(dst, src, key, nonce)
}
func DecryptChaCha20Stream(dst io.Writer, src io.Reader, key, nonce []byte) error {
return symm.DecryptChaCha20Stream(dst, src, key, nonce)
}
func EncryptChaCha20Poly1305(plain, key, nonce, aad []byte) ([]byte, error) {
return symm.EncryptChaCha20Poly1305(plain, key, nonce, aad)
}
func DecryptChaCha20Poly1305(ciphertext, key, nonce, aad []byte) ([]byte, error) {
return symm.DecryptChaCha20Poly1305(ciphertext, key, nonce, aad)
}

106
crc32.go
View File

@ -1,117 +1,23 @@
package starcrypto
import (
"encoding/binary"
"encoding/hex"
"hash/crc32"
)
import "b612.me/starcrypto/hashx"
// CheckCRC32A calculates CRC32A (ITU I.363.5 algorithm, popularized by BZIP2) checksum.
// This function will produce the same results as following PHP code:
//
// hexdec(hash('crc32', $data))
func CheckCRC32A(data []byte) uint32 {
b := digest(data)
return binary.BigEndian.Uint32(b)
return hashx.CheckCRC32A(data)
}
func Crc32Str(bstr []byte) string {
return String(Crc32(bstr))
return hashx.Crc32Str(bstr)
}
// CRC32 输出CRC32校验值
func Crc32(bstr []byte) []byte {
crcsum := crc32.NewIEEE()
crcsum.Write(bstr)
return crcsum.Sum(nil)
return hashx.Crc32(bstr)
}
func Crc32A(data []byte) []byte {
return digest(data)
return hashx.Crc32A(data)
}
// Crc32AStr is a convenience function that outputs CRC32A (ITU I.363.5 algorithm, popularized by BZIP2) checksum as a hex string.
// This function will produce the same results as following PHP code:
//
// hash('crc32', $data)
func Crc32AStr(data []byte) string {
b := digest(data)
return hex.EncodeToString(b)
}
// digest performs checksum calculation for each byte of provided data and returns digest in form of byte array.
func digest(data []byte) []byte {
var crc uint32
var digest = make([]byte, 4)
crc = ^crc
for i := 0; i < len(data); i++ {
crc = (crc << 8) ^ table[(crc>>24)^(uint32(data[i])&0xff)]
}
crc = ^crc
digest[3] = byte((crc >> 24) & 0xff)
digest[2] = byte((crc >> 16) & 0xff)
digest[1] = byte((crc >> 8) & 0xff)
digest[0] = byte(crc & 0xff)
return digest
}
// table is the pre-generated 0x04C11DB7 polynominal used for CRC32A.
var table = [256]uint32{
0x0,
0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6,
0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac,
0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f,
0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a,
0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58,
0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033,
0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe,
0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4,
0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5,
0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07,
0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c,
0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1,
0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b,
0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698,
0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d,
0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f,
0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80,
0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a,
0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629,
0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c,
0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e,
0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65,
0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8,
0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2,
0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74,
0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21,
0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a,
0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087,
0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d,
0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce,
0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb,
0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09,
0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf,
0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4,
return hashx.Crc32AStr(data)
}

159
crypto.go
View File

@ -1,171 +1,26 @@
package starcrypto
import (
"crypto/rand"
"encoding/binary"
"encoding/hex"
"io"
"os"
"b612.me/starcrypto/hashx"
"b612.me/starcrypto/legacy"
)
func String(bstr []byte) string {
return hex.EncodeToString(bstr)
return hashx.HexString(bstr)
}
func VicqueEncodeV1(srcdata []byte, key string) []byte {
var keys []int
var randCode1, randCode2 uint8
data := make([]byte, len(srcdata))
copy(data, srcdata)
binary.Read(rand.Reader, binary.LittleEndian, &randCode1)
binary.Read(rand.Reader, binary.LittleEndian, &randCode2)
keys = append(keys, len(key)+int(randCode1))
lens := len(data)
for _, v := range key {
keys = append(keys, int(byte(v))+int(randCode1)-int(randCode2))
}
lenkey := len(keys)
for k, v := range data {
if k == lens/2 {
break
}
nv := int(v)
t := 0
if k%2 == 0 {
nv += keys[k%lenkey]
if nv > 255 {
nv -= 256
}
t = int(data[lens-1-k])
t += keys[k%lenkey]
if t > 255 {
t -= 256
}
} else {
nv -= keys[k%lenkey]
if nv < 0 {
nv += 256
}
t = int(data[lens-1-k])
t -= keys[k%lenkey]
if t > 255 {
t += 256
}
}
data[k] = byte(t)
data[lens-1-k] = byte(nv)
}
data = append(data, byte(randCode1), byte(randCode2))
return data
return legacy.VicqueEncodeV1(srcdata, key)
}
func VicqueDecodeV1(srcdata []byte, key string) []byte {
var keys []int
var randCode1, randCode2 int
data := make([]byte, len(srcdata))
copy(data, srcdata)
lens := len(data)
randCode1 = int(data[lens-2])
randCode2 = int(data[lens-1])
keys = append(keys, len(key)+int(randCode1))
for _, v := range key {
keys = append(keys, int(byte(v))+int(randCode1)-int(randCode2))
}
lenkey := len(keys)
lens -= 2
for k, v := range data {
if k == lens/2 {
break
}
nv := int(v)
t := 0
if k%2 == 0 {
nv -= keys[k%lenkey]
if nv < 0 {
nv += 256
}
t = int(data[lens-1-k])
t -= keys[k%lenkey]
if t > 255 {
t += 256
}
} else {
nv += keys[k%lenkey]
if nv > 255 {
nv -= 256
}
t = int(data[lens-1-k])
t += keys[k%lenkey]
if t > 255 {
t -= 256
}
}
data[k] = byte(t)
data[lens-1-k] = byte(nv)
}
return data[:lens]
return legacy.VicqueDecodeV1(srcdata, key)
}
func VicqueEncodeV1File(src, dst, pwd string, shell func(float64)) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
stat, _ := os.Stat(src)
filebig := float64(stat.Size())
var sum int64
defer fpsrc.Close()
fpdst, err := os.Create(dst)
if err != nil {
return err
}
defer fpdst.Close()
for {
buf := make([]byte, 1048576)
n, err := fpsrc.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return err
}
sum += int64(n)
go shell(float64(sum) / filebig * 100)
data := VicqueEncodeV1(buf[0:n], pwd)
fpdst.Write(data)
}
return nil
return legacy.VicqueEncodeV1File(src, dst, pwd, shell)
}
func VicqueDecodeV1File(src, dst, pwd string, shell func(float64)) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
stat, _ := os.Stat(src)
filebig := float64(stat.Size())
var sum int64
defer fpsrc.Close()
fpdst, err := os.Create(dst)
if err != nil {
return err
}
defer fpdst.Close()
for {
buf := make([]byte, 1048578)
n, err := fpsrc.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return err
}
sum += int64(n)
go shell(float64(sum) / filebig * 100)
data := VicqueDecodeV1(buf[0:n], pwd)
fpdst.Write(data)
}
return nil
return legacy.VicqueDecodeV1File(src, dst, pwd, shell)
}

39
des.go Normal file
View File

@ -0,0 +1,39 @@
package starcrypto
import (
"io"
"b612.me/starcrypto/symm"
)
func EncryptDESCBC(data, key, iv []byte, paddingType string) ([]byte, error) {
return symm.EncryptDESCBC(data, key, iv, paddingType)
}
func DecryptDESCBC(src, key, iv []byte, paddingType string) ([]byte, error) {
return symm.DecryptDESCBC(src, key, iv, paddingType)
}
func EncryptDESCBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
return symm.EncryptDESCBCStream(dst, src, key, iv, paddingType)
}
func DecryptDESCBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
return symm.DecryptDESCBCStream(dst, src, key, iv, paddingType)
}
func Encrypt3DESCBC(data, key, iv []byte, paddingType string) ([]byte, error) {
return symm.Encrypt3DESCBC(data, key, iv, paddingType)
}
func Decrypt3DESCBC(src, key, iv []byte, paddingType string) ([]byte, error) {
return symm.Decrypt3DESCBC(src, key, iv, paddingType)
}
func Encrypt3DESCBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
return symm.Encrypt3DESCBCStream(dst, src, key, iv, paddingType)
}
func Decrypt3DESCBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
return symm.Decrypt3DESCBCStream(dst, src, key, iv, paddingType)
}

View File

@ -1,117 +1,35 @@
package starcrypto
import (
"b612.me/starcrypto/asymm"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"errors"
"golang.org/x/crypto/ssh"
)
func GenerateEcdsaKey(pubkeyCurve elliptic.Curve) (*ecdsa.PrivateKey, *ecdsa.PublicKey, error) {
// 随机挑选基点,生成私钥
priv, err := ecdsa.GenerateKey(pubkeyCurve, rand.Reader)
if err != nil {
return nil, nil, err
}
return priv, &priv.PublicKey, nil
return asymm.GenerateEcdsaKey(pubkeyCurve)
}
func EncodeEcdsaPrivateKey(private *ecdsa.PrivateKey, secret string) ([]byte, error) {
b, err := x509.MarshalECPrivateKey(private)
if err != nil {
return nil, err
}
if secret == "" {
return pem.EncodeToMemory(&pem.Block{
Bytes: b,
Type: "EC PRIVATE KEY",
}), err
}
chiper := x509.PEMCipherAES256
blk, err := x509.EncryptPEMBlock(rand.Reader, "EC PRIVATE KEY", b, []byte(secret), chiper)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(blk), err
return asymm.EncodeEcdsaPrivateKey(private, secret)
}
func EncodeEcdsaPublicKey(public *ecdsa.PublicKey) ([]byte, error) {
publicBytes, err := x509.MarshalPKIXPublicKey(public)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{
Bytes: publicBytes,
Type: "PUBLIC KEY",
}), nil
return asymm.EncodeEcdsaPublicKey(public)
}
func DecodeEcdsaPrivateKey(private []byte, password string) (*ecdsa.PrivateKey, error) {
var prikey *ecdsa.PrivateKey
var err error
var bytes []byte
blk, _ := pem.Decode(private)
if blk == nil {
return nil, errors.New("private key error")
}
if password != "" {
tmp, err := x509.DecryptPEMBlock(blk, []byte(password))
if err != nil {
return nil, err
}
bytes = tmp
} else {
bytes = blk.Bytes
}
prikey, err = x509.ParseECPrivateKey(bytes)
if err != nil {
tmp, err := x509.ParsePKCS8PrivateKey(bytes)
if err != nil {
return nil, err
}
prikey = tmp.(*ecdsa.PrivateKey)
}
return prikey, err
return asymm.DecodeEcdsaPrivateKey(private, password)
}
func DecodeEcdsaPublicKey(pubStr []byte) (*ecdsa.PublicKey, error) {
blk, _ := pem.Decode(pubStr)
if blk == nil {
return nil, errors.New("public key error")
}
pub, err := x509.ParsePKIXPublicKey(blk.Bytes)
if err != nil {
return nil, err
}
return pub.(*ecdsa.PublicKey), nil
return asymm.DecodeEcdsaPublicKey(pubStr)
}
func EncodeEcdsaSSHPublicKey(public *ecdsa.PublicKey) ([]byte, error) {
publicKey, err := ssh.NewPublicKey(public)
if err != nil {
return nil, err
}
return ssh.MarshalAuthorizedKey(publicKey), nil
return asymm.EncodeEcdsaSSHPublicKey(public)
}
func GenerateEcdsaSSHKeyPair(pubkeyCurve elliptic.Curve, secret string) (string, string, error) {
pkey, pubkey, err := GenerateEcdsaKey(pubkeyCurve)
if err != nil {
return "", "", err
}
pub, err := EncodeEcdsaSSHPublicKey(pubkey)
if err != nil {
return "", "", err
}
priv, err := EncodeEcdsaPrivateKey(pkey, secret)
if err != nil {
return "", "", err
}
return string(priv), string(pub), nil
return asymm.GenerateEcdsaSSHKeyPair(pubkeyCurve, secret)
}

388
encodingx/encoding.go Normal file
View File

@ -0,0 +1,388 @@
package encodingx
import (
"encoding/ascii85"
"encoding/base64"
"errors"
"io"
"os"
"strings"
)
var (
ErrLength = errors.New("base128: invalid length base128 string")
ErrBit = errors.New("base128: high bit set in base128 string")
)
var enctab = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~'")
var dectab = map[byte]byte{
'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7,
'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15,
'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23,
'Y': 24, 'Z': 25, 'a': 26, 'b': 27, 'c': 28, 'd': 29, 'e': 30, 'f': 31,
'g': 32, 'h': 33, 'i': 34, 'j': 35, 'k': 36, 'l': 37, 'm': 38, 'n': 39,
'o': 40, 'p': 41, 'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47,
'w': 48, 'x': 49, 'y': 50, 'z': 51, '0': 52, '1': 53, '2': 54, '3': 55,
'4': 56, '5': 57, '6': 58, '7': 59, '8': 60, '9': 61, '!': 62, '#': 63,
'$': 64, '%': 65, '&': 66, '(': 67, ')': 68, '*': 69, '+': 70, ',': 71,
'.': 72, '/': 73, ':': 74, ';': 75, '<': 76, '=': 77, '>': 78, '?': 79,
'@': 80, '[': 81, ']': 82, '^': 83, '_': 84, '`': 85, '{': 86, '|': 87,
'}': 88, '~': 89, '\'': 90,
}
func Base91EncodeToString(d []byte) string {
return string(Base91Encode(d))
}
func Base91Encode(d []byte) []byte {
var n, b uint
var o []byte
for i := 0; i < len(d); i++ {
b |= uint(d[i]) << n
n += 8
if n > 13 {
v := b & 8191
if v > 88 {
b >>= 13
n -= 13
} else {
v = b & 16383
b >>= 14
n -= 14
}
o = append(o, enctab[v%91], enctab[v/91])
}
}
if n > 0 {
o = append(o, enctab[b%91])
if n > 7 || b > 90 {
o = append(o, enctab[b/91])
}
}
return o
}
func Base91DecodeString(d string) []byte {
return Base91Decode([]byte(d))
}
func Base91Decode(d []byte) []byte {
var b, n uint
var o []byte
v := -1
for i := 0; i < len(d); i++ {
c, ok := dectab[d[i]]
if !ok {
continue
}
if v < 0 {
v = int(c)
} else {
v += int(c) * 91
b |= uint(v) << n
if v&8191 > 88 {
n += 13
} else {
n += 14
}
o = append(o, byte(b&255))
b >>= 8
n -= 8
for n > 7 {
o = append(o, byte(b&255))
b >>= 8
n -= 8
}
v = -1
}
}
if v+1 > 0 {
o = append(o, byte((b|uint(v)<<n)&255))
}
return o
}
func Base128Encode(dst, src []byte) int {
ret := Base128EncodedLen(len(src))
if len(dst) < ret {
panic("dst has insufficient length")
}
buf := dst[:0]
whichByte := uint(1)
bufByte := byte(0)
for _, v := range src {
buf = append(buf, bufByte|(v>>whichByte))
bufByte = (v & byte((1<<whichByte)-1)) << (7 - whichByte)
if whichByte == 7 {
buf = append(buf, bufByte)
bufByte = 0
whichByte = 0
}
whichByte++
}
buf = append(buf, bufByte)
return ret
}
func Base128Decode(dst, src []byte) (int, error) {
dLen := Base128DecodedLen(len(src))
if Base128EncodedLen(dLen) != len(src) {
return 0, ErrLength
}
if len(dst) < dLen {
panic("dst has insufficient length")
}
buf := dst[:0]
whichByte := uint(1)
bufByte := byte(0)
for _, v := range src {
if (v & 0x80) != 0 {
return len(buf), ErrBit
}
if whichByte > 1 {
buf = append(buf, bufByte|(v>>(8-whichByte)))
}
bufByte = v << whichByte
if whichByte == 8 {
whichByte = 0
}
whichByte++
}
return len(buf), nil
}
func Base128DecodeString(s string) ([]byte, error) {
src := []byte(s)
dst := make([]byte, Base128DecodedLen(len(src)))
n, err := Base128Decode(dst, src)
if err != nil {
return nil, err
}
return dst[:n], nil
}
func Base128DecodedLen(encLen int) int {
return encLen * 7 / 8
}
func Base128EncodedLen(dataLen int) int {
return ((dataLen * 8) + 6) / 7
}
func Base128EncodeToString(src []byte) string {
dst := make([]byte, Base128EncodedLen(len(src)))
Base128Encode(dst, src)
return string(dst)
}
func Base64Encode(bstr []byte) string {
return base64.StdEncoding.EncodeToString(bstr)
}
func Base64Decode(str string) ([]byte, error) {
return base64.StdEncoding.DecodeString(str)
}
func Base85Encode(bstr []byte) string {
out := make([]byte, ascii85.MaxEncodedLen(len(bstr)))
n := ascii85.Encode(out, bstr)
return string(out[:n])
}
func Base85Decode(str string) ([]byte, error) {
dec := ascii85.NewDecoder(strings.NewReader(str))
out, err := io.ReadAll(dec)
if err != nil {
return nil, err
}
return out, nil
}
func Base85EncodeFile(src, dst string, progress func(float64)) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
stat, err := fpsrc.Stat()
if err != nil {
return err
}
fpdst, err := os.Create(dst)
if err != nil {
return err
}
defer fpdst.Close()
enc := ascii85.NewEncoder(fpdst)
defer enc.Close()
var sum int64
buf := make([]byte, 1000*1024)
for {
n, readErr := fpsrc.Read(buf)
if n > 0 {
sum += int64(n)
if _, err := enc.Write(buf[:n]); err != nil {
return err
}
reportProgress(progress, sum, stat.Size())
}
if readErr != nil {
if readErr == io.EOF {
break
}
return readErr
}
}
return nil
}
func Base85DecodeFile(src, dst string, progress func(float64)) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
stat, err := fpsrc.Stat()
if err != nil {
return err
}
counter := &countingReader{r: fpsrc}
dec := ascii85.NewDecoder(counter)
fpdst, err := os.Create(dst)
if err != nil {
return err
}
defer fpdst.Close()
buf := make([]byte, 1250*1024)
for {
n, readErr := dec.Read(buf)
if n > 0 {
if _, err := fpdst.Write(buf[:n]); err != nil {
return err
}
reportProgress(progress, counter.n, stat.Size())
}
if readErr != nil {
if readErr == io.EOF {
break
}
return readErr
}
}
return nil
}
func Base64EncodeFile(src, dst string, progress func(float64)) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
stat, err := fpsrc.Stat()
if err != nil {
return err
}
fpdst, err := os.Create(dst)
if err != nil {
return err
}
defer fpdst.Close()
enc := base64.NewEncoder(base64.StdEncoding, fpdst)
defer enc.Close()
var sum int64
buf := make([]byte, 1024*1024)
for {
n, readErr := fpsrc.Read(buf)
if n > 0 {
sum += int64(n)
if _, err := enc.Write(buf[:n]); err != nil {
return err
}
reportProgress(progress, sum, stat.Size())
}
if readErr != nil {
if readErr == io.EOF {
break
}
return readErr
}
}
return nil
}
func Base64DecodeFile(src, dst string, progress func(float64)) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
stat, err := fpsrc.Stat()
if err != nil {
return err
}
counter := &countingReader{r: fpsrc}
dec := base64.NewDecoder(base64.StdEncoding, counter)
fpdst, err := os.Create(dst)
if err != nil {
return err
}
defer fpdst.Close()
buf := make([]byte, 1024*1024)
for {
n, readErr := dec.Read(buf)
if n > 0 {
if _, err := fpdst.Write(buf[:n]); err != nil {
return err
}
reportProgress(progress, counter.n, stat.Size())
}
if readErr != nil {
if readErr == io.EOF {
break
}
return readErr
}
}
return nil
}
type countingReader struct {
r io.Reader
n int64
}
func (c *countingReader) Read(p []byte) (int, error) {
n, err := c.r.Read(p)
c.n += int64(n)
return n, err
}
func reportProgress(progress func(float64), current, total int64) {
if progress == nil {
return
}
if total <= 0 {
progress(100)
return
}
progress(float64(current) / float64(total) * 100)
}

131
encodingx/encoding_test.go Normal file
View File

@ -0,0 +1,131 @@
package encodingx
import (
"bytes"
"os"
"path/filepath"
"testing"
)
func TestBase64AndBase85RoundTrip(t *testing.T) {
plain := []byte("encoding-roundtrip")
b64 := Base64Encode(plain)
d64, err := Base64Decode(b64)
if err != nil {
t.Fatalf("Base64Decode failed: %v", err)
}
if !bytes.Equal(d64, plain) {
t.Fatalf("base64 mismatch")
}
b85 := Base85Encode(plain)
d85, err := Base85Decode(b85)
if err != nil {
t.Fatalf("Base85Decode failed: %v", err)
}
if !bytes.Equal(d85, plain) {
t.Fatalf("base85 mismatch")
}
}
func TestBase91AndBase128RoundTrip(t *testing.T) {
plain := []byte("base91-base128")
e91 := Base91Encode(plain)
d91 := Base91Decode(e91)
if !bytes.Equal(d91, plain) {
t.Fatalf("base91 mismatch")
}
e128 := Base128EncodeToString(plain)
d128, err := Base128DecodeString(e128)
if err != nil {
t.Fatalf("Base128DecodeString failed: %v", err)
}
if !bytes.Equal(d128, plain) {
t.Fatalf("base128 mismatch")
}
}
func TestBase128DecodeInvalid(t *testing.T) {
_, err := Base128DecodeString(string([]byte{0x80}))
if err == nil {
t.Fatalf("expected base128 decode error")
}
}
func TestBase64AndBase85FileRoundTrip(t *testing.T) {
dir := t.TempDir()
src := filepath.Join(dir, "src.bin")
b64 := filepath.Join(dir, "src.b64")
dst64 := filepath.Join(dir, "src.64.out")
b85 := filepath.Join(dir, "src.b85")
dst85 := filepath.Join(dir, "src.85.out")
plain := []byte("file-roundtrip-encoding")
if err := os.WriteFile(src, plain, 0o644); err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
if err := Base64EncodeFile(src, b64, nil); err != nil {
t.Fatalf("Base64EncodeFile failed: %v", err)
}
if err := Base64DecodeFile(b64, dst64, nil); err != nil {
t.Fatalf("Base64DecodeFile failed: %v", err)
}
got64, err := os.ReadFile(dst64)
if err != nil {
t.Fatalf("ReadFile dst64 failed: %v", err)
}
if !bytes.Equal(got64, plain) {
t.Fatalf("base64 file roundtrip mismatch")
}
if err := Base85EncodeFile(src, b85, nil); err != nil {
t.Fatalf("Base85EncodeFile failed: %v", err)
}
if err := Base85DecodeFile(b85, dst85, nil); err != nil {
t.Fatalf("Base85DecodeFile failed: %v", err)
}
got85, err := os.ReadFile(dst85)
if err != nil {
t.Fatalf("ReadFile dst85 failed: %v", err)
}
if !bytes.Equal(got85, plain) {
t.Fatalf("base85 file roundtrip mismatch")
}
}
func TestBase85RoundTripEdgeLengths(t *testing.T) {
for n := 0; n <= 128; n++ {
plain := make([]byte, n)
for i := range plain {
plain[i] = byte((i*37 + n) % 256)
}
e := Base85Encode(plain)
d, err := Base85Decode(e)
if err != nil {
t.Fatalf("Base85Decode failed at len=%d: %v", n, err)
}
if !bytes.Equal(d, plain) {
t.Fatalf("base85 mismatch at len=%d", n)
}
}
}
func TestBase91RoundTripEdgeLengths(t *testing.T) {
for n := 0; n <= 256; n++ {
plain := make([]byte, n)
for i := range plain {
plain[i] = byte((i*53 + n) % 256)
}
e := Base91Encode(plain)
d := Base91Decode(e)
if !bytes.Equal(d, plain) {
t.Fatalf("base91 mismatch at len=%d", n)
}
}
}

35
encodingx/fuzz_test.go Normal file
View File

@ -0,0 +1,35 @@
package encodingx
import (
"bytes"
"testing"
)
func FuzzBase128RoundTrip(f *testing.F) {
f.Add([]byte("base128"))
f.Add([]byte{})
f.Fuzz(func(t *testing.T, data []byte) {
e := Base128EncodeToString(data)
d, err := Base128DecodeString(e)
if err != nil {
t.Fatalf("Base128DecodeString failed: %v", err)
}
if !bytes.Equal(d, data) {
t.Fatalf("base128 roundtrip mismatch")
}
})
}
func FuzzBase91RoundTrip(f *testing.F) {
f.Add([]byte("base91"))
f.Add([]byte{})
f.Fuzz(func(t *testing.T, data []byte) {
e := Base91Encode(data)
d := Base91Decode(e)
if !bytes.Equal(d, data) {
t.Fatalf("base91 roundtrip mismatch")
}
})
}

240
file.go
View File

@ -1,246 +1,30 @@
package starcrypto
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
import "b612.me/starcrypto/filex"
// Attach 合并src与dst文件并输出到output中
func Attach(src, dst, output string) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
fpdst, err := os.Open(dst)
if err != nil {
return err
}
defer fpdst.Close()
fpout, err := os.Create(output)
if err != nil {
return err
}
defer fpout.Close()
if _, err := io.Copy(fpout, fpsrc); err != nil {
return err
}
for {
buf := make([]byte, 1048574)
n, err := fpdst.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return err
}
fpout.Write(buf[0:n])
}
return nil
return filex.Attach(src, dst, output)
}
// Detach 按bytenum字节大小分割src文件到dst1与dst2两个新文件中去
func Detach(src string, bytenum int, dst1, dst2 string) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
fpdst1, err := os.Create(dst1)
if err != nil {
return err
}
defer fpdst1.Close()
fpdst2, err := os.Create(dst2)
if err != nil {
return err
}
defer fpdst2.Close()
sumall := 0
var buf []byte
for {
if bytenum-sumall < 1048576 {
buf = make([]byte, bytenum-sumall)
} else {
buf = make([]byte, 1048576)
}
n, err := fpsrc.Read(buf)
if err != nil {
return err
}
sumall += n
fpdst1.Write(buf[0:n])
if sumall == bytenum {
break
}
}
for {
buf = make([]byte, 1048576)
n, err := fpsrc.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return err
}
fpdst2.Write(buf[0:n])
}
return nil
return filex.Detach(src, bytenum, dst1, dst2)
}
// SplitFile 把src文件按要求分割到dst中去,dst应传入带*号字符串
// 如果bynum=true 则把文件分割成num份
// 如果bynum=false 则把文件按num字节分成多份
func SplitFile(src, dst string, num int, bynum bool, shell func(float64)) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
stat, _ := os.Stat(src)
filebig := float64(stat.Size())
if bynum {
if int(filebig) < num {
return errors.New("file is too small to split")
}
}
balance := int(filebig/float64(num)) + 1
if !bynum {
balance = num
}
nownum := 0
fpdst, err := os.Create(strings.Replace(dst, "*", fmt.Sprint(nownum), -1))
if err != nil {
return err
}
defer fpdst.Close()
var sum, tsum int = 0, 0
var buf []byte
for {
if balance-sum < 1048576 {
buf = make([]byte, balance-sum)
} else {
buf = make([]byte, 1048576)
}
n, err := fpsrc.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return err
}
sum += n
tsum += n
fpdst.Write(buf[0:n])
go shell(float64(tsum) / filebig * 100)
if sum == balance {
fpdst.Close()
nownum++
fpdst, err = os.Create(strings.Replace(dst, "*", fmt.Sprint(nownum), -1))
if err != nil {
return err
}
sum = 0
}
}
return nil
return filex.SplitFile(src, dst, num, bynum, shell)
}
// MergeFile 合并src文件到dst文件中去src文件应传入带*号字符串
func MergeFile(src, dst string, shell func(float64)) error {
tmp := strings.Replace(src, "*", "0", -1)
dir, err := ioutil.ReadDir(filepath.Dir(tmp))
if err != nil {
return err
}
base := filepath.Base(src)
tmp = strings.Replace(base, "*", "(\\d+)", -1)
reg := regexp.MustCompile(tmp)
count := 0
var filebig float64
for _, v := range dir {
if reg.MatchString(v.Name()) {
count++
filebig += float64(v.Size())
}
}
fpdst, err := os.Create(dst)
defer fpdst.Close()
if err != nil {
return err
}
var sum int64
for i := 0; i < count; i++ {
fpsrc, err := os.Open(strings.Replace(src, "*", strconv.Itoa(i), -1))
if err != nil {
return err
}
for {
buf := make([]byte, 1048576)
n, err := fpsrc.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return err
}
sum += int64(n)
go shell(float64(sum) / filebig * 100)
fpdst.Write(buf[0:n])
}
fpsrc.Close()
}
return nil
return filex.MergeFile(src, dst, shell)
}
// FillWithRandom 随机写filesize大小的文件每次buf大小为bufcap随机bufnum个字符
// FillWithRandom uses math/rand pseudo-random bytes and is not cryptographically secure.
func FillWithRandom(filepath string, filesize int, bufcap int, bufnum int, shell func(float64)) error {
var buf [][]byte
var buftmp []byte
rand.Seed(time.Now().Unix())
if bufnum <= 0 {
bufnum = 1
return filex.FillWithRandom(filepath, filesize, bufcap, bufnum, shell)
}
if bufcap > filesize {
bufcap = filesize
}
myfile, err := os.Create(filepath)
if err != nil {
return err
}
defer myfile.Close()
writer := bufio.NewWriter(myfile)
for i := 0; i < bufnum; i++ {
buftmp = []byte{}
for j := 0; j < bufcap; j++ {
buftmp = append(buftmp, byte(rand.Intn(256)))
}
buf = append(buf, buftmp)
}
sum := 0
for {
if filesize-sum < bufcap {
writer.Write(buf[rand.Intn(bufnum)][0 : filesize-sum])
sum += filesize - sum
} else {
writer.Write(buf[rand.Intn(bufnum)])
sum += bufcap
}
go shell(float64(sum) / float64(filesize) * 100)
if sum >= filesize {
break
}
}
writer.Flush()
return nil
// FillWithCryptoRandom uses crypto/rand secure random bytes.
// Throughput may be lower than FillWithRandom.
func FillWithCryptoRandom(filepath string, filesize int, bufcap int, shell func(float64)) error {
return filex.FillWithCryptoRandom(filepath, filesize, bufcap, shell)
}

363
filex/file.go Normal file
View File

@ -0,0 +1,363 @@
package filex
import (
"bufio"
crand "crypto/rand"
"errors"
"fmt"
"io"
mrand "math/rand"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"time"
)
var ErrInvalidSplitPattern = errors.New("split dst pattern must contain exactly one '*'")
func Attach(src, dst, output string) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
fpdst, err := os.Open(dst)
if err != nil {
return err
}
defer fpdst.Close()
fpout, err := os.Create(output)
if err != nil {
return err
}
defer fpout.Close()
if _, err := io.Copy(fpout, fpsrc); err != nil {
return err
}
if _, err := io.Copy(fpout, fpdst); err != nil {
return err
}
return nil
}
func Detach(src string, bytenum int, dst1, dst2 string) error {
if bytenum < 0 {
return errors.New("bytenum must be non-negative")
}
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
fpdst1, err := os.Create(dst1)
if err != nil {
return err
}
defer fpdst1.Close()
fpdst2, err := os.Create(dst2)
if err != nil {
return err
}
defer fpdst2.Close()
if bytenum > 0 {
if _, err := io.CopyN(fpdst1, fpsrc, int64(bytenum)); err != nil && err != io.EOF {
return err
}
}
if _, err := io.Copy(fpdst2, fpsrc); err != nil {
return err
}
return nil
}
func SplitFile(src, dst string, num int, bynum bool, progress func(float64)) error {
if num <= 0 {
return errors.New("num must be greater than zero")
}
if strings.Count(dst, "*") != 1 {
return ErrInvalidSplitPattern
}
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
stat, err := fpsrc.Stat()
if err != nil {
return err
}
total := stat.Size()
if total == 0 {
return errors.New("file is empty")
}
var sizes []int64
if bynum {
if total < int64(num) {
return errors.New("file is too small to split")
}
base := total / int64(num)
rest := total % int64(num)
sizes = make([]int64, 0, num)
for i := 0; i < num; i++ {
sz := base
if int64(i) < rest {
sz++
}
sizes = append(sizes, sz)
}
} else {
chunk := int64(num)
for remain := total; remain > 0; {
sz := chunk
if remain < chunk {
sz = remain
}
sizes = append(sizes, sz)
remain -= sz
}
}
var copied int64
buf := make([]byte, 1024*1024)
for i, partSize := range sizes {
name := strings.Replace(dst, "*", fmt.Sprint(i), -1)
fpdst, err := os.Create(name)
if err != nil {
return err
}
remaining := partSize
for remaining > 0 {
readLen := int64(len(buf))
if remaining < readLen {
readLen = remaining
}
n, readErr := fpsrc.Read(buf[:readLen])
if n > 0 {
if _, err := fpdst.Write(buf[:n]); err != nil {
fpdst.Close()
return err
}
remaining -= int64(n)
copied += int64(n)
reportProgress(progress, copied, total)
}
if readErr != nil {
if readErr == io.EOF && remaining == 0 {
break
}
fpdst.Close()
return readErr
}
}
if err := fpdst.Close(); err != nil {
return err
}
}
return nil
}
func MergeFile(src, dst string, progress func(float64)) error {
tmp := strings.Replace(src, "*", "0", -1)
dirEntries, err := os.ReadDir(filepath.Dir(tmp))
if err != nil {
return err
}
base := filepath.Base(src)
pattern := strings.Replace(base, "*", "(\\d+)", -1)
reg := regexp.MustCompile("^" + pattern + "$")
type indexedFile struct {
index int
name string
size int64
}
files := make([]indexedFile, 0)
var total int64
for _, entry := range dirEntries {
m := reg.FindStringSubmatch(entry.Name())
if len(m) != 2 {
continue
}
idx, err := strconv.Atoi(m[1])
if err != nil {
continue
}
info, err := entry.Info()
if err != nil {
return err
}
files = append(files, indexedFile{index: idx, name: entry.Name(), size: info.Size()})
total += info.Size()
}
if len(files) == 0 {
return errors.New("no split files found")
}
sort.Slice(files, func(i, j int) bool { return files[i].index < files[j].index })
fpdst, err := os.Create(dst)
if err != nil {
return err
}
defer fpdst.Close()
var copied int64
buf := make([]byte, 1024*1024)
for _, f := range files {
path := filepath.Join(filepath.Dir(tmp), f.name)
fpsrc, err := os.Open(path)
if err != nil {
return err
}
for {
n, readErr := fpsrc.Read(buf)
if n > 0 {
if _, err := fpdst.Write(buf[:n]); err != nil {
fpsrc.Close()
return err
}
copied += int64(n)
reportProgress(progress, copied, total)
}
if readErr != nil {
if readErr == io.EOF {
break
}
fpsrc.Close()
return readErr
}
}
if err := fpsrc.Close(); err != nil {
return err
}
}
return nil
}
// FillWithRandom fills file with pseudo-random bytes generated by math/rand.
// It is fast but not cryptographically secure.
func FillWithRandom(path string, filesize, bufcap, bufnum int, progress func(float64)) error {
if filesize < 0 {
return errors.New("filesize must be non-negative")
}
if bufnum <= 0 {
bufnum = 1
}
if bufcap <= 0 {
bufcap = 1
}
if bufcap > filesize && filesize > 0 {
bufcap = filesize
}
r := mrand.New(mrand.NewSource(time.Now().UnixNano()))
fp, err := os.Create(path)
if err != nil {
return err
}
defer fp.Close()
writer := bufio.NewWriter(fp)
if filesize == 0 {
reportProgress(progress, 0, 0)
return writer.Flush()
}
pool := make([][]byte, 0, bufnum)
for i := 0; i < bufnum; i++ {
b := make([]byte, bufcap)
for j := 0; j < bufcap; j++ {
b[j] = byte(r.Intn(256))
}
pool = append(pool, b)
}
written := 0
for written < filesize {
chunk := bufcap
if filesize-written < chunk {
chunk = filesize - written
}
buf := pool[r.Intn(len(pool))][:chunk]
if _, err := writer.Write(buf); err != nil {
return err
}
written += chunk
reportProgress(progress, int64(written), int64(filesize))
}
return writer.Flush()
}
// FillWithCryptoRandom fills file with cryptographically secure random bytes from crypto/rand.
// Security is stronger than FillWithRandom, but throughput may be lower.
func FillWithCryptoRandom(path string, filesize, bufcap int, progress func(float64)) error {
if filesize < 0 {
return errors.New("filesize must be non-negative")
}
if bufcap <= 0 {
bufcap = 1
}
if bufcap > filesize && filesize > 0 {
bufcap = filesize
}
fp, err := os.Create(path)
if err != nil {
return err
}
defer fp.Close()
writer := bufio.NewWriter(fp)
if filesize == 0 {
reportProgress(progress, 0, 0)
return writer.Flush()
}
buf := make([]byte, bufcap)
written := 0
for written < filesize {
chunk := bufcap
if filesize-written < chunk {
chunk = filesize - written
}
if _, err := io.ReadFull(crand.Reader, buf[:chunk]); err != nil {
return err
}
if _, err := writer.Write(buf[:chunk]); err != nil {
return err
}
written += chunk
reportProgress(progress, int64(written), int64(filesize))
}
return writer.Flush()
}
func reportProgress(progress func(float64), current, total int64) {
if progress == nil {
return
}
if total <= 0 {
progress(100)
return
}
progress(float64(current) / float64(total) * 100)
}

65
filex/file_random_test.go Normal file
View File

@ -0,0 +1,65 @@
package filex
import (
"bytes"
"os"
"path/filepath"
"testing"
)
func TestFillWithRandomAndCryptoRandom(t *testing.T) {
dir := t.TempDir()
pseudoPath := filepath.Join(dir, "pseudo.bin")
securePath := filepath.Join(dir, "secure.bin")
if err := FillWithRandom(pseudoPath, 2048, 128, 4, nil); err != nil {
t.Fatalf("FillWithRandom failed: %v", err)
}
if err := FillWithCryptoRandom(securePath, 2048, 128, nil); err != nil {
t.Fatalf("FillWithCryptoRandom failed: %v", err)
}
pseudoInfo, err := os.Stat(pseudoPath)
if err != nil {
t.Fatalf("stat pseudo file failed: %v", err)
}
if pseudoInfo.Size() != 2048 {
t.Fatalf("unexpected pseudo size: %d", pseudoInfo.Size())
}
secureInfo, err := os.Stat(securePath)
if err != nil {
t.Fatalf("stat secure file failed: %v", err)
}
if secureInfo.Size() != 2048 {
t.Fatalf("unexpected secure size: %d", secureInfo.Size())
}
pseudo, err := os.ReadFile(pseudoPath)
if err != nil {
t.Fatalf("read pseudo file failed: %v", err)
}
secure, err := os.ReadFile(securePath)
if err != nil {
t.Fatalf("read secure file failed: %v", err)
}
if bytes.Equal(secure, make([]byte, len(secure))) {
t.Fatalf("secure random output should not be all zero")
}
if bytes.Equal(pseudo, secure) {
t.Fatalf("pseudo and secure random outputs unexpectedly identical")
}
}
func TestFillWithRandomInvalidArgs(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "bad.bin")
if err := FillWithRandom(path, -1, 16, 1, nil); err == nil {
t.Fatalf("expected FillWithRandom negative filesize error")
}
if err := FillWithCryptoRandom(path, -1, 16, nil); err == nil {
t.Fatalf("expected FillWithCryptoRandom negative filesize error")
}
}

36
filex/file_split_test.go Normal file
View File

@ -0,0 +1,36 @@
package filex
import (
"bytes"
"errors"
"os"
"path/filepath"
"testing"
)
func TestSplitFilePatternValidation(t *testing.T) {
dir := t.TempDir()
src := filepath.Join(dir, "src.bin")
if err := os.WriteFile(src, bytes.Repeat([]byte{0x7f}, 64), 0o600); err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
if err := SplitFile(src, filepath.Join(dir, "part.bin"), 2, true, nil); !errors.Is(err, ErrInvalidSplitPattern) {
t.Fatalf("expected ErrInvalidSplitPattern for missing '*', got: %v", err)
}
if err := SplitFile(src, filepath.Join(dir, "part_*_*.bin"), 2, true, nil); !errors.Is(err, ErrInvalidSplitPattern) {
t.Fatalf("expected ErrInvalidSplitPattern for multiple '*', got: %v", err)
}
pattern := filepath.Join(dir, "part_*.bin")
if err := SplitFile(src, pattern, 2, true, nil); err != nil {
t.Fatalf("SplitFile valid pattern failed: %v", err)
}
if _, err := os.Stat(filepath.Join(dir, "part_0.bin")); err != nil {
t.Fatalf("part_0.bin not found: %v", err)
}
if _, err := os.Stat(filepath.Join(dir, "part_1.bin")); err != nil {
t.Fatalf("part_1.bin not found: %v", err)
}
}

15
go.mod
View File

@ -1,5 +1,16 @@
module b612.me/starcrypto
go 1.16
go 1.24.0
require golang.org/x/crypto v0.21.0
require (
github.com/emmansun/gmsm v0.41.1
github.com/stretchr/testify v1.11.1
golang.org/x/crypto v0.48.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

63
go.sum
View File

@ -1,45 +1,18 @@
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emmansun/gmsm v0.41.1 h1:mD1MqmaXTEqt+9UVmDpRYvcEMIa5vuslFEnw7IWp6/w=
github.com/emmansun/gmsm v0.41.1/go.mod h1:FD1EQk4XcSMkahZFzNwFoI/uXzAlODB9JVsJ9G5N7Do=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

197
hash.go
View File

@ -1,204 +1,15 @@
package starcrypto
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"errors"
"hash"
"hash/crc32"
"io"
"os"
)
import "b612.me/starcrypto/hashx"
// SumAll 可以对同一数据进行多种校验
func SumAll(data []byte, method []string) (map[string][]byte, error) {
result := make(map[string][]byte)
methods := make(map[string]hash.Hash)
var iscrc bool
if len(method) == 0 {
method = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32", "md5"}
}
sum512 := sha512.New()
sum384 := sha512.New384()
sum256 := sha256.New()
sum224 := sha256.New224()
sum1 := sha1.New()
crcsum := crc32.NewIEEE()
md5sum := md5.New()
for _, v := range method {
switch v {
case "md5":
methods["md5"] = md5sum
case "crc32":
iscrc = true
case "sha1":
methods["sha1"] = sum1
case "sha224":
methods["sha224"] = sum224
case "sha256":
methods["sha256"] = sum256
case "sha384":
methods["sha384"] = sum384
case "sha512":
methods["sha512"] = sum512
}
}
for _, v := range methods {
v.Write(data)
}
if iscrc {
crcsum.Write(data)
return hashx.SumAll(data, method)
}
for k, v := range methods {
result[k] = v.Sum(nil)
}
if iscrc {
result["crc32"] = crcsum.Sum(nil)
}
return result, nil
}
// FileSum 输出文件内容校验值method为单个校验方法,小写
// 例FileSum("./test.txt","md5",shell(pect float64){fmt.Sprintf("已完成 %f\r",pect)})
func FileSum(filepath, method string, shell func(float64)) (string, error) {
var sum hash.Hash
var sum32 hash.Hash32
var issum32 bool
var result string
fp, err := os.Open(filepath)
if err != nil {
return "", err
}
switch method {
case "sha512":
sum = sha512.New()
case "sha384":
sum = sha512.New384()
case "sha256":
sum = sha256.New()
case "sha224":
sum = sha256.New224()
case "sha1":
sum = sha1.New()
case "crc32":
sum32 = crc32.NewIEEE()
issum32 = true
case "md5":
sum = md5.New()
default:
return "", errors.New("Cannot Recognize The Method:" + method)
}
writer := 0
stat, _ := os.Stat(filepath)
filebig := float64(stat.Size())
if !issum32 {
// if _, err := io.Copy(sum, fp); err != nil {
for {
buf := make([]byte, 1048574)
n, err := fp.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return result, err
}
writer += n
pect := (float64(writer) / filebig) * 100
go shell(pect)
sum.Write(buf[0:n])
}
result = hex.EncodeToString(sum.Sum(nil))
} else {
for {
buf := make([]byte, 1048574)
n, err := fp.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return result, err
}
writer += n
pect := (float64(writer) / filebig) * 100
go shell(pect)
sum32.Write(buf[0:n])
}
result = hex.EncodeToString(sum32.Sum(nil))
}
return result, nil
return hashx.FileSum(filepath, method, shell)
}
// FileSumAll 可以对同一文件进行多种校验
func FileSumAll(filepath string, method []string, shell func(float64)) (map[string]string, error) {
result := make(map[string]string)
methods := make(map[string]hash.Hash)
var iscrc bool
if len(method) == 0 {
method = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32", "md5"}
}
fp, err := os.Open(filepath)
defer fp.Close()
if err != nil {
return result, err
}
stat, _ := os.Stat(filepath)
filebig := float64(stat.Size())
sum512 := sha512.New()
sum384 := sha512.New384()
sum256 := sha256.New()
sum224 := sha256.New224()
sum1 := sha1.New()
crcsum := crc32.NewIEEE()
md5sum := md5.New()
for _, v := range method {
switch v {
case "md5":
methods["md5"] = md5sum
case "crc32":
iscrc = true
case "sha1":
methods["sha1"] = sum1
case "sha224":
methods["sha224"] = sum224
case "sha256":
methods["sha256"] = sum256
case "sha384":
methods["sha384"] = sum384
case "sha512":
methods["sha512"] = sum512
}
}
writer := 0
for {
buf := make([]byte, 1048574)
n, err := fp.Read(buf)
if err != nil {
if err == io.EOF {
break
}
return result, err
}
writer += n
pect := (float64(writer) / filebig) * 100
go shell(pect)
for _, v := range methods {
v.Write(buf[0:n])
}
if iscrc {
crcsum.Write(buf[0:n])
}
}
for k, v := range methods {
result[k] = hex.EncodeToString(v.Sum(nil))
}
if iscrc {
result["crc32"] = hex.EncodeToString(crcsum.Sum(nil))
}
return result, nil
return hashx.FileSumAll(filepath, method, shell)
}

403
hashx/hashx.go Normal file
View File

@ -0,0 +1,403 @@
package hashx
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"hash"
"hash/crc32"
"io"
"os"
gmsm3 "github.com/emmansun/gmsm/sm3"
"golang.org/x/crypto/md4"
"golang.org/x/crypto/ripemd160"
)
var (
// ErrUnsupportedMethod reports an unknown hash method string.
ErrUnsupportedMethod = errors.New("unsupported hash method")
)
func HexString(b []byte) string {
return hex.EncodeToString(b)
}
func Md5(b []byte) []byte {
sum := md5.New()
_, _ = sum.Write(b)
return sum.Sum(nil)
}
func Md5Str(b []byte) string {
return HexString(Md5(b))
}
func Md4(b []byte) []byte {
sum := md4.New()
_, _ = sum.Write(b)
return sum.Sum(nil)
}
func Md4Str(b []byte) string {
return HexString(Md4(b))
}
func Sha512(b []byte) []byte {
sum := sha512.New()
_, _ = sum.Write(b)
return sum.Sum(nil)
}
func Sha512Str(b []byte) string {
return HexString(Sha512(b))
}
func Sha384(b []byte) []byte {
sum := sha512.New384()
_, _ = sum.Write(b)
return sum.Sum(nil)
}
func Sha384Str(b []byte) string {
return HexString(Sha384(b))
}
func Sha256(b []byte) []byte {
sum := sha256.New()
_, _ = sum.Write(b)
return sum.Sum(nil)
}
func Sha256Str(b []byte) string {
return HexString(Sha256(b))
}
func Sha224(b []byte) []byte {
sum := sha256.New224()
_, _ = sum.Write(b)
return sum.Sum(nil)
}
func Sha224Str(b []byte) string {
return HexString(Sha224(b))
}
func Sha1(b []byte) []byte {
sum := sha1.New()
_, _ = sum.Write(b)
return sum.Sum(nil)
}
func Sha1Str(b []byte) string {
return HexString(Sha1(b))
}
func RipeMd160(b []byte) []byte {
sum := ripemd160.New()
_, _ = sum.Write(b)
return sum.Sum(nil)
}
func RipeMd160Str(b []byte) string {
return HexString(RipeMd160(b))
}
func SM3(b []byte) []byte {
sum := gmsm3.New()
_, _ = sum.Write(b)
return sum.Sum(nil)
}
func SM3Str(b []byte) string {
return HexString(SM3(b))
}
// CheckCRC32A returns CRC32A as uint32 in big-endian view.
// CRC32A here is an MSB-first, non-reflected variant and is different
// from Go standard library CRC-32/IEEE.
func CheckCRC32A(data []byte) uint32 {
b := crc32aDigest(data)
return binary.BigEndian.Uint32(b)
}
func Crc32Str(b []byte) string {
return HexString(Crc32(b))
}
func Crc32(b []byte) []byte {
sum := crc32.NewIEEE()
_, _ = sum.Write(b)
return sum.Sum(nil)
}
// Crc32A computes CRC32A (MSB-first, non-reflected), which is not the
// same route as Go standard library CRC-32/IEEE.
func Crc32A(data []byte) []byte {
return crc32aDigest(data)
}
// Crc32AStr returns hex string of Crc32A digest.
func Crc32AStr(data []byte) string {
return hex.EncodeToString(crc32aDigest(data))
}
func buildHasher(method string) (hash.Hash, hash.Hash32, error) {
switch method {
case "md5":
return md5.New(), nil, nil
case "sha1":
return sha1.New(), nil, nil
case "sha224":
return sha256.New224(), nil, nil
case "sha256":
return sha256.New(), nil, nil
case "sha384":
return sha512.New384(), nil, nil
case "sha512":
return sha512.New(), nil, nil
case "crc32":
return nil, crc32.NewIEEE(), nil
default:
return nil, nil, fmt.Errorf("%w: %s", ErrUnsupportedMethod, method)
}
}
func SumAll(data []byte, methods []string) (map[string][]byte, error) {
if len(methods) == 0 {
methods = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32", "md5"}
}
result := make(map[string][]byte, len(methods))
hashers := make(map[string]hash.Hash, len(methods))
var crc hash.Hash32
for _, method := range methods {
h, h32, err := buildHasher(method)
if err != nil {
return nil, err
}
if h != nil {
hashers[method] = h
}
if h32 != nil && crc == nil {
crc = h32
}
}
for _, h := range hashers {
_, _ = h.Write(data)
}
if crc != nil {
_, _ = crc.Write(data)
}
for method, h := range hashers {
result[method] = h.Sum(nil)
}
if crc != nil {
result["crc32"] = crc.Sum(nil)
}
return result, nil
}
func FileSum(filePath, method string, progress func(float64)) (string, error) {
fp, err := os.Open(filePath)
if err != nil {
return "", err
}
defer fp.Close()
stat, err := fp.Stat()
if err != nil {
return "", err
}
h, h32, err := buildHasher(method)
if err != nil {
return "", err
}
var (
total int64
size = stat.Size()
)
buf := make([]byte, 1024*1024)
for {
n, readErr := fp.Read(buf)
if n > 0 {
total += int64(n)
if h32 != nil {
_, _ = h32.Write(buf[:n])
} else {
_, _ = h.Write(buf[:n])
}
reportProgress(progress, total, size)
}
if readErr != nil {
if readErr == io.EOF {
break
}
return "", readErr
}
}
if h32 != nil {
return hex.EncodeToString(h32.Sum(nil)), nil
}
return hex.EncodeToString(h.Sum(nil)), nil
}
func FileSumAll(filePath string, methods []string, progress func(float64)) (map[string]string, error) {
if len(methods) == 0 {
methods = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32", "md5"}
}
fp, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer fp.Close()
stat, err := fp.Stat()
if err != nil {
return nil, err
}
hashers := make(map[string]hash.Hash, len(methods))
var crc hash.Hash32
for _, method := range methods {
h, h32, err := buildHasher(method)
if err != nil {
return nil, err
}
if h != nil {
hashers[method] = h
}
if h32 != nil && crc == nil {
crc = h32
}
}
var total int64
size := stat.Size()
buf := make([]byte, 1024*1024)
for {
n, readErr := fp.Read(buf)
if n > 0 {
total += int64(n)
chunk := buf[:n]
for _, h := range hashers {
_, _ = h.Write(chunk)
}
if crc != nil {
_, _ = crc.Write(chunk)
}
reportProgress(progress, total, size)
}
if readErr != nil {
if readErr == io.EOF {
break
}
return nil, readErr
}
}
result := make(map[string]string, len(hashers)+1)
for method, h := range hashers {
result[method] = hex.EncodeToString(h.Sum(nil))
}
if crc != nil {
result["crc32"] = hex.EncodeToString(crc.Sum(nil))
}
return result, nil
}
func reportProgress(progress func(float64), current, total int64) {
if progress == nil {
return
}
if total <= 0 {
progress(100)
return
}
progress(float64(current) / float64(total) * 100)
}
func crc32aDigest(data []byte) []byte {
var crc uint32
digest := make([]byte, 4)
crc = ^crc
for i := 0; i < len(data); i++ {
crc = (crc << 8) ^ crc32aTable[(crc>>24)^(uint32(data[i])&0xff)]
}
crc = ^crc
digest[3] = byte((crc >> 24) & 0xff)
digest[2] = byte((crc >> 16) & 0xff)
digest[1] = byte((crc >> 8) & 0xff)
digest[0] = byte(crc & 0xff)
return digest
}
var crc32aTable = [256]uint32{
0x0,
0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6,
0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac,
0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f,
0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a,
0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58,
0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033,
0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe,
0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4,
0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5,
0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07,
0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c,
0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1,
0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b,
0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698,
0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d,
0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f,
0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80,
0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a,
0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629,
0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c,
0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e,
0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65,
0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8,
0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2,
0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74,
0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21,
0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a,
0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087,
0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d,
0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce,
0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb,
0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09,
0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf,
0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4,
}

142
hashx/hashx_test.go Normal file
View File

@ -0,0 +1,142 @@
package hashx
import (
"bytes"
"crypto/md5"
"encoding/hex"
"os"
"path/filepath"
"testing"
)
func TestSha1StrMatchesSha1(t *testing.T) {
got := Sha1Str([]byte("abc"))
const want = "a9993e364706816aba3e25717850c26c9cd0d89d"
if got != want {
t.Fatalf("Sha1Str mismatch, got %s want %s", got, want)
}
}
func TestSM3AndCRC32A(t *testing.T) {
if len(SM3([]byte("abc"))) != 32 {
t.Fatalf("SM3 digest size must be 32")
}
if Crc32AStr([]byte("123456789")) != "181989fc" {
t.Fatalf("Crc32AStr mismatch")
}
if CheckCRC32A([]byte("123456789")) != 0x181989fc {
t.Fatalf("CheckCRC32A mismatch")
}
}
func TestSumAllUnsupportedMethod(t *testing.T) {
_, err := SumAll([]byte("abc"), []string{"sha1", "unknown"})
if err == nil {
t.Fatalf("expected unsupported method error")
}
}
func TestFileSumAndFileSumAll(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "sum.txt")
data := []byte("hash-file-content")
if err := os.WriteFile(path, data, 0o644); err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
calls := 0
h, err := FileSum(path, "md5", func(float64) { calls++ })
if err != nil {
t.Fatalf("FileSum failed: %v", err)
}
expected := md5.Sum(data)
if h != hex.EncodeToString(expected[:]) {
t.Fatalf("md5 mismatch, got %s want %s", h, hex.EncodeToString(expected[:]))
}
if calls == 0 {
t.Fatalf("progress callback should be called")
}
all, err := FileSumAll(path, []string{"sha1", "crc32"}, nil)
if err != nil {
t.Fatalf("FileSumAll failed: %v", err)
}
if _, ok := all["sha1"]; !ok {
t.Fatalf("expected sha1 in FileSumAll")
}
if _, ok := all["crc32"]; !ok {
t.Fatalf("expected crc32 in FileSumAll")
}
}
func TestFileSumUnsupportedMethod(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "sum.txt")
if err := os.WriteFile(path, []byte("x"), 0o644); err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
if _, err := FileSum(path, "not-support", nil); err == nil {
t.Fatalf("expected unsupported method error")
}
}
func TestFileSumAllUnsupportedMethod(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "sum.txt")
if err := os.WriteFile(path, []byte("x"), 0o644); err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
if _, err := FileSumAll(path, []string{"sha256", "not-support"}, nil); err == nil {
t.Fatalf("expected unsupported method error")
}
}
func TestPBKDF2SHA256Vector(t *testing.T) {
got, err := DerivePBKDF2SHA256Key("password", []byte("salt"), 1, 32)
if err != nil {
t.Fatalf("DerivePBKDF2SHA256Key failed: %v", err)
}
const want = "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"
if hex.EncodeToString(got) != want {
t.Fatalf("pbkdf2-sha256 vector mismatch: got %x want %s", got, want)
}
}
func TestPBKDF2AndArgon2Deterministic(t *testing.T) {
a, err := DerivePBKDF2SHA512Key("password", []byte("salt"), 1000, 32)
if err != nil {
t.Fatalf("DerivePBKDF2SHA512Key failed: %v", err)
}
b, err := DerivePBKDF2SHA512Key("password", []byte("salt"), 1000, 32)
if err != nil {
t.Fatalf("DerivePBKDF2SHA512Key failed: %v", err)
}
if !bytes.Equal(a, b) {
t.Fatalf("pbkdf2-sha512 must be deterministic")
}
params := Argon2Params{Time: 1, Memory: 32 * 1024, Threads: 1, KeyLen: 32}
argA, err := DeriveArgon2idKey("password", []byte("salt-salt"), params)
if err != nil {
t.Fatalf("DeriveArgon2idKey failed: %v", err)
}
argB, err := DeriveArgon2idKey("password", []byte("salt-salt"), params)
if err != nil {
t.Fatalf("DeriveArgon2idKey failed: %v", err)
}
if !bytes.Equal(argA, argB) {
t.Fatalf("argon2id must be deterministic")
}
}
func TestKDFInvalidParams(t *testing.T) {
if _, err := DerivePBKDF2SHA256Key("password", nil, 1, 32); err == nil {
t.Fatalf("expected pbkdf2 salt error")
}
if _, err := DerivePBKDF2SHA256Key("password", []byte("salt"), 0, 32); err == nil {
t.Fatalf("expected pbkdf2 iterations error")
}
if _, err := DeriveArgon2idKey("password", []byte("salt"), Argon2Params{}); err == nil {
t.Fatalf("expected argon2 params error")
}
}

90
hashx/kdf.go Normal file
View File

@ -0,0 +1,90 @@
package hashx
import (
"crypto/pbkdf2"
"crypto/sha256"
"crypto/sha512"
"errors"
"golang.org/x/crypto/argon2"
)
var (
ErrInvalidKDFSalt = errors.New("kdf salt must be non-empty")
ErrInvalidKDFIterations = errors.New("kdf iterations must be > 0")
ErrInvalidKDFKeyLength = errors.New("kdf key length must be > 0")
ErrInvalidArgon2Params = errors.New("argon2 params must have time, memory, threads, and key length > 0")
)
// Argon2Params configures Argon2 key derivation.
type Argon2Params struct {
Time uint32
Memory uint32
Threads uint8
KeyLen uint32
}
// DefaultArgon2idParams returns a conservative default suitable for general online usage.
func DefaultArgon2idParams() Argon2Params {
return Argon2Params{
Time: 1,
Memory: 64 * 1024, // 64 MiB in KiB
Threads: 4,
KeyLen: 32,
}
}
func validatePBKDF2Params(salt []byte, iterations, keyLen int) error {
if len(salt) == 0 {
return ErrInvalidKDFSalt
}
if iterations <= 0 {
return ErrInvalidKDFIterations
}
if keyLen <= 0 {
return ErrInvalidKDFKeyLength
}
return nil
}
func validateArgon2Params(salt []byte, params Argon2Params) error {
if len(salt) == 0 {
return ErrInvalidKDFSalt
}
if params.Time == 0 || params.Memory == 0 || params.Threads == 0 || params.KeyLen == 0 {
return ErrInvalidArgon2Params
}
return nil
}
// DerivePBKDF2SHA256Key derives a key with PBKDF2-HMAC-SHA256.
func DerivePBKDF2SHA256Key(password string, salt []byte, iterations, keyLen int) ([]byte, error) {
if err := validatePBKDF2Params(salt, iterations, keyLen); err != nil {
return nil, err
}
return pbkdf2.Key(sha256.New, password, salt, iterations, keyLen)
}
// DerivePBKDF2SHA512Key derives a key with PBKDF2-HMAC-SHA512.
func DerivePBKDF2SHA512Key(password string, salt []byte, iterations, keyLen int) ([]byte, error) {
if err := validatePBKDF2Params(salt, iterations, keyLen); err != nil {
return nil, err
}
return pbkdf2.Key(sha512.New, password, salt, iterations, keyLen)
}
// DeriveArgon2idKey derives a key with Argon2id.
func DeriveArgon2idKey(password string, salt []byte, params Argon2Params) ([]byte, error) {
if err := validateArgon2Params(salt, params); err != nil {
return nil, err
}
return argon2.IDKey([]byte(password), salt, params.Time, params.Memory, params.Threads, params.KeyLen), nil
}
// DeriveArgon2iKey derives a key with Argon2i.
func DeriveArgon2iKey(password string, salt []byte, params Argon2Params) ([]byte, error) {
if err := validateArgon2Params(salt, params); err != nil {
return nil, err
}
return argon2.Key([]byte(password), salt, params.Time, params.Memory, params.Threads, params.KeyLen), nil
}

118
hmac.go
View File

@ -1,87 +1,131 @@
package starcrypto
import (
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"golang.org/x/crypto/md4"
"golang.org/x/crypto/ripemd160"
"hash"
)
func chmac(message, key []byte, f func() hash.Hash) []byte {
h := hmac.New(f, []byte(key))
h.Write([]byte(message))
return h.Sum(nil)
}
func chmacStr(message, key []byte, f func() hash.Hash) string {
return hex.EncodeToString(chmac(message, key, f))
}
import "b612.me/starcrypto/macx"
func HmacMd4(message, key []byte) []byte {
return chmac(message, key, md4.New)
return macx.HmacMd4(message, key)
}
func HmacMd4Str(message, key []byte) string {
return chmacStr(message, key, md4.New)
return macx.HmacMd4Str(message, key)
}
func VerifyHmacMd4(message, key, sum []byte) bool {
return macx.VerifyHmacMd4(message, key, sum)
}
func VerifyHmacMd4Str(message, key []byte, hexSum string) bool {
return macx.VerifyHmacMd4Str(message, key, hexSum)
}
func HmacMd5(message, key []byte) []byte {
return chmac(message, key, md5.New)
return macx.HmacMd5(message, key)
}
func HmacMd5Str(message, key []byte) string {
return chmacStr(message, key, md5.New)
return macx.HmacMd5Str(message, key)
}
func VerifyHmacMd5(message, key, sum []byte) bool {
return macx.VerifyHmacMd5(message, key, sum)
}
func VerifyHmacMd5Str(message, key []byte, hexSum string) bool {
return macx.VerifyHmacMd5Str(message, key, hexSum)
}
func HmacSHA1(message, key []byte) []byte {
return chmac(message, key, sha1.New)
return macx.HmacSHA1(message, key)
}
func HmacSHA1Str(message, key []byte) string {
return chmacStr(message, key, sha1.New)
return macx.HmacSHA1Str(message, key)
}
func VerifyHmacSHA1(message, key, sum []byte) bool {
return macx.VerifyHmacSHA1(message, key, sum)
}
func VerifyHmacSHA1Str(message, key []byte, hexSum string) bool {
return macx.VerifyHmacSHA1Str(message, key, hexSum)
}
func HmacSHA256(message, key []byte) []byte {
return chmac(message, key, sha256.New)
return macx.HmacSHA256(message, key)
}
func HmacSHA256Str(message, key []byte) string {
return chmacStr(message, key, sha256.New)
return macx.HmacSHA256Str(message, key)
}
func VerifyHmacSHA256(message, key, sum []byte) bool {
return macx.VerifyHmacSHA256(message, key, sum)
}
func VerifyHmacSHA256Str(message, key []byte, hexSum string) bool {
return macx.VerifyHmacSHA256Str(message, key, hexSum)
}
func HmacSHA384(message, key []byte) []byte {
return chmac(message, key, sha512.New384)
return macx.HmacSHA384(message, key)
}
func HmacSHA384Str(message, key []byte) string {
return chmacStr(message, key, sha512.New384)
return macx.HmacSHA384Str(message, key)
}
func VerifyHmacSHA384(message, key, sum []byte) bool {
return macx.VerifyHmacSHA384(message, key, sum)
}
func VerifyHmacSHA384Str(message, key []byte, hexSum string) bool {
return macx.VerifyHmacSHA384Str(message, key, hexSum)
}
func HmacSHA512(message, key []byte) []byte {
return chmac(message, key, sha512.New)
return macx.HmacSHA512(message, key)
}
func HmacSHA512Str(message, key []byte) string {
return chmacStr(message, key, sha512.New)
return macx.HmacSHA512Str(message, key)
}
func VerifyHmacSHA512(message, key, sum []byte) bool {
return macx.VerifyHmacSHA512(message, key, sum)
}
func VerifyHmacSHA512Str(message, key []byte, hexSum string) bool {
return macx.VerifyHmacSHA512Str(message, key, hexSum)
}
func HmacSHA224(message, key []byte) []byte {
return chmac(message, key, sha256.New224)
return macx.HmacSHA224(message, key)
}
func HmacSHA224Str(message, key []byte) string {
return chmacStr(message, key, sha256.New224)
return macx.HmacSHA224Str(message, key)
}
func VerifyHmacSHA224(message, key, sum []byte) bool {
return macx.VerifyHmacSHA224(message, key, sum)
}
func VerifyHmacSHA224Str(message, key []byte, hexSum string) bool {
return macx.VerifyHmacSHA224Str(message, key, hexSum)
}
func HmacRipeMd160(message, key []byte) []byte {
return chmac(message, key, ripemd160.New)
return macx.HmacRipeMd160(message, key)
}
func HmacRipeMd160Str(message, key []byte) string {
return chmacStr(message, key, ripemd160.New)
return macx.HmacRipeMd160Str(message, key)
}
func VerifyHmacRipeMd160(message, key, sum []byte) bool {
return macx.VerifyHmacRipeMd160(message, key, sum)
}
func VerifyHmacRipeMd160Str(message, key []byte, hexSum string) bool {
return macx.VerifyHmacRipeMd160Str(message, key, hexSum)
}

25
kdf.go Normal file
View File

@ -0,0 +1,25 @@
package starcrypto
import "b612.me/starcrypto/hashx"
type Argon2Params = hashx.Argon2Params
func DefaultArgon2idParams() Argon2Params {
return hashx.DefaultArgon2idParams()
}
func DerivePBKDF2SHA256Key(password string, salt []byte, iterations, keyLen int) ([]byte, error) {
return hashx.DerivePBKDF2SHA256Key(password, salt, iterations, keyLen)
}
func DerivePBKDF2SHA512Key(password string, salt []byte, iterations, keyLen int) ([]byte, error) {
return hashx.DerivePBKDF2SHA512Key(password, salt, iterations, keyLen)
}
func DeriveArgon2idKey(password string, salt []byte, params Argon2Params) ([]byte, error) {
return hashx.DeriveArgon2idKey(password, salt, params)
}
func DeriveArgon2iKey(password string, salt []byte, params Argon2Params) ([]byte, error) {
return hashx.DeriveArgon2iKey(password, salt, params)
}

201
legacy/vicque.go Normal file
View File

@ -0,0 +1,201 @@
package legacy
import (
"crypto/rand"
"io"
"os"
)
func VicqueEncodeV1(srcdata []byte, key string) []byte {
var keys []int
var randCode [2]byte
_, _ = io.ReadFull(rand.Reader, randCode[:])
data := make([]byte, len(srcdata))
copy(data, srcdata)
randCode1 := int(randCode[0])
randCode2 := int(randCode[1])
keys = append(keys, len(key)+randCode1)
for _, v := range key {
keys = append(keys, int(byte(v))+randCode1-randCode2)
}
lens := len(data)
lenkey := len(keys)
for k, v := range data {
if k == lens/2 {
break
}
nv := int(v)
t := 0
if k%2 == 0 {
nv += keys[k%lenkey]
if nv > 255 {
nv -= 256
}
t = int(data[lens-1-k])
t += keys[k%lenkey]
if t > 255 {
t -= 256
}
} else {
nv -= keys[k%lenkey]
if nv < 0 {
nv += 256
}
t = int(data[lens-1-k])
t -= keys[k%lenkey]
if t < 0 {
t += 256
}
}
data[k] = byte(t)
data[lens-1-k] = byte(nv)
}
data = append(data, randCode[0], randCode[1])
return data
}
func VicqueDecodeV1(srcdata []byte, key string) []byte {
if len(srcdata) < 2 {
return nil
}
data := make([]byte, len(srcdata))
copy(data, srcdata)
lens := len(data)
randCode1 := int(data[lens-2])
randCode2 := int(data[lens-1])
keys := []int{len(key) + randCode1}
for _, v := range key {
keys = append(keys, int(byte(v))+randCode1-randCode2)
}
lenkey := len(keys)
lens -= 2
for k, v := range data {
if k == lens/2 {
break
}
nv := int(v)
t := 0
if k%2 == 0 {
nv -= keys[k%lenkey]
if nv < 0 {
nv += 256
}
t = int(data[lens-1-k])
t -= keys[k%lenkey]
if t < 0 {
t += 256
}
} else {
nv += keys[k%lenkey]
if nv > 255 {
nv -= 256
}
t = int(data[lens-1-k])
t += keys[k%lenkey]
if t > 255 {
t -= 256
}
}
data[k] = byte(t)
data[lens-1-k] = byte(nv)
}
return data[:lens]
}
func VicqueEncodeV1File(src, dst, pwd string, progress func(float64)) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
stat, err := fpsrc.Stat()
if err != nil {
return err
}
fpdst, err := os.Create(dst)
if err != nil {
return err
}
defer fpdst.Close()
var sum int64
buf := make([]byte, 1024*1024)
for {
n, readErr := fpsrc.Read(buf)
if n > 0 {
sum += int64(n)
data := VicqueEncodeV1(buf[:n], pwd)
if _, err := fpdst.Write(data); err != nil {
return err
}
reportProgress(progress, sum, stat.Size())
}
if readErr != nil {
if readErr == io.EOF {
break
}
return readErr
}
}
return nil
}
func VicqueDecodeV1File(src, dst, pwd string, progress func(float64)) error {
fpsrc, err := os.Open(src)
if err != nil {
return err
}
defer fpsrc.Close()
stat, err := fpsrc.Stat()
if err != nil {
return err
}
fpdst, err := os.Create(dst)
if err != nil {
return err
}
defer fpdst.Close()
var sum int64
buf := make([]byte, 1024*1024+2)
for {
n, readErr := fpsrc.Read(buf)
if n > 0 {
sum += int64(n)
data := VicqueDecodeV1(buf[:n], pwd)
if _, err := fpdst.Write(data); err != nil {
return err
}
reportProgress(progress, sum, stat.Size())
}
if readErr != nil {
if readErr == io.EOF {
break
}
return readErr
}
}
return nil
}
func reportProgress(progress func(float64), current, total int64) {
if progress == nil {
return
}
if total <= 0 {
progress(100)
return
}
progress(float64(current) / float64(total) * 100)
}

41
legacy/vicque_test.go Normal file
View File

@ -0,0 +1,41 @@
package legacy
import (
"bytes"
"os"
"path/filepath"
"testing"
)
func TestVicqueRoundTrip(t *testing.T) {
plain := []byte("legacy-vicque-roundtrip")
enc := VicqueEncodeV1(plain, "secret")
dec := VicqueDecodeV1(enc, "secret")
if !bytes.Equal(dec, plain) {
t.Fatalf("vicque roundtrip mismatch")
}
}
func TestVicqueFileRoundTrip(t *testing.T) {
dir := t.TempDir()
src := filepath.Join(dir, "src.bin")
enc := filepath.Join(dir, "src.enc")
out := filepath.Join(dir, "src.out")
plain := []byte("legacy-vicque-file-roundtrip")
if err := os.WriteFile(src, plain, 0o644); err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
if err := VicqueEncodeV1File(src, enc, "pwd", nil); err != nil {
t.Fatalf("VicqueEncodeV1File failed: %v", err)
}
if err := VicqueDecodeV1File(enc, out, "pwd", nil); err != nil {
t.Fatalf("VicqueDecodeV1File failed: %v", err)
}
got, err := os.ReadFile(out)
if err != nil {
t.Fatalf("ReadFile failed: %v", err)
}
if !bytes.Equal(got, plain) {
t.Fatalf("vicque file roundtrip mismatch")
}
}

166
macx/hmac.go Normal file
View File

@ -0,0 +1,166 @@
package macx
import (
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"hash"
"strings"
"golang.org/x/crypto/md4"
"golang.org/x/crypto/ripemd160"
)
func chmac(message, key []byte, f func() hash.Hash) []byte {
h := hmac.New(f, key)
_, _ = h.Write(message)
return h.Sum(nil)
}
func chmacStr(message, key []byte, f func() hash.Hash) string {
return hex.EncodeToString(chmac(message, key, f))
}
func verifyHMAC(message, key, sum []byte, f func() hash.Hash) bool {
expected := chmac(message, key, f)
return hmac.Equal(expected, sum)
}
func verifyHMACStr(message, key []byte, hexSum string, f func() hash.Hash) bool {
sum, err := hex.DecodeString(strings.TrimSpace(hexSum))
if err != nil {
return false
}
return verifyHMAC(message, key, sum, f)
}
func HmacMd4(message, key []byte) []byte {
return chmac(message, key, md4.New)
}
func HmacMd4Str(message, key []byte) string {
return chmacStr(message, key, md4.New)
}
func VerifyHmacMd4(message, key, sum []byte) bool {
return verifyHMAC(message, key, sum, md4.New)
}
func VerifyHmacMd4Str(message, key []byte, hexSum string) bool {
return verifyHMACStr(message, key, hexSum, md4.New)
}
func HmacMd5(message, key []byte) []byte {
return chmac(message, key, md5.New)
}
func HmacMd5Str(message, key []byte) string {
return chmacStr(message, key, md5.New)
}
func VerifyHmacMd5(message, key, sum []byte) bool {
return verifyHMAC(message, key, sum, md5.New)
}
func VerifyHmacMd5Str(message, key []byte, hexSum string) bool {
return verifyHMACStr(message, key, hexSum, md5.New)
}
func HmacSHA1(message, key []byte) []byte {
return chmac(message, key, sha1.New)
}
func HmacSHA1Str(message, key []byte) string {
return chmacStr(message, key, sha1.New)
}
func VerifyHmacSHA1(message, key, sum []byte) bool {
return verifyHMAC(message, key, sum, sha1.New)
}
func VerifyHmacSHA1Str(message, key []byte, hexSum string) bool {
return verifyHMACStr(message, key, hexSum, sha1.New)
}
func HmacSHA256(message, key []byte) []byte {
return chmac(message, key, sha256.New)
}
func HmacSHA256Str(message, key []byte) string {
return chmacStr(message, key, sha256.New)
}
func VerifyHmacSHA256(message, key, sum []byte) bool {
return verifyHMAC(message, key, sum, sha256.New)
}
func VerifyHmacSHA256Str(message, key []byte, hexSum string) bool {
return verifyHMACStr(message, key, hexSum, sha256.New)
}
func HmacSHA384(message, key []byte) []byte {
return chmac(message, key, sha512.New384)
}
func HmacSHA384Str(message, key []byte) string {
return chmacStr(message, key, sha512.New384)
}
func VerifyHmacSHA384(message, key, sum []byte) bool {
return verifyHMAC(message, key, sum, sha512.New384)
}
func VerifyHmacSHA384Str(message, key []byte, hexSum string) bool {
return verifyHMACStr(message, key, hexSum, sha512.New384)
}
func HmacSHA512(message, key []byte) []byte {
return chmac(message, key, sha512.New)
}
func HmacSHA512Str(message, key []byte) string {
return chmacStr(message, key, sha512.New)
}
func VerifyHmacSHA512(message, key, sum []byte) bool {
return verifyHMAC(message, key, sum, sha512.New)
}
func VerifyHmacSHA512Str(message, key []byte, hexSum string) bool {
return verifyHMACStr(message, key, hexSum, sha512.New)
}
func HmacSHA224(message, key []byte) []byte {
return chmac(message, key, sha256.New224)
}
func HmacSHA224Str(message, key []byte) string {
return chmacStr(message, key, sha256.New224)
}
func VerifyHmacSHA224(message, key, sum []byte) bool {
return verifyHMAC(message, key, sum, sha256.New224)
}
func VerifyHmacSHA224Str(message, key []byte, hexSum string) bool {
return verifyHMACStr(message, key, hexSum, sha256.New224)
}
func HmacRipeMd160(message, key []byte) []byte {
return chmac(message, key, ripemd160.New)
}
func HmacRipeMd160Str(message, key []byte) string {
return chmacStr(message, key, ripemd160.New)
}
func VerifyHmacRipeMd160(message, key, sum []byte) bool {
return verifyHMAC(message, key, sum, ripemd160.New)
}
func VerifyHmacRipeMd160Str(message, key []byte, hexSum string) bool {
return verifyHMACStr(message, key, hexSum, ripemd160.New)
}

58
macx/hmac_test.go Normal file
View File

@ -0,0 +1,58 @@
package macx
import "testing"
type hmacCase struct {
name string
sum func([]byte, []byte) []byte
sumStr func([]byte, []byte) string
verify func([]byte, []byte, []byte) bool
verifyStr func([]byte, []byte, string) bool
}
func TestHMACVerifyCases(t *testing.T) {
msg := []byte("macx-verify-message")
key := []byte("macx-verify-key")
cases := []hmacCase{
{"md4", HmacMd4, HmacMd4Str, VerifyHmacMd4, VerifyHmacMd4Str},
{"md5", HmacMd5, HmacMd5Str, VerifyHmacMd5, VerifyHmacMd5Str},
{"sha1", HmacSHA1, HmacSHA1Str, VerifyHmacSHA1, VerifyHmacSHA1Str},
{"sha224", HmacSHA224, HmacSHA224Str, VerifyHmacSHA224, VerifyHmacSHA224Str},
{"sha256", HmacSHA256, HmacSHA256Str, VerifyHmacSHA256, VerifyHmacSHA256Str},
{"sha384", HmacSHA384, HmacSHA384Str, VerifyHmacSHA384, VerifyHmacSHA384Str},
{"sha512", HmacSHA512, HmacSHA512Str, VerifyHmacSHA512, VerifyHmacSHA512Str},
{"ripemd160", HmacRipeMd160, HmacRipeMd160Str, VerifyHmacRipeMd160, VerifyHmacRipeMd160Str},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
sum := tc.sum(msg, key)
if !tc.verify(msg, key, sum) {
t.Fatalf("verify bytes should pass")
}
hexSum := tc.sumStr(msg, key)
if !tc.verifyStr(msg, key, hexSum) {
t.Fatalf("verify hex should pass")
}
if !tc.verifyStr(msg, key, " \t"+hexSum+"\n") {
t.Fatalf("verify hex with spaces should pass")
}
bad := make([]byte, len(sum))
copy(bad, sum)
bad[0] ^= 0xff
if tc.verify(msg, key, bad) {
t.Fatalf("verify bytes should fail for tampered sum")
}
if tc.verifyStr(msg, key, "not-hex") {
t.Fatalf("verify hex should fail for invalid hex")
}
if tc.verify([]byte("wrong-msg"), key, sum) {
t.Fatalf("verify bytes should fail for wrong message")
}
})
}
}

18
md5.go
View File

@ -1,27 +1,19 @@
package starcrypto
import (
"crypto/md5"
"golang.org/x/crypto/md4"
)
import "b612.me/starcrypto/hashx"
// MD5 输出MD5校验值
func Md5(bstr []byte) []byte {
md5sum := md5.New()
md5sum.Write(bstr)
return md5sum.Sum(nil)
return hashx.Md5(bstr)
}
func Md5Str(bstr []byte) string {
return String(Md5(bstr))
return hashx.Md5Str(bstr)
}
func Md4(bstr []byte) []byte {
md4sum := md4.New()
md4sum.Write(bstr)
return md4sum.Sum(nil)
return hashx.Md4(bstr)
}
func Md4Str(bstr []byte) string {
return String(Md4(bstr))
return hashx.Md4Str(bstr)
}

25
paddingx/fuzz_test.go Normal file
View File

@ -0,0 +1,25 @@
package paddingx
import "testing"
func FuzzPadUnpadRoundTrip(f *testing.F) {
f.Add([]byte("abc"))
f.Add([]byte{})
modes := []string{PKCS7, ZERO, ANSIX923}
f.Fuzz(func(t *testing.T, data []byte) {
for _, mode := range modes {
padded, err := Pad(data, 16, mode)
if err != nil {
t.Fatalf("Pad failed: %v", err)
}
out, err := Unpad(padded, 16, mode)
if err != nil {
t.Fatalf("Unpad failed: %v", err)
}
if mode != ZERO && string(out) != string(data) {
t.Fatalf("roundtrip mismatch for mode %s", mode)
}
}
})
}

128
paddingx/padding.go Normal file
View File

@ -0,0 +1,128 @@
package paddingx
import (
"bytes"
"errors"
"strings"
)
const (
PKCS5 = "PKCS5"
PKCS7 = "PKCS7"
ZERO = "ZERO"
ANSIX923 = "ANSIX923"
)
func Pad(data []byte, blockSize int, mode string) ([]byte, error) {
if blockSize <= 0 {
return nil, errors.New("block size must be greater than zero")
}
switch normalizeMode(mode) {
case "", PKCS7:
return PKCS7Padding(data, blockSize), nil
case PKCS5:
// Compatibility mode: historically PKCS5 was used generically in this project.
return PKCS7Padding(data, blockSize), nil
case ZERO:
return zeroPadding(data, blockSize), nil
case ANSIX923:
return ansiX923Padding(data, blockSize), nil
default:
return nil, errors.New("padding type not supported")
}
}
func Unpad(data []byte, blockSize int, mode string) ([]byte, error) {
if blockSize <= 0 {
return nil, errors.New("block size must be greater than zero")
}
switch normalizeMode(mode) {
case "", PKCS7:
return PKCS7Unpadding(data, blockSize)
case PKCS5:
// Compatibility mode: historically PKCS5 was used generically in this project.
return PKCS7Unpadding(data, blockSize)
case ZERO:
return zeroUnpadding(data)
case ANSIX923:
return ansiX923Unpadding(data, blockSize)
default:
return nil, errors.New("padding type not supported")
}
}
func PKCS7Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padText...)
}
func PKCS7Unpadding(data []byte, blockSize int) ([]byte, error) {
if len(data) == 0 || len(data)%blockSize != 0 {
return nil, errors.New("invalid PKCS7 padding")
}
padding := int(data[len(data)-1])
if padding <= 0 || padding > blockSize || padding > len(data) {
return nil, errors.New("invalid PKCS7 padding")
}
for i := len(data) - padding; i < len(data); i++ {
if int(data[i]) != padding {
return nil, errors.New("invalid PKCS7 padding")
}
}
return data[:len(data)-padding], nil
}
func PKCS5Padding(data []byte) []byte {
return PKCS7Padding(data, 8)
}
func PKCS5Unpadding(data []byte) ([]byte, error) {
return PKCS7Unpadding(data, 8)
}
func zeroPadding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
if padding == blockSize {
return data
}
return append(data, bytes.Repeat([]byte{0x00}, padding)...)
}
func zeroUnpadding(data []byte) ([]byte, error) {
idx := len(data)
for idx > 0 && data[idx-1] == 0x00 {
idx--
}
return data[:idx], nil
}
func ansiX923Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
if padding == 0 {
padding = blockSize
}
pad := make([]byte, padding)
pad[len(pad)-1] = byte(padding)
return append(data, pad...)
}
func ansiX923Unpadding(data []byte, blockSize int) ([]byte, error) {
if len(data) == 0 || len(data)%blockSize != 0 {
return nil, errors.New("invalid ANSI X9.23 padding")
}
padding := int(data[len(data)-1])
if padding <= 0 || padding > blockSize || padding > len(data) {
return nil, errors.New("invalid ANSI X9.23 padding")
}
for i := len(data) - padding; i < len(data)-1; i++ {
if data[i] != 0x00 {
return nil, errors.New("invalid ANSI X9.23 padding")
}
}
return data[:len(data)-padding], nil
}
func normalizeMode(mode string) string {
return strings.ToUpper(strings.TrimSpace(mode))
}

94
paddingx/padding_test.go Normal file
View File

@ -0,0 +1,94 @@
package paddingx
import (
"bytes"
"testing"
)
func TestPadAndUnpadPKCS7(t *testing.T) {
plain := []byte("hello-world")
padded, err := Pad(plain, 16, PKCS7)
if err != nil {
t.Fatalf("Pad PKCS7 failed: %v", err)
}
if len(padded)%16 != 0 {
t.Fatalf("padded length should be block aligned, got %d", len(padded))
}
got, err := Unpad(padded, 16, PKCS7)
if err != nil {
t.Fatalf("Unpad PKCS7 failed: %v", err)
}
if !bytes.Equal(got, plain) {
t.Fatalf("roundtrip mismatch, got %x want %x", got, plain)
}
}
func TestPadAndUnpadPKCS5Compatibility(t *testing.T) {
plain := []byte("DES-plaintext")
padded, err := Pad(plain, 8, PKCS5)
if err != nil {
t.Fatalf("Pad PKCS5 failed: %v", err)
}
got, err := Unpad(padded, 8, PKCS5)
if err != nil {
t.Fatalf("Unpad PKCS5 failed: %v", err)
}
if !bytes.Equal(got, plain) {
t.Fatalf("roundtrip mismatch, got %x want %x", got, plain)
}
}
func TestPadAndUnpadZero(t *testing.T) {
plain := []byte("abc\x00\x00")
padded, err := Pad(plain, 8, ZERO)
if err != nil {
t.Fatalf("Pad ZERO failed: %v", err)
}
got, err := Unpad(padded, 8, ZERO)
if err != nil {
t.Fatalf("Unpad ZERO failed: %v", err)
}
if !bytes.Equal(got, []byte("abc")) {
t.Fatalf("zero unpadding mismatch, got %q", got)
}
}
func TestPadAndUnpadANSIX923(t *testing.T) {
plain := []byte("ansi-x923")
padded, err := Pad(plain, 16, ANSIX923)
if err != nil {
t.Fatalf("Pad ANSIX923 failed: %v", err)
}
got, err := Unpad(padded, 16, ANSIX923)
if err != nil {
t.Fatalf("Unpad ANSIX923 failed: %v", err)
}
if !bytes.Equal(got, plain) {
t.Fatalf("roundtrip mismatch, got %x want %x", got, plain)
}
}
func TestPadUnsupportedMode(t *testing.T) {
if _, err := Pad([]byte("x"), 8, "UNKNOWN"); err == nil {
t.Fatalf("expected error for unsupported mode")
}
}
func TestUnpadInvalidPKCS7(t *testing.T) {
_, err := Unpad([]byte{1, 2, 3, 4}, 4, PKCS7)
if err == nil {
t.Fatalf("expected invalid PKCS7 padding error")
}
}
func TestPKCS5Helpers(t *testing.T) {
plain := []byte("1234567")
padded := PKCS5Padding(plain)
got, err := PKCS5Unpadding(padded)
if err != nil {
t.Fatalf("PKCS5Unpadding failed: %v", err)
}
if !bytes.Equal(got, plain) {
t.Fatalf("PKCS5 helper mismatch, got %x want %x", got, plain)
}
}

10
ripe.go
View File

@ -1,15 +1,11 @@
package starcrypto
import (
"golang.org/x/crypto/ripemd160"
)
import "b612.me/starcrypto/hashx"
func RipeMd160(bstr []byte) []byte {
ripe := ripemd160.New()
ripe.Write(bstr)
return ripe.Sum(nil)
return hashx.RipeMd160(bstr)
}
func RipeMd160Str(bstr []byte) string {
return String(RipeMd160(bstr))
return hashx.RipeMd160Str(bstr)
}

265
rsa.go
View File

@ -1,280 +1,95 @@
package starcrypto
import (
"b612.me/starcrypto/asymm"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"golang.org/x/crypto/ssh"
"math/big"
)
func GenerateRsaKey(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error) {
private, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, nil, err
}
return private, &private.PublicKey, nil
return asymm.GenerateRsaKey(bits)
}
func EncodeRsaPrivateKey(private *rsa.PrivateKey, secret string) ([]byte, error) {
if secret == "" {
return pem.EncodeToMemory(&pem.Block{
Bytes: x509.MarshalPKCS1PrivateKey(private),
Type: "RSA PRIVATE KEY",
}), nil
return asymm.EncodeRsaPrivateKey(private, secret)
}
chiper := x509.PEMCipherAES256
blk, err := x509.EncryptPEMBlock(rand.Reader, "RSA PRIVATE KEY", x509.MarshalPKCS1PrivateKey(private), []byte(secret), chiper)
if err != nil {
return nil, err
func EncodeRsaPrivateKeyWithLegacy(private *rsa.PrivateKey, secret string, legacy bool) ([]byte, error) {
return asymm.EncodeRsaPrivateKeyWithLegacy(private, secret, legacy)
}
return pem.EncodeToMemory(blk), err
func EncodeRsaPrivateKeyPKCS8(private *rsa.PrivateKey, secret string) ([]byte, error) {
return asymm.EncodeRsaPrivateKeyPKCS8(private, secret)
}
func EncodeRsaPublicKey(public *rsa.PublicKey) ([]byte, error) {
publicBytes, err := x509.MarshalPKIXPublicKey(public)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{
Bytes: publicBytes,
Type: "PUBLIC KEY",
}), nil
return asymm.EncodeRsaPublicKey(public)
}
func DecodeRsaPrivateKey(private []byte, password string) (*rsa.PrivateKey, error) {
var prikey *rsa.PrivateKey
var err error
var bytes []byte
blk, _ := pem.Decode(private)
if blk == nil {
return nil, errors.New("private key error")
return asymm.DecodeRsaPrivateKey(private, password)
}
if password != "" {
tmp, err := x509.DecryptPEMBlock(blk, []byte(password))
if err != nil {
return nil, err
func DecodeRsaPrivateKeyWithLegacy(private []byte, password string, legacy bool) (*rsa.PrivateKey, error) {
return asymm.DecodeRsaPrivateKeyWithLegacy(private, password, legacy)
}
bytes = tmp
} else {
bytes = blk.Bytes
}
prikey, err = x509.ParsePKCS1PrivateKey(bytes)
if err != nil {
tmp, err := x509.ParsePKCS8PrivateKey(bytes)
if err != nil {
return nil, err
}
prikey = tmp.(*rsa.PrivateKey)
}
return prikey, err
func DecodeRsaPrivateKeyPKCS8(private []byte, password string) (*rsa.PrivateKey, error) {
return asymm.DecodeRsaPrivateKeyPKCS8(private, password)
}
func DecodeRsaPublicKey(pubStr []byte) (*rsa.PublicKey, error) {
blk, _ := pem.Decode(pubStr)
if blk == nil {
return nil, errors.New("public key error")
}
pub, err := x509.ParsePKIXPublicKey(blk.Bytes)
if err != nil {
return nil, err
}
return pub.(*rsa.PublicKey), nil
return asymm.DecodeRsaPublicKey(pubStr)
}
func EncodeRsaSSHPublicKey(public *rsa.PublicKey) ([]byte, error) {
publicKey, err := ssh.NewPublicKey(public)
if err != nil {
return nil, err
}
return ssh.MarshalAuthorizedKey(publicKey), nil
return asymm.EncodeRsaSSHPublicKey(public)
}
func GenerateRsaSSHKeyPair(bits int, secret string) (string, string, error) {
pkey, pubkey, err := GenerateRsaKey(bits)
if err != nil {
return "", "", err
return asymm.GenerateRsaSSHKeyPair(bits, secret)
}
pub, err := EncodeRsaSSHPublicKey(pubkey)
if err != nil {
return "", "", err
func GenerateRsaSSHKeyPairWithLegacy(bits int, secret string, legacy bool) (string, string, error) {
return asymm.GenerateRsaSSHKeyPairWithLegacy(bits, secret, legacy)
}
priv, err := EncodeRsaPrivateKey(pkey, secret)
if err != nil {
return "", "", err
}
return string(priv), string(pub), nil
}
// RSAEncrypt RSA公钥加密
func RSAEncrypt(pub *rsa.PublicKey, data []byte) ([]byte, error) {
return rsa.EncryptPKCS1v15(rand.Reader, pub, data)
return asymm.RSAEncrypt(pub, data)
}
// RSADecrypt RSA私钥解密
func RSADecrypt(prikey *rsa.PrivateKey, data []byte) ([]byte, error) {
return rsa.DecryptPKCS1v15(rand.Reader, prikey, data)
return asymm.RSADecrypt(prikey, data)
}
func RSAEncryptOAEP(pub *rsa.PublicKey, data, label []byte, hashType crypto.Hash) ([]byte, error) {
return asymm.RSAEncryptOAEP(pub, data, label, hashType)
}
func RSADecryptOAEP(prikey *rsa.PrivateKey, data, label []byte, hashType crypto.Hash) ([]byte, error) {
return asymm.RSADecryptOAEP(prikey, data, label, hashType)
}
// RSASign RSA私钥签名加密
func RSASign(msg, priKey []byte, password string, hashType crypto.Hash) ([]byte, error) {
var prikey *rsa.PrivateKey
var err error
var bytes []byte
blk, _ := pem.Decode(priKey)
if blk == nil {
return []byte{}, errors.New("private key error")
}
if password != "" {
tmp, err := x509.DecryptPEMBlock(blk, []byte(password))
if err != nil {
return []byte{}, err
}
bytes = tmp
} else {
bytes = blk.Bytes
}
prikey, err = x509.ParsePKCS1PrivateKey(bytes)
if err != nil {
tmp, err := x509.ParsePKCS8PrivateKey(bytes)
if err != nil {
return []byte{}, err
}
prikey = tmp.(*rsa.PrivateKey)
}
hashMethod := hashType.New()
_, err = hashMethod.Write(msg)
if err != nil {
return nil, err
}
return rsa.SignPKCS1v15(rand.Reader, prikey, hashType, hashMethod.Sum(nil))
return asymm.RSASign(msg, priKey, password, hashType)
}
// RSAVerify RSA公钥签名验证
func RSAVerify(data, msg, pubKey []byte, hashType crypto.Hash) error {
blk, _ := pem.Decode(pubKey)
if blk == nil {
return errors.New("public key error")
}
pubkey, err := x509.ParsePKIXPublicKey(blk.Bytes)
if err != nil {
return err
}
hashMethod := hashType.New()
_, err = hashMethod.Write(msg)
if err != nil {
return err
}
return rsa.VerifyPKCS1v15(pubkey.(*rsa.PublicKey), hashType, hashMethod.Sum(nil), data)
return asymm.RSAVerify(data, msg, pubKey, hashType)
}
// copy from crypt/rsa/pkcs1v5.go
var hashPrefixes = map[crypto.Hash][]byte{
crypto.MD5: {0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10},
crypto.SHA1: {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14},
crypto.SHA224: {0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c},
crypto.SHA256: {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20},
crypto.SHA384: {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30},
crypto.SHA512: {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40},
crypto.MD5SHA1: {}, // A special TLS case which doesn't use an ASN1 prefix.
crypto.RIPEMD160: {0x30, 0x20, 0x30, 0x08, 0x06, 0x06, 0x28, 0xcf, 0x06, 0x03, 0x00, 0x31, 0x04, 0x14},
func RSASignPSS(msg, priKey []byte, password string, hashType crypto.Hash, opts *rsa.PSSOptions) ([]byte, error) {
return asymm.RSASignPSS(msg, priKey, password, hashType, opts)
}
// copy from crypt/rsa/pkcs1v5.go
func encrypt(c *big.Int, pub *rsa.PublicKey, m *big.Int) *big.Int {
e := big.NewInt(int64(pub.E))
c.Exp(m, e, pub.N)
return c
}
// copy from crypt/rsa/pkcs1v5.go
func pkcs1v15HashInfo(hash crypto.Hash, inLen int) (hashLen int, prefix []byte, err error) {
// Special case: crypto.Hash(0) is used to indicate that the data is
// signed directly.
if hash == 0 {
return inLen, nil, nil
}
hashLen = hash.Size()
if inLen != hashLen {
return 0, nil, errors.New("crypto/rsa: input must be hashed message")
}
prefix, ok := hashPrefixes[hash]
if !ok {
return 0, nil, errors.New("crypto/rsa: unsupported hash function")
}
return
}
// copy from crypt/rsa/pkcs1v5.go
func leftPad(input []byte, size int) (out []byte) {
n := len(input)
if n > size {
n = size
}
out = make([]byte, size)
copy(out[len(out)-n:], input)
return
}
func unLeftPad(input []byte) (out []byte) {
n := len(input)
t := 2
for i := 2; i < n; i++ {
if input[i] == 0xff {
t = t + 1
} else {
if input[i] == input[0] {
t = t + int(input[1])
}
break
}
}
out = make([]byte, n-t)
copy(out, input[t:])
return
}
// copy&modified from crypt/rsa/pkcs1v5.go
func publicDecrypt(pub *rsa.PublicKey, hash crypto.Hash, hashed []byte, sig []byte) (out []byte, err error) {
hashLen, prefix, err := pkcs1v15HashInfo(hash, len(hashed))
if err != nil {
return nil, err
}
tLen := len(prefix) + hashLen
k := (pub.N.BitLen() + 7) / 8
if k < tLen+11 {
return nil, fmt.Errorf("length illegal")
}
c := new(big.Int).SetBytes(sig)
m := encrypt(new(big.Int), pub, c)
em := leftPad(m.Bytes(), k)
out = unLeftPad(em)
err = nil
return
func RSAVerifyPSS(sig, msg, pubKey []byte, hashType crypto.Hash, opts *rsa.PSSOptions) error {
return asymm.RSAVerifyPSS(sig, msg, pubKey, hashType, opts)
}
func RSAEncryptByPrivkey(privt *rsa.PrivateKey, data []byte) ([]byte, error) {
signData, err := rsa.SignPKCS1v15(nil, privt, crypto.Hash(0), data)
if err != nil {
return nil, err
}
return signData, nil
return asymm.RSAEncryptByPrivkey(privt, data)
}
func RSADecryptByPubkey(pub *rsa.PublicKey, data []byte) ([]byte, error) {
decData, err := publicDecrypt(pub, crypto.Hash(0), nil, data)
if err != nil {
return nil, err
}
return decData, nil
return asymm.RSADecryptByPubkey(pub, data)
}

41
sha.go
View File

@ -1,62 +1,43 @@
package starcrypto
import (
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
)
import "b612.me/starcrypto/hashx"
// SHA512 输出SHA512校验值
func Sha512(bstr []byte) []byte {
shasum := sha512.New()
shasum.Write(bstr)
return shasum.Sum(nil)
return hashx.Sha512(bstr)
}
func Sha512Str(bstr []byte) string {
return String(Sha512(bstr))
return hashx.Sha512Str(bstr)
}
// SHA384 输出SHA384校验值
func Sha384(bstr []byte) []byte {
shasum := sha512.New384()
shasum.Write(bstr)
return shasum.Sum(nil)
return hashx.Sha384(bstr)
}
func Sha384Str(bstr []byte) string {
return String(Sha384(bstr))
return hashx.Sha384Str(bstr)
}
// SHA256 输出SHA256校验值
func Sha256(bstr []byte) []byte {
shasum := sha256.New()
shasum.Write(bstr)
return shasum.Sum(nil)
return hashx.Sha256(bstr)
}
func Sha256Str(bstr []byte) string {
return String(Sha256(bstr))
return hashx.Sha256Str(bstr)
}
// SHA224 输出SHA224校验值
func Sha224(bstr []byte) []byte {
shasum := sha256.New224()
shasum.Write(bstr)
return shasum.Sum(nil)
return hashx.Sha224(bstr)
}
func Sha224Str(bstr []byte) string {
return String(Sha224(bstr))
return hashx.Sha224Str(bstr)
}
// SHA1 输出SHA1校验值
func Sha1(bstr []byte) []byte {
shasum := sha1.New()
shasum.Write(bstr)
return shasum.Sum(nil)
return hashx.Sha1(bstr)
}
func Sha1Str(bstr []byte) string {
return String(Sha512(bstr))
return hashx.Sha1Str(bstr)
}

57
sm2.go Normal file
View File

@ -0,0 +1,57 @@
package starcrypto
import (
"b612.me/starcrypto/asymm"
"crypto"
"crypto/ecdsa"
"github.com/emmansun/gmsm/sm2"
)
func GenerateSM2Key() (*sm2.PrivateKey, *ecdsa.PublicKey, error) {
return asymm.GenerateSM2Key()
}
func EncodeSM2PrivateKey(private *sm2.PrivateKey, secret string) ([]byte, error) {
return asymm.EncodeSM2PrivateKey(private, secret)
}
func EncodeSM2PublicKey(public *ecdsa.PublicKey) ([]byte, error) {
return asymm.EncodeSM2PublicKey(public)
}
func DecodeSM2PrivateKey(private []byte, password string) (*sm2.PrivateKey, error) {
return asymm.DecodeSM2PrivateKey(private, password)
}
func DecodeSM2PublicKey(pubStr []byte) (*ecdsa.PublicKey, error) {
return asymm.DecodeSM2PublicKey(pubStr)
}
func SM2EncryptASN1(pub *ecdsa.PublicKey, data []byte) ([]byte, error) {
return asymm.SM2EncryptASN1(pub, data)
}
func SM2DecryptASN1(priv *sm2.PrivateKey, data []byte) ([]byte, error) {
return asymm.SM2DecryptASN1(priv, data)
}
func SM2Sign(priv *sm2.PrivateKey, msg, uid []byte) ([]byte, error) {
return asymm.SM2Sign(priv, msg, uid)
}
func SM2Verify(pub *ecdsa.PublicKey, msg, sig, uid []byte) bool {
return asymm.SM2Verify(pub, msg, sig, uid)
}
func SM2SignByPEM(msg, priKey []byte, password string, uid []byte) ([]byte, error) {
return asymm.SM2SignByPEM(msg, priKey, password, uid)
}
func SM2VerifyByPEM(sig, msg, pubKey []byte, uid []byte) (bool, error) {
return asymm.SM2VerifyByPEM(sig, msg, pubKey, uid)
}
func IsSM2PublicKey(public crypto.PublicKey) bool {
return asymm.IsSM2PublicKey(public)
}

10
sm3.go
View File

@ -1,15 +1,11 @@
package starcrypto
import (
"b612.me/starcrypto/sm3"
)
import "b612.me/starcrypto/hashx"
func SM3(bstr []byte) []byte {
sm3sum := sm3.New()
sm3sum.Write(bstr)
return sm3sum.Sum(nil)
return hashx.SM3(bstr)
}
func SM3Str(bstr []byte) string {
return String(SM3(bstr))
return hashx.SM3Str(bstr)
}

View File

@ -1,259 +1,46 @@
/*
Copyright Suzhou Tongji Fintech Research Institute 2017 All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sm3
import (
"encoding/binary"
"hash"
gmsm3 "github.com/emmansun/gmsm/sm3"
)
type SM3 struct {
digest [8]uint32 // digest represents the partial evaluation of V
length uint64 // length of the message
unhandleMsg []byte // uint8 //
h hash.Hash
}
func (sm3 *SM3) ff0(x, y, z uint32) uint32 { return x ^ y ^ z }
func (sm3 *SM3) ff1(x, y, z uint32) uint32 { return (x & y) | (x & z) | (y & z) }
func (sm3 *SM3) gg0(x, y, z uint32) uint32 { return x ^ y ^ z }
func (sm3 *SM3) gg1(x, y, z uint32) uint32 { return (x & y) | (^x & z) }
func (sm3 *SM3) p0(x uint32) uint32 { return x ^ sm3.leftRotate(x, 9) ^ sm3.leftRotate(x, 17) }
func (sm3 *SM3) p1(x uint32) uint32 { return x ^ sm3.leftRotate(x, 15) ^ sm3.leftRotate(x, 23) }
func (sm3 *SM3) leftRotate(x uint32, i uint32) uint32 { return x<<(i%32) | x>>(32-i%32) }
func (sm3 *SM3) pad() []byte {
msg := sm3.unhandleMsg
msg = append(msg, 0x80) // Append '1'
blockSize := 64 // Append until the resulting message length (in bits) is congruent to 448 (mod 512)
for len(msg)%blockSize != 56 {
msg = append(msg, 0x00)
}
// append message length
msg = append(msg, uint8(sm3.length>>56&0xff))
msg = append(msg, uint8(sm3.length>>48&0xff))
msg = append(msg, uint8(sm3.length>>40&0xff))
msg = append(msg, uint8(sm3.length>>32&0xff))
msg = append(msg, uint8(sm3.length>>24&0xff))
msg = append(msg, uint8(sm3.length>>16&0xff))
msg = append(msg, uint8(sm3.length>>8&0xff))
msg = append(msg, uint8(sm3.length>>0&0xff))
if len(msg)%64 != 0 {
panic("------SM3 Pad: error msgLen =")
}
return msg
}
func (sm3 *SM3) update(msg []byte) {
var w [68]uint32
var w1 [64]uint32
a, b, c, d, e, f, g, h := sm3.digest[0], sm3.digest[1], sm3.digest[2], sm3.digest[3], sm3.digest[4], sm3.digest[5], sm3.digest[6], sm3.digest[7]
for len(msg) >= 64 {
for i := 0; i < 16; i++ {
w[i] = binary.BigEndian.Uint32(msg[4*i : 4*(i+1)])
}
for i := 16; i < 68; i++ {
w[i] = sm3.p1(w[i-16]^w[i-9]^sm3.leftRotate(w[i-3], 15)) ^ sm3.leftRotate(w[i-13], 7) ^ w[i-6]
}
for i := 0; i < 64; i++ {
w1[i] = w[i] ^ w[i+4]
}
A, B, C, D, E, F, G, H := a, b, c, d, e, f, g, h
for i := 0; i < 16; i++ {
SS1 := sm3.leftRotate(sm3.leftRotate(A, 12)+E+sm3.leftRotate(0x79cc4519, uint32(i)), 7)
SS2 := SS1 ^ sm3.leftRotate(A, 12)
TT1 := sm3.ff0(A, B, C) + D + SS2 + w1[i]
TT2 := sm3.gg0(E, F, G) + H + SS1 + w[i]
D = C
C = sm3.leftRotate(B, 9)
B = A
A = TT1
H = G
G = sm3.leftRotate(F, 19)
F = E
E = sm3.p0(TT2)
}
for i := 16; i < 64; i++ {
SS1 := sm3.leftRotate(sm3.leftRotate(A, 12)+E+sm3.leftRotate(0x7a879d8a, uint32(i)), 7)
SS2 := SS1 ^ sm3.leftRotate(A, 12)
TT1 := sm3.ff1(A, B, C) + D + SS2 + w1[i]
TT2 := sm3.gg1(E, F, G) + H + SS1 + w[i]
D = C
C = sm3.leftRotate(B, 9)
B = A
A = TT1
H = G
G = sm3.leftRotate(F, 19)
F = E
E = sm3.p0(TT2)
}
a ^= A
b ^= B
c ^= C
d ^= D
e ^= E
f ^= F
g ^= G
h ^= H
msg = msg[64:]
}
sm3.digest[0], sm3.digest[1], sm3.digest[2], sm3.digest[3], sm3.digest[4], sm3.digest[5], sm3.digest[6], sm3.digest[7] = a, b, c, d, e, f, g, h
}
func (sm3 *SM3) update2(msg []byte) [8]uint32 {
var w [68]uint32
var w1 [64]uint32
a, b, c, d, e, f, g, h := sm3.digest[0], sm3.digest[1], sm3.digest[2], sm3.digest[3], sm3.digest[4], sm3.digest[5], sm3.digest[6], sm3.digest[7]
for len(msg) >= 64 {
for i := 0; i < 16; i++ {
w[i] = binary.BigEndian.Uint32(msg[4*i : 4*(i+1)])
}
for i := 16; i < 68; i++ {
w[i] = sm3.p1(w[i-16]^w[i-9]^sm3.leftRotate(w[i-3], 15)) ^ sm3.leftRotate(w[i-13], 7) ^ w[i-6]
}
for i := 0; i < 64; i++ {
w1[i] = w[i] ^ w[i+4]
}
A, B, C, D, E, F, G, H := a, b, c, d, e, f, g, h
for i := 0; i < 16; i++ {
SS1 := sm3.leftRotate(sm3.leftRotate(A, 12)+E+sm3.leftRotate(0x79cc4519, uint32(i)), 7)
SS2 := SS1 ^ sm3.leftRotate(A, 12)
TT1 := sm3.ff0(A, B, C) + D + SS2 + w1[i]
TT2 := sm3.gg0(E, F, G) + H + SS1 + w[i]
D = C
C = sm3.leftRotate(B, 9)
B = A
A = TT1
H = G
G = sm3.leftRotate(F, 19)
F = E
E = sm3.p0(TT2)
}
for i := 16; i < 64; i++ {
SS1 := sm3.leftRotate(sm3.leftRotate(A, 12)+E+sm3.leftRotate(0x7a879d8a, uint32(i)), 7)
SS2 := SS1 ^ sm3.leftRotate(A, 12)
TT1 := sm3.ff1(A, B, C) + D + SS2 + w1[i]
TT2 := sm3.gg1(E, F, G) + H + SS1 + w[i]
D = C
C = sm3.leftRotate(B, 9)
B = A
A = TT1
H = G
G = sm3.leftRotate(F, 19)
F = E
E = sm3.p0(TT2)
}
a ^= A
b ^= B
c ^= C
d ^= D
e ^= E
f ^= F
g ^= G
h ^= H
msg = msg[64:]
}
var digest [8]uint32
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7] = a, b, c, d, e, f, g, h
return digest
}
// 创建哈希计算实例
func New() hash.Hash {
var sm3 SM3
sm3.Reset()
return &sm3
s := &SM3{}
s.Reset()
return s
}
// BlockSize returns the hash's underlying block size.
// The Write method must be able to accept any amount
// of data, but it may operate more efficiently if all writes
// are a multiple of the block size.
func (sm3 *SM3) BlockSize() int { return 64 }
func (sm3 *SM3) BlockSize() int { return gmsm3.BlockSize }
// Size returns the number of bytes Sum will return.
func (sm3 *SM3) Size() int { return 32 }
func (sm3 *SM3) Size() int { return gmsm3.Size }
// Reset clears the internal state by zeroing bytes in the state buffer.
// This can be skipped for a newly-created hash state; the default zero-allocated state is correct.
func (sm3 *SM3) Reset() {
// Reset digest
sm3.digest[0] = 0x7380166f
sm3.digest[1] = 0x4914b2b9
sm3.digest[2] = 0x172442d7
sm3.digest[3] = 0xda8a0600
sm3.digest[4] = 0xa96f30bc
sm3.digest[5] = 0x163138aa
sm3.digest[6] = 0xe38dee4d
sm3.digest[7] = 0xb0fb0e4e
sm3.length = 0 // Reset numberic states
sm3.unhandleMsg = []byte{}
sm3.h = gmsm3.New()
}
// Write (via the embedded io.Writer interface) adds more data to the running hash.
// It never returns an error.
func (sm3 *SM3) Write(p []byte) (int, error) {
toWrite := len(p)
sm3.length += uint64(len(p) * 8)
msg := append(sm3.unhandleMsg, p...)
nblocks := len(msg) / sm3.BlockSize()
sm3.update(msg)
// Update unhandleMsg
sm3.unhandleMsg = msg[nblocks*sm3.BlockSize():]
return toWrite, nil
if sm3.h == nil {
sm3.Reset()
}
return sm3.h.Write(p)
}
// 返回SM3哈希算法摘要值
// Sum appends the current hash to b and returns the resulting slice.
// It does not change the underlying hash state.
func (sm3 *SM3) Sum(in []byte) []byte {
_, _ = sm3.Write(in)
msg := sm3.pad()
//Finalize
digest := sm3.update2(msg)
// save hash to in
needed := sm3.Size()
if cap(in)-len(in) < needed {
newIn := make([]byte, len(in), len(in)+needed)
copy(newIn, in)
in = newIn
if sm3.h == nil {
sm3.Reset()
}
out := in[len(in) : len(in)+needed]
for i := 0; i < 8; i++ {
binary.BigEndian.PutUint32(out[i*4:], digest[i])
}
return out
return sm3.h.Sum(in)
}
func Sm3Sum(data []byte) []byte {
var sm3 SM3
sm3.Reset()
_, _ = sm3.Write(data)
return sm3.Sum(nil)
sum := gmsm3.Sum(data)
out := make([]byte, len(sum))
copy(out, sum[:])
return out
}

View File

@ -1,64 +1,36 @@
/*
Copyright Suzhou Tongji Fintech Research Institute 2017 All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sm3
import (
"fmt"
"io/ioutil"
"os"
"encoding/hex"
"testing"
)
func byteToString(b []byte) string {
ret := ""
for i := 0; i < len(b); i++ {
ret += fmt.Sprintf("%02x", b[i])
func TestSm3KnownVector(t *testing.T) {
got := Sm3Sum([]byte("abc"))
const want = "66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0"
if hex.EncodeToString(got) != want {
t.Fatalf("Sm3Sum mismatch, got %s want %s", hex.EncodeToString(got), want)
}
fmt.Println("ret = ", ret)
return ret
}
func TestSm3(t *testing.T) {
msg := []byte("test")
err := ioutil.WriteFile("ifile", msg, os.FileMode(0644)) // 生成测试文件
if err != nil {
t.Fatal(err)
}
msg, err = ioutil.ReadFile("ifile")
if err != nil {
t.Fatal(err)
}
hw := New()
hw.Write(msg)
hash := hw.Sum(nil)
fmt.Println(hash)
fmt.Printf("hash = %d\n", len(hash))
fmt.Printf("%s\n", byteToString(hash))
hash1 := Sm3Sum(msg)
fmt.Println(hash1)
fmt.Printf("%s\n", byteToString(hash1))
}
func BenchmarkSm3(t *testing.B) {
t.ReportAllocs()
msg := []byte("test")
hw := New()
for i := 0; i < t.N; i++ {
func TestHashSumDoesNotMutateState(t *testing.T) {
h := New()
if _, err := h.Write([]byte("ab")); err != nil {
t.Fatalf("Write failed: %v", err)
}
a := h.Sum(nil)
if _, err := h.Write([]byte("c")); err != nil {
t.Fatalf("Write failed: %v", err)
}
b := h.Sum(nil)
if hex.EncodeToString(a) == hex.EncodeToString(b) {
t.Fatalf("hash state should evolve after further writes")
}
}
hw.Sum(nil)
Sm3Sum(msg)
func BenchmarkSm3Sum(b *testing.B) {
msg := []byte("benchmark")
for i := 0; i < b.N; i++ {
_ = Sm3Sum(msg)
}
}

242
sm4.go Normal file
View File

@ -0,0 +1,242 @@
package starcrypto
import (
"io"
"b612.me/starcrypto/symm"
)
func EncryptSM4(data, key, iv []byte, mode, paddingType string) ([]byte, error) {
return symm.EncryptSM4(data, key, iv, mode, paddingType)
}
func DecryptSM4(src, key, iv []byte, mode, paddingType string) ([]byte, error) {
return symm.DecryptSM4(src, key, iv, mode, paddingType)
}
func EncryptSM4Stream(dst io.Writer, src io.Reader, key, iv []byte, mode, paddingType string) error {
return symm.EncryptSM4Stream(dst, src, key, iv, mode, paddingType)
}
func DecryptSM4Stream(dst io.Writer, src io.Reader, key, iv []byte, mode, paddingType string) error {
return symm.DecryptSM4Stream(dst, src, key, iv, mode, paddingType)
}
func EncryptSM4WithOptions(data, key []byte, opts *CipherOptions) ([]byte, error) {
return symm.EncryptSM4WithOptions(data, key, opts)
}
func DecryptSM4WithOptions(src, key []byte, opts *CipherOptions) ([]byte, error) {
return symm.DecryptSM4WithOptions(src, key, opts)
}
func EncryptSM4StreamWithOptions(dst io.Writer, src io.Reader, key []byte, opts *CipherOptions) error {
return symm.EncryptSM4StreamWithOptions(dst, src, key, opts)
}
func DecryptSM4StreamWithOptions(dst io.Writer, src io.Reader, key []byte, opts *CipherOptions) error {
return symm.DecryptSM4StreamWithOptions(dst, src, key, opts)
}
func EncryptSM4GCM(plain, key, nonce, aad []byte) ([]byte, error) {
return symm.EncryptSM4GCM(plain, key, nonce, aad)
}
func DecryptSM4GCM(ciphertext, key, nonce, aad []byte) ([]byte, error) {
return symm.DecryptSM4GCM(ciphertext, key, nonce, aad)
}
func EncryptSM4GCMChunk(plain, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return symm.EncryptSM4GCMChunk(plain, key, nonce, aad, chunkIndex)
}
func DecryptSM4GCMChunk(ciphertext, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return symm.DecryptSM4GCMChunk(ciphertext, key, nonce, aad, chunkIndex)
}
func EncryptSM4GCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return symm.EncryptSM4GCMStream(dst, src, key, nonce, aad)
}
func DecryptSM4GCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return symm.DecryptSM4GCMStream(dst, src, key, nonce, aad)
}
func EncryptSM4CCM(plain, key, nonce, aad []byte) ([]byte, error) {
return symm.EncryptSM4CCM(plain, key, nonce, aad)
}
func DecryptSM4CCM(ciphertext, key, nonce, aad []byte) ([]byte, error) {
return symm.DecryptSM4CCM(ciphertext, key, nonce, aad)
}
func EncryptSM4CCMChunk(plain, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return symm.EncryptSM4CCMChunk(plain, key, nonce, aad, chunkIndex)
}
func DecryptSM4CCMChunk(ciphertext, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return symm.DecryptSM4CCMChunk(ciphertext, key, nonce, aad, chunkIndex)
}
func EncryptSM4CCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return symm.EncryptSM4CCMStream(dst, src, key, nonce, aad)
}
func DecryptSM4CCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return symm.DecryptSM4CCMStream(dst, src, key, nonce, aad)
}
func EncryptSM4CFB(origData, key []byte) ([]byte, error) {
return symm.EncryptSM4CFB(origData, key)
}
func DecryptSM4CFB(encrypted, key []byte) ([]byte, error) {
return symm.DecryptSM4CFB(encrypted, key)
}
func EncryptSM4CFBNoBlock(origData, key, iv []byte) ([]byte, error) {
return symm.EncryptSM4CFBNoBlock(origData, key, iv)
}
func DecryptSM4CFBNoBlock(encrypted, key, iv []byte) ([]byte, error) {
return symm.DecryptSM4CFBNoBlock(encrypted, key, iv)
}
func EncryptSM4ECB(data, key []byte, paddingType string) ([]byte, error) {
return symm.EncryptSM4ECB(data, key, paddingType)
}
func DecryptSM4ECB(src, key []byte, paddingType string) ([]byte, error) {
return symm.DecryptSM4ECB(src, key, paddingType)
}
func EncryptSM4CBC(data, key, iv []byte, paddingType string) ([]byte, error) {
return symm.EncryptSM4CBC(data, key, iv, paddingType)
}
func DecryptSM4CBC(src, key, iv []byte, paddingType string) ([]byte, error) {
return symm.DecryptSM4CBC(src, key, iv, paddingType)
}
func EncryptSM4OFB(data, key, iv []byte) ([]byte, error) {
return symm.EncryptSM4OFB(data, key, iv)
}
func DecryptSM4OFB(src, key, iv []byte) ([]byte, error) {
return symm.DecryptSM4OFB(src, key, iv)
}
func EncryptSM4CTR(data, key, iv []byte) ([]byte, error) {
return symm.EncryptSM4CTR(data, key, iv)
}
func DecryptSM4CTR(src, key, iv []byte) ([]byte, error) {
return symm.DecryptSM4CTR(src, key, iv)
}
func EncryptSM4CTRAt(data, key, iv []byte, offset int64) ([]byte, error) {
return symm.EncryptSM4CTRAt(data, key, iv, offset)
}
func DecryptSM4CTRAt(src, key, iv []byte, offset int64) ([]byte, error) {
return symm.DecryptSM4CTRAt(src, key, iv, offset)
}
func EncryptSM4ECBStream(dst io.Writer, src io.Reader, key []byte, paddingType string) error {
return symm.EncryptSM4ECBStream(dst, src, key, paddingType)
}
func DecryptSM4ECBStream(dst io.Writer, src io.Reader, key []byte, paddingType string) error {
return symm.DecryptSM4ECBStream(dst, src, key, paddingType)
}
func EncryptSM4CFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.EncryptSM4CFBStream(dst, src, key, iv)
}
func DecryptSM4CFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.DecryptSM4CFBStream(dst, src, key, iv)
}
func EncryptSM4CBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
return symm.EncryptSM4CBCStream(dst, src, key, iv, paddingType)
}
func DecryptSM4CBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
return symm.DecryptSM4CBCStream(dst, src, key, iv, paddingType)
}
func EncryptSM4OFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.EncryptSM4OFBStream(dst, src, key, iv)
}
func DecryptSM4OFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.DecryptSM4OFBStream(dst, src, key, iv)
}
func EncryptSM4CTRStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.EncryptSM4CTRStream(dst, src, key, iv)
}
func DecryptSM4CTRStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.DecryptSM4CTRStream(dst, src, key, iv)
}
func EncryptSM4CFB8(data, key, iv []byte) ([]byte, error) {
return symm.EncryptSM4CFB8(data, key, iv)
}
func DecryptSM4CFB8(src, key, iv []byte) ([]byte, error) {
return symm.DecryptSM4CFB8(src, key, iv)
}
func EncryptSM4CFB8Stream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.EncryptSM4CFB8Stream(dst, src, key, iv)
}
func DecryptSM4CFB8Stream(dst io.Writer, src io.Reader, key, iv []byte) error {
return symm.DecryptSM4CFB8Stream(dst, src, key, iv)
}
func DecryptSM4ECBBlocks(src, key []byte) ([]byte, error) {
return symm.DecryptSM4ECBBlocks(src, key)
}
func DecryptSM4CBCFromSecondBlock(src, key, prevCipherBlock []byte) ([]byte, error) {
return symm.DecryptSM4CBCFromSecondBlock(src, key, prevCipherBlock)
}
func DecryptSM4CFBFromSecondBlock(src, key, prevCipherBlock []byte) ([]byte, error) {
return symm.DecryptSM4CFBFromSecondBlock(src, key, prevCipherBlock)
}
func EncryptSM4XTS(plain, key1, key2 []byte, dataUnitSize int) ([]byte, error) {
return symm.EncryptSM4XTS(plain, key1, key2, dataUnitSize)
}
func DecryptSM4XTS(ciphertext, key1, key2 []byte, dataUnitSize int) ([]byte, error) {
return symm.DecryptSM4XTS(ciphertext, key1, key2, dataUnitSize)
}
func EncryptSM4XTSAt(plain, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) ([]byte, error) {
return symm.EncryptSM4XTSAt(plain, key1, key2, dataUnitSize, dataUnitIndex)
}
func DecryptSM4XTSAt(ciphertext, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) ([]byte, error) {
return symm.DecryptSM4XTSAt(ciphertext, key1, key2, dataUnitSize, dataUnitIndex)
}
func EncryptSM4XTSStream(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int) error {
return symm.EncryptSM4XTSStream(dst, src, key1, key2, dataUnitSize)
}
func DecryptSM4XTSStream(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int) error {
return symm.DecryptSM4XTSStream(dst, src, key1, key2, dataUnitSize)
}
func EncryptSM4XTSStreamAt(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) error {
return symm.EncryptSM4XTSStreamAt(dst, src, key1, key2, dataUnitSize, dataUnitIndex)
}
func DecryptSM4XTSStreamAt(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) error {
return symm.DecryptSM4XTSStreamAt(dst, src, key1, key2, dataUnitSize, dataUnitIndex)
}

108
sm9.go Normal file
View File

@ -0,0 +1,108 @@
package starcrypto
import (
"b612.me/starcrypto/asymm"
gmsm9 "github.com/emmansun/gmsm/sm9"
)
const (
SM9SignHID = asymm.SM9SignHID
SM9EncryptHID = asymm.SM9EncryptHID
)
func GenerateSM9SignMasterKey() (*gmsm9.SignMasterPrivateKey, *gmsm9.SignMasterPublicKey, error) {
return asymm.GenerateSM9SignMasterKey()
}
func GenerateSM9EncryptMasterKey() (*gmsm9.EncryptMasterPrivateKey, *gmsm9.EncryptMasterPublicKey, error) {
return asymm.GenerateSM9EncryptMasterKey()
}
func GenerateSM9SignUserKey(master *gmsm9.SignMasterPrivateKey, uid []byte, hid byte) (*gmsm9.SignPrivateKey, error) {
return asymm.GenerateSM9SignUserKey(master, uid, hid)
}
func GenerateSM9EncryptUserKey(master *gmsm9.EncryptMasterPrivateKey, uid []byte, hid byte) (*gmsm9.EncryptPrivateKey, error) {
return asymm.GenerateSM9EncryptUserKey(master, uid, hid)
}
func EncodeSM9SignMasterPrivateKey(key *gmsm9.SignMasterPrivateKey) ([]byte, error) {
return asymm.EncodeSM9SignMasterPrivateKey(key)
}
func DecodeSM9SignMasterPrivateKey(data []byte) (*gmsm9.SignMasterPrivateKey, error) {
return asymm.DecodeSM9SignMasterPrivateKey(data)
}
func EncodeSM9SignMasterPublicKey(key *gmsm9.SignMasterPublicKey) ([]byte, error) {
return asymm.EncodeSM9SignMasterPublicKey(key)
}
func DecodeSM9SignMasterPublicKey(data []byte) (*gmsm9.SignMasterPublicKey, error) {
return asymm.DecodeSM9SignMasterPublicKey(data)
}
func EncodeSM9SignPrivateKey(key *gmsm9.SignPrivateKey) ([]byte, error) {
return asymm.EncodeSM9SignPrivateKey(key)
}
func DecodeSM9SignPrivateKey(data []byte) (*gmsm9.SignPrivateKey, error) {
return asymm.DecodeSM9SignPrivateKey(data)
}
func EncodeSM9EncryptMasterPrivateKey(key *gmsm9.EncryptMasterPrivateKey) ([]byte, error) {
return asymm.EncodeSM9EncryptMasterPrivateKey(key)
}
func DecodeSM9EncryptMasterPrivateKey(data []byte) (*gmsm9.EncryptMasterPrivateKey, error) {
return asymm.DecodeSM9EncryptMasterPrivateKey(data)
}
func EncodeSM9EncryptMasterPublicKey(key *gmsm9.EncryptMasterPublicKey) ([]byte, error) {
return asymm.EncodeSM9EncryptMasterPublicKey(key)
}
func DecodeSM9EncryptMasterPublicKey(data []byte) (*gmsm9.EncryptMasterPublicKey, error) {
return asymm.DecodeSM9EncryptMasterPublicKey(data)
}
func EncodeSM9EncryptPrivateKey(key *gmsm9.EncryptPrivateKey) ([]byte, error) {
return asymm.EncodeSM9EncryptPrivateKey(key)
}
func DecodeSM9EncryptPrivateKey(data []byte) (*gmsm9.EncryptPrivateKey, error) {
return asymm.DecodeSM9EncryptPrivateKey(data)
}
func SM9SignHashASN1(priv *gmsm9.SignPrivateKey, hash []byte) ([]byte, error) {
return asymm.SM9SignHashASN1(priv, hash)
}
func SM9SignASN1(priv *gmsm9.SignPrivateKey, message []byte) ([]byte, error) {
return asymm.SM9SignASN1(priv, message)
}
func SM9VerifyHashASN1(pub *gmsm9.SignMasterPublicKey, uid []byte, hid byte, hash, sig []byte) bool {
return asymm.SM9VerifyHashASN1(pub, uid, hid, hash, sig)
}
func SM9VerifyASN1(pub *gmsm9.SignMasterPublicKey, uid []byte, hid byte, message, sig []byte) bool {
return asymm.SM9VerifyASN1(pub, uid, hid, message, sig)
}
func SM9Encrypt(pub *gmsm9.EncryptMasterPublicKey, uid []byte, hid byte, plaintext []byte) ([]byte, error) {
return asymm.SM9Encrypt(pub, uid, hid, plaintext)
}
func SM9Decrypt(priv *gmsm9.EncryptPrivateKey, uid, ciphertext []byte) ([]byte, error) {
return asymm.SM9Decrypt(priv, uid, ciphertext)
}
func SM9EncryptASN1(pub *gmsm9.EncryptMasterPublicKey, uid []byte, hid byte, plaintext []byte) ([]byte, error) {
return asymm.SM9EncryptASN1(pub, uid, hid, plaintext)
}
func SM9DecryptASN1(priv *gmsm9.EncryptPrivateKey, uid, ciphertext []byte) ([]byte, error) {
return asymm.SM9DecryptASN1(priv, uid, ciphertext)
}

338
symm/aes.go Normal file
View File

@ -0,0 +1,338 @@
package symm
import (
"crypto/aes"
"crypto/rand"
"errors"
"io"
"b612.me/starcrypto/paddingx"
)
const (
PKCS5PADDING = paddingx.PKCS5
PKCS7PADDING = paddingx.PKCS7
ZEROPADDING = paddingx.ZERO
ANSIX923PADDING = paddingx.ANSIX923
)
var (
ErrInvalidGCMNonceLength = errors.New("gcm nonce length must be 12 bytes")
ErrInvalidCCMNonceLength = errors.New("ccm nonce length must be 12 bytes")
)
const (
aeadCCMTagSize = 16
aeadCCMNonceSize = 12
)
var (
aesGCMFactory = newGCMFactory(aes.NewCipher)
aesCCMFactory = newCCMFactory(aes.NewCipher)
)
func EncryptAes(data, key, iv []byte, mode, paddingType string) ([]byte, error) {
return encryptBlockCipher(data, key, iv, mode, paddingType, PKCS7PADDING, aes.NewCipher, EncryptAesGCM, EncryptAesCCM)
}
func DecryptAes(src, key, iv []byte, mode, paddingType string) ([]byte, error) {
return decryptBlockCipher(src, key, iv, mode, paddingType, PKCS7PADDING, aes.NewCipher, DecryptAesGCM, DecryptAesCCM)
}
func EncryptAesStream(dst io.Writer, src io.Reader, key, iv []byte, mode, paddingType string) error {
return encryptBlockCipherStream(dst, src, key, iv, mode, paddingType, PKCS7PADDING, aes.NewCipher, EncryptAesGCMStream, EncryptAesCCMStream)
}
func DecryptAesStream(dst io.Writer, src io.Reader, key, iv []byte, mode, paddingType string) error {
return decryptBlockCipherStream(dst, src, key, iv, mode, paddingType, PKCS7PADDING, aes.NewCipher, DecryptAesGCMStream, DecryptAesCCMStream)
}
func EncryptAesWithOptions(data, key []byte, opts *CipherOptions) ([]byte, error) {
return encryptBlockWithOptions(data, key, opts, EncryptAesGCM, EncryptAesCCM, EncryptAes)
}
func DecryptAesWithOptions(src, key []byte, opts *CipherOptions) ([]byte, error) {
return decryptBlockWithOptions(src, key, opts, DecryptAesGCM, DecryptAesCCM, DecryptAes)
}
func EncryptAesStreamWithOptions(dst io.Writer, src io.Reader, key []byte, opts *CipherOptions) error {
return encryptBlockStreamWithOptions(dst, src, key, opts, EncryptAesGCMStream, EncryptAesCCMStream, EncryptAesStream)
}
func DecryptAesStreamWithOptions(dst io.Writer, src io.Reader, key []byte, opts *CipherOptions) error {
return decryptBlockStreamWithOptions(dst, src, key, opts, DecryptAesGCMStream, DecryptAesCCMStream, DecryptAesStream)
}
func EncryptAesGCM(plain, key, nonce, aad []byte) ([]byte, error) {
return encryptAEAD(aesGCMFactory, plain, key, nonce, aad, ErrInvalidGCMNonceLength)
}
func DecryptAesGCM(ciphertext, key, nonce, aad []byte) ([]byte, error) {
return decryptAEAD(aesGCMFactory, ciphertext, key, nonce, aad, ErrInvalidGCMNonceLength)
}
func EncryptAesCCM(plain, key, nonce, aad []byte) ([]byte, error) {
return encryptAEAD(aesCCMFactory, plain, key, nonce, aad, ErrInvalidCCMNonceLength)
}
func DecryptAesCCM(ciphertext, key, nonce, aad []byte) ([]byte, error) {
return decryptAEAD(aesCCMFactory, ciphertext, key, nonce, aad, ErrInvalidCCMNonceLength)
}
func EncryptAesCCMChunk(plain, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return encryptAEADChunk(aesCCMFactory, plain, key, nonce, aad, chunkIndex, ErrInvalidCCMNonceLength, encryptCCMChunk)
}
func DecryptAesCCMChunk(ciphertext, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return decryptAEADChunk(aesCCMFactory, ciphertext, key, nonce, aad, chunkIndex, ErrInvalidCCMNonceLength, decryptCCMChunk)
}
func EncryptAesCCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return encryptAEADStream(aesCCMFactory, dst, src, key, nonce, aad, ErrInvalidCCMNonceLength, encryptCCMChunkedStream)
}
func DecryptAesCCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return decryptAEADStream(aesCCMFactory, dst, src, key, nonce, aad, ErrInvalidCCMNonceLength, decryptCCMChunkedOrLegacyStream)
}
func EncryptAesGCMChunk(plain, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return encryptAEADChunk(aesGCMFactory, plain, key, nonce, aad, chunkIndex, ErrInvalidGCMNonceLength, encryptGCMChunk)
}
func DecryptAesGCMChunk(ciphertext, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return decryptAEADChunk(aesGCMFactory, ciphertext, key, nonce, aad, chunkIndex, ErrInvalidGCMNonceLength, decryptGCMChunk)
}
func EncryptAesGCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return encryptAEADStream(aesGCMFactory, dst, src, key, nonce, aad, ErrInvalidGCMNonceLength, encryptGCMChunkedStream)
}
func DecryptAesGCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return decryptAEADStream(aesGCMFactory, dst, src, key, nonce, aad, ErrInvalidGCMNonceLength, decryptGCMChunkedOrLegacyStream)
}
func EncryptAesECB(data, key []byte, paddingType string) ([]byte, error) {
return EncryptAes(data, key, nil, MODEECB, paddingType)
}
func DecryptAesECB(src, key []byte, paddingType string) ([]byte, error) {
return DecryptAes(src, key, nil, MODEECB, paddingType)
}
func EncryptAesCBC(data, key, iv []byte, paddingType string) ([]byte, error) {
return EncryptAes(data, key, iv, MODECBC, paddingType)
}
func DecryptAesCBC(src, key, iv []byte, paddingType string) ([]byte, error) {
return DecryptAes(src, key, iv, MODECBC, paddingType)
}
func EncryptAesCFB(data, key, iv []byte) ([]byte, error) {
return EncryptAes(data, key, iv, MODECFB, "")
}
func DecryptAesCFB(src, key, iv []byte) ([]byte, error) {
return DecryptAes(src, key, iv, MODECFB, "")
}
func EncryptAesOFB(data, key, iv []byte) ([]byte, error) {
return EncryptAes(data, key, iv, MODEOFB, "")
}
func DecryptAesOFB(src, key, iv []byte) ([]byte, error) {
return DecryptAes(src, key, iv, MODEOFB, "")
}
func EncryptAesCTR(data, key, iv []byte) ([]byte, error) {
return EncryptAes(data, key, iv, MODECTR, "")
}
func DecryptAesCTR(src, key, iv []byte) ([]byte, error) {
return DecryptAes(src, key, iv, MODECTR, "")
}
func EncryptAesCTRAt(data, key, iv []byte, offset int64) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return xorCTRAtOffset(block, data, iv, offset)
}
func DecryptAesCTRAt(src, key, iv []byte, offset int64) ([]byte, error) {
return EncryptAesCTRAt(src, key, iv, offset)
}
func EncryptAesECBStream(dst io.Writer, src io.Reader, key []byte, paddingType string) error {
return EncryptAesStream(dst, src, key, nil, MODEECB, paddingType)
}
func DecryptAesECBStream(dst io.Writer, src io.Reader, key []byte, paddingType string) error {
return DecryptAesStream(dst, src, key, nil, MODEECB, paddingType)
}
func EncryptAesCBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
return EncryptAesStream(dst, src, key, iv, MODECBC, paddingType)
}
func DecryptAesCBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
return DecryptAesStream(dst, src, key, iv, MODECBC, paddingType)
}
func EncryptAesCFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return EncryptAesStream(dst, src, key, iv, MODECFB, "")
}
func DecryptAesCFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return DecryptAesStream(dst, src, key, iv, MODECFB, "")
}
func EncryptAesOFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return EncryptAesStream(dst, src, key, iv, MODEOFB, "")
}
func DecryptAesOFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return DecryptAesStream(dst, src, key, iv, MODEOFB, "")
}
func EncryptAesCTRStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return EncryptAesStream(dst, src, key, iv, MODECTR, "")
}
func DecryptAesCTRStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return DecryptAesStream(dst, src, key, iv, MODECTR, "")
}
func CustomEncryptAesCFB(origData, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
encrypted := make([]byte, aes.BlockSize+len(origData))
iv := encrypted[:block.BlockSize()]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
body, err := EncryptAesCFB(origData, key, iv)
if err != nil {
return nil, err
}
copy(encrypted[block.BlockSize():], body)
return encrypted, nil
}
func CustomDecryptAesCFB(encrypted, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(encrypted) < block.BlockSize() {
return nil, errors.New("ciphertext too short")
}
iv := encrypted[:block.BlockSize()]
return DecryptAesCFB(encrypted[block.BlockSize():], key, iv)
}
func CustomEncryptAesCFBNoBlock(origData, key, iv []byte) ([]byte, error) {
return EncryptAesCFB(origData, key, iv)
}
func CustomDecryptAesCFBNoBlock(encrypted, key, iv []byte) ([]byte, error) {
return DecryptAesCFB(encrypted, key, iv)
}
func PKCS5Padding(cipherText []byte, blockSize int) []byte {
out, _ := paddingx.Pad(cipherText, blockSize, PKCS5PADDING)
return out
}
func PKCS5Trimming(encrypted []byte) []byte {
if len(encrypted) == 0 {
return nil
}
padding := int(encrypted[len(encrypted)-1])
if padding <= 0 || padding > len(encrypted) {
return nil
}
for i := len(encrypted) - padding; i < len(encrypted); i++ {
if int(encrypted[i]) != padding {
return nil
}
}
return encrypted[:len(encrypted)-padding]
}
func PKCS7Padding(cipherText []byte, blockSize int) []byte {
out, _ := paddingx.Pad(cipherText, blockSize, PKCS7PADDING)
return out
}
func PKCS7Trimming(encrypted []byte, blockSize int) []byte {
out, err := paddingx.Unpad(encrypted, blockSize, PKCS7PADDING)
if err != nil {
return nil
}
return out
}
func EncryptAesCFB8(data, key, iv []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return encryptCFB8(block, data, iv)
}
func DecryptAesCFB8(src, key, iv []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return decryptCFB8(block, src, iv)
}
func EncryptAesCFB8Stream(dst io.Writer, src io.Reader, key, iv []byte) error {
block, err := aes.NewCipher(key)
if err != nil {
return err
}
return encryptCFB8Stream(block, dst, src, iv, false)
}
func DecryptAesCFB8Stream(dst io.Writer, src io.Reader, key, iv []byte) error {
block, err := aes.NewCipher(key)
if err != nil {
return err
}
return encryptCFB8Stream(block, dst, src, iv, true)
}
func DecryptAesECBBlocks(src, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return decryptECBBlocks(block, src)
}
// DecryptAesCBCFromSecondBlock decrypts a CBC ciphertext segment that starts from block 2 or later.
// prevCipherBlock must be the previous ciphertext block. For data from block 2, pass block 1 as prevCipherBlock.
func DecryptAesCBCFromSecondBlock(src, key, prevCipherBlock []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return decryptCBCFromSecondBlock(block, src, prevCipherBlock)
}
// DecryptAesCFBFromSecondBlock decrypts a CFB ciphertext segment that starts from block 2 or later.
// prevCipherBlock must be the previous ciphertext block. For data from block 2, pass block 1 as prevCipherBlock.
func DecryptAesCFBFromSecondBlock(src, key, prevCipherBlock []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return decryptCFBFromSecondBlock(block, src, prevCipherBlock)
}

98
symm/bench_test.go Normal file
View File

@ -0,0 +1,98 @@
package symm
import (
"bytes"
"testing"
)
var (
benchPlain4K = bytes.Repeat([]byte("0123456789abcdef"), 256) // 4 KiB
benchPlain256K = bytes.Repeat([]byte("0123456789abcdef"), 16384) // 256 KiB
benchAAD = []byte("benchmark-aad")
benchAESKey = []byte("0123456789abcdef")
benchSM4Key = []byte("0123456789abcdef")
benchXTSKey2 = []byte("fedcba9876543210")
benchNonce12Byte = []byte("123456789012")
)
func BenchmarkAesGCMEncrypt4K(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(len(benchPlain4K)))
for i := 0; i < b.N; i++ {
if _, err := EncryptAesGCM(benchPlain4K, benchAESKey, benchNonce12Byte, benchAAD); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkAesCCMEncrypt4K(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(len(benchPlain4K)))
for i := 0; i < b.N; i++ {
if _, err := EncryptAesCCM(benchPlain4K, benchAESKey, benchNonce12Byte, benchAAD); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkAesXTSEncrypt4K(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(len(benchPlain4K)))
for i := 0; i < b.N; i++ {
if _, err := EncryptAesXTS(benchPlain4K, benchAESKey, benchXTSKey2, 512); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkSM4GCMEncrypt4K(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(len(benchPlain4K)))
for i := 0; i < b.N; i++ {
if _, err := EncryptSM4GCM(benchPlain4K, benchSM4Key, benchNonce12Byte, benchAAD); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkSM4CCMEncrypt4K(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(len(benchPlain4K)))
for i := 0; i < b.N; i++ {
if _, err := EncryptSM4CCM(benchPlain4K, benchSM4Key, benchNonce12Byte, benchAAD); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkSM4XTSEncrypt4K(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(len(benchPlain4K)))
for i := 0; i < b.N; i++ {
if _, err := EncryptSM4XTS(benchPlain4K, benchSM4Key, benchXTSKey2, 512); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkAesXTSStreamEncrypt256K(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(len(benchPlain256K)))
for i := 0; i < b.N; i++ {
var dst bytes.Buffer
if err := EncryptAesXTSStream(&dst, bytes.NewReader(benchPlain256K), benchAESKey, benchXTSKey2, 512); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkSM4XTSStreamEncrypt256K(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(len(benchPlain256K)))
for i := 0; i < b.N; i++ {
var dst bytes.Buffer
if err := EncryptSM4XTSStream(&dst, bytes.NewReader(benchPlain256K), benchSM4Key, benchXTSKey2, 512); err != nil {
b.Fatal(err)
}
}
}

127
symm/ccm_stream.go Normal file
View File

@ -0,0 +1,127 @@
package symm
import (
"bytes"
"crypto/cipher"
"encoding/binary"
"errors"
"io"
)
const ccmStreamMagic = "SCC1"
var ErrInvalidCCMStreamChunk = errors.New("invalid ccm stream chunk")
func encryptCCMChunk(aead cipher.AEAD, plain, nonce, aad []byte, chunkIndex uint64) []byte {
chunkNonce := deriveChunkNonce(nonce, chunkIndex)
return aead.Seal(nil, chunkNonce, plain, aad)
}
func decryptCCMChunk(aead cipher.AEAD, ciphertext, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
chunkNonce := deriveChunkNonce(nonce, chunkIndex)
return aead.Open(nil, chunkNonce, ciphertext, aad)
}
func encryptCCMChunkedStream(dst io.Writer, src io.Reader, aead cipher.AEAD, nonce, aad []byte) error {
if _, err := dst.Write([]byte(ccmStreamMagic)); err != nil {
return err
}
buf := make([]byte, gcmStreamChunkSize)
lenBuf := make([]byte, 4)
var chunkIndex uint64
for {
n, err := src.Read(buf)
if n > 0 {
sealed := encryptCCMChunk(aead, buf[:n], nonce, aad, chunkIndex)
binary.BigEndian.PutUint32(lenBuf, uint32(len(sealed)))
if _, werr := dst.Write(lenBuf); werr != nil {
return werr
}
if _, werr := dst.Write(sealed); werr != nil {
return werr
}
chunkIndex++
}
if err != nil {
if err == io.EOF {
return nil
}
return err
}
}
}
func decryptCCMChunkedOrLegacyStream(dst io.Writer, src io.Reader, aead cipher.AEAD, nonce, aad []byte) error {
header := make([]byte, len(ccmStreamMagic))
n, err := io.ReadFull(src, header)
if err != nil {
if err == io.EOF {
return nil
}
if err != io.ErrUnexpectedEOF {
return err
}
return decryptCCMLegacyBuffered(dst, io.MultiReader(bytes.NewReader(header[:n]), src), aead, nonce, aad)
}
if string(header) != ccmStreamMagic {
return decryptCCMLegacyBuffered(dst, io.MultiReader(bytes.NewReader(header), src), aead, nonce, aad)
}
return decryptCCMChunkedStream(dst, src, aead, nonce, aad)
}
func decryptCCMChunkedStream(dst io.Writer, src io.Reader, aead cipher.AEAD, nonce, aad []byte) error {
lenBuf := make([]byte, 4)
maxChunkLen := uint32(gcmStreamChunkSize + aead.Overhead())
var chunkIndex uint64
for {
_, err := io.ReadFull(src, lenBuf)
if err != nil {
if err == io.EOF {
return nil
}
if err == io.ErrUnexpectedEOF {
return io.ErrUnexpectedEOF
}
return err
}
chunkLen := binary.BigEndian.Uint32(lenBuf)
if chunkLen < uint32(aead.Overhead()) || chunkLen > maxChunkLen {
return ErrInvalidCCMStreamChunk
}
chunk := make([]byte, chunkLen)
if _, err := io.ReadFull(src, chunk); err != nil {
if err == io.ErrUnexpectedEOF {
return io.ErrUnexpectedEOF
}
return err
}
plain, err := decryptCCMChunk(aead, chunk, nonce, aad, chunkIndex)
if err != nil {
return err
}
if _, err := dst.Write(plain); err != nil {
return err
}
chunkIndex++
}
}
func decryptCCMLegacyBuffered(dst io.Writer, src io.Reader, aead cipher.AEAD, nonce, aad []byte) error {
enc, err := io.ReadAll(src)
if err != nil {
return err
}
plain, err := aead.Open(nil, nonce, enc, aad)
if err != nil {
return err
}
_, err = dst.Write(plain)
return err
}

103
symm/cfb8.go Normal file
View File

@ -0,0 +1,103 @@
package symm
import (
"crypto/cipher"
"errors"
"io"
)
func encryptCFB8(block cipher.Block, data, iv []byte) ([]byte, error) {
if len(iv) != block.BlockSize() {
return nil, errors.New("iv length must match block size")
}
reg := make([]byte, len(iv))
copy(reg, iv)
regView := make([]byte, block.BlockSize())
streamBlock := make([]byte, block.BlockSize())
out := make([]byte, len(data))
head := 0
for i := 0; i < len(data); i++ {
buildCFB8Register(regView, reg, head)
block.Encrypt(streamBlock, regView)
c := data[i] ^ streamBlock[0]
out[i] = c
advanceCFB8Register(reg, &head, c)
}
return out, nil
}
func decryptCFB8(block cipher.Block, src, iv []byte) ([]byte, error) {
if len(iv) != block.BlockSize() {
return nil, errors.New("iv length must match block size")
}
reg := make([]byte, len(iv))
copy(reg, iv)
regView := make([]byte, block.BlockSize())
streamBlock := make([]byte, block.BlockSize())
out := make([]byte, len(src))
head := 0
for i := 0; i < len(src); i++ {
buildCFB8Register(regView, reg, head)
block.Encrypt(streamBlock, regView)
p := src[i] ^ streamBlock[0]
out[i] = p
advanceCFB8Register(reg, &head, src[i])
}
return out, nil
}
func encryptCFB8Stream(block cipher.Block, dst io.Writer, src io.Reader, iv []byte, decrypt bool) error {
if len(iv) != block.BlockSize() {
return errors.New("iv length must match block size")
}
reg := make([]byte, len(iv))
copy(reg, iv)
regView := make([]byte, block.BlockSize())
streamBlock := make([]byte, block.BlockSize())
buf := make([]byte, 32*1024)
out := make([]byte, 32*1024)
head := 0
for {
n, err := src.Read(buf)
if n > 0 {
for i := 0; i < n; i++ {
buildCFB8Register(regView, reg, head)
block.Encrypt(streamBlock, regView)
if decrypt {
out[i] = buf[i] ^ streamBlock[0]
advanceCFB8Register(reg, &head, buf[i])
} else {
c := buf[i] ^ streamBlock[0]
out[i] = c
advanceCFB8Register(reg, &head, c)
}
}
if _, werr := dst.Write(out[:n]); werr != nil {
return werr
}
}
if err != nil {
if err == io.EOF {
return nil
}
return err
}
}
}
func buildCFB8Register(dst, reg []byte, head int) {
first := len(reg) - head
copy(dst, reg[head:])
copy(dst[first:], reg[:head])
}
func advanceCFB8Register(reg []byte, head *int, feedback byte) {
reg[*head] = feedback
*head = *head + 1
if *head == len(reg) {
*head = 0
}
}

65
symm/chacha20.go Normal file
View File

@ -0,0 +1,65 @@
package symm
import (
"crypto/cipher"
"errors"
"io"
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/chacha20poly1305"
)
var ErrInvalidChaCha20NonceLength = errors.New("chacha20 nonce length must be 12 or 24 bytes")
func EncryptChaCha20(data, key, nonce []byte) ([]byte, error) {
stream, err := chacha20.NewUnauthenticatedCipher(key, nonce)
if err != nil {
return nil, err
}
out := make([]byte, len(data))
stream.XORKeyStream(out, data)
return out, nil
}
func DecryptChaCha20(src, key, nonce []byte) ([]byte, error) {
return EncryptChaCha20(src, key, nonce)
}
func EncryptChaCha20Stream(dst io.Writer, src io.Reader, key, nonce []byte) error {
stream, err := chacha20.NewUnauthenticatedCipher(key, nonce)
if err != nil {
return err
}
return xorStreamCopy(dst, src, stream)
}
func DecryptChaCha20Stream(dst io.Writer, src io.Reader, key, nonce []byte) error {
return EncryptChaCha20Stream(dst, src, key, nonce)
}
func EncryptChaCha20Poly1305(plain, key, nonce, aad []byte) ([]byte, error) {
aead, err := newChaCha20AEAD(key, nonce)
if err != nil {
return nil, err
}
return aead.Seal(nil, nonce, plain, aad), nil
}
func DecryptChaCha20Poly1305(ciphertext, key, nonce, aad []byte) ([]byte, error) {
aead, err := newChaCha20AEAD(key, nonce)
if err != nil {
return nil, err
}
return aead.Open(nil, nonce, ciphertext, aad)
}
func newChaCha20AEAD(key, nonce []byte) (cipher.AEAD, error) {
switch len(nonce) {
case chacha20poly1305.NonceSize:
return chacha20poly1305.New(key)
case chacha20poly1305.NonceSizeX:
return chacha20poly1305.NewX(key)
default:
return nil, ErrInvalidChaCha20NonceLength
}
}

223
symm/cipher_common.go Normal file
View File

@ -0,0 +1,223 @@
package symm
import (
"crypto/cipher"
"io"
"b612.me/starcrypto/ccm"
)
type blockCipherFactory func(key []byte) (cipher.Block, error)
type aeadFactory func(key []byte) (cipher.AEAD, error)
type aeadBytesFunc func(data, key, nonce, aad []byte) ([]byte, error)
type aeadStreamFunc func(dst io.Writer, src io.Reader, key, nonce, aad []byte) error
type blockModeBytesFunc func(data, key, iv []byte, mode, paddingType string) ([]byte, error)
type blockModeStreamFunc func(dst io.Writer, src io.Reader, key, iv []byte, mode, paddingType string) error
type aeadChunkEncryptFunc func(aead cipher.AEAD, plain, nonce, aad []byte, chunkIndex uint64) []byte
type aeadChunkDecryptFunc func(aead cipher.AEAD, ciphertext, nonce, aad []byte, chunkIndex uint64) ([]byte, error)
type aeadStreamCodec func(dst io.Writer, src io.Reader, aead cipher.AEAD, nonce, aad []byte) error
func newGCMFactory(newBlock blockCipherFactory) aeadFactory {
return func(key []byte) (cipher.AEAD, error) {
block, err := newBlock(key)
if err != nil {
return nil, err
}
return cipher.NewGCM(block)
}
}
func newCCMFactory(newBlock blockCipherFactory) aeadFactory {
return func(key []byte) (cipher.AEAD, error) {
block, err := newBlock(key)
if err != nil {
return nil, err
}
return ccm.NewCCM(block, aeadCCMTagSize, aeadCCMNonceSize)
}
}
func normalizeModeOrDefaultAEAD(mode string) string {
mode = normalizeCipherMode(mode)
if mode == "" {
mode = MODEGCM
}
return mode
}
func encryptBlockCipher(data, key, iv []byte, mode, paddingType, defaultPadding string, newBlock blockCipherFactory, encryptGCM, encryptCCM aeadBytesFunc) ([]byte, error) {
mode = normalizeModeOrDefaultAEAD(mode)
switch mode {
case MODEGCM:
return encryptGCM(data, key, iv, nil)
case MODECCM:
return encryptCCM(data, key, iv, nil)
default:
block, err := newBlock(key)
if err != nil {
return nil, err
}
return encryptWithBlockMode(block, data, iv, mode, paddingType, defaultPadding)
}
}
func decryptBlockCipher(src, key, iv []byte, mode, paddingType, defaultPadding string, newBlock blockCipherFactory, decryptGCM, decryptCCM aeadBytesFunc) ([]byte, error) {
mode = normalizeModeOrDefaultAEAD(mode)
switch mode {
case MODEGCM:
return decryptGCM(src, key, iv, nil)
case MODECCM:
return decryptCCM(src, key, iv, nil)
default:
block, err := newBlock(key)
if err != nil {
return nil, err
}
return decryptWithBlockMode(block, src, iv, mode, paddingType, defaultPadding)
}
}
func encryptBlockCipherStream(dst io.Writer, src io.Reader, key, iv []byte, mode, paddingType, defaultPadding string, newBlock blockCipherFactory, encryptGCMStream, encryptCCMStream aeadStreamFunc) error {
mode = normalizeModeOrDefaultAEAD(mode)
switch mode {
case MODEGCM:
return encryptGCMStream(dst, src, key, iv, nil)
case MODECCM:
return encryptCCMStream(dst, src, key, iv, nil)
default:
block, err := newBlock(key)
if err != nil {
return err
}
return encryptWithBlockModeStream(block, dst, src, iv, mode, paddingType, defaultPadding)
}
}
func decryptBlockCipherStream(dst io.Writer, src io.Reader, key, iv []byte, mode, paddingType, defaultPadding string, newBlock blockCipherFactory, decryptGCMStream, decryptCCMStream aeadStreamFunc) error {
mode = normalizeModeOrDefaultAEAD(mode)
switch mode {
case MODEGCM:
return decryptGCMStream(dst, src, key, iv, nil)
case MODECCM:
return decryptCCMStream(dst, src, key, iv, nil)
default:
block, err := newBlock(key)
if err != nil {
return err
}
return decryptWithBlockModeStream(block, dst, src, iv, mode, paddingType, defaultPadding)
}
}
func encryptBlockWithOptions(data, key []byte, opts *CipherOptions, encryptGCM, encryptCCM aeadBytesFunc, encryptBlock blockModeBytesFunc) ([]byte, error) {
cfg := normalizeCipherOptions(opts)
mode := normalizeModeOrDefaultAEAD(cfg.Mode)
switch mode {
case MODEGCM:
return encryptGCM(data, key, nonceFromOptions(cfg), cfg.AAD)
case MODECCM:
return encryptCCM(data, key, nonceFromOptions(cfg), cfg.AAD)
default:
return encryptBlock(data, key, cfg.IV, mode, cfg.Padding)
}
}
func decryptBlockWithOptions(data, key []byte, opts *CipherOptions, decryptGCM, decryptCCM aeadBytesFunc, decryptBlock blockModeBytesFunc) ([]byte, error) {
cfg := normalizeCipherOptions(opts)
mode := normalizeModeOrDefaultAEAD(cfg.Mode)
switch mode {
case MODEGCM:
return decryptGCM(data, key, nonceFromOptions(cfg), cfg.AAD)
case MODECCM:
return decryptCCM(data, key, nonceFromOptions(cfg), cfg.AAD)
default:
return decryptBlock(data, key, cfg.IV, mode, cfg.Padding)
}
}
func encryptBlockStreamWithOptions(dst io.Writer, src io.Reader, key []byte, opts *CipherOptions, encryptGCM, encryptCCM aeadStreamFunc, encryptBlockStream blockModeStreamFunc) error {
cfg := normalizeCipherOptions(opts)
mode := normalizeModeOrDefaultAEAD(cfg.Mode)
switch mode {
case MODEGCM:
return encryptGCM(dst, src, key, nonceFromOptions(cfg), cfg.AAD)
case MODECCM:
return encryptCCM(dst, src, key, nonceFromOptions(cfg), cfg.AAD)
default:
return encryptBlockStream(dst, src, key, cfg.IV, mode, cfg.Padding)
}
}
func decryptBlockStreamWithOptions(dst io.Writer, src io.Reader, key []byte, opts *CipherOptions, decryptGCM, decryptCCM aeadStreamFunc, decryptBlockStream blockModeStreamFunc) error {
cfg := normalizeCipherOptions(opts)
mode := normalizeModeOrDefaultAEAD(cfg.Mode)
switch mode {
case MODEGCM:
return decryptGCM(dst, src, key, nonceFromOptions(cfg), cfg.AAD)
case MODECCM:
return decryptCCM(dst, src, key, nonceFromOptions(cfg), cfg.AAD)
default:
return decryptBlockStream(dst, src, key, cfg.IV, mode, cfg.Padding)
}
}
func buildAEAD(factory aeadFactory, key, nonce []byte, errInvalidNonce error) (cipher.AEAD, error) {
aead, err := factory(key)
if err != nil {
return nil, err
}
if len(nonce) != aead.NonceSize() {
return nil, errInvalidNonce
}
return aead, nil
}
func encryptAEAD(factory aeadFactory, plain, key, nonce, aad []byte, errInvalidNonce error) ([]byte, error) {
aead, err := buildAEAD(factory, key, nonce, errInvalidNonce)
if err != nil {
return nil, err
}
return aead.Seal(nil, nonce, plain, aad), nil
}
func decryptAEAD(factory aeadFactory, ciphertext, key, nonce, aad []byte, errInvalidNonce error) ([]byte, error) {
aead, err := buildAEAD(factory, key, nonce, errInvalidNonce)
if err != nil {
return nil, err
}
return aead.Open(nil, nonce, ciphertext, aad)
}
func encryptAEADChunk(factory aeadFactory, plain, key, nonce, aad []byte, chunkIndex uint64, errInvalidNonce error, encryptChunk aeadChunkEncryptFunc) ([]byte, error) {
aead, err := buildAEAD(factory, key, nonce, errInvalidNonce)
if err != nil {
return nil, err
}
return encryptChunk(aead, plain, nonce, aad, chunkIndex), nil
}
func decryptAEADChunk(factory aeadFactory, ciphertext, key, nonce, aad []byte, chunkIndex uint64, errInvalidNonce error, decryptChunk aeadChunkDecryptFunc) ([]byte, error) {
aead, err := buildAEAD(factory, key, nonce, errInvalidNonce)
if err != nil {
return nil, err
}
return decryptChunk(aead, ciphertext, nonce, aad, chunkIndex)
}
func encryptAEADStream(factory aeadFactory, dst io.Writer, src io.Reader, key, nonce, aad []byte, errInvalidNonce error, encryptStream aeadStreamCodec) error {
aead, err := buildAEAD(factory, key, nonce, errInvalidNonce)
if err != nil {
return err
}
return encryptStream(dst, src, aead, nonce, aad)
}
func decryptAEADStream(factory aeadFactory, dst io.Writer, src io.Reader, key, nonce, aad []byte, errInvalidNonce error, decryptStream aeadStreamCodec) error {
aead, err := buildAEAD(factory, key, nonce, errInvalidNonce)
if err != nil {
return err
}
return decryptStream(dst, src, aead, nonce, aad)
}

56
symm/ctr_seek.go Normal file
View File

@ -0,0 +1,56 @@
package symm
import (
"crypto/cipher"
"errors"
)
var (
ErrInvalidCTROffset = errors.New("ctr offset must be non-negative")
ErrCTRCounterOverflow = errors.New("ctr counter overflow")
)
func xorCTRAtOffset(block cipher.Block, data, iv []byte, offset int64) ([]byte, error) {
if offset < 0 {
return nil, ErrInvalidCTROffset
}
if len(iv) != block.BlockSize() {
return nil, errors.New("iv length must match block size")
}
counter := make([]byte, len(iv))
copy(counter, iv)
blockSize := int64(block.BlockSize())
blockOffset := uint64(offset / blockSize)
byteOffset := int(offset % blockSize)
if err := addUint64ToCounter(counter, blockOffset); err != nil {
return nil, err
}
stream := cipher.NewCTR(block, counter)
if byteOffset > 0 {
skip := make([]byte, byteOffset)
stream.XORKeyStream(skip, skip)
}
out := make([]byte, len(data))
stream.XORKeyStream(out, data)
return out, nil
}
func addUint64ToCounter(counter []byte, inc uint64) error {
if inc == 0 {
return nil
}
for i := len(counter) - 1; i >= 0 && inc > 0; i-- {
sum := uint64(counter[i]) + (inc & 0xff)
counter[i] = byte(sum)
inc = (inc >> 8) + (sum >> 8)
}
if inc > 0 {
return ErrCTRCounterOverflow
}
return nil
}

70
symm/des.go Normal file
View File

@ -0,0 +1,70 @@
package symm
import (
"crypto/des"
"io"
)
func EncryptDESCBC(data, key, iv []byte, paddingType string) ([]byte, error) {
block, err := des.NewCipher(key)
if err != nil {
return nil, err
}
return encryptWithBlockMode(block, data, iv, MODECBC, paddingType, PKCS5PADDING)
}
func DecryptDESCBC(src, key, iv []byte, paddingType string) ([]byte, error) {
block, err := des.NewCipher(key)
if err != nil {
return nil, err
}
return decryptWithBlockMode(block, src, iv, MODECBC, paddingType, PKCS5PADDING)
}
func EncryptDESCBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
block, err := des.NewCipher(key)
if err != nil {
return err
}
return encryptWithBlockModeStream(block, dst, src, iv, MODECBC, paddingType, PKCS5PADDING)
}
func DecryptDESCBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
block, err := des.NewCipher(key)
if err != nil {
return err
}
return decryptWithBlockModeStream(block, dst, src, iv, MODECBC, paddingType, PKCS5PADDING)
}
func Encrypt3DESCBC(data, key, iv []byte, paddingType string) ([]byte, error) {
block, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
return encryptWithBlockMode(block, data, iv, MODECBC, paddingType, PKCS5PADDING)
}
func Decrypt3DESCBC(src, key, iv []byte, paddingType string) ([]byte, error) {
block, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
return decryptWithBlockMode(block, src, iv, MODECBC, paddingType, PKCS5PADDING)
}
func Encrypt3DESCBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
block, err := des.NewTripleDESCipher(key)
if err != nil {
return err
}
return encryptWithBlockModeStream(block, dst, src, iv, MODECBC, paddingType, PKCS5PADDING)
}
func Decrypt3DESCBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
block, err := des.NewTripleDESCipher(key)
if err != nil {
return err
}
return decryptWithBlockModeStream(block, dst, src, iv, MODECBC, paddingType, PKCS5PADDING)
}

191
symm/fuzz_test.go Normal file
View File

@ -0,0 +1,191 @@
package symm
import (
"bytes"
"testing"
)
func FuzzAesCBCRoundTrip(f *testing.F) {
f.Add([]byte("fuzz-aes-cbc"))
f.Add([]byte{})
key := []byte("0123456789abcdef")
iv := []byte("abcdef9876543210")
f.Fuzz(func(t *testing.T, data []byte) {
enc, err := EncryptAesCBC(data, key, iv, "")
if err != nil {
t.Fatalf("EncryptAesCBC failed: %v", err)
}
dec, err := DecryptAesCBC(enc, key, iv, "")
if err != nil {
t.Fatalf("DecryptAesCBC failed: %v", err)
}
if !bytes.Equal(dec, data) {
t.Fatalf("aes cbc fuzz roundtrip mismatch")
}
})
}
func FuzzChaCha20RoundTrip(f *testing.F) {
f.Add([]byte("fuzz-chacha20"))
f.Add([]byte{})
key := []byte("0123456789abcdef0123456789abcdef")
nonce := []byte("123456789012")
f.Fuzz(func(t *testing.T, data []byte) {
enc, err := EncryptChaCha20(data, key, nonce)
if err != nil {
t.Fatalf("EncryptChaCha20 failed: %v", err)
}
dec, err := DecryptChaCha20(enc, key, nonce)
if err != nil {
t.Fatalf("DecryptChaCha20 failed: %v", err)
}
if !bytes.Equal(dec, data) {
t.Fatalf("chacha20 fuzz roundtrip mismatch")
}
})
}
func FuzzAesCBCStreamRoundTrip(f *testing.F) {
f.Add([]byte("fuzz-aes-stream"))
f.Add([]byte{})
key := []byte("0123456789abcdef")
iv := []byte("abcdef9876543210")
f.Fuzz(func(t *testing.T, data []byte) {
enc := &bytes.Buffer{}
dec := &bytes.Buffer{}
if err := EncryptAesCBCStream(enc, bytes.NewReader(data), key, iv, ""); err != nil {
t.Fatalf("EncryptAesCBCStream failed: %v", err)
}
if err := DecryptAesCBCStream(dec, bytes.NewReader(enc.Bytes()), key, iv, ""); err != nil {
t.Fatalf("DecryptAesCBCStream failed: %v", err)
}
if !bytes.Equal(dec.Bytes(), data) {
t.Fatalf("aes cbc stream fuzz roundtrip mismatch")
}
})
}
func xtsFuzzDataUnitSize(selector uint8) int {
sizes := [...]int{16, 32, 64, 128, 256, 512}
return sizes[int(selector)%len(sizes)]
}
func xtsFuzzNormalizeData(data []byte, maxLen int) []byte {
if len(data) > maxLen {
data = data[:maxLen]
}
return data[:len(data)/16*16]
}
func FuzzAesXTSRoundTrip(f *testing.F) {
f.Add([]byte("fuzz-aes-xts-seed-0000"), uint64(0), uint8(0))
f.Add(bytes.Repeat([]byte{0x42}, 65), uint64(7), uint8(3))
key1 := []byte("0123456789abcdef")
key2 := []byte("fedcba9876543210")
f.Fuzz(func(t *testing.T, data []byte, dataUnitIndex uint64, selector uint8) {
bounded := data
if len(bounded) > 4097 {
bounded = bounded[:4097]
}
plain := xtsFuzzNormalizeData(bounded, 4096)
dataUnitSize := xtsFuzzDataUnitSize(selector)
enc, err := EncryptAesXTSAt(plain, key1, key2, dataUnitSize, dataUnitIndex)
if err != nil {
t.Fatalf("EncryptAesXTSAt failed: %v", err)
}
dec, err := DecryptAesXTSAt(enc, key1, key2, dataUnitSize, dataUnitIndex)
if err != nil {
t.Fatalf("DecryptAesXTSAt failed: %v", err)
}
if !bytes.Equal(dec, plain) {
t.Fatalf("aes xts roundtrip mismatch")
}
encStream := &bytes.Buffer{}
if err := EncryptAesXTSStreamAt(encStream, bytes.NewReader(plain), key1, key2, dataUnitSize, dataUnitIndex); err != nil {
t.Fatalf("EncryptAesXTSStreamAt failed: %v", err)
}
if !bytes.Equal(encStream.Bytes(), enc) {
t.Fatalf("aes xts bytes/stream encrypt mismatch")
}
decStream := &bytes.Buffer{}
if err := DecryptAesXTSStreamAt(decStream, bytes.NewReader(enc), key1, key2, dataUnitSize, dataUnitIndex); err != nil {
t.Fatalf("DecryptAesXTSStreamAt failed: %v", err)
}
if !bytes.Equal(decStream.Bytes(), plain) {
t.Fatalf("aes xts stream decrypt mismatch")
}
if len(bounded)%16 != 0 {
if _, err := EncryptAesXTSAt(bounded, key1, key2, dataUnitSize, dataUnitIndex); err == nil {
t.Fatalf("expected aes xts bytes error for non-block-aligned input")
}
if err := EncryptAesXTSStreamAt(&bytes.Buffer{}, bytes.NewReader(bounded), key1, key2, dataUnitSize, dataUnitIndex); err == nil {
t.Fatalf("expected aes xts stream error for non-block-aligned input")
}
}
})
}
func FuzzSM4XTSRoundTrip(f *testing.F) {
f.Add([]byte("fuzz-sm4-xts-seed-0000"), uint64(0), uint8(0))
f.Add(bytes.Repeat([]byte{0x5a}, 79), uint64(11), uint8(4))
key1 := []byte("0123456789abcdef")
key2 := []byte("fedcba9876543210")
f.Fuzz(func(t *testing.T, data []byte, dataUnitIndex uint64, selector uint8) {
bounded := data
if len(bounded) > 4097 {
bounded = bounded[:4097]
}
plain := xtsFuzzNormalizeData(bounded, 4096)
dataUnitSize := xtsFuzzDataUnitSize(selector)
enc, err := EncryptSM4XTSAt(plain, key1, key2, dataUnitSize, dataUnitIndex)
if err != nil {
t.Fatalf("EncryptSM4XTSAt failed: %v", err)
}
dec, err := DecryptSM4XTSAt(enc, key1, key2, dataUnitSize, dataUnitIndex)
if err != nil {
t.Fatalf("DecryptSM4XTSAt failed: %v", err)
}
if !bytes.Equal(dec, plain) {
t.Fatalf("sm4 xts roundtrip mismatch")
}
encStream := &bytes.Buffer{}
if err := EncryptSM4XTSStreamAt(encStream, bytes.NewReader(plain), key1, key2, dataUnitSize, dataUnitIndex); err != nil {
t.Fatalf("EncryptSM4XTSStreamAt failed: %v", err)
}
if !bytes.Equal(encStream.Bytes(), enc) {
t.Fatalf("sm4 xts bytes/stream encrypt mismatch")
}
decStream := &bytes.Buffer{}
if err := DecryptSM4XTSStreamAt(decStream, bytes.NewReader(enc), key1, key2, dataUnitSize, dataUnitIndex); err != nil {
t.Fatalf("DecryptSM4XTSStreamAt failed: %v", err)
}
if !bytes.Equal(decStream.Bytes(), plain) {
t.Fatalf("sm4 xts stream decrypt mismatch")
}
if len(bounded)%16 != 0 {
if _, err := EncryptSM4XTSAt(bounded, key1, key2, dataUnitSize, dataUnitIndex); err == nil {
t.Fatalf("expected sm4 xts bytes error for non-block-aligned input")
}
if err := EncryptSM4XTSStreamAt(&bytes.Buffer{}, bytes.NewReader(bounded), key1, key2, dataUnitSize, dataUnitIndex); err == nil {
t.Fatalf("expected sm4 xts stream error for non-block-aligned input")
}
}
})
}

146
symm/gcm_stream.go Normal file
View File

@ -0,0 +1,146 @@
package symm
import (
"bytes"
"crypto/cipher"
"encoding/binary"
"errors"
"io"
)
const (
gcmStreamMagic = "SCG1"
gcmStreamChunkSize = 32 * 1024
)
var ErrInvalidGCMStreamChunk = errors.New("invalid gcm stream chunk")
func encryptGCMChunk(aead cipher.AEAD, plain, nonce, aad []byte, chunkIndex uint64) []byte {
chunkNonce := deriveChunkNonce(nonce, chunkIndex)
return aead.Seal(nil, chunkNonce, plain, aad)
}
func decryptGCMChunk(aead cipher.AEAD, ciphertext, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
chunkNonce := deriveChunkNonce(nonce, chunkIndex)
return aead.Open(nil, chunkNonce, ciphertext, aad)
}
func encryptGCMChunkedStream(dst io.Writer, src io.Reader, aead cipher.AEAD, nonce, aad []byte) error {
if _, err := dst.Write([]byte(gcmStreamMagic)); err != nil {
return err
}
buf := make([]byte, gcmStreamChunkSize)
lenBuf := make([]byte, 4)
var chunkIndex uint64
for {
n, err := src.Read(buf)
if n > 0 {
sealed := encryptGCMChunk(aead, buf[:n], nonce, aad, chunkIndex)
binary.BigEndian.PutUint32(lenBuf, uint32(len(sealed)))
if _, werr := dst.Write(lenBuf); werr != nil {
return werr
}
if _, werr := dst.Write(sealed); werr != nil {
return werr
}
chunkIndex++
}
if err != nil {
if err == io.EOF {
return nil
}
return err
}
}
}
func decryptGCMChunkedOrLegacyStream(dst io.Writer, src io.Reader, aead cipher.AEAD, nonce, aad []byte) error {
header := make([]byte, len(gcmStreamMagic))
n, err := io.ReadFull(src, header)
if err != nil {
if err == io.EOF {
return nil
}
if err != io.ErrUnexpectedEOF {
return err
}
return decryptGCMLegacyBuffered(dst, io.MultiReader(bytes.NewReader(header[:n]), src), aead, nonce, aad)
}
if string(header) != gcmStreamMagic {
return decryptGCMLegacyBuffered(dst, io.MultiReader(bytes.NewReader(header), src), aead, nonce, aad)
}
return decryptGCMChunkedStream(dst, src, aead, nonce, aad)
}
func decryptGCMChunkedStream(dst io.Writer, src io.Reader, aead cipher.AEAD, nonce, aad []byte) error {
lenBuf := make([]byte, 4)
maxChunkLen := uint32(gcmStreamChunkSize + aead.Overhead())
var chunkIndex uint64
for {
_, err := io.ReadFull(src, lenBuf)
if err != nil {
if err == io.EOF {
return nil
}
if err == io.ErrUnexpectedEOF {
return io.ErrUnexpectedEOF
}
return err
}
chunkLen := binary.BigEndian.Uint32(lenBuf)
if chunkLen < uint32(aead.Overhead()) || chunkLen > maxChunkLen {
return ErrInvalidGCMStreamChunk
}
chunk := make([]byte, chunkLen)
if _, err := io.ReadFull(src, chunk); err != nil {
if err == io.ErrUnexpectedEOF {
return io.ErrUnexpectedEOF
}
return err
}
plain, err := decryptGCMChunk(aead, chunk, nonce, aad, chunkIndex)
if err != nil {
return err
}
if _, err := dst.Write(plain); err != nil {
return err
}
chunkIndex++
}
}
func decryptGCMLegacyBuffered(dst io.Writer, src io.Reader, aead cipher.AEAD, nonce, aad []byte) error {
enc, err := io.ReadAll(src)
if err != nil {
return err
}
plain, err := aead.Open(nil, nonce, enc, aad)
if err != nil {
return err
}
_, err = dst.Write(plain)
return err
}
func deriveChunkNonce(baseNonce []byte, chunkIndex uint64) []byte {
nonce := make([]byte, len(baseNonce))
copy(nonce, baseNonce)
if len(nonce) < 8 {
return nonce
}
var indexBytes [8]byte
binary.BigEndian.PutUint64(indexBytes[:], chunkIndex)
off := len(nonce) - 8
for i := 0; i < 8; i++ {
nonce[off+i] ^= indexBytes[i]
}
return nonce
}

326
symm/mode.go Normal file
View File

@ -0,0 +1,326 @@
package symm
import (
"crypto/cipher"
"errors"
"io"
"strings"
"b612.me/starcrypto/paddingx"
)
const (
MODEECB = "ECB"
MODECBC = "CBC"
MODECFB = "CFB"
MODEOFB = "OFB"
MODECTR = "CTR"
MODEGCM = "GCM"
MODECCM = "CCM"
)
var ErrUnsupportedCipherMode = errors.New("cipher mode not supported")
func normalizeCipherMode(mode string) string {
return strings.ToUpper(strings.TrimSpace(mode))
}
func encryptWithBlockMode(block cipher.Block, data, iv []byte, mode, paddingType, defaultPadding string) ([]byte, error) {
mode = normalizeCipherMode(mode)
if mode == "" {
mode = MODECBC
}
switch mode {
case MODEECB:
if paddingType == "" {
paddingType = defaultPadding
}
content, err := paddingx.Pad(data, block.BlockSize(), paddingType)
if err != nil {
return nil, err
}
out := make([]byte, len(content))
ecbEncryptBlocks(block, out, content)
return out, nil
case MODECBC:
if len(iv) != block.BlockSize() {
return nil, errors.New("iv length must match block size")
}
if paddingType == "" {
paddingType = defaultPadding
}
content, err := paddingx.Pad(data, block.BlockSize(), paddingType)
if err != nil {
return nil, err
}
out := make([]byte, len(content))
cipher.NewCBCEncrypter(block, iv).CryptBlocks(out, content)
return out, nil
case MODECFB, MODEOFB, MODECTR:
if len(iv) != block.BlockSize() {
return nil, errors.New("iv length must match block size")
}
stream, err := newCipherStream(block, iv, mode, false)
if err != nil {
return nil, err
}
out := make([]byte, len(data))
stream.XORKeyStream(out, data)
return out, nil
default:
return nil, ErrUnsupportedCipherMode
}
}
func decryptWithBlockMode(block cipher.Block, src, iv []byte, mode, paddingType, defaultPadding string) ([]byte, error) {
mode = normalizeCipherMode(mode)
if mode == "" {
mode = MODECBC
}
switch mode {
case MODEECB:
if len(src) == 0 || len(src)%block.BlockSize() != 0 {
return nil, errors.New("ciphertext is not a full block size")
}
if paddingType == "" {
paddingType = defaultPadding
}
decrypted := make([]byte, len(src))
ecbDecryptBlocks(block, decrypted, src)
return paddingx.Unpad(decrypted, block.BlockSize(), paddingType)
case MODECBC:
if len(iv) != block.BlockSize() {
return nil, errors.New("iv length must match block size")
}
if len(src) == 0 || len(src)%block.BlockSize() != 0 {
return nil, errors.New("ciphertext is not a full block size")
}
if paddingType == "" {
paddingType = defaultPadding
}
decrypted := make([]byte, len(src))
cipher.NewCBCDecrypter(block, iv).CryptBlocks(decrypted, src)
return paddingx.Unpad(decrypted, block.BlockSize(), paddingType)
case MODECFB, MODEOFB, MODECTR:
if len(iv) != block.BlockSize() {
return nil, errors.New("iv length must match block size")
}
stream, err := newCipherStream(block, iv, mode, true)
if err != nil {
return nil, err
}
out := make([]byte, len(src))
stream.XORKeyStream(out, src)
return out, nil
default:
return nil, ErrUnsupportedCipherMode
}
}
func encryptWithBlockModeStream(block cipher.Block, dst io.Writer, src io.Reader, iv []byte, mode, paddingType, defaultPadding string) error {
mode = normalizeCipherMode(mode)
if mode == "" {
mode = MODECBC
}
switch mode {
case MODEECB:
if paddingType == "" {
paddingType = defaultPadding
}
return encryptPaddedBlockStream(dst, src, block.BlockSize(), paddingType, func(out, in []byte) {
ecbEncryptBlocks(block, out, in)
})
case MODECBC:
if len(iv) != block.BlockSize() {
return errors.New("iv length must match block size")
}
if paddingType == "" {
paddingType = defaultPadding
}
modeEnc := cipher.NewCBCEncrypter(block, iv)
return encryptPaddedBlockStream(dst, src, block.BlockSize(), paddingType, modeEnc.CryptBlocks)
case MODECFB, MODEOFB, MODECTR:
if len(iv) != block.BlockSize() {
return errors.New("iv length must match block size")
}
stream, err := newCipherStream(block, iv, mode, false)
if err != nil {
return err
}
return xorStreamCopy(dst, src, stream)
default:
return ErrUnsupportedCipherMode
}
}
func decryptWithBlockModeStream(block cipher.Block, dst io.Writer, src io.Reader, iv []byte, mode, paddingType, defaultPadding string) error {
mode = normalizeCipherMode(mode)
if mode == "" {
mode = MODECBC
}
switch mode {
case MODEECB:
if paddingType == "" {
paddingType = defaultPadding
}
return decryptPaddedBlockStream(dst, src, block.BlockSize(), paddingType, func(out, in []byte) {
ecbDecryptBlocks(block, out, in)
})
case MODECBC:
if len(iv) != block.BlockSize() {
return errors.New("iv length must match block size")
}
if paddingType == "" {
paddingType = defaultPadding
}
modeDec := cipher.NewCBCDecrypter(block, iv)
return decryptPaddedBlockStream(dst, src, block.BlockSize(), paddingType, modeDec.CryptBlocks)
case MODECFB, MODEOFB, MODECTR:
if len(iv) != block.BlockSize() {
return errors.New("iv length must match block size")
}
stream, err := newCipherStream(block, iv, mode, true)
if err != nil {
return err
}
return xorStreamCopy(dst, src, stream)
default:
return ErrUnsupportedCipherMode
}
}
func newCipherStream(block cipher.Block, iv []byte, mode string, decrypt bool) (cipher.Stream, error) {
switch mode {
case MODECFB:
if decrypt {
return cipher.NewCFBDecrypter(block, iv), nil
}
return cipher.NewCFBEncrypter(block, iv), nil
case MODEOFB:
return cipher.NewOFB(block, iv), nil
case MODECTR:
return cipher.NewCTR(block, iv), nil
default:
return nil, ErrUnsupportedCipherMode
}
}
func xorStreamCopy(dst io.Writer, src io.Reader, stream cipher.Stream) error {
buf := make([]byte, 32*1024)
out := make([]byte, 32*1024)
for {
n, err := src.Read(buf)
if n > 0 {
stream.XORKeyStream(out[:n], buf[:n])
if _, werr := dst.Write(out[:n]); werr != nil {
return werr
}
}
if err != nil {
if err == io.EOF {
return nil
}
return err
}
}
}
func encryptPaddedBlockStream(dst io.Writer, src io.Reader, blockSize int, paddingType string, cryptBlocks func(dst, src []byte)) error {
pending := make([]byte, 0, blockSize*2)
buf := make([]byte, 32*1024)
for {
n, err := src.Read(buf)
if n > 0 {
pending = append(pending, buf[:n]...)
processLen := len(pending) - blockSize
if processLen > 0 {
processLen -= processLen % blockSize
if processLen > 0 {
out := make([]byte, processLen)
cryptBlocks(out, pending[:processLen])
if _, werr := dst.Write(out); werr != nil {
return werr
}
pending = append([]byte(nil), pending[processLen:]...)
}
}
}
if err != nil {
if err == io.EOF {
break
}
return err
}
}
content, err := paddingx.Pad(pending, blockSize, paddingType)
if err != nil {
return err
}
out := make([]byte, len(content))
cryptBlocks(out, content)
_, err = dst.Write(out)
return err
}
func decryptPaddedBlockStream(dst io.Writer, src io.Reader, blockSize int, paddingType string, cryptBlocks func(dst, src []byte)) error {
pending := make([]byte, 0, blockSize*2)
buf := make([]byte, 32*1024)
for {
n, err := src.Read(buf)
if n > 0 {
pending = append(pending, buf[:n]...)
processLen := len(pending) - blockSize
if processLen > 0 {
processLen -= processLen % blockSize
if processLen > 0 {
out := make([]byte, processLen)
cryptBlocks(out, pending[:processLen])
if _, werr := dst.Write(out); werr != nil {
return werr
}
pending = append([]byte(nil), pending[processLen:]...)
}
}
}
if err != nil {
if err == io.EOF {
break
}
return err
}
}
if len(pending) == 0 || len(pending)%blockSize != 0 {
return errors.New("ciphertext is not a full block size")
}
decrypted := make([]byte, len(pending))
cryptBlocks(decrypted, pending)
out, err := paddingx.Unpad(decrypted, blockSize, paddingType)
if err != nil {
return err
}
_, err = dst.Write(out)
return err
}
func ecbEncryptBlocks(block cipher.Block, dst, src []byte) {
blockSize := block.BlockSize()
for i := 0; i < len(src); i += blockSize {
block.Encrypt(dst[i:i+blockSize], src[i:i+blockSize])
}
}
func ecbDecryptBlocks(block cipher.Block, dst, src []byte) {
blockSize := block.BlockSize()
for i := 0; i < len(src); i += blockSize {
block.Decrypt(dst[i:i+blockSize], src[i:i+blockSize])
}
}

22
symm/options.go Normal file
View File

@ -0,0 +1,22 @@
package symm
// CipherOptions provides a unified configuration for symmetric APIs.
// For AEAD modes (GCM/CCM), Nonce must be set explicitly.
type CipherOptions struct {
Mode string
Padding string
IV []byte
Nonce []byte
AAD []byte
}
func normalizeCipherOptions(opts *CipherOptions) CipherOptions {
if opts == nil {
return CipherOptions{}
}
return *opts
}
func nonceFromOptions(opts CipherOptions) []byte {
return opts.Nonce
}

View File

@ -0,0 +1,28 @@
package symm
import (
"errors"
"testing"
)
func TestAEADOptionsRequireNonce(t *testing.T) {
aesKey := []byte("0123456789abcdef")
sm4Key := []byte("0123456789abcdef")
plain := []byte("nonce-required")
gcmIVOnly := &CipherOptions{Mode: MODEGCM, IV: []byte("123456789012")}
if _, err := EncryptAesWithOptions(plain, aesKey, gcmIVOnly); !errors.Is(err, ErrInvalidGCMNonceLength) {
t.Fatalf("expected ErrInvalidGCMNonceLength for AES GCM with IV-only opts, got: %v", err)
}
if _, err := EncryptSM4WithOptions(plain, sm4Key, gcmIVOnly); !errors.Is(err, ErrInvalidGCMNonceLength) {
t.Fatalf("expected ErrInvalidGCMNonceLength for SM4 GCM with IV-only opts, got: %v", err)
}
ccmIVOnly := &CipherOptions{Mode: MODECCM, IV: []byte("123456789012")}
if _, err := EncryptAesWithOptions(plain, aesKey, ccmIVOnly); !errors.Is(err, ErrInvalidCCMNonceLength) {
t.Fatalf("expected ErrInvalidCCMNonceLength for AES CCM with IV-only opts, got: %v", err)
}
if _, err := EncryptSM4WithOptions(plain, sm4Key, ccmIVOnly); !errors.Is(err, ErrInvalidCCMNonceLength) {
t.Fatalf("expected ErrInvalidCCMNonceLength for SM4 CCM with IV-only opts, got: %v", err)
}
}

54
symm/segment_decrypt.go Normal file
View File

@ -0,0 +1,54 @@
package symm
import (
"crypto/cipher"
"errors"
)
var (
ErrSegmentNotFullBlocks = errors.New("ciphertext segment is not a full block size")
)
func decryptECBBlocks(block cipher.Block, src []byte) ([]byte, error) {
if len(src) == 0 {
return []byte{}, nil
}
if len(src)%block.BlockSize() != 0 {
return nil, ErrSegmentNotFullBlocks
}
out := make([]byte, len(src))
ecbDecryptBlocks(block, out, src)
return out, nil
}
// decryptCBCFromSecondBlock decrypts a CBC ciphertext segment that starts from the second block (or later).
// prevCipherBlock must be the previous ciphertext block (C[i-1]); for i=1 this is the original IV.
func decryptCBCFromSecondBlock(block cipher.Block, src, prevCipherBlock []byte) ([]byte, error) {
if len(src) == 0 {
return []byte{}, nil
}
if len(prevCipherBlock) != block.BlockSize() {
return nil, errors.New("prev cipher block length must match block size")
}
if len(src)%block.BlockSize() != 0 {
return nil, ErrSegmentNotFullBlocks
}
out := make([]byte, len(src))
cipher.NewCBCDecrypter(block, prevCipherBlock).CryptBlocks(out, src)
return out, nil
}
// decryptCFBFromSecondBlock decrypts a CFB ciphertext segment that starts from the second block (or later).
// prevCipherBlock must be the previous ciphertext block (C[i-1]); for i=1 this is the original IV.
func decryptCFBFromSecondBlock(block cipher.Block, src, prevCipherBlock []byte) ([]byte, error) {
if len(src) == 0 {
return []byte{}, nil
}
if len(prevCipherBlock) != block.BlockSize() {
return nil, errors.New("prev cipher block length must match block size")
}
stream := cipher.NewCFBDecrypter(block, prevCipherBlock)
out := make([]byte, len(src))
stream.XORKeyStream(out, src)
return out, nil
}

275
symm/sm4.go Normal file
View File

@ -0,0 +1,275 @@
package symm
import (
"crypto/rand"
"errors"
"io"
"github.com/emmansun/gmsm/sm4"
)
var (
sm4GCMFactory = newGCMFactory(sm4.NewCipher)
sm4CCMFactory = newCCMFactory(sm4.NewCipher)
)
func EncryptSM4(data, key, iv []byte, mode, paddingType string) ([]byte, error) {
return encryptBlockCipher(data, key, iv, mode, paddingType, PKCS7PADDING, sm4.NewCipher, EncryptSM4GCM, EncryptSM4CCM)
}
func DecryptSM4(src, key, iv []byte, mode, paddingType string) ([]byte, error) {
return decryptBlockCipher(src, key, iv, mode, paddingType, PKCS7PADDING, sm4.NewCipher, DecryptSM4GCM, DecryptSM4CCM)
}
func EncryptSM4Stream(dst io.Writer, src io.Reader, key, iv []byte, mode, paddingType string) error {
return encryptBlockCipherStream(dst, src, key, iv, mode, paddingType, PKCS7PADDING, sm4.NewCipher, EncryptSM4GCMStream, EncryptSM4CCMStream)
}
func DecryptSM4Stream(dst io.Writer, src io.Reader, key, iv []byte, mode, paddingType string) error {
return decryptBlockCipherStream(dst, src, key, iv, mode, paddingType, PKCS7PADDING, sm4.NewCipher, DecryptSM4GCMStream, DecryptSM4CCMStream)
}
func EncryptSM4WithOptions(data, key []byte, opts *CipherOptions) ([]byte, error) {
return encryptBlockWithOptions(data, key, opts, EncryptSM4GCM, EncryptSM4CCM, EncryptSM4)
}
func DecryptSM4WithOptions(src, key []byte, opts *CipherOptions) ([]byte, error) {
return decryptBlockWithOptions(src, key, opts, DecryptSM4GCM, DecryptSM4CCM, DecryptSM4)
}
func EncryptSM4StreamWithOptions(dst io.Writer, src io.Reader, key []byte, opts *CipherOptions) error {
return encryptBlockStreamWithOptions(dst, src, key, opts, EncryptSM4GCMStream, EncryptSM4CCMStream, EncryptSM4Stream)
}
func DecryptSM4StreamWithOptions(dst io.Writer, src io.Reader, key []byte, opts *CipherOptions) error {
return decryptBlockStreamWithOptions(dst, src, key, opts, DecryptSM4GCMStream, DecryptSM4CCMStream, DecryptSM4Stream)
}
func EncryptSM4GCM(plain, key, nonce, aad []byte) ([]byte, error) {
return encryptAEAD(sm4GCMFactory, plain, key, nonce, aad, ErrInvalidGCMNonceLength)
}
func DecryptSM4GCM(ciphertext, key, nonce, aad []byte) ([]byte, error) {
return decryptAEAD(sm4GCMFactory, ciphertext, key, nonce, aad, ErrInvalidGCMNonceLength)
}
func EncryptSM4GCMChunk(plain, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return encryptAEADChunk(sm4GCMFactory, plain, key, nonce, aad, chunkIndex, ErrInvalidGCMNonceLength, encryptGCMChunk)
}
func DecryptSM4GCMChunk(ciphertext, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return decryptAEADChunk(sm4GCMFactory, ciphertext, key, nonce, aad, chunkIndex, ErrInvalidGCMNonceLength, decryptGCMChunk)
}
func EncryptSM4GCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return encryptAEADStream(sm4GCMFactory, dst, src, key, nonce, aad, ErrInvalidGCMNonceLength, encryptGCMChunkedStream)
}
func DecryptSM4GCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return decryptAEADStream(sm4GCMFactory, dst, src, key, nonce, aad, ErrInvalidGCMNonceLength, decryptGCMChunkedOrLegacyStream)
}
func EncryptSM4CCM(plain, key, nonce, aad []byte) ([]byte, error) {
return encryptAEAD(sm4CCMFactory, plain, key, nonce, aad, ErrInvalidCCMNonceLength)
}
func DecryptSM4CCM(ciphertext, key, nonce, aad []byte) ([]byte, error) {
return decryptAEAD(sm4CCMFactory, ciphertext, key, nonce, aad, ErrInvalidCCMNonceLength)
}
func EncryptSM4CCMChunk(plain, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return encryptAEADChunk(sm4CCMFactory, plain, key, nonce, aad, chunkIndex, ErrInvalidCCMNonceLength, encryptCCMChunk)
}
func DecryptSM4CCMChunk(ciphertext, key, nonce, aad []byte, chunkIndex uint64) ([]byte, error) {
return decryptAEADChunk(sm4CCMFactory, ciphertext, key, nonce, aad, chunkIndex, ErrInvalidCCMNonceLength, decryptCCMChunk)
}
func EncryptSM4CCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return encryptAEADStream(sm4CCMFactory, dst, src, key, nonce, aad, ErrInvalidCCMNonceLength, encryptCCMChunkedStream)
}
func DecryptSM4CCMStream(dst io.Writer, src io.Reader, key, nonce, aad []byte) error {
return decryptAEADStream(sm4CCMFactory, dst, src, key, nonce, aad, ErrInvalidCCMNonceLength, decryptCCMChunkedOrLegacyStream)
}
func EncryptSM4CFB(origData, key []byte) ([]byte, error) {
block, err := sm4.NewCipher(key)
if err != nil {
return nil, err
}
out := make([]byte, block.BlockSize()+len(origData))
iv := out[:block.BlockSize()]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
body, err := EncryptSM4CFBNoBlock(origData, key, iv)
if err != nil {
return nil, err
}
copy(out[block.BlockSize():], body)
return out, nil
}
func DecryptSM4CFB(encrypted, key []byte) ([]byte, error) {
block, err := sm4.NewCipher(key)
if err != nil {
return nil, err
}
if len(encrypted) < block.BlockSize() {
return nil, errors.New("ciphertext too short")
}
iv := encrypted[:block.BlockSize()]
return DecryptSM4CFBNoBlock(encrypted[block.BlockSize():], key, iv)
}
func EncryptSM4CFBNoBlock(origData, key, iv []byte) ([]byte, error) {
return EncryptSM4(origData, key, iv, MODECFB, "")
}
func DecryptSM4CFBNoBlock(encrypted, key, iv []byte) ([]byte, error) {
return DecryptSM4(encrypted, key, iv, MODECFB, "")
}
func EncryptSM4ECB(data, key []byte, paddingType string) ([]byte, error) {
return EncryptSM4(data, key, nil, MODEECB, paddingType)
}
func DecryptSM4ECB(src, key []byte, paddingType string) ([]byte, error) {
return DecryptSM4(src, key, nil, MODEECB, paddingType)
}
func EncryptSM4CBC(data, key, iv []byte, paddingType string) ([]byte, error) {
return EncryptSM4(data, key, iv, MODECBC, paddingType)
}
func DecryptSM4CBC(src, key, iv []byte, paddingType string) ([]byte, error) {
return DecryptSM4(src, key, iv, MODECBC, paddingType)
}
func EncryptSM4OFB(data, key, iv []byte) ([]byte, error) {
return EncryptSM4(data, key, iv, MODEOFB, "")
}
func DecryptSM4OFB(src, key, iv []byte) ([]byte, error) {
return DecryptSM4(src, key, iv, MODEOFB, "")
}
func EncryptSM4CTR(data, key, iv []byte) ([]byte, error) {
return EncryptSM4(data, key, iv, MODECTR, "")
}
func DecryptSM4CTR(src, key, iv []byte) ([]byte, error) {
return DecryptSM4(src, key, iv, MODECTR, "")
}
func EncryptSM4CTRAt(data, key, iv []byte, offset int64) ([]byte, error) {
block, err := sm4.NewCipher(key)
if err != nil {
return nil, err
}
return xorCTRAtOffset(block, data, iv, offset)
}
func DecryptSM4CTRAt(src, key, iv []byte, offset int64) ([]byte, error) {
return EncryptSM4CTRAt(src, key, iv, offset)
}
func EncryptSM4ECBStream(dst io.Writer, src io.Reader, key []byte, paddingType string) error {
return EncryptSM4Stream(dst, src, key, nil, MODEECB, paddingType)
}
func DecryptSM4ECBStream(dst io.Writer, src io.Reader, key []byte, paddingType string) error {
return DecryptSM4Stream(dst, src, key, nil, MODEECB, paddingType)
}
func EncryptSM4CBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
return EncryptSM4Stream(dst, src, key, iv, MODECBC, paddingType)
}
func DecryptSM4CBCStream(dst io.Writer, src io.Reader, key, iv []byte, paddingType string) error {
return DecryptSM4Stream(dst, src, key, iv, MODECBC, paddingType)
}
func EncryptSM4CFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return EncryptSM4Stream(dst, src, key, iv, MODECFB, "")
}
func DecryptSM4CFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return DecryptSM4Stream(dst, src, key, iv, MODECFB, "")
}
func EncryptSM4OFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return EncryptSM4Stream(dst, src, key, iv, MODEOFB, "")
}
func DecryptSM4OFBStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return DecryptSM4Stream(dst, src, key, iv, MODEOFB, "")
}
func EncryptSM4CTRStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return EncryptSM4Stream(dst, src, key, iv, MODECTR, "")
}
func DecryptSM4CTRStream(dst io.Writer, src io.Reader, key, iv []byte) error {
return DecryptSM4Stream(dst, src, key, iv, MODECTR, "")
}
func EncryptSM4CFB8(data, key, iv []byte) ([]byte, error) {
block, err := sm4.NewCipher(key)
if err != nil {
return nil, err
}
return encryptCFB8(block, data, iv)
}
func DecryptSM4CFB8(src, key, iv []byte) ([]byte, error) {
block, err := sm4.NewCipher(key)
if err != nil {
return nil, err
}
return decryptCFB8(block, src, iv)
}
func EncryptSM4CFB8Stream(dst io.Writer, src io.Reader, key, iv []byte) error {
block, err := sm4.NewCipher(key)
if err != nil {
return err
}
return encryptCFB8Stream(block, dst, src, iv, false)
}
func DecryptSM4CFB8Stream(dst io.Writer, src io.Reader, key, iv []byte) error {
block, err := sm4.NewCipher(key)
if err != nil {
return err
}
return encryptCFB8Stream(block, dst, src, iv, true)
}
func DecryptSM4ECBBlocks(src, key []byte) ([]byte, error) {
block, err := sm4.NewCipher(key)
if err != nil {
return nil, err
}
return decryptECBBlocks(block, src)
}
// DecryptSM4CBCFromSecondBlock decrypts a CBC ciphertext segment that starts from block 2 or later.
// prevCipherBlock must be the previous ciphertext block. For data from block 2, pass block 1 as prevCipherBlock.
func DecryptSM4CBCFromSecondBlock(src, key, prevCipherBlock []byte) ([]byte, error) {
block, err := sm4.NewCipher(key)
if err != nil {
return nil, err
}
return decryptCBCFromSecondBlock(block, src, prevCipherBlock)
}
// DecryptSM4CFBFromSecondBlock decrypts a CFB ciphertext segment that starts from block 2 or later.
// prevCipherBlock must be the previous ciphertext block. For data from block 2, pass block 1 as prevCipherBlock.
func DecryptSM4CFBFromSecondBlock(src, key, prevCipherBlock []byte) ([]byte, error) {
block, err := sm4.NewCipher(key)
if err != nil {
return nil, err
}
return decryptCFBFromSecondBlock(block, src, prevCipherBlock)
}

1339
symm/symm_test.go Normal file

File diff suppressed because it is too large Load Diff

263
symm/xts.go Normal file
View File

@ -0,0 +1,263 @@
package symm
import (
"crypto/aes"
"crypto/cipher"
"errors"
"io"
"github.com/emmansun/gmsm/sm4"
"golang.org/x/crypto/xts"
)
const xtsBlockSize = 16
var (
ErrInvalidXTSDataUnitSize = errors.New("xts data unit size must be a positive multiple of 16")
ErrInvalidXTSDataLength = errors.New("xts data length must be a multiple of 16")
ErrInvalidXTSKeyLength = errors.New("xts key lengths must be non-empty and equal")
ErrInvalidXTSMasterKeyLength = errors.New("xts master key length must be non-empty and even")
ErrInvalidAESXTSMasterKeyLength = errors.New("aes xts master key length must be 32, 48, or 64 bytes")
ErrInvalidSM4XTSMasterKeyLength = errors.New("sm4 xts master key length must be 32 bytes")
)
type xtsCipherFactory func(key1, key2 []byte) (*xts.Cipher, error)
func combineXTSKeys(key1, key2 []byte) ([]byte, error) {
if len(key1) == 0 || len(key1) != len(key2) {
return nil, ErrInvalidXTSKeyLength
}
out := make([]byte, len(key1)+len(key2))
copy(out, key1)
copy(out[len(key1):], key2)
return out, nil
}
func splitXTSMasterKey(masterKey []byte) ([]byte, []byte, error) {
if len(masterKey) == 0 || len(masterKey)%2 != 0 {
return nil, nil, ErrInvalidXTSMasterKeyLength
}
half := len(masterKey) / 2
k1 := make([]byte, half)
k2 := make([]byte, half)
copy(k1, masterKey[:half])
copy(k2, masterKey[half:])
return k1, k2, nil
}
// SplitXTSMasterKey splits a master key into two equal XTS keys.
func SplitXTSMasterKey(masterKey []byte) ([]byte, []byte, error) {
return splitXTSMasterKey(masterKey)
}
// SplitAesXTSMasterKey splits AES-XTS master key and validates length (32/48/64 bytes).
func SplitAesXTSMasterKey(masterKey []byte) ([]byte, []byte, error) {
switch len(masterKey) {
case 32, 48, 64:
return splitXTSMasterKey(masterKey)
default:
return nil, nil, ErrInvalidAESXTSMasterKeyLength
}
}
// SplitSM4XTSMasterKey splits SM4-XTS master key and validates length (32 bytes).
func SplitSM4XTSMasterKey(masterKey []byte) ([]byte, []byte, error) {
if len(masterKey) != 32 {
return nil, nil, ErrInvalidSM4XTSMasterKeyLength
}
return splitXTSMasterKey(masterKey)
}
func validateXTSDataUnitSize(dataUnitSize int) error {
if dataUnitSize <= 0 || dataUnitSize%xtsBlockSize != 0 {
return ErrInvalidXTSDataUnitSize
}
return nil
}
func validateXTSDataLength(data []byte) error {
if len(data)%xtsBlockSize != 0 {
return ErrInvalidXTSDataLength
}
return nil
}
func cryptXTSAt(c *xts.Cipher, in []byte, dataUnitSize int, dataUnitIndex uint64, decrypt bool) ([]byte, error) {
if err := validateXTSDataUnitSize(dataUnitSize); err != nil {
return nil, err
}
if err := validateXTSDataLength(in); err != nil {
return nil, err
}
if len(in) == 0 {
return []byte{}, nil
}
out := make([]byte, len(in))
off := 0
unit := dataUnitIndex
for off < len(in) {
chunkLen := dataUnitSize
if remain := len(in) - off; remain < chunkLen {
chunkLen = remain
}
if decrypt {
c.Decrypt(out[off:off+chunkLen], in[off:off+chunkLen], unit)
} else {
c.Encrypt(out[off:off+chunkLen], in[off:off+chunkLen], unit)
}
off += chunkLen
unit++
}
return out, nil
}
func cryptXTSStreamAt(dst io.Writer, src io.Reader, c *xts.Cipher, dataUnitSize int, dataUnitIndex uint64, decrypt bool) error {
if err := validateXTSDataUnitSize(dataUnitSize); err != nil {
return err
}
buf := make([]byte, 32*1024)
pending := make([]byte, 0, dataUnitSize*2)
unit := dataUnitIndex
for {
n, err := src.Read(buf)
if n > 0 {
pending = append(pending, buf[:n]...)
processLen := len(pending) / dataUnitSize * dataUnitSize
if processLen > 0 {
out := make([]byte, processLen)
for off := 0; off < processLen; off += dataUnitSize {
if decrypt {
c.Decrypt(out[off:off+dataUnitSize], pending[off:off+dataUnitSize], unit)
} else {
c.Encrypt(out[off:off+dataUnitSize], pending[off:off+dataUnitSize], unit)
}
unit++
}
if _, werr := dst.Write(out); werr != nil {
return werr
}
pending = append([]byte(nil), pending[processLen:]...)
}
}
if err != nil {
if err == io.EOF {
break
}
return err
}
}
if len(pending) == 0 {
return nil
}
if err := validateXTSDataLength(pending); err != nil {
return err
}
out := make([]byte, len(pending))
if decrypt {
c.Decrypt(out, pending, unit)
} else {
c.Encrypt(out, pending, unit)
}
_, err := dst.Write(out)
return err
}
func newXTSCipher(newBlock func([]byte) (cipher.Block, error), key1, key2 []byte) (*xts.Cipher, error) {
key, err := combineXTSKeys(key1, key2)
if err != nil {
return nil, err
}
return xts.NewCipher(newBlock, key)
}
func newAesXTS(key1, key2 []byte) (*xts.Cipher, error) {
return newXTSCipher(aes.NewCipher, key1, key2)
}
func newSM4XTS(key1, key2 []byte) (*xts.Cipher, error) {
return newXTSCipher(sm4.NewCipher, key1, key2)
}
func cryptXTSAtWithFactory(factory xtsCipherFactory, in []byte, dataUnitSize int, dataUnitIndex uint64, decrypt bool, key1, key2 []byte) ([]byte, error) {
c, err := factory(key1, key2)
if err != nil {
return nil, err
}
return cryptXTSAt(c, in, dataUnitSize, dataUnitIndex, decrypt)
}
func cryptXTSStreamAtWithFactory(factory xtsCipherFactory, dst io.Writer, src io.Reader, dataUnitSize int, dataUnitIndex uint64, decrypt bool, key1, key2 []byte) error {
c, err := factory(key1, key2)
if err != nil {
return err
}
return cryptXTSStreamAt(dst, src, c, dataUnitSize, dataUnitIndex, decrypt)
}
func EncryptAesXTS(plain, key1, key2 []byte, dataUnitSize int) ([]byte, error) {
return EncryptAesXTSAt(plain, key1, key2, dataUnitSize, 0)
}
func DecryptAesXTS(ciphertext, key1, key2 []byte, dataUnitSize int) ([]byte, error) {
return DecryptAesXTSAt(ciphertext, key1, key2, dataUnitSize, 0)
}
func EncryptAesXTSAt(plain, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) ([]byte, error) {
return cryptXTSAtWithFactory(newAesXTS, plain, dataUnitSize, dataUnitIndex, false, key1, key2)
}
func DecryptAesXTSAt(ciphertext, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) ([]byte, error) {
return cryptXTSAtWithFactory(newAesXTS, ciphertext, dataUnitSize, dataUnitIndex, true, key1, key2)
}
func EncryptAesXTSStream(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int) error {
return EncryptAesXTSStreamAt(dst, src, key1, key2, dataUnitSize, 0)
}
func DecryptAesXTSStream(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int) error {
return DecryptAesXTSStreamAt(dst, src, key1, key2, dataUnitSize, 0)
}
func EncryptAesXTSStreamAt(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) error {
return cryptXTSStreamAtWithFactory(newAesXTS, dst, src, dataUnitSize, dataUnitIndex, false, key1, key2)
}
func DecryptAesXTSStreamAt(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) error {
return cryptXTSStreamAtWithFactory(newAesXTS, dst, src, dataUnitSize, dataUnitIndex, true, key1, key2)
}
func EncryptSM4XTS(plain, key1, key2 []byte, dataUnitSize int) ([]byte, error) {
return EncryptSM4XTSAt(plain, key1, key2, dataUnitSize, 0)
}
func DecryptSM4XTS(ciphertext, key1, key2 []byte, dataUnitSize int) ([]byte, error) {
return DecryptSM4XTSAt(ciphertext, key1, key2, dataUnitSize, 0)
}
func EncryptSM4XTSAt(plain, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) ([]byte, error) {
return cryptXTSAtWithFactory(newSM4XTS, plain, dataUnitSize, dataUnitIndex, false, key1, key2)
}
func DecryptSM4XTSAt(ciphertext, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) ([]byte, error) {
return cryptXTSAtWithFactory(newSM4XTS, ciphertext, dataUnitSize, dataUnitIndex, true, key1, key2)
}
func EncryptSM4XTSStream(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int) error {
return EncryptSM4XTSStreamAt(dst, src, key1, key2, dataUnitSize, 0)
}
func DecryptSM4XTSStream(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int) error {
return DecryptSM4XTSStreamAt(dst, src, key1, key2, dataUnitSize, 0)
}
func EncryptSM4XTSStreamAt(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) error {
return cryptXTSStreamAtWithFactory(newSM4XTS, dst, src, dataUnitSize, dataUnitIndex, false, key1, key2)
}
func DecryptSM4XTSStreamAt(dst io.Writer, src io.Reader, key1, key2 []byte, dataUnitSize int, dataUnitIndex uint64) error {
return cryptXTSStreamAtWithFactory(newSM4XTS, dst, src, dataUnitSize, dataUnitIndex, true, key1, key2)
}

79
symm/xts_vector_test.go Normal file
View File

@ -0,0 +1,79 @@
package symm
import (
"bytes"
"testing"
)
// AES-XTS vectors from IEEE P1619/D16 Annex B (same set used by golang.org/x/crypto/xts tests).
var aesXTSStandardVectors = []struct {
key string
dataUnitIndex uint64
plaintext string
ciphertext string
}{
{
key: "0000000000000000000000000000000000000000000000000000000000000000",
dataUnitIndex: 0,
plaintext: "0000000000000000000000000000000000000000000000000000000000000000",
ciphertext: "917cf69ebd68b2ec9b9fe9a3eadda692cd43d2f59598ed858c02c2652fbf922e",
},
{
key: "1111111111111111111111111111111122222222222222222222222222222222",
dataUnitIndex: 0x3333333333,
plaintext: "4444444444444444444444444444444444444444444444444444444444444444",
ciphertext: "c454185e6a16936e39334038acef838bfb186fff7480adc4289382ecd6d394f0",
},
{
key: "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f022222222222222222222222222222222",
dataUnitIndex: 0x3333333333,
plaintext: "4444444444444444444444444444444444444444444444444444444444444444",
ciphertext: "af85336b597afc1a900b2eb21ec949d292df4c047e0b21532186a5971a227a89",
},
}
func TestAesXTSStandardVectors(t *testing.T) {
for i, tc := range aesXTSStandardVectors {
master := mustHex(t, tc.key)
key1, key2, err := SplitAesXTSMasterKey(master)
if err != nil {
t.Fatalf("#%d split key failed: %v", i, err)
}
plain := mustHex(t, tc.plaintext)
wantCipher := mustHex(t, tc.ciphertext)
dataUnitSize := len(plain)
gotCipher, err := EncryptAesXTSAt(plain, key1, key2, dataUnitSize, tc.dataUnitIndex)
if err != nil {
t.Fatalf("#%d EncryptAesXTSAt failed: %v", i, err)
}
if !bytes.Equal(gotCipher, wantCipher) {
t.Fatalf("#%d ciphertext mismatch", i)
}
gotPlain, err := DecryptAesXTSAt(wantCipher, key1, key2, dataUnitSize, tc.dataUnitIndex)
if err != nil {
t.Fatalf("#%d DecryptAesXTSAt failed: %v", i, err)
}
if !bytes.Equal(gotPlain, plain) {
t.Fatalf("#%d plaintext mismatch", i)
}
encStream := &bytes.Buffer{}
if err := EncryptAesXTSStreamAt(encStream, bytes.NewReader(plain), key1, key2, dataUnitSize, tc.dataUnitIndex); err != nil {
t.Fatalf("#%d EncryptAesXTSStreamAt failed: %v", i, err)
}
if !bytes.Equal(encStream.Bytes(), wantCipher) {
t.Fatalf("#%d stream ciphertext mismatch", i)
}
decStream := &bytes.Buffer{}
if err := DecryptAesXTSStreamAt(decStream, bytes.NewReader(wantCipher), key1, key2, dataUnitSize, tc.dataUnitIndex); err != nil {
t.Fatalf("#%d DecryptAesXTSStreamAt failed: %v", i, err)
}
if !bytes.Equal(decStream.Bytes(), plain) {
t.Fatalf("#%d stream plaintext mismatch", i)
}
}
}

15
xts_keysplit.go Normal file
View File

@ -0,0 +1,15 @@
package starcrypto
import "b612.me/starcrypto/symm"
func SplitXTSMasterKey(masterKey []byte) ([]byte, []byte, error) {
return symm.SplitXTSMasterKey(masterKey)
}
func SplitAesXTSMasterKey(masterKey []byte) ([]byte, []byte, error) {
return symm.SplitAesXTSMasterKey(masterKey)
}
func SplitSM4XTSMasterKey(masterKey []byte) ([]byte, []byte, error) {
return symm.SplitSM4XTSMasterKey(masterKey)
}