-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
368 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
|
||
// Copyright 2024 Sun Yimin. All rights reserved. | ||
// Use of this source code is governed by a MIT-style | ||
// license that can be found in the LICENSE file. | ||
|
||
//go:build purego || !s390x | ||
|
||
package cipher | ||
|
||
import ( | ||
_cipher "crypto/cipher" | ||
) | ||
|
||
// A proxy of Golang cipher gcm mode. | ||
|
||
// NewGCM returns the given 128-bit, block cipher wrapped in Galois Counter Mode | ||
// with the standard nonce length. | ||
// | ||
// In general, the GHASH operation performed by this implementation of GCM is not constant-time. | ||
// An exception is when the underlying Block was created by aes.NewCipher | ||
// on systems with hardware support for AES. See the crypto/aes package documentation for details. | ||
func NewGCM(cipher _cipher.Block) (_cipher.AEAD, error) { | ||
return _cipher.NewGCM(cipher) | ||
} | ||
|
||
// NewGCMWithNonceSize returns the given 128-bit, block cipher wrapped in Galois | ||
// Counter Mode, which accepts nonces of the given length. The length must not | ||
// be zero. | ||
// | ||
// Only use this function if you require compatibility with an existing | ||
// cryptosystem that uses non-standard nonce lengths. All other users should use | ||
// NewGCM, which is faster and more resistant to misuse. | ||
func NewGCMWithNonceSize(cipher _cipher.Block, size int) (_cipher.AEAD, error) { | ||
return _cipher.NewGCMWithNonceSize(cipher, size) | ||
} | ||
|
||
// NewGCMWithTagSize returns the given 128-bit, block cipher wrapped in Galois | ||
// Counter Mode, which generates tags with the given length. | ||
// | ||
// Tag sizes between 12 and 16 bytes are allowed. | ||
// | ||
// Only use this function if you require compatibility with an existing | ||
// cryptosystem that uses non-standard tag lengths. All other users should use | ||
// NewGCM, which is more resistant to misuse. | ||
func NewGCMWithTagSize(cipher _cipher.Block, tagSize int) (_cipher.AEAD, error) { | ||
return _cipher.NewGCMWithTagSize(cipher, tagSize) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,285 @@ | ||
// Copyright 2024 Sun Yimin. All rights reserved. | ||
// Use of this source code is governed by a MIT-style | ||
// license that can be found in the LICENSE file. | ||
|
||
//go:build !purego | ||
|
||
package cipher | ||
|
||
import ( | ||
_cipher "crypto/cipher" | ||
_subtle "crypto/subtle" | ||
"encoding/binary" | ||
"errors" | ||
|
||
"github.com/emmansun/gmsm/internal/alias" | ||
"github.com/emmansun/gmsm/internal/subtle" | ||
) | ||
|
||
// gcmAble is an interface implemented by ciphers that have a specific optimized | ||
// implementation of GCM, like crypto/aes. NewGCM will check for this interface | ||
// and return the specific AEAD if found. | ||
type gcmAble interface { | ||
NewGCM(nonceSize, tagSize int) (_cipher.AEAD, error) | ||
} | ||
|
||
// gcmHashKey represents the 16-byte hash key required by the GHASH algorithm. | ||
type gcmHashKey [16]byte | ||
|
||
// gcm represents a Galois Counter Mode with a specific key. See | ||
// https://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf | ||
type gcm struct { | ||
cipher _cipher.Block | ||
nonceSize int | ||
tagSize int | ||
hashKey gcmHashKey | ||
} | ||
|
||
// NewGCM returns the given 128-bit, block cipher wrapped in Galois Counter Mode | ||
// with the standard nonce length. | ||
// | ||
// In general, the GHASH operation performed by this implementation of GCM is not constant-time. | ||
// An exception is when the underlying Block was created by aes.NewCipher | ||
// on systems with hardware support for AES. See the crypto/aes package documentation for details. | ||
func NewGCM(cipher _cipher.Block) (_cipher.AEAD, error) { | ||
return newGCMWithNonceAndTagSize(cipher, gcmStandardNonceSize, gcmTagSize) | ||
} | ||
|
||
// NewGCMWithNonceSize returns the given 128-bit, block cipher wrapped in Galois | ||
// Counter Mode, which accepts nonces of the given length. The length must not | ||
// be zero. | ||
// | ||
// Only use this function if you require compatibility with an existing | ||
// cryptosystem that uses non-standard nonce lengths. All other users should use | ||
// NewGCM, which is faster and more resistant to misuse. | ||
func NewGCMWithNonceSize(cipher _cipher.Block, size int) (_cipher.AEAD, error) { | ||
return newGCMWithNonceAndTagSize(cipher, size, gcmTagSize) | ||
} | ||
|
||
// NewGCMWithTagSize returns the given 128-bit, block cipher wrapped in Galois | ||
// Counter Mode, which generates tags with the given length. | ||
// | ||
// Tag sizes between 12 and 16 bytes are allowed. | ||
// | ||
// Only use this function if you require compatibility with an existing | ||
// cryptosystem that uses non-standard tag lengths. All other users should use | ||
// NewGCM, which is more resistant to misuse. | ||
func NewGCMWithTagSize(cipher _cipher.Block, tagSize int) (_cipher.AEAD, error) { | ||
return newGCMWithNonceAndTagSize(cipher, gcmStandardNonceSize, tagSize) | ||
} | ||
|
||
func newGCMWithNonceAndTagSize(cipher _cipher.Block, nonceSize, tagSize int) (_cipher.AEAD, error) { | ||
if tagSize < gcmMinimumTagSize || tagSize > gcmBlockSize { | ||
return nil, errors.New("cipher: incorrect tag size given to GCM") | ||
} | ||
|
||
if nonceSize <= 0 { | ||
return nil, errors.New("cipher: the nonce can't have zero length, or the security of the key will be immediately compromised") | ||
} | ||
|
||
if cipher, ok := cipher.(gcmAble); ok { | ||
return cipher.NewGCM(nonceSize, tagSize) | ||
} | ||
|
||
if cipher.BlockSize() != gcmBlockSize { | ||
return nil, errors.New("cipher: NewGCM requires 128-bit block cipher") | ||
} | ||
|
||
var hk gcmHashKey | ||
cipher.Encrypt(hk[:], hk[:]) | ||
|
||
g := &gcm{cipher: cipher, nonceSize: nonceSize, tagSize: tagSize, hashKey: hk} | ||
|
||
return g, nil | ||
} | ||
|
||
const ( | ||
gcmBlockSize = 16 | ||
gcmTagSize = 16 | ||
gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes. | ||
gcmStandardNonceSize = 12 | ||
) | ||
|
||
func (g *gcm) NonceSize() int { | ||
return g.nonceSize | ||
} | ||
|
||
func (g *gcm) Overhead() int { | ||
return g.tagSize | ||
} | ||
|
||
// gcmCount represents a 16-byte big-endian count value. | ||
type gcmCount [16]byte | ||
|
||
// inc increments the rightmost 32-bits of the count value by 1. | ||
func (x *gcmCount) inc() { | ||
binary.BigEndian.PutUint32(x[len(x)-4:], binary.BigEndian.Uint32(x[len(x)-4:])+1) | ||
} | ||
|
||
// ghash uses the GHASH algorithm to hash data with the given key. The initial | ||
// hash value is given by hash which will be updated with the new hash value. | ||
// The length of data must be a multiple of 16-bytes. | ||
// | ||
//go:noescape | ||
func ghash(key *gcmHashKey, hash *[16]byte, data []byte) | ||
|
||
// paddedGHASH pads data with zeroes until its length is a multiple of | ||
// 16-bytes. It then calculates a new value for hash using the GHASH algorithm. | ||
func (g *gcm) paddedGHASH(hash *[16]byte, data []byte) { | ||
siz := len(data) &^ 0xf // align size to 16-bytes | ||
if siz > 0 { | ||
ghash(&g.hashKey, hash, data[:siz]) | ||
data = data[siz:] | ||
} | ||
if len(data) > 0 { | ||
var s [16]byte | ||
copy(s[:], data) | ||
ghash(&g.hashKey, hash, s[:]) | ||
} | ||
} | ||
|
||
// gcmLengths writes len0 || len1 as big-endian values to a 16-byte array. | ||
func gcmLengths(len0, len1 uint64) [16]byte { | ||
v := [16]byte{} | ||
binary.BigEndian.PutUint64(v[0:], len0) | ||
binary.BigEndian.PutUint64(v[8:], len1) | ||
return v | ||
} | ||
|
||
// auth calculates GHASH(ciphertext, additionalData), masks the result with | ||
// tagMask and writes the result to out. | ||
func (g *gcm) auth(out, ciphertext, additionalData []byte, tagMask *[gcmTagSize]byte) { | ||
var hash [16]byte | ||
g.paddedGHASH(&hash, additionalData) | ||
g.paddedGHASH(&hash, ciphertext) | ||
lens := gcmLengths(uint64(len(additionalData))*8, uint64(len(ciphertext))*8) | ||
g.paddedGHASH(&hash, lens[:]) | ||
|
||
copy(out, hash[:]) | ||
for i := range out { | ||
out[i] ^= tagMask[i] | ||
} | ||
} | ||
|
||
// deriveCounter computes the initial GCM counter state from the given nonce. | ||
// See NIST SP 800-38D, section 7.1. This assumes that counter is filled with | ||
// zeros on entry. | ||
func (g *gcm) deriveCounter(nonce []byte) gcmCount { | ||
// GCM has two modes of operation with respect to the initial counter | ||
// state: a "fast path" for 96-bit (12-byte) nonces, and a "slow path" | ||
// for nonces of other lengths. For a 96-bit nonce, the nonce, along | ||
// with a four-byte big-endian counter starting at one, is used | ||
// directly as the starting counter. For other nonce sizes, the counter | ||
// is computed by passing it through the GHASH function. | ||
var counter gcmCount | ||
if len(nonce) == gcmStandardNonceSize { | ||
copy(counter[:], nonce) | ||
counter[gcmBlockSize-1] = 1 | ||
} else { | ||
var hash [16]byte | ||
g.paddedGHASH(&hash, nonce) | ||
lens := gcmLengths(0, uint64(len(nonce))*8) | ||
g.paddedGHASH(&hash, lens[:]) | ||
copy(counter[:], hash[:]) | ||
} | ||
return counter | ||
} | ||
|
||
// counterCrypt crypts in to out using g.cipher in counter mode. | ||
func (g *gcm) counterCrypt(out, in []byte, counter *gcmCount) { | ||
var mask [gcmBlockSize]byte | ||
|
||
for len(in) >= gcmBlockSize { | ||
g.cipher.Encrypt(mask[:], counter[:]) | ||
counter.inc() | ||
|
||
subtle.XORBytes(out, in, mask[:]) | ||
out = out[gcmBlockSize:] | ||
in = in[gcmBlockSize:] | ||
} | ||
|
||
if len(in) > 0 { | ||
g.cipher.Encrypt(mask[:], counter[:]) | ||
counter.inc() | ||
subtle.XORBytes(out, in, mask[:]) | ||
} | ||
} | ||
|
||
// Seal encrypts and authenticates plaintext. See the cipher.AEAD interface for | ||
// details. | ||
func (g *gcm) Seal(dst, nonce, plaintext, data []byte) []byte { | ||
if len(nonce) != g.nonceSize { | ||
panic("crypto/cipher: incorrect nonce length given to GCM") | ||
} | ||
if uint64(len(plaintext)) > ((1<<32)-2)*uint64(g.cipher.BlockSize()) { | ||
panic("crypto/cipher: message too large for GCM") | ||
} | ||
|
||
ret, out := alias.SliceForAppend(dst, len(plaintext)+g.tagSize) | ||
if alias.InexactOverlap(out[:len(plaintext)], plaintext) { | ||
panic("crypto/cipher: invalid buffer overlap") | ||
} | ||
|
||
counter := g.deriveCounter(nonce) | ||
|
||
var tagMask [gcmBlockSize]byte | ||
g.cipher.Encrypt(tagMask[:], counter[:]) | ||
counter.inc() | ||
|
||
var tagOut [gcmTagSize]byte | ||
g.counterCrypt(out, plaintext, &counter) | ||
g.auth(tagOut[:], out[:len(plaintext)], data, &tagMask) | ||
copy(out[len(plaintext):], tagOut[:]) | ||
|
||
return ret | ||
} | ||
|
||
// Open authenticates and decrypts ciphertext. See the cipher.AEAD interface | ||
// for details. | ||
func (g *gcm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { | ||
if len(nonce) != g.nonceSize { | ||
panic("crypto/cipher: incorrect nonce length given to GCM") | ||
} | ||
// Sanity check to prevent the authentication from always succeeding if an implementation | ||
// leaves tagSize uninitialized, for example. | ||
if g.tagSize < gcmMinimumTagSize { | ||
panic("crypto/cipher: incorrect GCM tag size") | ||
} | ||
if len(ciphertext) < g.tagSize { | ||
return nil, errOpen | ||
} | ||
if uint64(len(ciphertext)) > ((1<<32)-2)*uint64(g.cipher.BlockSize())+uint64(g.tagSize) { | ||
return nil, errOpen | ||
} | ||
|
||
tag := ciphertext[len(ciphertext)-g.tagSize:] | ||
ciphertext = ciphertext[:len(ciphertext)-g.tagSize] | ||
|
||
counter := g.deriveCounter(nonce) | ||
|
||
var tagMask [gcmBlockSize]byte | ||
g.cipher.Encrypt(tagMask[:], counter[:]) | ||
counter.inc() | ||
|
||
var expectedTag [gcmTagSize]byte | ||
g.auth(expectedTag[:], ciphertext, data, &tagMask) | ||
|
||
ret, out := alias.SliceForAppend(dst, len(ciphertext)) | ||
if alias.InexactOverlap(out, ciphertext) { | ||
panic("crypto/cipher: invalid buffer overlap") | ||
} | ||
|
||
if _subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 { | ||
// The AESNI code decrypts and authenticates concurrently, and | ||
// so overwrites dst in the event of a tag mismatch. That | ||
// behavior is mimicked here in order to be consistent across | ||
// platforms. | ||
for i := range out { | ||
out[i] = 0 | ||
} | ||
return nil, errOpen | ||
} | ||
|
||
g.counterCrypt(out, ciphertext, &counter) | ||
return ret, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
|
||
// Copyright 2016 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
//go:build !purego | ||
|
||
#include "textflag.h" | ||
|
||
// func ghash(key *gcmHashKey, hash *[16]byte, data []byte) | ||
TEXT ·ghash(SB),NOSPLIT,$32-40 | ||
MOVD $65, R0 // GHASH function code | ||
MOVD key+0(FP), R2 | ||
LMG (R2), R6, R7 | ||
MOVD hash+8(FP), R8 | ||
LMG (R8), R4, R5 | ||
MOVD $params-32(SP), R1 | ||
STMG R4, R7, (R1) | ||
LMG data+16(FP), R2, R3 // R2=base, R3=len | ||
loop: | ||
WORD $0xB93E0002 // compute intermediate message digest (KIMD) | ||
BVS loop // branch back if interrupted | ||
MVC $16, (R1), (R8) | ||
MOVD $0, R0 | ||
RET |
Oops, something went wrong.