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  }