From b1184c24cfaa736a07998a8379ad45673fc109b6 Mon Sep 17 00:00:00 2001 From: Emman Date: Thu, 8 Apr 2021 11:12:52 +0800 Subject: [PATCH] MAGIC - xts mode --- cipher/xts.go | 246 +++++++++++++++++++++++++++++++++++++ cipher/xts_test.go | 115 +++++++++++++++++ sm4/cipher_asm.go | 30 +++++ sm4_test/benchmark_test.go | 52 ++++++++ sm4_test/xts_sm4_test.go | 93 ++++++++++++++ 5 files changed, 536 insertions(+) create mode 100644 cipher/xts.go create mode 100644 cipher/xts_test.go create mode 100644 sm4_test/xts_sm4_test.go diff --git a/cipher/xts.go b/cipher/xts.go new file mode 100644 index 0000000..cc4641d --- /dev/null +++ b/cipher/xts.go @@ -0,0 +1,246 @@ +package cipher + +import ( + _cipher "crypto/cipher" + "encoding/binary" + "errors" + "sync" +) + +const GF128_FDBK byte = 0x87 + +type CipherCreator func([]byte) (_cipher.Block, error) + +type concurrentBlocks interface { + Concurrency() int + EncryptBlocks(dst, src []byte) + DecryptBlocks(dst, src []byte) +} + +// A XTSBlockMode represents a block cipher running in a XTS mode +type XTSBlockMode interface { + // BlockSize returns the mode's block size. + BlockSize() int + + // Encrypt encrypts or decrypts a number of blocks. The length of + // src must be a multiple of the block size. Dst and src must overlap + // entirely or not at all. + // + Encrypt(dst, src []byte, sectorNum uint64) + + // Decrypt decrypts a number of blocks. The length of + // src must be a multiple of the block size. Dst and src must overlap + // entirely or not at all. + // + Decrypt(dst, src []byte, sectorNum uint64) +} + +// Cipher contains an expanded key structure. It is safe for concurrent use if +// the underlying block cipher is safe for concurrent use. +type xts struct { + k1, k2 _cipher.Block +} + +// blockSize is the block size that the underlying cipher must have. XTS is +// only defined for 16-byte ciphers. +const blockSize = 16 + +var tweakPool = sync.Pool{ + New: func() interface{} { + return new([blockSize]byte) + }, +} + +// NewXTS creates a Cipher given a function for creating the underlying +// block cipher (which must have a block size of 16 bytes). The key must be +// twice the length of the underlying cipher's key. +func NewXTS(cipherFunc CipherCreator, key []byte) (XTSBlockMode, error) { + k1, err := cipherFunc(key[:len(key)/2]) + if err != nil { + return nil, err + } + k2, err := cipherFunc(key[len(key)/2:]) + c := &xts{ + k1, + k2, + } + + if c.k1.BlockSize() != blockSize { + err = errors.New("xts: cipher does not have a block size of 16") + return nil, err + } + return c, nil +} + +func (c *xts) BlockSize() int { + return blockSize +} + +// Encrypt encrypts a sector of plaintext and puts the result into ciphertext. +// Plaintext and ciphertext must overlap entirely or not at all. +// Sectors must be a multiple of 16 bytes and less than 2²⁴ bytes. +func (c *xts) Encrypt(ciphertext, plaintext []byte, sectorNum uint64) { + if len(ciphertext) < len(plaintext) { + panic("xts: ciphertext is smaller than plaintext") + } + if len(plaintext) < blockSize { + panic("xts: plaintext length is smaller than the block size") + } + if InexactOverlap(ciphertext[:len(plaintext)], plaintext) { + panic("xts: invalid buffer overlap") + } + + tweak := tweakPool.Get().(*[blockSize]byte) + + for i := range tweak { + tweak[i] = 0 + } + binary.LittleEndian.PutUint64(tweak[:8], sectorNum) + c.k2.Encrypt(tweak[:], tweak[:]) + + lastCiphertext := ciphertext + + if concCipher, ok := c.k1.(concurrentBlocks); ok { + batchSize := concCipher.Concurrency() * blockSize + var tweaks []byte = make([]byte, batchSize) + + for len(plaintext) >= batchSize { + for i := 0; i < concCipher.Concurrency(); i++ { + copy(tweaks[blockSize*i:], tweak[:]) + mul2(tweak) + } + XorBytes(ciphertext, plaintext, tweaks) + concCipher.EncryptBlocks(ciphertext, ciphertext) + XorBytes(ciphertext, ciphertext, tweaks) + plaintext = plaintext[batchSize:] + lastCiphertext = ciphertext[batchSize-blockSize:] + ciphertext = ciphertext[batchSize:] + } + } + for len(plaintext) >= blockSize { + XorBytes(ciphertext, plaintext, tweak[:]) + c.k1.Encrypt(ciphertext, ciphertext) + XorBytes(ciphertext, ciphertext, tweak[:]) + plaintext = plaintext[blockSize:] + lastCiphertext = ciphertext + ciphertext = ciphertext[blockSize:] + mul2(tweak) + } + // is there a final partial block to handle? + if remain := len(plaintext); remain > 0 { + var x [blockSize]byte + //Copy the final ciphertext bytes + copy(ciphertext, lastCiphertext[:remain]) + //Copy the final plaintext bytes + copy(x[:], plaintext) + //Steal ciphertext to complete the block + copy(x[remain:], lastCiphertext[remain:blockSize]) + //Merge the tweak into the input block + XorBytes(x[:], x[:], tweak[:]) + //Encrypt the final block using K1 + c.k1.Encrypt(x[:], x[:]) + //Merge the tweak into the output block + XorBytes(lastCiphertext, x[:], tweak[:]) + } + tweakPool.Put(tweak) +} + +// Decrypt decrypts a sector of ciphertext and puts the result into plaintext. +// Plaintext and ciphertext must overlap entirely or not at all. +// Sectors must be a multiple of 16 bytes and less than 2²⁴ bytes. +func (c *xts) Decrypt(plaintext, ciphertext []byte, sectorNum uint64) { + if len(plaintext) < len(ciphertext) { + panic("xts: plaintext is smaller than ciphertext") + } + if len(ciphertext) < blockSize { + panic("xts: ciphertext length is smaller than the block size") + } + if InexactOverlap(plaintext[:len(ciphertext)], ciphertext) { + panic("xts: invalid buffer overlap") + } + + tweak := tweakPool.Get().(*[blockSize]byte) + for i := range tweak { + tweak[i] = 0 + } + binary.LittleEndian.PutUint64(tweak[:8], sectorNum) + + c.k2.Encrypt(tweak[:], tweak[:]) + + if concCipher, ok := c.k1.(concurrentBlocks); ok { + batchSize := concCipher.Concurrency() * blockSize + var tweaks []byte = make([]byte, batchSize) + + for len(ciphertext) >= batchSize { + for i := 0; i < concCipher.Concurrency(); i++ { + copy(tweaks[blockSize*i:], tweak[:]) + mul2(tweak) + } + XorBytes(plaintext, ciphertext, tweaks) + concCipher.DecryptBlocks(plaintext, plaintext) + XorBytes(plaintext, plaintext, tweaks) + plaintext = plaintext[batchSize:] + ciphertext = ciphertext[batchSize:] + } + } + + for len(ciphertext) >= 2*blockSize { + XorBytes(plaintext, ciphertext, tweak[:]) + c.k1.Decrypt(plaintext, plaintext) + XorBytes(plaintext, plaintext, tweak[:]) + plaintext = plaintext[blockSize:] + ciphertext = ciphertext[blockSize:] + + mul2(tweak) + } + + if remain := len(ciphertext); remain >= blockSize { + var x [blockSize]byte + if remain > blockSize { + var tt [blockSize]byte + copy(tt[:], tweak[:]) + mul2(&tt) + XorBytes(x[:], ciphertext, tt[:]) + c.k1.Decrypt(x[:], x[:]) + XorBytes(plaintext, x[:], tt[:]) + + //Retrieve the length of the final block + remain -= blockSize + + //Copy the final plaintext bytes + copy(plaintext[blockSize:], plaintext) + //Copy the final ciphertext bytes + copy(x[:], ciphertext[blockSize:]) + //Steal ciphertext to complete the block + copy(x[remain:], plaintext[remain:blockSize]) + } else { + //The last block contains exactly 128 bits + copy(x[:], ciphertext) + } + XorBytes(x[:], x[:], tweak[:]) + c.k1.Decrypt(x[:], x[:]) + XorBytes(plaintext, x[:], tweak[:]) + } + + tweakPool.Put(tweak) +} + +// mul2 multiplies tweak by 2 in GF(2¹²⁸) with an irreducible polynomial of +// x¹²⁸ + x⁷ + x² + x + 1. +func mul2(tweak *[blockSize]byte) { + var carryIn byte + for j := range tweak { + carryOut := tweak[j] >> 7 + tweak[j] = (tweak[j] << 1) + carryIn + carryIn = carryOut + } + if carryIn != 0 { + // If we have a carry bit then we need to subtract a multiple + // of the irreducible polynomial (x¹²⁸ + x⁷ + x² + x + 1). + // By dropping the carry bit, we're subtracting the x^128 term + // so all that remains is to subtract x⁷ + x² + x + 1. + // Subtraction (and addition) in this representation is just + // XOR. + tweak[0] ^= GF128_FDBK // 1<<7 | 1<<2 | 1<<1 | 1 + } +} diff --git a/cipher/xts_test.go b/cipher/xts_test.go new file mode 100644 index 0000000..f4c1c34 --- /dev/null +++ b/cipher/xts_test.go @@ -0,0 +1,115 @@ +package cipher + +import ( + "bytes" + "crypto/aes" + "encoding/hex" + "testing" +) + +// These test vectors have been taken from IEEE P1619/D16, Annex B. +var xtsTestVectors = []struct { + key string + sector uint64 + plaintext string + ciphertext string +}{ + { // XTS-AES-128 applied for a data unit of 32 bytes + "0000000000000000000000000000000000000000000000000000000000000000", + 0, + "0000000000000000000000000000000000000000000000000000000000000000", + "917cf69ebd68b2ec9b9fe9a3eadda692cd43d2f59598ed858c02c2652fbf922e", + }, { + "1111111111111111111111111111111122222222222222222222222222222222", + 0x3333333333, + "4444444444444444444444444444444444444444444444444444444444444444", + "c454185e6a16936e39334038acef838bfb186fff7480adc4289382ecd6d394f0", + }, { + "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f022222222222222222222222222222222", + 0x3333333333, + "4444444444444444444444444444444444444444444444444444444444444444", + "af85336b597afc1a900b2eb21ec949d292df4c047e0b21532186a5971a227a89", + }, { // XTS-AES-128 applied for a data unit of 512 bytes + "2718281828459045235360287471352631415926535897932384626433832795", + 0, + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "27a7479befa1d476489f308cd4cfa6e2a96e4bbe3208ff25287dd3819616e89cc78cf7f5e543445f8333d8fa7f56000005279fa5d8b5e4ad40e736ddb4d35412328063fd2aab53e5ea1e0a9f332500a5df9487d07a5c92cc512c8866c7e860ce93fdf166a24912b422976146ae20ce846bb7dc9ba94a767aaef20c0d61ad02655ea92dc4c4e41a8952c651d33174be51a10c421110e6d81588ede82103a252d8a750e8768defffed9122810aaeb99f9172af82b604dc4b8e51bcb08235a6f4341332e4ca60482a4ba1a03b3e65008fc5da76b70bf1690db4eae29c5f1badd03c5ccf2a55d705ddcd86d449511ceb7ec30bf12b1fa35b913f9f747a8afd1b130e94bff94effd01a91735ca1726acd0b197c4e5b03393697e126826fb6bbde8ecc1e08298516e2c9ed03ff3c1b7860f6de76d4cecd94c8119855ef5297ca67e9f3e7ff72b1e99785ca0a7e7720c5b36dc6d72cac9574c8cbbc2f801e23e56fd344b07f22154beba0f08ce8891e643ed995c94d9a69c9f1b5f499027a78572aeebd74d20cc39881c213ee770b1010e4bea718846977ae119f7a023ab58cca0ad752afe656bb3c17256a9f6e9bf19fdd5a38fc82bbe872c5539edb609ef4f79c203ebb140f2e583cb2ad15b4aa5b655016a8449277dbd477ef2c8d6c017db738b18deb4a427d1923ce3ff262735779a418f20a282df920147beabe421ee5319d0568", + }, { // Vector 5 + "2718281828459045235360287471352631415926535897932384626433832795", + 1, + "27a7479befa1d476489f308cd4cfa6e2a96e4bbe3208ff25287dd3819616e89cc78cf7f5e543445f8333d8fa7f56000005279fa5d8b5e4ad40e736ddb4d35412328063fd2aab53e5ea1e0a9f332500a5df9487d07a5c92cc512c8866c7e860ce93fdf166a24912b422976146ae20ce846bb7dc9ba94a767aaef20c0d61ad02655ea92dc4c4e41a8952c651d33174be51a10c421110e6d81588ede82103a252d8a750e8768defffed9122810aaeb99f9172af82b604dc4b8e51bcb08235a6f4341332e4ca60482a4ba1a03b3e65008fc5da76b70bf1690db4eae29c5f1badd03c5ccf2a55d705ddcd86d449511ceb7ec30bf12b1fa35b913f9f747a8afd1b130e94bff94effd01a91735ca1726acd0b197c4e5b03393697e126826fb6bbde8ecc1e08298516e2c9ed03ff3c1b7860f6de76d4cecd94c8119855ef5297ca67e9f3e7ff72b1e99785ca0a7e7720c5b36dc6d72cac9574c8cbbc2f801e23e56fd344b07f22154beba0f08ce8891e643ed995c94d9a69c9f1b5f499027a78572aeebd74d20cc39881c213ee770b1010e4bea718846977ae119f7a023ab58cca0ad752afe656bb3c17256a9f6e9bf19fdd5a38fc82bbe872c5539edb609ef4f79c203ebb140f2e583cb2ad15b4aa5b655016a8449277dbd477ef2c8d6c017db738b18deb4a427d1923ce3ff262735779a418f20a282df920147beabe421ee5319d0568", + "264d3ca8512194fec312c8c9891f279fefdd608d0c027b60483a3fa811d65ee59d52d9e40ec5672d81532b38b6b089ce951f0f9c35590b8b978d175213f329bb1c2fd30f2f7f30492a61a532a79f51d36f5e31a7c9a12c286082ff7d2394d18f783e1a8e72c722caaaa52d8f065657d2631fd25bfd8e5baad6e527d763517501c68c5edc3cdd55435c532d7125c8614deed9adaa3acade5888b87bef641c4c994c8091b5bcd387f3963fb5bc37aa922fbfe3df4e5b915e6eb514717bdd2a74079a5073f5c4bfd46adf7d282e7a393a52579d11a028da4d9cd9c77124f9648ee383b1ac763930e7162a8d37f350b2f74b8472cf09902063c6b32e8c2d9290cefbd7346d1c779a0df50edcde4531da07b099c638e83a755944df2aef1aa31752fd323dcb710fb4bfbb9d22b925bc3577e1b8949e729a90bbafeacf7f7879e7b1147e28ba0bae940db795a61b15ecf4df8db07b824bb062802cc98a9545bb2aaeed77cb3fc6db15dcd7d80d7d5bc406c4970a3478ada8899b329198eb61c193fb6275aa8ca340344a75a862aebe92eee1ce032fd950b47d7704a3876923b4ad62844bf4a09c4dbe8b4397184b7471360c9564880aedddb9baa4af2e75394b08cd32ff479c57a07d3eab5d54de5f9738b8d27f27a9f0ab11799d7b7ffefb2704c95c6ad12c39f1e867a4b7b1d7818a4b753dfd2a89ccb45e001a03a867b187f225dd", + }, { // Vector 10, XTS-AES-256 applied for a data unit of 512 bytes + "27182818284590452353602874713526624977572470936999595749669676273141592653589793238462643383279502884197169399375105820974944592", + 0xff, + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "1c3b3a102f770386e4836c99e370cf9bea00803f5e482357a4ae12d414a3e63b5d31e276f8fe4a8d66b317f9ac683f44680a86ac35adfc3345befecb4bb188fd5776926c49a3095eb108fd1098baec70aaa66999a72a82f27d848b21d4a741b0c5cd4d5fff9dac89aeba122961d03a757123e9870f8acf1000020887891429ca2a3e7a7d7df7b10355165c8b9a6d0a7de8b062c4500dc4cd120c0f7418dae3d0b5781c34803fa75421c790dfe1de1834f280d7667b327f6c8cd7557e12ac3a0f93ec05c52e0493ef31a12d3d9260f79a289d6a379bc70c50841473d1a8cc81ec583e9645e07b8d9670655ba5bbcfecc6dc3966380ad8fecb17b6ba02469a020a84e18e8f84252070c13e9f1f289be54fbc481457778f616015e1327a02b140f1505eb309326d68378f8374595c849d84f4c333ec4423885143cb47bd71c5edae9be69a2ffeceb1bec9de244fbe15992b11b77c040f12bd8f6a975a44a0f90c29a9abc3d4d893927284c58754cce294529f8614dcd2aba991925fedc4ae74ffac6e333b93eb4aff0479da9a410e4450e0dd7ae4c6e2910900575da401fc07059f645e8b7e9bfdef33943054ff84011493c27b3429eaedb4ed5376441a77ed43851ad77f16f541dfd269d50d6a5f14fb0aab1cbb4c1550be97f7ab4066193c4caa773dad38014bd2092fa755c824bb5e54c4f36ffda9fcea70b9c6e693e148c151", + }, { // XTS-AES-128 applied for a data unit that is not a multiple of 16 bytes, but should be a complte byte + "c46acc2e7e013cb71cdbf750cf76b000249fbf4fb6cd17607773c23ffa2c4330", + 94, + "7e9c2289cba460e470222953439cdaa892a5433d4dab2a3f67", + "9af624641d42b036377ef37b4a158f49e49f6ee308ad449ecf", + }, { + "56ffcc9bbbdf413f0fc0f888f44b7493bb1925a39b8adf02d9009bb16db0a887", + 144, + "9a839cc14363bafcfc0cc93b14f8e769d35b94cc98267438e3", + "fbd8dcfc4d662259f48b151728c3b37233a35127a77051ee9d", + }, + { + "7454a43b87b1cf0dec95032c22873be3cace3bb795568854c1a008c07c5813f3", + 108, + "41088fa15195b2733fe824d2c1fdc8306080863945fb2a73cf", + "f916d877f817ae390f42dc54723bda0ad3ba5f331a72d05ccb", + }, +} + +func fromHex(s string) []byte { + ret, err := hex.DecodeString(s) + if err != nil { + panic("xts: invalid hex in test") + } + return ret +} + +func TestXTS(t *testing.T) { + for i, test := range xtsTestVectors { + c, err := NewXTS(aes.NewCipher, fromHex(test.key)) + if err != nil { + t.Errorf("#%d: failed to create cipher: %s", i, err) + continue + } + plaintext := fromHex(test.plaintext) + ciphertext := make([]byte, len(plaintext)) + + c.Encrypt(ciphertext, plaintext, test.sector) + expectedCiphertext := fromHex(test.ciphertext) + if !bytes.Equal(ciphertext, expectedCiphertext) { + t.Errorf("#%d: encrypted failed, got: %x, want: %x", i, ciphertext, expectedCiphertext) + continue + } + + decrypted := make([]byte, len(ciphertext)) + c.Decrypt(decrypted, ciphertext, test.sector) + if !bytes.Equal(decrypted, plaintext) { + t.Errorf("#%d: decryption failed, got: %x, want: %x", i, decrypted, plaintext) + } + } +} + +func TestShorterCiphertext(t *testing.T) { + c, err := NewXTS(aes.NewCipher, make([]byte, 32)) + if err != nil { + t.Fatalf("NewCipher failed: %s", err) + } + + plaintext := make([]byte, 32) + encrypted := make([]byte, 48) + decrypted := make([]byte, 48) + + c.Encrypt(encrypted, plaintext, 0) + c.Decrypt(decrypted, encrypted[:len(plaintext)], 0) + + if !bytes.Equal(plaintext, decrypted[:len(plaintext)]) { + t.Errorf("En/Decryption is not inverse") + } +} diff --git a/sm4/cipher_asm.go b/sm4/cipher_asm.go index 7817144..0540cfd 100644 --- a/sm4/cipher_asm.go +++ b/sm4/cipher_asm.go @@ -39,8 +39,12 @@ func newCipher(key []byte) (cipher.Block, error) { const FourBlocksSize = 64 +const BatchBlocks = 4 + func (c *sm4CipherAsm) BlockSize() int { return BlockSize } +func (c *sm4CipherAsm) Concurrency() int { return BatchBlocks } + func (c *sm4CipherAsm) Encrypt(dst, src []byte) { if len(src) < BlockSize { panic("sm4: input not full block") @@ -54,6 +58,19 @@ func (c *sm4CipherAsm) Encrypt(dst, src []byte) { encryptBlockAsm(&c.enc[0], &dst[0], &src[0]) } +func (c *sm4CipherAsm) EncryptBlocks(dst, src []byte) { + if len(src) < FourBlocksSize { + panic("sm4: input not full blocks") + } + if len(dst) < FourBlocksSize { + panic("sm4: output not full blocks") + } + if smcipher.InexactOverlap(dst[:FourBlocksSize], src[:FourBlocksSize]) { + panic("sm4: invalid buffer overlap") + } + encryptBlocksAsm(&c.enc[0], &dst[0], &src[0]) +} + func (c *sm4CipherAsm) Decrypt(dst, src []byte) { if len(src) < BlockSize { panic("sm4: input not full block") @@ -67,6 +84,19 @@ func (c *sm4CipherAsm) Decrypt(dst, src []byte) { encryptBlockAsm(&c.dec[0], &dst[0], &src[0]) } +func (c *sm4CipherAsm) DecryptBlocks(dst, src []byte) { + if len(src) < FourBlocksSize { + panic("sm4: input not full blocks") + } + if len(dst) < FourBlocksSize { + panic("sm4: output not full blocks") + } + if smcipher.InexactOverlap(dst[:FourBlocksSize], src[:FourBlocksSize]) { + panic("sm4: invalid buffer overlap") + } + encryptBlocksAsm(&c.dec[0], &dst[0], &src[0]) +} + // expandKey is used by BenchmarkExpand to ensure that the asm implementation // of key expansion is used for the benchmark when it is available. func expandKey(key []byte, enc, dec []uint32) { diff --git a/sm4_test/benchmark_test.go b/sm4_test/benchmark_test.go index a7a9523..bb6e1a8 100644 --- a/sm4_test/benchmark_test.go +++ b/sm4_test/benchmark_test.go @@ -361,3 +361,55 @@ func benchmarkSM4CCMOpen(b *testing.B, buf []byte) { sm4gcm, _ := smcipher.NewCCM(c) benchmarkGCMOpen(b, sm4gcm, buf) } + +func benchmarkXTS(b *testing.B, cipherFunc func([]byte) (cipher.Block, error), length, keylen int64) { + c, err := smcipher.NewXTS(cipherFunc, make([]byte, keylen)) + if err != nil { + b.Fatalf("NewCipher failed: %s", err) + } + plaintext := make([]byte, length) + encrypted := make([]byte, length) + //decrypted := make([]byte, length) + b.SetBytes(int64(len(plaintext))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.Encrypt(encrypted, plaintext, 0) + //c.Decrypt(decrypted, encrypted[:len(plaintext)], 0) + } +} + +func BenchmarkAES128XTSEncrypt512(b *testing.B) { + benchmarkXTS(b, aes.NewCipher, 512, 32) +} + +func BenchmarkAES128XTSEncrypt1K(b *testing.B) { + benchmarkXTS(b, aes.NewCipher, 1024, 32) +} + +func BenchmarkAES128XTSEncrypt4K(b *testing.B) { + benchmarkXTS(b, aes.NewCipher, 4096, 32) +} + +func BenchmarkAES256XTSEncrypt512(b *testing.B) { + benchmarkXTS(b, aes.NewCipher, 512, 64) +} + +func BenchmarkAES256XTSEncrypt1K(b *testing.B) { + benchmarkXTS(b, aes.NewCipher, 1024, 64) +} + +func BenchmarkAES256XTSEncrypt4K(b *testing.B) { + benchmarkXTS(b, aes.NewCipher, 4096, 64) +} + +func BenchmarkSM4XTSEncrypt512(b *testing.B) { + benchmarkXTS(b, sm4.NewCipher, 512, 32) +} + +func BenchmarkSM4XTSEncrypt1K(b *testing.B) { + benchmarkXTS(b, sm4.NewCipher, 1024, 32) +} + +func BenchmarkSM4XTSEncrypt4K(b *testing.B) { + benchmarkXTS(b, sm4.NewCipher, 4096, 32) +} diff --git a/sm4_test/xts_sm4_test.go b/sm4_test/xts_sm4_test.go new file mode 100644 index 0000000..e0ee4ab --- /dev/null +++ b/sm4_test/xts_sm4_test.go @@ -0,0 +1,93 @@ +package sm4_test + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/emmansun/gmsm/cipher" + "github.com/emmansun/gmsm/sm4" +) + +var xtsTestVectors = []struct { + key string + sector uint64 + plaintext string + ciphertext string +}{ + { // XTS-SM4-128 applied for a data unit of 32 bytes + "0000000000000000000000000000000000000000000000000000000000000000", + 0, + "0000000000000000000000000000000000000000000000000000000000000000", + "d9b421f731c894fdc35b77291fe4e3b02a1fb76698d59f0e51376c4ada5bc75d", + }, { + "1111111111111111111111111111111122222222222222222222222222222222", + 0x3333333333, + "4444444444444444444444444444444444444444444444444444444444444444", + "a74d726c11196a32be04e001ff29d0c7932f9f3ec29bfcb64dd17f63cbd3ea31", + }, { + "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f022222222222222222222222222222222", + 0x3333333333, + "4444444444444444444444444444444444444444444444444444444444444444", + "7f76088effadf70c02ea9f95da0628d351bfcb9eac0563bcf17b710dab0a9826", + }, { // XTS-SM4-128 applied for a data unit of 512 bytes + "2718281828459045235360287471352631415926535897932384626433832795", + 0, + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "54dd65b6326faea8fad1a83c63614af39f721d8dfe177a30b66abf6a449980e1cdbe06afb73336f37a4d39de964a30d7d04a3799169c60258f6b748a61861aa5ec92a2c15b2b7c615a42aba499bbd6b71db9c789b2182089a25dd3df800ed1864d19f7ed45fd17a9480b0fb82d9b7fc3ed57e9a1140eaa778dd2dd679e3edc3dc4d55c950ebc531d9592f7c4638256d56518292a20af98fdd3a63600350a70ab5a40f4c285037ca01f251f19ecae0329ff77ad88cd5a4cdea2aeabc22148ffbd239bd10515bde1131dec8404e443dc763140d5f22bf33e0c6872d6b81d630f6f00cdd058fe80f9cbfb77707f93cee2ca92b915b8304027c190a84e2d65e018cc6a387d3766acdb28253284e8db9acf8f52280ddc6d0033d2ccaaa4f9aeff123669bc024fd6768edf8bc1f8d622c19c609ef97f609190cd110241e7fb084ed8942da1f9b9cf1b514b61a388b30ea61a4a745b381ee7ad6c4db1275453b8413f98df6e4a40986ee4b59af5dfaecd301265179067a00d7ca35ab95abd617adea28ec1c26a97de28b8bfe30120d6aefbd258c59e42d161e8065a78106bdca5cd90fb3aac4e93866c8a7f9676860a79145bd92e02e819a90be0b97cc522b32106856fdf0e54d88e4624155a2f1c14eaeaa163f858e99a806e791acd82f1b0e29f0028a4c38e976f571a93f4fd57d787c24db0e01ca304e5a5c4dd50cf8bdbf491e57c", + }, { // Vector 5 + "2718281828459045235360287471352631415926535897932384626433832795", + 1, + "27a7479befa1d476489f308cd4cfa6e2a96e4bbe3208ff25287dd3819616e89cc78cf7f5e543445f8333d8fa7f56000005279fa5d8b5e4ad40e736ddb4d35412328063fd2aab53e5ea1e0a9f332500a5df9487d07a5c92cc512c8866c7e860ce93fdf166a24912b422976146ae20ce846bb7dc9ba94a767aaef20c0d61ad02655ea92dc4c4e41a8952c651d33174be51a10c421110e6d81588ede82103a252d8a750e8768defffed9122810aaeb99f9172af82b604dc4b8e51bcb08235a6f4341332e4ca60482a4ba1a03b3e65008fc5da76b70bf1690db4eae29c5f1badd03c5ccf2a55d705ddcd86d449511ceb7ec30bf12b1fa35b913f9f747a8afd1b130e94bff94effd01a91735ca1726acd0b197c4e5b03393697e126826fb6bbde8ecc1e08298516e2c9ed03ff3c1b7860f6de76d4cecd94c8119855ef5297ca67e9f3e7ff72b1e99785ca0a7e7720c5b36dc6d72cac9574c8cbbc2f801e23e56fd344b07f22154beba0f08ce8891e643ed995c94d9a69c9f1b5f499027a78572aeebd74d20cc39881c213ee770b1010e4bea718846977ae119f7a023ab58cca0ad752afe656bb3c17256a9f6e9bf19fdd5a38fc82bbe872c5539edb609ef4f79c203ebb140f2e583cb2ad15b4aa5b655016a8449277dbd477ef2c8d6c017db738b18deb4a427d1923ce3ff262735779a418f20a282df920147beabe421ee5319d0568", + "0e36ba273dd121afc77e0d8c00aa4a662b21f363470d333f2fe2ddcbcc51ecd523022f5fa7970062800cd3859cacead369263681543db431f3844a3638e837cf025cecc3b778e14ac1fd02bb684d0e3cc3d05758cf4b3827bae92f9f09a45487e0a830154a4206a14c4077bcc928e6039b78cdf8f915236c5a4efc21a0ba7173232cef6f18f8b53be5e1eb37282bed31a24f322cf1bba02dfd2583ce216a73726915116fd8ce46d58aa562b5a5d88076792d6e35cba40552db6a19776eaf255c3fc927adc41cb83a83884f98176267f37e543ce34fa32960d1d05aa05ff04103037a730175f1d59a32b64f308925fc9fa9c60421b4ab438e14504227cba20c8c06b508554fb02e52b92a1cd0a8e386511bc4c2fb62998d0ac5d9e7614080a10039b8cddf24a644b3e0aa02bb5d6c0897a84bfe0d12690cbd9fb92fd39b5b9504deeeaab0c5b9839b6283b87abe6439d28f0afb0508104fd4db9fd6e0301c6a488e76fd2a4801d2b7df57e0179506e9a8dbd7312be3922ea4e7339227061485452296dabc3b0f178a2e4ba012bbb6e836dec5d25abaa0f399ca622c5f075dfae7b2ffef4e396cd74b9bc3aeb7c212a5fd5c42b73fcf92e1f4ca458bb50e7257c4ffea253f30f7eaf9a6762ce15177f55ba250a4293d6ecdbd2e9a80c942b38dbdbd74773245a7a7db6b91d1f6c74bd32b7a7a193a2d260d266b64dd19b959ae42", + }, { // XTS-SM4-128 applied for a data unit that is not a multiple of 16 bytes, but should be a complte byte + "c46acc2e7e013cb71cdbf750cf76b000249fbf4fb6cd17607773c23ffa2c4330", + 94, + "7e9c2289cba460e470222953439cdaa892a5433d4dab2a3f67", + "c3cf5445c64aa518f4abce2848faddfb4605d9fb66f1f12c0c", + }, { + "56ffcc9bbbdf413f0fc0f888f44b7493bb1925a39b8adf02d9009bb16db0a887", + 144, + "9a839cc14363bafcfc0cc93b14f8e769d35b94cc98267438e3", + "af027012c829206c32a31706999d046f10a83bcacbc5c96353", + }, + { + "7454a43b87b1cf0dec95032c22873be3cace3bb795568854c1a008c07c5813f3", + 108, + "41088fa15195b2733fe824d2c1fdc8306080863945fb2a73cf", + "614ee9311a53791889338eb2f66fedff7dc15126349bed1465", + }, +} + +func fromHex(s string) []byte { + ret, err := hex.DecodeString(s) + if err != nil { + panic("xts: invalid hex in test") + } + return ret +} + +func TestXTS(t *testing.T) { + for i, test := range xtsTestVectors { + c, err := cipher.NewXTS(sm4.NewCipher, fromHex(test.key)) + if err != nil { + t.Errorf("#%d: failed to create cipher: %s", i, err) + continue + } + plaintext := fromHex(test.plaintext) + ciphertext := make([]byte, len(plaintext)) + + c.Encrypt(ciphertext, plaintext, test.sector) + expectedCiphertext := fromHex(test.ciphertext) + if !bytes.Equal(ciphertext, expectedCiphertext) { + t.Errorf("#%d: encrypted failed, got: %x, want: %x", i, ciphertext, expectedCiphertext) + continue + } + + decrypted := make([]byte, len(ciphertext)) + c.Decrypt(decrypted, ciphertext, test.sector) + if !bytes.Equal(decrypted, plaintext) { + t.Errorf("#%d: decryption failed, got: %x, want: %x", i, decrypted, plaintext) + } + } +}