Files
2026-05-27 23:03:00 +08:00

266 lines
5.8 KiB
Go

package blockmode_test
import (
"bytes"
"encoding/hex"
"fmt"
"testing"
"time"
"xdx.jelly/xgcl/grand"
"xdx.jelly/xgcl/sm/sm4"
"xdx.jelly/xgcl/utils/blockmode"
)
// sm4_GCM模式
var sm4GCMTests = []struct {
key, nonce, plaintext, ad, result string
}{
{
"11754cd72aec309bf52f7687212e8957",
"3c819d9a9bed087615030b65", // nonce should be 12 bytes.
"plaintext",
"additional message not need encrypt, empty is ok",
"6111f78f2f82b913c20e333160bfec034c3720ac133a6203b1",
},
}
func TestSM4Speed(t *testing.T) {
key, err := hex.DecodeString("12345678123456781234567812345678")
if err != nil {
t.Fatal(err)
}
block, err := sm4.NewCipher(key)
if err != nil {
t.Fatal(err)
}
gcm, err := blockmode.NewGCM(blockmode.Wrap(block))
if err != nil {
t.Fatal(err)
}
nonce, err := hex.DecodeString("123456781234567812345678")
if err != nil {
t.Fatal(err)
}
plaintext := grand.GetRandom(1024 * 1024)
ad := []byte("additional message not need encrypt, empty is ok")
ct2 := make([]byte, 0, 1024*1024+gcm.Overhead())
cnt := 100
start := time.Now()
for i := 0; i < cnt; i++ {
err = gcm.EncryptInit(nonce)
if err != nil {
t.Fatal(err)
}
gcm.SpecifyADD(ad)
ct2, err := gcm.EncryptUpdate(ct2, plaintext)
if err != nil {
t.Fatal(err)
}
ct2, err = gcm.EncryptFinal(ct2)
if err != nil {
t.Fatal(err)
}
}
end := time.Now()
elapsed := end.Sub(start)
fmt.Printf("%f Bps\n", float64(len(plaintext)*cnt)/1024/1024*1000/float64(elapsed.Milliseconds()))
}
func TestSM4GCM(t *testing.T) {
for i, test := range sm4GCMTests {
key, _ := hex.DecodeString(test.key)
block, _ := sm4.NewCipher(key)
gcm, err := blockmode.NewGCM(blockmode.Wrap(block))
if err != nil {
t.Fatal(err)
}
nonce, _ := hex.DecodeString(test.nonce)
plaintext := []byte(test.plaintext)
ad := []byte(test.ad)
// 提前分配好空间
for i := 0; i < gcm.Overhead(); i++ {
plaintext = append(plaintext, 0)
}
plaintext = plaintext[:len(plaintext)-gcm.Overhead()]
ct := gcm.Seal(plaintext[:0], nonce, plaintext, ad)
if ctHex := hex.EncodeToString(ct); ctHex != test.result {
t.Errorf("#%d: got %s, want %s", i, ctHex, test.result)
continue
}
plaintext, err = gcm.Open(ct[:0], nonce, ct, ad)
if err != nil {
t.Errorf("#%d: Open failed", i)
continue
}
if !bytes.Equal(plaintext, []byte(test.plaintext)) {
t.Errorf("#%d: plaintext's don't match: got %x vs %x", i, test.plaintext, plaintext)
continue
}
// if ad, nonce, ct was changed, return err
if len(ad) > 0 {
ad[0] ^= 0x80
if _, err := gcm.Open(nil, nonce, ct, ad); err == nil {
t.Errorf("#%d: Open was successful after altering additional data", i)
}
ad[0] ^= 0x80
}
nonce[0] ^= 0x80
if _, err := gcm.Open(nil, nonce, ct, ad); err == nil {
t.Errorf("#%d: Open was successful after altering nonce", i)
}
nonce[0] ^= 0x80
ct[0] ^= 0x80
if _, err := gcm.Open(nil, nonce, ct, ad); err == nil {
t.Errorf("#%d: Open was successful after altering ciphertext", i)
}
ct[0] ^= 0x80
}
}
func TestGcmUpdate(t *testing.T) {
test := sm4GCMTests[0]
key, _ := hex.DecodeString(test.key)
block, _ := sm4.NewCipher(key)
gcm, err := blockmode.NewGCM(blockmode.Wrap(block))
if err != nil {
t.Fatal(err)
}
nonce, _ := hex.DecodeString(test.nonce)
plaintext := grand.GetRandom(0)
ad := []byte(test.ad)
ct1 := gcm.Seal(nil, nonce, plaintext, ad)
ct2 := make([]byte, 0, 116)
err = gcm.EncryptInit(nonce)
if err != nil {
t.Fatal(err)
}
gcm.SpecifyADD(ad)
for i := range plaintext {
ct2, err = gcm.EncryptUpdate(ct2, []byte{plaintext[i]})
if err != nil {
t.Fatal(err)
}
}
ct2, err = gcm.EncryptFinal(ct2)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(ct1, ct2) {
t.Errorf("Seal failed")
}
if err = gcm.DecryptInit(nonce); err != nil {
t.Fatal(err)
}
gcm.SpecifyADD(ad)
decrypted := make([]byte, 0, len(plaintext))
for i := range ct1 {
decrypted, err = gcm.DecryptUpdate(decrypted, []byte{ct1[i]})
if err != nil {
t.Fatal(err)
}
}
decrypted, err = gcm.DecryptFinal(decrypted)
if !bytes.Equal(decrypted, plaintext) {
t.Fatal("plaintext unequal decrypted plaintext")
}
if err != nil {
t.Fatal("auth failed")
}
}
func FuzzSm4Gcm(f *testing.F) {
nonce := grand.GetRandom(12)
key := grand.GetRandom(16)
block, _ := sm4.NewCipher(key)
gcm, err := blockmode.NewGCM(blockmode.Wrap(block))
if err != nil {
f.Fatal(err)
}
stdgcm, _ := sm4.NewGCM(key)
f.Add([]byte{}, []byte{})
f.Fuzz(func(t *testing.T, plaintext, ad []byte) {
stdct := stdgcm.Seal(nil, nonce, plaintext, ad)
ct1 := gcm.Seal(nil, nonce, plaintext, ad)
if !bytes.Equal(ct1, stdct) {
t.Errorf("Seal failed")
}
if err := gcm.EncryptInit(nonce); err != nil {
t.Fatal(err)
}
// if additional data is empty, then call
// gcm.SpecifyADD(nil)
gcm.SpecifyADD(ad)
ct2, err := gcm.EncryptUpdate(nil, plaintext[:len(plaintext)/2])
if err != nil {
t.Fatal(err)
}
for _, p := range plaintext[len(plaintext)/2:] {
if ct2, err = gcm.EncryptUpdate(ct2, []byte{p}); err != nil {
t.Fatal(err)
}
}
ct2, err = gcm.EncryptFinal(ct2)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(ct2, stdct) {
t.Errorf("Encrypt failed")
}
pt1, err1 := gcm.Open(nil, nonce, ct1, ad)
if err1 != nil {
t.Errorf("Open faile: %v\n", err1)
}
if err := gcm.DecryptInit(nonce); err != nil {
t.Fatal(err)
}
gcm.SpecifyADD(ad)
pt2, err := gcm.DecryptUpdate(nil, ct2[:len(ct2)/2])
if err != nil {
t.Fatal(err)
}
for _, p := range ct2[len(ct2)/2:] {
if pt2, err = gcm.DecryptUpdate(pt2, []byte{p}); err != nil {
t.Fatal(err)
}
}
pt2, err = gcm.DecryptFinal(pt2)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(pt2, plaintext) {
t.Errorf("Decrypt failed")
}
if !bytes.Equal(pt1, plaintext) {
t.Errorf("Open failed")
}
})
}