1// Copyright 2020 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14// 15//////////////////////////////////////////////////////////////////////////////// 16 17package subtle 18 19import ( 20 "encoding/binary" 21 "fmt" 22) 23 24const ( 25 // PolyvalBlockSize is the block size (in bytes) that POLYVAL uses. 26 PolyvalBlockSize = 16 27 28 u32Sel0 uint32 = 0x11111111 29 u32Sel1 uint32 = 0x22222222 30 u32Sel2 uint32 = 0x44444444 31 u32Sel3 uint32 = 0x88888888 32 33 u64Sel0 uint64 = 0x1111111111111111 34 u64Sel1 uint64 = 0x2222222222222222 35 u64Sel2 uint64 = 0x4444444444444444 36 u64Sel3 uint64 = 0x8888888888888888 37) 38 39// Polyval (RFC 8452) is a universal hash function which operates on GF(2^128) 40// and can be used for constructing a Message Authentication Code (MAC). 41// See Section 3 of go/rfc/8452 for definition. 42type Polyval interface { 43 // update the accumulator in the object with the blocks from data. If data 44 // is not a multiple of 16 bytes, it is automatically zero padded. 45 Update(data []byte) 46 47 // finish completes the polyval computation and returns the result. 48 Finish() [PolyvalBlockSize]byte 49} 50 51// fieldElement represents a value in GF(2^128). 52// In order to reflect the Polyval standard and make binary.LittleEndian suitable 53// for marshaling these values, the bits are stored in little endian order. 54// For example: 55// the coefficient of x^0 can be obtained by v.lo & 1. 56// the coefficient of x^63 can be obtained by v.lo >> 63. 57// the coefficient of x^64 can be obtained by v.hi & 1. 58// the coefficient of x^127 can be obtained by v.hi >> 63. 59type fieldElement struct { 60 lo, hi uint64 61} 62 63// polyval implements the POLYVAL function as defined by go/rfc/8452. 64type polyval struct { 65 key fieldElement 66 acc fieldElement 67} 68 69// Assert that polyval implements Polyval interface 70var _ Polyval = (*polyval)(nil) 71 72// mul32 multiplies two 32 bit polynomials in GF(2^128) using Karatsuba multiplication. 73func mul32(a uint32, b uint32) uint64 { 74 a0 := uint64(a & u32Sel0) 75 a1 := uint64(a & u32Sel1) 76 a2 := uint64(a & u32Sel2) 77 a3 := uint64(a & u32Sel3) 78 79 b0 := uint64(b & u32Sel0) 80 b1 := uint64(b & u32Sel1) 81 b2 := uint64(b & u32Sel2) 82 b3 := uint64(b & u32Sel3) 83 84 c0 := (a0 * b0) ^ (a1 * b3) ^ (a2 * b2) ^ (a3 * b1) 85 c1 := (a0 * b1) ^ (a1 * b0) ^ (a2 * b3) ^ (a3 * b2) 86 c2 := (a0 * b2) ^ (a1 * b1) ^ (a2 * b0) ^ (a3 * b3) 87 c3 := (a0 * b3) ^ (a1 * b2) ^ (a2 * b1) ^ (a3 * b0) 88 89 return (c0 & u64Sel0) | (c1 & u64Sel1) | (c2 & u64Sel2) | (c3 & u64Sel3) 90} 91 92// mul64 multiplies two 64 bit polynomials in GF(2^128) using Karatsuba multiplication. 93func mul64(a uint64, b uint64) fieldElement { 94 a0 := uint32(a & 0xffffffff) 95 a1 := uint32(a >> 32) 96 97 b0 := uint32(b & 0xffffffff) 98 b1 := uint32(b >> 32) 99 100 lo := mul32(a0, b0) 101 hi := mul32(a1, b1) 102 mid := mul32(a0^a1, b0^b1) ^ lo ^ hi 103 104 return fieldElement{lo: lo ^ (mid << 32), hi: hi ^ (mid >> 32)} 105} 106 107// polyvalDot implements the dot operation defined by go/rfc/8452. 108// dot(a, b) = a * b * x^-128. 109// The value of the field element x^-128 is equal to x^127 + x^124 + x^121 + x^114 + 1. 110// The result of this multiplication, dot(a, b), is another field element. 111// The implementation here is inspired from BoringSSL's implementation of gcm_polyval_nohw(). 112// Ref: https://boringssl.googlesource.com/boringssl/+/master/crypto/fipsmodule/modes/gcm_nohw.c 113func polyvalDot(a fieldElement, b fieldElement) fieldElement { 114 // Karatsuba multiplication. The product of |a| and |b| is stored in |r0| and |r1| 115 // Note there is no byte or bit reversal because we are evaluating POLYVAL. 116 r0 := mul64(a.lo, b.lo) 117 r1 := mul64(a.hi, b.hi) 118 119 mid := mul64(a.lo^a.hi, b.lo^b.hi) 120 mid.lo ^= r0.lo ^ r1.lo 121 mid.hi ^= r0.hi ^ r1.hi 122 123 r1.lo ^= mid.hi 124 r0.hi ^= mid.lo 125 126 // Now we multiply our 256-bit result by x^-128 and reduce. 127 // |r1| shifts into position and we must multiply |r0| by x^-128. We have: 128 // 129 // 1 = x^121 + x^126 + x^127 + x^128 130 // x^-128 = x^-7 + x^-2 + x^-1 + 1 131 // 132 // This is the GHASH reduction step, but with bits flowing in reverse. 133 // The x^-7, x^-2, and x^-1 terms shift bits past x^0, which would require 134 // another reduction steps. Instead, we gather the excess bits, incorporate 135 // them into |r0| and reduce once. 136 // Ref: slides 17-19 of https://crypto.stanford.edu/RealWorldCrypto/slides/gueron.pdf. 137 r0.hi ^= (r0.lo << 63) ^ (r0.lo << 62) ^ (r0.lo << 57) 138 139 // 1 140 r1.lo ^= r0.lo 141 r1.hi ^= r0.hi 142 143 // x^-1 144 r1.lo ^= r0.lo >> 1 145 r1.lo ^= r0.hi << 63 146 r1.hi ^= r0.hi >> 1 147 148 // x^-2 149 r1.lo ^= r0.lo >> 2 150 r1.lo ^= r0.hi << 62 151 r1.hi ^= r0.hi >> 2 152 153 // x^-7 154 r1.lo ^= r0.lo >> 7 155 r1.lo ^= r0.hi << 57 156 r1.hi ^= r0.hi >> 7 157 158 return r1 159} 160 161// NewPolyval returns a Polyval instance. 162func NewPolyval(key []byte) (Polyval, error) { 163 if len(key) != PolyvalBlockSize { 164 return nil, fmt.Errorf("polyval: Invalid key size: %d", len(key)) 165 } 166 167 return &polyval{ 168 key: fieldElement{ 169 lo: binary.LittleEndian.Uint64(key[:8]), 170 hi: binary.LittleEndian.Uint64(key[8:]), 171 }, 172 }, nil 173} 174 175func (p *polyval) Update(data []byte) { 176 var block []byte 177 for len(data) > 0 { 178 if len(data) >= PolyvalBlockSize { 179 block = data[:PolyvalBlockSize] 180 data = data[PolyvalBlockSize:] 181 } else { 182 var partialBlock [PolyvalBlockSize]byte 183 copy(partialBlock[:], data) 184 block = partialBlock[:] 185 data = data[len(data):] 186 } 187 188 p.acc.lo ^= binary.LittleEndian.Uint64(block[:8]) 189 p.acc.hi ^= binary.LittleEndian.Uint64(block[8:]) 190 p.acc = polyvalDot(p.acc, p.key) 191 } 192} 193 194func (p *polyval) Finish() (hash [PolyvalBlockSize]byte) { 195 binary.LittleEndian.PutUint64(hash[:8], p.acc.lo) 196 binary.LittleEndian.PutUint64(hash[8:], p.acc.hi) 197 return 198} 199