mirror of
https://github.com/emmansun/gmsm.git
synced 2025-04-26 12:16:20 +08:00
cipher: hctr POC
This commit is contained in:
parent
68372f62c4
commit
9d467f8051
@ -397,7 +397,7 @@ func TestGCMCounterWrap(t *testing.T) {
|
|||||||
func TestSM4GCMRandom(t *testing.T) {
|
func TestSM4GCMRandom(t *testing.T) {
|
||||||
key := []byte("0123456789ABCDEF")
|
key := []byte("0123456789ABCDEF")
|
||||||
nonce := []byte("0123456789AB")
|
nonce := []byte("0123456789AB")
|
||||||
plaintext := make([]byte, 464)
|
plaintext := make([]byte, 0x198)
|
||||||
|
|
||||||
io.ReadFull(rand.Reader, plaintext)
|
io.ReadFull(rand.Reader, plaintext)
|
||||||
c, err := sm4.NewCipher(key)
|
c, err := sm4.NewCipher(key)
|
||||||
@ -410,11 +410,11 @@ func TestSM4GCMRandom(t *testing.T) {
|
|||||||
}
|
}
|
||||||
got := aead.Seal(nil, nonce, plaintext, nil)
|
got := aead.Seal(nil, nonce, plaintext, nil)
|
||||||
|
|
||||||
result, err := aead.Open(nil, nonce, got, nil)
|
result, err := aead.Open(got[:0], nonce, got, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(result, plaintext) {
|
if !bytes.Equal(result, plaintext) {
|
||||||
t.Error("gcm seal/open 464 bytes fail")
|
t.Error("gcm seal/open 408 bytes fail")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
212
cipher/hctr.go
Normal file
212
cipher/hctr.go
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
package cipher
|
||||||
|
|
||||||
|
import (
|
||||||
|
_cipher "crypto/cipher"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/emmansun/gmsm/internal/alias"
|
||||||
|
"github.com/emmansun/gmsm/internal/subtle"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A LengthPreservingMode represents a block cipher running in a length preserving mode (HCTR,
|
||||||
|
// HCTR2 etc).
|
||||||
|
type LengthPreservingMode interface {
|
||||||
|
// Encrypt encrypts a number of plaintext bytes. The length of
|
||||||
|
// src must be NOT smaller than block size. Dst and src must overlap
|
||||||
|
// entirely or not at all.
|
||||||
|
//
|
||||||
|
// If len(dst) < len(src), Encrypt should panic. It is acceptable
|
||||||
|
// to pass a dst bigger than src, and in that case, Encrypt will
|
||||||
|
// only update dst[:len(src)] and will not touch the rest of dst.
|
||||||
|
//
|
||||||
|
// Multiple calls to Encrypt behave NOT same as if the concatenation of
|
||||||
|
// the src buffers was passed in a single run.
|
||||||
|
Encrypt(dst, src []byte)
|
||||||
|
|
||||||
|
// Decrypt decrypts a number of ciphertext bytes. The length of
|
||||||
|
// src must be NOT smaller than block size. Dst and src must overlap
|
||||||
|
// entirely or not at all.
|
||||||
|
//
|
||||||
|
// If len(dst) < len(src), Decrypt should panic. It is acceptable
|
||||||
|
// to pass a dst bigger than src, and in that case, Decrypt will
|
||||||
|
// only update dst[:len(src)] and will not touch the rest of dst.
|
||||||
|
//
|
||||||
|
// Multiple calls to Decrypt behave NOT same as if the concatenation of
|
||||||
|
// the src buffers was passed in a single run.
|
||||||
|
Decrypt(dst, src []byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hctr represents a Varaible-Input-Length enciphering mode with a specific block cipher,
|
||||||
|
// and specific tweak and a hash key. See
|
||||||
|
// https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.470.5288
|
||||||
|
// GB/T 17964-2021 第11章 带泛杂凑函数的计数器工作模式
|
||||||
|
type hctr struct {
|
||||||
|
cipher _cipher.Block
|
||||||
|
tweak [blockSize]byte
|
||||||
|
hkey [blockSize]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHCTR returns a [LengthPreservingMode] which encrypts/decrypts useing the given [Block]
|
||||||
|
// in HCTR mode. The lenght of tweak and hash key must be the same as the [Block]'s block size.
|
||||||
|
func NewHCTR(cipher _cipher.Block, tweak, hkey []byte) (LengthPreservingMode, error) {
|
||||||
|
if len(tweak) != blockSize || len(hkey) != blockSize {
|
||||||
|
return nil, errors.New("hctr: invalid tweak and/or hash key length")
|
||||||
|
}
|
||||||
|
c := &hctr{}
|
||||||
|
c.cipher = cipher
|
||||||
|
copy(c.hkey[:], hkey)
|
||||||
|
copy(c.tweak[:], tweak)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func _mul2(v *[blockSize]byte) {
|
||||||
|
var carryIn byte
|
||||||
|
for j := range v {
|
||||||
|
carryOut := (v[j] << 7) & 0x80
|
||||||
|
v[j] = (v[j] >> 1) + carryIn
|
||||||
|
carryIn = carryOut
|
||||||
|
}
|
||||||
|
if carryIn != 0 {
|
||||||
|
v[0] ^= 0xE1 // 1<<7 | 1<<6 | 1<<5 | 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mul sets y to y*hkey.
|
||||||
|
func (h *hctr) mul(y *[blockSize]byte) {
|
||||||
|
var z [blockSize]byte
|
||||||
|
for _, i := range h.hkey {
|
||||||
|
for k := 0; k < 8; k++ {
|
||||||
|
if (i>>(7-k))&1 == 1 {
|
||||||
|
subtle.XORBytes(z[:], z[:], y[:])
|
||||||
|
}
|
||||||
|
_mul2(y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
copy(y[:], z[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Universal Hash Function.
|
||||||
|
// Chapter 3.3 in https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.470.5288.
|
||||||
|
func (h *hctr) uhash(m []byte, dst *[blockSize]byte) {
|
||||||
|
for k := 0; k < blockSize; k++ {
|
||||||
|
dst[k] = 0
|
||||||
|
}
|
||||||
|
msg := m
|
||||||
|
for len(msg) >= blockSize {
|
||||||
|
subtle.XORBytes(dst[:], dst[:], msg[:blockSize])
|
||||||
|
h.mul(dst)
|
||||||
|
msg = msg[blockSize:]
|
||||||
|
}
|
||||||
|
var v [blockSize]byte
|
||||||
|
if len(msg) > 0 {
|
||||||
|
copy(v[:], msg)
|
||||||
|
copy(v[len(msg):], h.tweak[:])
|
||||||
|
subtle.XORBytes(dst[:], dst[:], v[:])
|
||||||
|
h.mul(dst)
|
||||||
|
copy(v[:], h.tweak[len(msg):])
|
||||||
|
for i := len(msg); i < blockSize; i++ {
|
||||||
|
v[i] = 0
|
||||||
|
}
|
||||||
|
subtle.XORBytes(dst[:], dst[:], v[:])
|
||||||
|
h.mul(dst)
|
||||||
|
for i := 0; i < len(msg); i++ {
|
||||||
|
v[i] = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
subtle.XORBytes(dst[:], dst[:], h.tweak[:])
|
||||||
|
h.mul(dst)
|
||||||
|
}
|
||||||
|
// (|M|)₂
|
||||||
|
binary.BigEndian.PutUint64(v[8:], uint64(len(m)+blockSize)<<3)
|
||||||
|
subtle.XORBytes(dst[:], dst[:], v[:])
|
||||||
|
h.mul(dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hctr) Encrypt(ciphertext, plaintext []byte) {
|
||||||
|
if len(ciphertext) < len(plaintext) {
|
||||||
|
panic("hctr: ciphertext is smaller than plaintext")
|
||||||
|
}
|
||||||
|
if len(plaintext) < blockSize {
|
||||||
|
panic("hctr: plaintext length is smaller than the block size")
|
||||||
|
}
|
||||||
|
if alias.InexactOverlap(ciphertext[:len(plaintext)], plaintext) {
|
||||||
|
panic("hctr: invalid buffer overlap")
|
||||||
|
}
|
||||||
|
|
||||||
|
var z1, z2 [blockSize]byte
|
||||||
|
|
||||||
|
// a) z1 generation
|
||||||
|
h.uhash(plaintext[blockSize:], &z1)
|
||||||
|
subtle.XORBytes(z1[:], z1[:], plaintext[:blockSize])
|
||||||
|
// b) z2 generation
|
||||||
|
h.cipher.Encrypt(z2[:], z1[:])
|
||||||
|
// c) CTR
|
||||||
|
subtle.XORBytes(z1[:], z1[:], z2[:])
|
||||||
|
h.ctr(ciphertext[blockSize:], plaintext[blockSize:], &z1)
|
||||||
|
// d) first ciphertext block generation
|
||||||
|
h.uhash(ciphertext[blockSize:], &z1)
|
||||||
|
subtle.XORBytes(ciphertext, z2[:], z1[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hctr) Decrypt(plaintext, ciphertext []byte) {
|
||||||
|
if len(plaintext) < len(ciphertext) {
|
||||||
|
panic("hctr: plaintext is smaller than cihpertext")
|
||||||
|
}
|
||||||
|
if len(ciphertext) < blockSize {
|
||||||
|
panic("hctr: ciphertext length is smaller than the block size")
|
||||||
|
}
|
||||||
|
if alias.InexactOverlap(plaintext[:len(ciphertext)], ciphertext) {
|
||||||
|
panic("hctr: invalid buffer overlap")
|
||||||
|
}
|
||||||
|
|
||||||
|
var z1, z2 [blockSize]byte
|
||||||
|
|
||||||
|
// a) z2 generation
|
||||||
|
h.uhash(ciphertext[blockSize:], &z2)
|
||||||
|
subtle.XORBytes(z2[:], z2[:], ciphertext[:blockSize])
|
||||||
|
// b) z1 generation
|
||||||
|
h.cipher.Decrypt(z1[:], z2[:])
|
||||||
|
// c) CTR
|
||||||
|
subtle.XORBytes(z2[:], z2[:], z1[:])
|
||||||
|
h.ctr(plaintext[blockSize:], ciphertext[blockSize:], &z2)
|
||||||
|
// d) first plaintext block generation
|
||||||
|
h.uhash(plaintext[blockSize:], &z2)
|
||||||
|
subtle.XORBytes(plaintext, z2[:], z1[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hctr) ctr(dst, src []byte, baseCtr *[blockSize]byte) {
|
||||||
|
ctr := make([]byte, blockSize)
|
||||||
|
num := make([]byte, blockSize)
|
||||||
|
i := uint64(1)
|
||||||
|
|
||||||
|
if concCipher, ok := h.cipher.(concurrentBlocks); ok {
|
||||||
|
batchSize := concCipher.Concurrency() * blockSize
|
||||||
|
if len(src) >= batchSize {
|
||||||
|
var ctrs []byte = make([]byte, batchSize)
|
||||||
|
for len(src) >= batchSize {
|
||||||
|
for j := 0; j < concCipher.Concurrency(); j++ {
|
||||||
|
// (i)₂
|
||||||
|
binary.BigEndian.PutUint64(num[blockSize-8:], i)
|
||||||
|
subtle.XORBytes(ctrs[j*blockSize:], baseCtr[:], num)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
concCipher.EncryptBlocks(ctrs, ctrs)
|
||||||
|
subtle.XORBytes(dst, src, ctrs)
|
||||||
|
src = src[batchSize:]
|
||||||
|
dst = dst[batchSize:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(src) > 0 {
|
||||||
|
// (i)₂
|
||||||
|
binary.BigEndian.PutUint64(num[blockSize-8:], i)
|
||||||
|
subtle.XORBytes(ctr, baseCtr[:], num)
|
||||||
|
h.cipher.Encrypt(ctr, ctr)
|
||||||
|
n := subtle.XORBytes(dst, src, ctr)
|
||||||
|
src = src[n:]
|
||||||
|
dst = dst[n:]
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
75
cipher/hctr_test.go
Normal file
75
cipher/hctr_test.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package cipher_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/emmansun/gmsm/cipher"
|
||||||
|
"github.com/emmansun/gmsm/sm4"
|
||||||
|
)
|
||||||
|
|
||||||
|
var hctrSM4TestVectors = []struct {
|
||||||
|
key string
|
||||||
|
hashKey string
|
||||||
|
tweak string
|
||||||
|
plaintext string
|
||||||
|
ciphertext string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"2B7E151628AED2A6ABF7158809CF4F3C",
|
||||||
|
"000102030405060708090A0B0C0D0E0F",
|
||||||
|
"F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF",
|
||||||
|
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c37106bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c37106bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
|
||||||
|
"8858dda3034233e377936b76ce7edeb6a245075a37800b0b996e8e974c9032ac8de40d90ee4ee5fb58bc10cbc95779485ab38ffb0b4f961d85f086db705ff723edbeaec649b3b406b11b96a418a9c2c51ef41cdd24e472c18336e9efcd07b7e264a1e2d46615198eb74938d72104fa89294a6360cdb6b032a704cf07a087bb2283598552701b2f710d6528d9c3f4dab529afef4413f25169b6cbf8168ccbfa02a2f507513d0cb3802da34dbd928b67e6afc30ca91011070cfd40c2ef3d4ac041",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"2B7E151628AED2A6ABF7158809CF4F3C",
|
||||||
|
"000102030405060708090A0B0C0D0E0F",
|
||||||
|
"F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF",
|
||||||
|
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
|
||||||
|
"9cd7481d3b7ca904b14b4084d9d4c83ed39eac8e16747895fc2ae1eecd220276af3d0d2f21cb3807561347c81ad138117dd85c652afe16a47dc68eb884068ae3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"2B7E151628AED2A6ABF7158809CF4F3C",
|
||||||
|
"000102030405060708090A0B0C0D0E0F",
|
||||||
|
"F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF",
|
||||||
|
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417b",
|
||||||
|
"f7505aff357ac13107cdb2848c6bb2dcdda473f7a6ea939d44f52c986c11ca9341042f2b0091a1ca5c8f708cae8ca6a5c59e2228b3616c4455627722",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"2B7E151628AED2A6ABF7158809CF4F3C",
|
||||||
|
"000102030405060708090A0B0C0D0E0F",
|
||||||
|
"F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF",
|
||||||
|
"6bc1bee22e409f96e93d7e117393172a",
|
||||||
|
"b7b1dd75f608012dc69621d4ea720a60",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHCTR(t *testing.T) {
|
||||||
|
for i, test := range hctrSM4TestVectors {
|
||||||
|
key1, _ := hex.DecodeString(test.key)
|
||||||
|
key2, _ := hex.DecodeString(test.hashKey)
|
||||||
|
tw, _ := hex.DecodeString(test.tweak)
|
||||||
|
plaintext, _ := hex.DecodeString(test.plaintext)
|
||||||
|
ciphertext, _ := hex.DecodeString(test.ciphertext)
|
||||||
|
got := make([]byte, len(plaintext))
|
||||||
|
c, err := sm4.NewCipher(key1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hctr, err := cipher.NewHCTR(c, tw, key2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hctr.Encrypt(got, plaintext)
|
||||||
|
if !bytes.Equal(got, ciphertext) {
|
||||||
|
t.Fatalf("%v case encrypt failed, got %x\n", i+1, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
hctr.Decrypt(got, ciphertext)
|
||||||
|
if !bytes.Equal(got, plaintext) {
|
||||||
|
t.Fatalf("%v case decrypt failed, got %x\n", i+1, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -317,8 +317,10 @@ func (c *xtsDecrypter) CryptBlocks(plaintext, ciphertext []byte) {
|
|||||||
func mul2Generic(tweak *[blockSize]byte, isGB bool) {
|
func mul2Generic(tweak *[blockSize]byte, isGB bool) {
|
||||||
var carryIn byte
|
var carryIn byte
|
||||||
if !isGB {
|
if !isGB {
|
||||||
// tweak[0] represents the coefficients of {x^7, x^6, ..., x^0}
|
// the coefficient of x⁰ can be obtained by tweak[0] & 1
|
||||||
// tweak[15] represents the coefficients of {x^127, x^126, ..., x^120}
|
// the coefficient of x⁷ can be obtained by tweak[0] >> 7
|
||||||
|
// the coefficient of x¹²⁰ can be obtained by tweak[15] & 1
|
||||||
|
// the coefficient of x¹²⁷ can be obtained by tweak[15] >> 7
|
||||||
for j := range tweak {
|
for j := range tweak {
|
||||||
carryOut := tweak[j] >> 7
|
carryOut := tweak[j] >> 7
|
||||||
tweak[j] = (tweak[j] << 1) + carryIn
|
tweak[j] = (tweak[j] << 1) + carryIn
|
||||||
@ -334,9 +336,11 @@ func mul2Generic(tweak *[blockSize]byte, isGB bool) {
|
|||||||
tweak[0] ^= GF128_FDBK // 1<<7 | 1<<2 | 1<<1 | 1
|
tweak[0] ^= GF128_FDBK // 1<<7 | 1<<2 | 1<<1 | 1
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// GB/T 17964-2021, because of the bit-ordering, doubling is actually a right shift.
|
// GB/T 17964-2021,
|
||||||
// tweak[0] represents the coefficients of {x^0, x^1, ..., x^7}
|
// the coefficient of x⁰ can be obtained by tweak[0] >> 7
|
||||||
// tweak[15] represents the coefficients of {x^120, x^121, ..., x^127}
|
// the coefficient of x⁷ can be obtained by tweak[0] & 1
|
||||||
|
// the coefficient of x¹²⁰ can be obtained by tweak[15] >> 7
|
||||||
|
// the coefficient of x¹²⁷ can be obtained by tweak[15] & 1
|
||||||
for j := range tweak {
|
for j := range tweak {
|
||||||
carryOut := (tweak[j] << 7) & 0x80
|
carryOut := (tweak[j] << 7) & 0x80
|
||||||
tweak[j] = (tweak[j] >> 1) + carryIn
|
tweak[j] = (tweak[j] >> 1) + carryIn
|
||||||
|
@ -62,7 +62,7 @@ ret:
|
|||||||
RET
|
RET
|
||||||
|
|
||||||
avx2:
|
avx2:
|
||||||
TESTQ $31, DX // AND 31 & len, if not zero jump to not_aligned.
|
TESTQ $31, DX // AND 31 & len, if not zero jump to avx2_not_aligned.
|
||||||
JNZ avx2_not_aligned
|
JNZ avx2_not_aligned
|
||||||
|
|
||||||
avx2_aligned:
|
avx2_aligned:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user