Browse Source

Add a constant time GHASH(). #6

Taken from BearSSL again, because the implementation seems sensible.
Unlike AES, I probably only will do the `ghash_ctmul64.c` code, since
the benchmarks on the site indicate that running the 64 bit code on
32 bit systems isn't a devastating performance hit.

And really, the 90s called, they want their processors back.
Yawning Angel 2 years ago
parent
commit
9fd815f19c
2 changed files with 284 additions and 0 deletions
  1. 129 0
      ghash/ghash.go
  2. 155 0
      ghash/ghash_test.go

+ 129 - 0
ghash/ghash.go

@@ -0,0 +1,129 @@
+// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
+// Copyright (c) 2017 Yawning Angel <yawning at schwanenlied dot me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+// Package ghash is a constant time 64 bit optimized GHASH implementation.
+package ghash
+
+import "encoding/binary"
+
+const blockSize = 16
+
+func bmul64(x, y uint64) uint64 {
+	x0 := x & 0x1111111111111111
+	x1 := x & 0x2222222222222222
+	x2 := x & 0x4444444444444444
+	x3 := x & 0x8888888888888888
+	y0 := y & 0x1111111111111111
+	y1 := y & 0x2222222222222222
+	y2 := y & 0x4444444444444444
+	y3 := y & 0x8888888888888888
+	z0 := (x0 * y0) ^ (x1 * y3) ^ (x2 * y2) ^ (x3 * y1)
+	z1 := (x0 * y1) ^ (x1 * y0) ^ (x2 * y3) ^ (x3 * y2)
+	z2 := (x0 * y2) ^ (x1 * y1) ^ (x2 * y0) ^ (x3 * y3)
+	z3 := (x0 * y3) ^ (x1 * y2) ^ (x2 * y1) ^ (x3 * y0)
+	z0 &= 0x1111111111111111
+	z1 &= 0x2222222222222222
+	z2 &= 0x4444444444444444
+	z3 &= 0x8888888888888888
+	return z0 | z1 | z2 | z3
+}
+
+func rev64(x uint64) uint64 {
+	x = ((x & 0x5555555555555555) << 1) | ((x >> 1) & 0x5555555555555555)
+	x = ((x & 0x3333333333333333) << 2) | ((x >> 2) & 0x3333333333333333)
+	x = ((x & 0x0F0F0F0F0F0F0F0F) << 4) | ((x >> 4) & 0x0F0F0F0F0F0F0F0F)
+	x = ((x & 0x00FF00FF00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF00FF00FF)
+	x = ((x & 0x0000FFFF0000FFFF) << 16) | ((x >> 16) & 0x0000FFFF0000FFFF)
+	return (x << 32) | (x >> 32)
+}
+
+// Ghash calculates the GHASH of data, with key h, and input y, and stores the
+// resulting digest in y.
+func Ghash(y, h *[blockSize]byte, data []byte) {
+	var tmp [blockSize]byte
+	var src []byte
+
+	buf := data
+	l := len(buf)
+
+	y1 := binary.BigEndian.Uint64(y[:])
+	y0 := binary.BigEndian.Uint64(y[8:])
+	h1 := binary.BigEndian.Uint64(h[:])
+	h0 := binary.BigEndian.Uint64(h[8:])
+	h0r := rev64(h0)
+	h1r := rev64(h1)
+	h2 := h0 ^ h1
+	h2r := h0r ^ h1r
+
+	for l > 0 {
+		if l >= blockSize {
+			src = buf
+			buf = buf[blockSize:]
+			l -= blockSize
+		} else {
+			copy(tmp[:], buf)
+			src = tmp[:]
+			l = 0
+		}
+		y1 ^= binary.BigEndian.Uint64(src)
+		y0 ^= binary.BigEndian.Uint64(src[8:])
+
+		y0r := rev64(y0)
+		y1r := rev64(y1)
+		y2 := y0 ^ y1
+		y2r := y0r ^ y1r
+
+		z0 := bmul64(y0, h0)
+		z1 := bmul64(y1, h1)
+		z2 := bmul64(y2, h2)
+		z0h := bmul64(y0r, h0r)
+		z1h := bmul64(y1r, h1r)
+		z2h := bmul64(y2r, h2r)
+		z2 ^= z0 ^ z1
+		z2h ^= z0h ^ z1h
+		z0h = rev64(z0h) >> 1
+		z1h = rev64(z1h) >> 1
+		z2h = rev64(z2h) >> 1
+
+		v0 := z0
+		v1 := z0h ^ z2
+		v2 := z1 ^ z2h
+		v3 := z1h
+
+		v3 = (v3 << 1) | (v2 >> 63)
+		v2 = (v2 << 1) | (v1 >> 63)
+		v1 = (v1 << 1) | (v0 >> 63)
+		v0 = (v0 << 1)
+
+		v2 ^= v0 ^ (v0 >> 1) ^ (v0 >> 2) ^ (v0 >> 7)
+		v1 ^= (v0 << 63) ^ (v0 << 62) ^ (v0 << 57)
+		v3 ^= v1 ^ (v1 >> 1) ^ (v1 >> 2) ^ (v1 >> 7)
+		v2 ^= (v1 << 63) ^ (v1 << 62) ^ (v1 << 57)
+
+		y0 = v2
+		y1 = v3
+	}
+
+	binary.BigEndian.PutUint64(y[:], y1)
+	binary.BigEndian.PutUint64(y[8:], y0)
+}

+ 155 - 0
ghash/ghash_test.go

@@ -0,0 +1,155 @@
+// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
+// Copyright (c) 2017 Yawning Angel <yawning at schwanenlied dot me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+package ghash
+
+import (
+	"bytes"
+	"crypto/rand"
+	"encoding/binary"
+	"encoding/hex"
+	"testing"
+)
+
+// The test vectors are shamelessly stolen from "The  Galois/Counter Mode of
+// Operation (GCM)", which is what BearSSL does.
+//
+// http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf
+
+var ghashVectors = []struct {
+	h string
+	a string
+	c string
+	y string
+}{
+	{
+		"66e94bd4ef8a2c3b884cfa59ca342b2e",
+		"",
+		"",
+		"00000000000000000000000000000000",
+	},
+	{
+		"66e94bd4ef8a2c3b884cfa59ca342b2e",
+		"",
+		"0388dace60b6a392f328c2b971b2fe78",
+		"f38cbb1ad69223dcc3457ae5b6b0f885",
+	},
+	{
+		"b83b533708bf535d0aa6e52980d53b78",
+		"",
+		"42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985",
+		"7f1b32b81b820d02614f8895ac1d4eac",
+	},
+	{
+		"b83b533708bf535d0aa6e52980d53b78",
+		"feedfacedeadbeeffeedfacedeadbeefabaddad2",
+		"42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091",
+		"698e57f70e6ecc7fd9463b7260a9ae5f",
+	},
+	{
+		"b83b533708bf535d0aa6e52980d53b78",
+		"feedfacedeadbeeffeedfacedeadbeefabaddad2",
+		"61353b4c2806934a777ff51fa22a4755699b2a714fcdc6f83766e5f97b6c742373806900e49f24b22b097544d4896b424989b5e1ebac0f07c23f4598",
+		"df586bb4c249b92cb6922877e444d37b",
+	},
+	{
+		"b83b533708bf535d0aa6e52980d53b78",
+		"feedfacedeadbeeffeedfacedeadbeefabaddad2",
+		"8ce24998625615b603a033aca13fb894be9112a5c3a211a8ba262a3cca7e2ca701e4a9a4fba43c90ccdcb281d48c7c6fd62875d2aca417034c34aee5",
+		"1c5afe9760d3932f3c9a878aac3dc3de",
+	},
+}
+
+func gcmGHASH(y, h *[blockSize]byte, a, c []byte) {
+	var p [blockSize]byte
+	Ghash(y, h, a)
+	Ghash(y, h, c)
+	binary.BigEndian.PutUint32(p[4:], uint32(len(a))<<3)
+	binary.BigEndian.PutUint32(p[12:], uint32(len(c))<<3)
+	Ghash(y, h, p[:])
+}
+
+func TestGHASH(t *testing.T) {
+	for i, vec := range ghashVectors {
+		hh, err := hex.DecodeString(vec.h[:])
+		if err != nil {
+			t.Fatal(err)
+		}
+		a, err := hex.DecodeString(vec.a[:])
+		if err != nil {
+			t.Fatal(err)
+		}
+		c, err := hex.DecodeString(vec.c[:])
+		if err != nil {
+			t.Fatal(err)
+		}
+		yy, err := hex.DecodeString(vec.y[:])
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		var h, y [blockSize]byte
+		copy(h[:], hh)
+
+		gcmGHASH(&y, &h, a, c)
+		assertEqual(t, i, yy[:], y[:])
+	}
+}
+
+func assertEqual(t *testing.T, idx int, expected, actual []byte) {
+	if !bytes.Equal(expected, actual) {
+		for i, v := range actual {
+			if expected[i] != v {
+				t.Errorf("[%d] first mismatch at offset: %d (%02x != %02x)", idx, i, expected[i], v)
+				break
+			}
+		}
+		t.Errorf("expected: %s", hex.Dump(expected))
+		t.Errorf("actual: %s", hex.Dump(actual))
+		t.FailNow()
+	}
+}
+
+var ghashBenchOutput [blockSize]byte
+
+func BenchmarkGHASH(b *testing.B) {
+	var y, h [blockSize]byte
+	var buf [8192]byte
+
+	if _, err := rand.Read(buf[:]); err != nil {
+		b.Error(err)
+		b.Fail()
+	}
+	if _, err := rand.Read(h[:]); err != nil {
+		b.Error(err)
+		b.Fail()
+	}
+
+	b.SetBytes(int64(len(buf)))
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		Ghash(&y, &h, buf[:])
+	}
+	b.StopTimer()
+	copy(ghashBenchOutput[:], y[:])
+}