github.com/emmansun/gmsm@v0.29.1/sm4/gcm_ppc64x.go (about) 1 // Copyright 2024 Sun Yimin. All rights reserved. 2 // Use of this source code is governed by a MIT-style 3 // license that can be found in the LICENSE file. 4 5 //go:build (ppc64 || ppc64le) && !purego 6 7 package sm4 8 9 import ( 10 "crypto/cipher" 11 _subtle "crypto/subtle" 12 "encoding/binary" 13 "errors" 14 "runtime" 15 16 "github.com/emmansun/gmsm/internal/alias" 17 "github.com/emmansun/gmsm/internal/subtle" 18 ) 19 20 // Assert that sm4CipherAsm implements the gcmAble interface. 21 var _ gcmAble = (*sm4CipherAsm)(nil) 22 23 var errOpen = errors.New("cipher: message authentication failed") 24 25 //go:noescape 26 func gcmInit(productTable *[256]byte, h []byte) 27 28 //go:noescape 29 func gcmHash(output []byte, productTable *[256]byte, inp []byte, len int) 30 31 //go:noescape 32 func gcmMul(output []byte, productTable *[256]byte) 33 34 const ( 35 gcmBlockSize = 16 36 gcmTagSize = 16 37 gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes. 38 gcmStandardNonceSize = 12 39 ) 40 41 type gcmAsm struct { 42 cipher *sm4CipherAsm 43 nonceSize int 44 tagSize int 45 // productTable contains pre-computed multiples of the binary-field 46 // element used in GHASH. 47 productTable [256]byte 48 } 49 50 // NewGCM returns the AES cipher wrapped in Galois Counter Mode. This is only 51 // called by [crypto/cipher.NewGCM] via the gcmAble interface. 52 func (c *sm4CipherAsm) NewGCM(nonceSize, tagSize int) (cipher.AEAD, error) { 53 var h1, h2 uint64 54 g := &gcmAsm{cipher: c, nonceSize: nonceSize, tagSize: tagSize} 55 56 hle := make([]byte, gcmBlockSize) 57 58 c.Encrypt(hle, hle) 59 60 // Reverse the bytes in each 8 byte chunk 61 // Load little endian, store big endian 62 if runtime.GOARCH == "ppc64le" { 63 h1 = binary.LittleEndian.Uint64(hle[:8]) 64 h2 = binary.LittleEndian.Uint64(hle[8:]) 65 } else { 66 h1 = binary.BigEndian.Uint64(hle[:8]) 67 h2 = binary.BigEndian.Uint64(hle[8:]) 68 } 69 binary.BigEndian.PutUint64(hle[:8], h1) 70 binary.BigEndian.PutUint64(hle[8:], h2) 71 gcmInit(&g.productTable, hle) 72 73 return g, nil 74 } 75 76 func (g *gcmAsm) NonceSize() int { 77 return g.nonceSize 78 } 79 80 func (g *gcmAsm) Overhead() int { 81 return g.tagSize 82 } 83 84 // deriveCounter computes the initial GCM counter state from the given nonce. 85 func (g *gcmAsm) deriveCounter(counter *[gcmBlockSize]byte, nonce []byte) { 86 if len(nonce) == gcmStandardNonceSize { 87 copy(counter[:], nonce) 88 counter[gcmBlockSize-1] = 1 89 } else { 90 var hash [16]byte 91 g.paddedGHASH(&hash, nonce) 92 lens := gcmLengths(0, uint64(len(nonce))*8) 93 g.paddedGHASH(&hash, lens[:]) 94 copy(counter[:], hash[:]) 95 } 96 } 97 98 const fourBlocksSize = 64 99 const eightBlocksSize = fourBlocksSize * 2 100 101 // counterCrypt encrypts in using SM4 in counter mode and places the result 102 // into out. counter is the initial count value and will be updated with the next 103 // count value. The length of out must be greater than or equal to the length 104 // of in. 105 func (g *gcmAsm) counterCrypt(out, in []byte, counter *[gcmBlockSize]byte) { 106 var mask [eightBlocksSize]byte 107 var counters [eightBlocksSize]byte 108 109 for len(in) >= eightBlocksSize { 110 for i := 0; i < 8; i++ { 111 copy(counters[i*gcmBlockSize:(i+1)*gcmBlockSize], counter[:]) 112 gcmInc32(counter) 113 } 114 g.cipher.EncryptBlocks(mask[:], counters[:]) 115 subtle.XORBytes(out, in, mask[:]) 116 out = out[eightBlocksSize:] 117 in = in[eightBlocksSize:] 118 } 119 120 if len(in) >= fourBlocksSize { 121 for i := 0; i < 4; i++ { 122 copy(counters[i*gcmBlockSize:(i+1)*gcmBlockSize], counter[:]) 123 gcmInc32(counter) 124 } 125 g.cipher.EncryptBlocks(mask[:], counters[:]) 126 subtle.XORBytes(out, in, mask[:fourBlocksSize]) 127 out = out[fourBlocksSize:] 128 in = in[fourBlocksSize:] 129 } 130 131 if len(in) > 0 { 132 blocks := (len(in) + gcmBlockSize - 1) / gcmBlockSize 133 if blocks > 1 { 134 for i := 0; i < blocks; i++ { 135 copy(counters[i*gcmBlockSize:], counter[:]) 136 gcmInc32(counter) 137 } 138 g.cipher.EncryptBlocks(mask[:], counters[:]) 139 } else { 140 g.cipher.Encrypt(mask[:], counter[:]) 141 gcmInc32(counter) 142 } 143 subtle.XORBytes(out, in, mask[:blocks*gcmBlockSize]) 144 } 145 } 146 147 // increments the rightmost 32-bits of the count value by 1. 148 func gcmInc32(counterBlock *[16]byte) { 149 c := counterBlock[len(counterBlock)-4:] 150 x := binary.BigEndian.Uint32(c) + 1 151 binary.BigEndian.PutUint32(c, x) 152 } 153 154 // paddedGHASH pads data with zeroes until its length is a multiple of 155 // 16-bytes. It then calculates a new value for hash using the ghash 156 // algorithm. 157 func (g *gcmAsm) paddedGHASH(hash *[16]byte, data []byte) { 158 if siz := len(data) - (len(data) % gcmBlockSize); siz > 0 { 159 gcmHash(hash[:], &g.productTable, data[:], siz) 160 data = data[siz:] 161 } 162 if len(data) > 0 { 163 var s [16]byte 164 copy(s[:], data) 165 gcmHash(hash[:], &g.productTable, s[:], len(s)) 166 } 167 } 168 169 // auth calculates GHASH(ciphertext, additionalData), masks the result with 170 // tagMask and writes the result to out. 171 func (g *gcmAsm) auth(out, ciphertext, aad []byte, tagMask *[gcmTagSize]byte) { 172 var hash [gcmTagSize]byte 173 g.paddedGHASH(&hash, aad) 174 g.paddedGHASH(&hash, ciphertext) 175 lens := gcmLengths(uint64(len(aad))*8, uint64(len(ciphertext))*8) 176 g.paddedGHASH(&hash, lens[:]) 177 subtle.XORBytes(out, hash[:], tagMask[:]) 178 } 179 180 // Seal encrypts and authenticates plaintext. See the [cipher.AEAD] interface for 181 // details. 182 func (g *gcmAsm) Seal(dst, nonce, plaintext, data []byte) []byte { 183 if len(nonce) != g.nonceSize { 184 panic("cipher: incorrect nonce length given to GCM") 185 } 186 if uint64(len(plaintext)) > ((1<<32)-2)*BlockSize { 187 panic("cipher: message too large for GCM") 188 } 189 190 ret, out := alias.SliceForAppend(dst, len(plaintext)+g.tagSize) 191 if alias.InexactOverlap(out[:len(plaintext)], plaintext) { 192 panic("cipher: invalid buffer overlap") 193 } 194 195 var counter, tagMask [gcmBlockSize]byte 196 g.deriveCounter(&counter, nonce) 197 198 g.cipher.Encrypt(tagMask[:], counter[:]) 199 gcmInc32(&counter) 200 201 g.counterCrypt(out, plaintext, &counter) 202 var tag [gcmTagSize]byte 203 g.auth(tag[:], out[:len(plaintext)], data, &tagMask) 204 copy(out[len(plaintext):], tag[:]) 205 206 return ret 207 } 208 209 // Open authenticates and decrypts ciphertext. See the [cipher.AEAD] interface 210 // for details. 211 func (g *gcmAsm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { 212 if len(nonce) != g.nonceSize { 213 panic("cipher: incorrect nonce length given to GCM") 214 } 215 if len(ciphertext) < g.tagSize { 216 return nil, errOpen 217 } 218 if uint64(len(ciphertext)) > ((1<<32)-2)*uint64(BlockSize)+uint64(g.tagSize) { 219 return nil, errOpen 220 } 221 222 tag := ciphertext[len(ciphertext)-g.tagSize:] 223 ciphertext = ciphertext[:len(ciphertext)-g.tagSize] 224 225 var counter, tagMask [gcmBlockSize]byte 226 g.deriveCounter(&counter, nonce) 227 228 g.cipher.Encrypt(tagMask[:], counter[:]) 229 gcmInc32(&counter) 230 231 var expectedTag [gcmTagSize]byte 232 g.auth(expectedTag[:], ciphertext, data, &tagMask) 233 234 ret, out := alias.SliceForAppend(dst, len(ciphertext)) 235 if alias.InexactOverlap(out, ciphertext) { 236 panic("cipher: invalid buffer overlap") 237 } 238 239 if _subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 { 240 // clear(out) 241 for i := range out { 242 out[i] = 0 243 } 244 return nil, errOpen 245 } 246 247 g.counterCrypt(out, ciphertext, &counter) 248 return ret, nil 249 } 250 251 func gcmLengths(len0, len1 uint64) [16]byte { 252 return [16]byte{ 253 byte(len0 >> 56), 254 byte(len0 >> 48), 255 byte(len0 >> 40), 256 byte(len0 >> 32), 257 byte(len0 >> 24), 258 byte(len0 >> 16), 259 byte(len0 >> 8), 260 byte(len0), 261 byte(len1 >> 56), 262 byte(len1 >> 48), 263 byte(len1 >> 40), 264 byte(len1 >> 32), 265 byte(len1 >> 24), 266 byte(len1 >> 16), 267 byte(len1 >> 8), 268 byte(len1), 269 } 270 }