您可以从国家标准全文公开系统在线阅读这些标准。
保密性算法EEA实现了cipher.Stream接口,所以和其它流密码算法使用类似,只是创建方法不同而已。
| ZUC-128 | ZUC-256 | |
|---|---|---|
| Key字节数 | 16 | 32 |
| IV字节数 | 16 | 23 |
func ExampleNewCipher() {
// Load your secret key from a safe place and reuse it across multiple
// NewCipher calls. (Obviously don't use this example key for anything
// real.) If you want to convert a passphrase to a key, use a suitable
// package like bcrypt or scrypt.
key, _ := hex.DecodeString("6368616e676520746869732070617373")
plaintext := []byte("some plaintext")
const ivSize = zuc.IVSize128
// The IV needs to be unique, but not secure. Therefore it's common to
// include it at the beginning of the ciphertext.
ciphertext := make([]byte, ivSize+len(plaintext))
iv := ciphertext[:ivSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
}
stream, err := zuc.NewCipher(key, iv)
if err != nil {
panic(err)
}
stream.XORKeyStream(ciphertext[ivSize:], plaintext)
// It's important to remember that ciphertexts must be authenticated
// (i.e. by using crypto/hmac) as well as being encrypted in order to
// be secure.
// Stream cipher is the same for both encryption and decryption, so we can
// also decrypt that ciphertext with NewCTR.
plaintext2 := make([]byte, len(plaintext))
stream, err = zuc.NewCipher(key, iv)
if err != nil {
panic(err)
}
stream.XORKeyStream(plaintext2, ciphertext[ivSize:])
fmt.Printf("%s\n", plaintext2)
// Output: some plaintext
}
func ExampleNewCipher_zuc256() {
// Load your secret key from a safe place and reuse it across multiple
// NewCipher calls. (Obviously don't use this example key for anything
// real.) If you want to convert a passphrase to a key, use a suitable
// package like bcrypt or scrypt.
key, _ := hex.DecodeString("6368616e6765207468697320706173736368616e676520746869732070617373")
plaintext := []byte("some plaintext")
const ivSize = zuc.IVSize256
// The IV needs to be unique, but not secure. Therefore it's common to
// include it at the beginning of the ciphertext.
ciphertext := make([]byte, ivSize+len(plaintext))
iv := ciphertext[:ivSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
}
stream, err := zuc.NewCipher(key, iv)
if err != nil {
panic(err)
}
stream.XORKeyStream(ciphertext[ivSize:], plaintext)
// It's important to remember that ciphertexts must be authenticated
// (i.e. by using crypto/hmac) as well as being encrypted in order to
// be secure.
// Stream cipher is the same for both encryption and decryption, so we can
// also decrypt that ciphertext with NewCTR.
plaintext2 := make([]byte, len(plaintext))
stream, err = zuc.NewCipher(key, iv)
if err != nil {
panic(err)
}
stream.XORKeyStream(plaintext2, ciphertext[ivSize:])
fmt.Printf("%s\n", plaintext2)
// Output: some plaintext
}
完整性算法支持Seekable Stream,也就是随机定位到某点进行处理,内部实现了分桶缓存状态,每个状态的大小大概是88字节,bucketSize的大小可以结合要处理的流大小以及内存占用来平衡考虑。同时,bucketSize内部会被处理成128字节的倍数,以利于实现。
如果您没有对同一个流反复进行前进、后退加解密的需求,可以使用NewCipher或者NewEEACipher方法,避免内部状态缓存。
完整性算法实现了hash.Hash接口,所以其使用方法和其它哈希算法类似。
| ZUC-128 | ZUC-256 | |
|---|---|---|
| Key字节数 | 16 | 32 |
| IV字节数 | 16 | 23 |
| MAC字节数 | 4 | 4/8/16 |
func ExampleNewHash() {
// Load your secret key from a safe place and reuse it across multiple
// NewCipher calls. (Obviously don't use this example key for anything
// real.) If you want to convert a passphrase to a key, use a suitable
// package like bcrypt or scrypt.
key, _ := hex.DecodeString("6368616e676520746869732070617373")
// iv should be generated randomly
iv, _ := hex.DecodeString("6368616e676520746869732070617373")
h, err := zuc.NewHash(key, iv)
if err != nil {
panic(err)
}
h.Write([]byte("hello world\n"))
fmt.Printf("%x", h.Sum(nil))
// Output: c43cd26a
}
func ExampleNewHash256_tagSize4() {
// Load your secret key from a safe place and reuse it across multiple
// NewCipher calls. (Obviously don't use this example key for anything
// real.) If you want to convert a passphrase to a key, use a suitable
// package like bcrypt or scrypt.
key, _ := hex.DecodeString("6368616e6765207468697320706173736368616e676520746869732070617373")
// iv should be generated randomly
iv, _ := hex.DecodeString("6368616e6765207468697320706173736368616e676520")
h, err := zuc.NewHash256(key, iv, 4)
if err != nil {
panic(err)
}
h.Write([]byte("hello world\n"))
fmt.Printf("%x", h.Sum(nil))
// Output: b76f96ed
}
func ExampleNewHash256_tagSize8() {
// Load your secret key from a safe place and reuse it across multiple
// NewCipher calls. (Obviously don't use this example key for anything
// real.) If you want to convert a passphrase to a key, use a suitable
// package like bcrypt or scrypt.
key, _ := hex.DecodeString("6368616e6765207468697320706173736368616e676520746869732070617373")
// iv should be generated randomly
iv, _ := hex.DecodeString("6368616e6765207468697320706173736368616e676520")
h, err := zuc.NewHash256(key, iv, 8)
if err != nil {
panic(err)
}
h.Write([]byte("hello world\n"))
fmt.Printf("%x", h.Sum(nil))
// Output: f28aea6c9db3dc69
}
func ExampleNewHash256_tagSize16() {
// Load your secret key from a safe place and reuse it across multiple
// NewCipher calls. (Obviously don't use this example key for anything
// real.) If you want to convert a passphrase to a key, use a suitable
// package like bcrypt or scrypt.
key, _ := hex.DecodeString("6368616e6765207468697320706173736368616e676520746869732070617373")
// iv should be generated randomly
iv, _ := hex.DecodeString("6368616e6765207468697320706173736368616e676520")
h, err := zuc.NewHash256(key, iv, 16)
if err != nil {
panic(err)
}
h.Write([]byte("hello world\n"))
fmt.Printf("%x", h.Sum(nil))
// Output: fd8d10ea65b6369cccc07d50b4657d84
}
要支持位为单位的话,可以调用Finish方法。
func ExampleZUC128Mac_Finish() {
key := make([]byte, 16)
iv := make([]byte, 16)
h, err := zuc.NewHash(key, iv)
if err != nil {
panic(err)
}
fmt.Printf("%x", h.Finish([]byte{0}, 1))
// Output: c8a9595e
}
func ExampleZUC128Mac_Finish_mixed() {
key := []byte{
0xc9, 0xe6, 0xce, 0xc4, 0x60, 0x7c, 0x72, 0xdb,
0x00, 0x0a, 0xef, 0xa8, 0x83, 0x85, 0xab, 0x0a,
}
// iv should be generated randomly
iv, _ := hex.DecodeString("a94059da50000000294059da50008000")
h, err := zuc.NewHash(key, iv)
if err != nil {
panic(err)
}
in, _ := hex.DecodeString("983b41d47d780c9e1ad11d7eb70391b1de0b35da2dc62f83e7b78d6306ca0ea07e941b7be91348f9fcb170e2217fecd97f9f68adb16e5d7d21e569d280ed775cebde3f4093c53881")
h.Write(in)
fmt.Printf("%x", h.Finish([]byte{0}, 1))
// Output: fae8ff0b
}
GM/T 0001.4-2024《祖冲之序列密码算法 第4部分:鉴别式加密机制》定义了两种基于 ZUC 流密码的认证加密模式:GXM(第6章)和 MUR(第7章)。两者均在 cipher 包中实现,通过内部复用 GHASH 函数构造消息认证码。
GXM(Galois XOR Mode)是一种类 GCM 的认证加密模式,使用单个流密码实例进行加密并借助 GHASH 生成认证标签。
密钥材料
| 参数 | 说明 |
|---|---|
k(流密码密钥) |
ZUC 密钥,传入 zuc.NewCipher |
iv |
ZUC IV,传入 zuc.NewCipher |
h(哈希密钥) |
16 字节,独立于流密码密钥 |
使用步骤
k/iv 创建 zuc.NewCipher,得到流密码实例 stream;stream 和 h 构造 GXM 实例;Seal / Open。注意:同一个
stream实例不得复用,因为 GXM 在初始化时会消耗流密码的首个密钥流块来生成标签掩码。
import (
"encoding/hex"
gocipher "crypto/cipher"
"github.com/emmansun/gmsm/cipher"
"github.com/emmansun/gmsm/zuc"
)
// GM/T 0001.4-2024 附录 C.2 示例
key, _ := hex.DecodeString("edbe06afed8075576aad04afdec91d32")
iv, _ := hex.DecodeString("b3a6db3c870c3e99245e0d1c06b747de")
hkey, _ := hex.DecodeString("6db45e4f9572f4e6fe0d91acda6801d5")
aad, _ := hex.DecodeString("9de18b1fdab0ca9902b9729d492c807ec599d5")
// 加密
stream, err := zuc.NewCipher(key, iv)
if err != nil {
panic(err)
}
g, err := cipher.NewGXM(stream, hkey)
if err != nil {
panic(err)
}
ciphertext := g.Seal(nil, plaintext, aad) // ciphertext = 密文 || 16字节标签
// 解密(需要重新创建 stream,不可复用)
stream2, err := zuc.NewCipher(key, iv)
if err != nil {
panic(err)
}
g2, err := cipher.NewGXM(stream2, hkey)
if err != nil {
panic(err)
}
plaintext2, err := g2.Open(nil, ciphertext, aad)
if err != nil {
panic(err) // 标签验证失败
}
标签长度默认为 16 字节;如需 8 字节标签,使用 cipher.NewGXMWithTagSize(stream, hkey, 8)。
MUR(Misuse-resistant)是一种抗误用认证加密模式:即使 IV 被意外重用,也不会像 GCM 那样彻底失去保密性。代价是需要两个独立的流密码密钥(dataKey 和 tagKey),以及两次流密码调用(合成 IV 派生机制)。
密钥材料
| 参数 | 说明 |
|---|---|
h(哈希密钥) |
16 字节,用于 GHASH |
k1(dataKey) |
ZUC 密钥,用于加密 |
k2(tagKey) |
ZUC 密钥,用于生成标签 |
iv |
公共 IV,两路流密码均使用(长度需一致) |
工作原理(Seal)
(k2, IV₁) 创建流密码,加密全零块得到 tag;tag XOR iv 作为 IV₂,用 (k1, IV₂) 加密明文;| 输出:密文 | 标签。 |
import (
_cipher "crypto/cipher"
"github.com/emmansun/gmsm/cipher"
"github.com/emmansun/gmsm/zuc"
)
zucCreator := func(key, iv []byte) (_cipher.Stream, error) {
return zuc.NewCipher(key, iv)
}
iv, _ := hex.DecodeString("bb8b76cfe5f0d9335029008b2a3b2b21")
hkey, _ := hex.DecodeString("ee767d503bb3d5d1b585f57a0418c673")
k1, _ := hex.DecodeString("e4b5c1f8578034ce6424f58c675597ac") // dataKey
k2, _ := hex.DecodeString("608053f6af9efda562d95dc013bea6b5") // tagKey
aad, _ := hex.DecodeString("fcdd4cb97995da30efd957194eac4d2a...")
g, err := cipher.NewMUR(zucCreator, hkey)
if err != nil {
panic(err)
}
// 加密
ciphertext, err := g.Seal(iv, k1, k2, nil, plaintext, aad)
if err != nil {
panic(err)
}
// 解密
plaintext2, err := g.Open(iv, k1, k2, nil, ciphertext, aad)
if err != nil {
panic(err) // 标签验证失败
}
g(*mur 实例)可安全复用:MUR 不在实例内部保存任何每次加密的状态,流密码实例在每次 Seal/Open 内部按需创建。
标签长度默认 16 字节;如需 8 字节标签,使用 cipher.NewMURWithTagSize(zucCreator, hkey, 8)。
| 属性 | GXM | MUR |
|---|---|---|
| 密钥数量 | 1(流密码)+ 1(哈希) | 2(流密码)+ 1(哈希) |
| 流密码调用次数 | 1 | 2 |
| 实例可复用 | 否(stream 不可复用) | 是(*mur 可复用) |
| IV 误用安全 | 否 | 是(合成 IV) |
| 标准来源 | GM/T 0001.4-2024 第6章 | GM/T 0001.4-2024 第7章 |
| 接口风格 | 类似 cipher.AEAD |
扩展参数(含双密钥) |