mirror of
https://github.com/XTLS/REALITY.git
synced 2025-08-22 22:48:36 +00:00
162 lines
5.2 KiB
Go
162 lines
5.2 KiB
Go
// Copyright 2024 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.
|
|
|
|
package gcm
|
|
|
|
import (
|
|
"encoding/binary"
|
|
)
|
|
|
|
// gcmFieldElement represents a value in GF(2¹²⁸). In order to reflect the GCM
|
|
// standard and make binary.BigEndian suitable for marshaling these values, the
|
|
// bits are stored in big endian order. For example:
|
|
//
|
|
// the coefficient of x⁰ can be obtained by v.low >> 63.
|
|
// the coefficient of x⁶³ can be obtained by v.low & 1.
|
|
// the coefficient of x⁶⁴ can be obtained by v.high >> 63.
|
|
// the coefficient of x¹²⁷ can be obtained by v.high & 1.
|
|
type gcmFieldElement struct {
|
|
low, high uint64
|
|
}
|
|
|
|
// GHASH is exposed to allow crypto/cipher to implement non-AES GCM modes.
|
|
// It is not allowed as a stand-alone operation in FIPS mode because it
|
|
// is not ACVP tested.
|
|
func GHASH(key *[16]byte, inputs ...[]byte) []byte {
|
|
// fips140.RecordNonApproved()
|
|
var out [gcmBlockSize]byte
|
|
ghash(&out, key, inputs...)
|
|
return out[:]
|
|
}
|
|
|
|
// ghash is a variable-time generic implementation of GHASH, which shouldn't
|
|
// be used on any architecture with hardware support for AES-GCM.
|
|
//
|
|
// Each input is zero-padded to 128-bit before being absorbed.
|
|
func ghash(out, H *[gcmBlockSize]byte, inputs ...[]byte) {
|
|
// productTable contains the first sixteen powers of the key, H.
|
|
// However, they are in bit reversed order.
|
|
var productTable [16]gcmFieldElement
|
|
|
|
// We precompute 16 multiples of H. However, when we do lookups
|
|
// into this table we'll be using bits from a field element and
|
|
// therefore the bits will be in the reverse order. So normally one
|
|
// would expect, say, 4*H to be in index 4 of the table but due to
|
|
// this bit ordering it will actually be in index 0010 (base 2) = 2.
|
|
x := gcmFieldElement{
|
|
binary.BigEndian.Uint64(H[:8]),
|
|
binary.BigEndian.Uint64(H[8:]),
|
|
}
|
|
productTable[reverseBits(1)] = x
|
|
|
|
for i := 2; i < 16; i += 2 {
|
|
productTable[reverseBits(i)] = ghashDouble(&productTable[reverseBits(i/2)])
|
|
productTable[reverseBits(i+1)] = ghashAdd(&productTable[reverseBits(i)], &x)
|
|
}
|
|
|
|
var y gcmFieldElement
|
|
for _, input := range inputs {
|
|
ghashUpdate(&productTable, &y, input)
|
|
}
|
|
|
|
binary.BigEndian.PutUint64(out[:], y.low)
|
|
binary.BigEndian.PutUint64(out[8:], y.high)
|
|
}
|
|
|
|
// reverseBits reverses the order of the bits of 4-bit number in i.
|
|
func reverseBits(i int) int {
|
|
i = ((i << 2) & 0xc) | ((i >> 2) & 0x3)
|
|
i = ((i << 1) & 0xa) | ((i >> 1) & 0x5)
|
|
return i
|
|
}
|
|
|
|
// ghashAdd adds two elements of GF(2¹²⁸) and returns the sum.
|
|
func ghashAdd(x, y *gcmFieldElement) gcmFieldElement {
|
|
// Addition in a characteristic 2 field is just XOR.
|
|
return gcmFieldElement{x.low ^ y.low, x.high ^ y.high}
|
|
}
|
|
|
|
// ghashDouble returns the result of doubling an element of GF(2¹²⁸).
|
|
func ghashDouble(x *gcmFieldElement) (double gcmFieldElement) {
|
|
msbSet := x.high&1 == 1
|
|
|
|
// Because of the bit-ordering, doubling is actually a right shift.
|
|
double.high = x.high >> 1
|
|
double.high |= x.low << 63
|
|
double.low = x.low >> 1
|
|
|
|
// If the most-significant bit was set before shifting then it,
|
|
// conceptually, becomes a term of x^128. This is greater than the
|
|
// irreducible polynomial so the result has to be reduced. The
|
|
// irreducible polynomial is 1+x+x^2+x^7+x^128. We can subtract that to
|
|
// eliminate the term at x^128 which also means subtracting the other
|
|
// four terms. In characteristic 2 fields, subtraction == addition ==
|
|
// XOR.
|
|
if msbSet {
|
|
double.low ^= 0xe100000000000000
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
var ghashReductionTable = []uint16{
|
|
0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
|
|
0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0,
|
|
}
|
|
|
|
// ghashMul sets y to y*H, where H is the GCM key, fixed during New.
|
|
func ghashMul(productTable *[16]gcmFieldElement, y *gcmFieldElement) {
|
|
var z gcmFieldElement
|
|
|
|
for i := 0; i < 2; i++ {
|
|
word := y.high
|
|
if i == 1 {
|
|
word = y.low
|
|
}
|
|
|
|
// Multiplication works by multiplying z by 16 and adding in
|
|
// one of the precomputed multiples of H.
|
|
for j := 0; j < 64; j += 4 {
|
|
msw := z.high & 0xf
|
|
z.high >>= 4
|
|
z.high |= z.low << 60
|
|
z.low >>= 4
|
|
z.low ^= uint64(ghashReductionTable[msw]) << 48
|
|
|
|
// the values in |table| are ordered for little-endian bit
|
|
// positions. See the comment in New.
|
|
t := productTable[word&0xf]
|
|
|
|
z.low ^= t.low
|
|
z.high ^= t.high
|
|
word >>= 4
|
|
}
|
|
}
|
|
|
|
*y = z
|
|
}
|
|
|
|
// updateBlocks extends y with more polynomial terms from blocks, based on
|
|
// Horner's rule. There must be a multiple of gcmBlockSize bytes in blocks.
|
|
func updateBlocks(productTable *[16]gcmFieldElement, y *gcmFieldElement, blocks []byte) {
|
|
for len(blocks) > 0 {
|
|
y.low ^= binary.BigEndian.Uint64(blocks)
|
|
y.high ^= binary.BigEndian.Uint64(blocks[8:])
|
|
ghashMul(productTable, y)
|
|
blocks = blocks[gcmBlockSize:]
|
|
}
|
|
}
|
|
|
|
// ghashUpdate extends y with more polynomial terms from data. If data is not a
|
|
// multiple of gcmBlockSize bytes long then the remainder is zero padded.
|
|
func ghashUpdate(productTable *[16]gcmFieldElement, y *gcmFieldElement, data []byte) {
|
|
fullBlocks := (len(data) >> 4) << 4
|
|
updateBlocks(productTable, y, data[:fullBlocks])
|
|
|
|
if len(data) != fullBlocks {
|
|
var partialBlock [gcmBlockSize]byte
|
|
copy(partialBlock[:], data[fullBlocks:])
|
|
updateBlocks(productTable, y, partialBlock[:])
|
|
}
|
|
} |