]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/cipher: add GCM mode.
authorAdam Langley <agl@golang.org>
Mon, 5 Aug 2013 18:31:58 +0000 (14:31 -0400)
committerAdam Langley <agl@golang.org>
Mon, 5 Aug 2013 18:31:58 +0000 (14:31 -0400)
GCM is Galois Counter Mode, an authenticated encryption mode that is,
nearly always, used with AES.

R=rsc
CC=golang-dev
https://golang.org/cl/12375043

src/pkg/crypto/cipher/gcm.go [new file with mode: 0644]
src/pkg/crypto/cipher/gcm_test.go [new file with mode: 0644]
src/pkg/go/build/deps_test.go

diff --git a/src/pkg/crypto/cipher/gcm.go b/src/pkg/crypto/cipher/gcm.go
new file mode 100644 (file)
index 0000000..2bcb469
--- /dev/null
@@ -0,0 +1,350 @@
+// Copyright 2013 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 cipher
+
+import (
+       "crypto/subtle"
+       "errors"
+)
+
+// AEAD is a cipher mode providing authenticated encryption with associated
+// data.
+type AEAD interface {
+       // NonceSize returns the size of the nonce that must be passed to Seal
+       // and Open.
+       NonceSize() int
+
+       // Overhead returns the maximum difference between the lengths of a
+       // plaintext and ciphertext.
+       Overhead() int
+
+       // Seal encrypts and authenticates plaintext, authenticates the
+       // additional data and appends the result to dst, returning the updated
+       // slice. The nonce must be NonceSize() bytes long and unique for all
+       // time, for a given key.
+       //
+       // The plaintext and dst may alias exactly or not at all.
+       Seal(dst, nonce, plaintext, data []byte) []byte
+
+       // Open decrypts and authenticates ciphertext, authenticates the
+       // additional data and, if successful, appends the resulting plaintext
+       // to dst, returning the updated slice and true. On error, nil and
+       // false is returned. The nonce must be NonceSize() bytes long and both
+       // it and the additional data must match the value passed to Seal.
+       //
+       // The ciphertext and dst may alias exactly or not at all.
+       Open(dst, nonce, ciphertext, data []byte) ([]byte, error)
+}
+
+// gcmFieldElement represents a value in GF(2¹²⁸). In order to reflect the GCM
+// standard and make getUint64 suitable for marshaling these values, the bits
+// are stored backwards. 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
+}
+
+// gcm represents a Galois Counter Mode with a specific key. See
+// http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf
+type gcm struct {
+       cipher Block
+       // productTable contains the first sixteen powers of the key, H.
+       // However, they are in bit reversed order. See NewGCM.
+       productTable [16]gcmFieldElement
+}
+
+// NewGCM returns the given 128-bit, block cipher wrapped in Galois Counter Mode.
+func NewGCM(cipher Block) (AEAD, error) {
+       if cipher.BlockSize() != gcmBlockSize {
+               return nil, errors.New("cipher: NewGCM requires 128-bit block cipher")
+       }
+
+       var key [gcmBlockSize]byte
+       cipher.Encrypt(key[:], key[:])
+
+       g := &gcm{cipher: cipher}
+
+       // We precompute 16 multiples of |key|. 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*key 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{
+               getUint64(key[:8]),
+               getUint64(key[8:]),
+       }
+       g.productTable[reverseBits(1)] = x
+
+       for i := 2; i < 16; i += 2 {
+               g.productTable[reverseBits(i)] = gcmDouble(&g.productTable[reverseBits(i/2)])
+               g.productTable[reverseBits(i+1)] = gcmAdd(&g.productTable[reverseBits(i)], &x)
+       }
+
+       return g, nil
+}
+
+const (
+       gcmBlockSize = 16
+       gcmTagSize   = 16
+       gcmNonceSize = 12
+)
+
+func (*gcm) NonceSize() int {
+       return gcmNonceSize
+}
+
+func (*gcm) Overhead() int {
+       return gcmTagSize
+}
+
+func (g *gcm) Seal(dst, nonce, plaintext, data []byte) []byte {
+       if len(nonce) != gcmNonceSize {
+               panic("cipher: incorrect nonce length given to GCM")
+       }
+
+       ret, out := sliceForAppend(dst, len(plaintext)+gcmTagSize)
+
+       // See GCM spec, section 7.1.
+       var counter, tagMask [gcmBlockSize]byte
+       copy(counter[:], nonce)
+       counter[gcmBlockSize-1] = 1
+
+       g.cipher.Encrypt(tagMask[:], counter[:])
+       gcmInc32(&counter)
+
+       g.counterCrypt(out, plaintext, &counter)
+       g.auth(out[len(plaintext):], out[:len(plaintext)], data, &tagMask)
+
+       return ret
+}
+
+var errOpen = errors.New("cipher: message authentication failed")
+
+func (g *gcm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
+       if len(nonce) != gcmNonceSize {
+               panic("cipher: incorrect nonce length given to GCM")
+       }
+
+       if len(ciphertext) < gcmTagSize {
+               return nil, errOpen
+       }
+       tag := ciphertext[len(ciphertext)-gcmTagSize:]
+       ciphertext = ciphertext[:len(ciphertext)-gcmTagSize]
+
+       // See GCM spec, section 7.1.
+       var counter, tagMask [gcmBlockSize]byte
+       copy(counter[:], nonce)
+       counter[gcmBlockSize-1] = 1
+
+       g.cipher.Encrypt(tagMask[:], counter[:])
+       gcmInc32(&counter)
+
+       var expectedTag [gcmTagSize]byte
+       g.auth(expectedTag[:], ciphertext, data, &tagMask)
+
+       if subtle.ConstantTimeCompare(expectedTag[:], tag) != 1 {
+               return nil, errOpen
+       }
+
+       ret, out := sliceForAppend(dst, len(ciphertext))
+       g.counterCrypt(out, ciphertext, &counter)
+
+       return ret, nil
+}
+
+// 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
+}
+
+// gcmAdd adds two elements of GF(2¹²⁸) and returns the sum.
+func gcmAdd(x, y *gcmFieldElement) gcmFieldElement {
+       // Addition in a characteristic 2 field is just XOR.
+       return gcmFieldElement{x.low ^ y.low, x.high ^ y.high}
+}
+
+// gcmDouble returns the result of doubling an element of GF(2¹²⁸).
+func gcmDouble(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 gcmReductionTable = []uint16{
+       0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
+       0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0,
+}
+
+// mul sets y to y*H, where H is the GCM key, fixed during NewGCM.
+func (g *gcm) mul(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(gcmReductionTable[msw]) << 48
+
+                       // the values in |table| are ordered for
+                       // little-endian bit positions. See the comment
+                       // in NewGCM.
+                       t := &g.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 (g *gcm) updateBlocks(y *gcmFieldElement, blocks []byte) {
+       for len(blocks) > 0 {
+               y.low ^= getUint64(blocks)
+               y.high ^= getUint64(blocks[8:])
+               g.mul(y)
+               blocks = blocks[gcmBlockSize:]
+       }
+}
+
+// update 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 (g *gcm) update(y *gcmFieldElement, data []byte) {
+       fullBlocks := (len(data) >> 4) << 4
+       g.updateBlocks(y, data[:fullBlocks])
+
+       if len(data) != fullBlocks {
+               var partialBlock [gcmBlockSize]byte
+               copy(partialBlock[:], data[fullBlocks:])
+               g.updateBlocks(y, partialBlock[:])
+       }
+}
+
+// gcmInc32 treats the final four bytes of counterBlock as a big-endian value
+// and increments it.
+func gcmInc32(counterBlock *[16]byte) {
+       c := 1
+       for i := gcmBlockSize - 1; i >= gcmBlockSize-4; i-- {
+               c += int(counterBlock[i])
+               counterBlock[i] = byte(c)
+               c >>= 8
+       }
+}
+
+// sliceForAppend takes a slice and a requested number of bytes. It returns a
+// slice with the contents of the given slice followed by that many bytes and a
+// second slice that aliases into it and contains only the extra bytes. If the
+// original slice has sufficient capacity then no allocation is performed.
+func sliceForAppend(in []byte, n int) (head, tail []byte) {
+       if total := len(in) + n; cap(in) >= total {
+               head = in[:total]
+       } else {
+               head = make([]byte, total)
+               copy(head, in)
+       }
+       tail = head[len(in):]
+       return
+}
+
+// counterCrypt crypts in to out using g.cipher in counter mode.
+func (g *gcm) counterCrypt(out, in []byte, counter *[gcmBlockSize]byte) {
+       var mask [gcmBlockSize]byte
+
+       for len(in) >= gcmBlockSize {
+               g.cipher.Encrypt(mask[:], counter[:])
+               gcmInc32(counter)
+
+               for i := range mask {
+                       out[i] = in[i] ^ mask[i]
+               }
+               out = out[gcmBlockSize:]
+               in = in[gcmBlockSize:]
+       }
+
+       if len(in) > 0 {
+               g.cipher.Encrypt(mask[:], counter[:])
+               gcmInc32(counter)
+
+               for i := range in {
+                       out[i] = in[i] ^ mask[i]
+               }
+       }
+}
+
+// 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 y gcmFieldElement
+       g.update(&y, additionalData)
+       g.update(&y, ciphertext)
+
+       y.low ^= uint64(len(additionalData)) * 8
+       y.high ^= uint64(len(ciphertext)) * 8
+
+       g.mul(&y)
+
+       putUint64(out, y.low)
+       putUint64(out[8:], y.high)
+
+       for i := range tagMask {
+               out[i] ^= tagMask[i]
+       }
+}
+
+func getUint64(data []byte) uint64 {
+       r := uint64(data[0])<<56 |
+               uint64(data[1])<<48 |
+               uint64(data[2])<<40 |
+               uint64(data[3])<<32 |
+               uint64(data[4])<<24 |
+               uint64(data[5])<<16 |
+               uint64(data[6])<<8 |
+               uint64(data[7])
+       return r
+}
+
+func putUint64(out []byte, v uint64) {
+       out[0] = byte(v >> 56)
+       out[1] = byte(v >> 48)
+       out[2] = byte(v >> 40)
+       out[3] = byte(v >> 32)
+       out[4] = byte(v >> 24)
+       out[5] = byte(v >> 16)
+       out[6] = byte(v >> 8)
+       out[7] = byte(v)
+}
diff --git a/src/pkg/crypto/cipher/gcm_test.go b/src/pkg/crypto/cipher/gcm_test.go
new file mode 100644 (file)
index 0000000..02d4215
--- /dev/null
@@ -0,0 +1,175 @@
+// Copyright 2013 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 cipher_test
+
+import (
+       "bytes"
+       "crypto/aes"
+       "crypto/cipher"
+       "encoding/hex"
+       "testing"
+)
+
+// AES-GCM test vectors taken from gcmEncryptExtIV128.rsp from
+// http://csrc.nist.gov/groups/STM/cavp/index.html.
+var aesGCMTests = []struct {
+       key, nonce, plaintext, ad, result string
+}{
+       {
+               "11754cd72aec309bf52f7687212e8957",
+               "3c819d9a9bed087615030b65",
+               "",
+               "",
+               "250327c674aaf477aef2675748cf6971",
+       },
+       {
+               "ca47248ac0b6f8372a97ac43508308ed",
+               "ffd2b598feabc9019262d2be",
+               "",
+               "",
+               "60d20404af527d248d893ae495707d1a",
+       },
+       {
+               "77be63708971c4e240d1cb79e8d77feb",
+               "e0e00f19fed7ba0136a797f3",
+               "",
+               "7a43ec1d9c0a5a78a0b16533a6213cab",
+               "209fcc8d3675ed938e9c7166709dd946",
+       },
+       {
+               "7680c5d3ca6154758e510f4d25b98820",
+               "f8f105f9c3df4965780321f8",
+               "",
+               "c94c410194c765e3dcc7964379758ed3",
+               "94dca8edfcf90bb74b153c8d48a17930",
+       },
+       {
+               "7fddb57453c241d03efbed3ac44e371c",
+               "ee283a3fc75575e33efd4887",
+               "d5de42b461646c255c87bd2962d3b9a2",
+               "",
+               "2ccda4a5415cb91e135c2a0f78c9b2fdb36d1df9b9d5e596f83e8b7f52971cb3",
+       },
+       {
+               "ab72c77b97cb5fe9a382d9fe81ffdbed",
+               "54cc7dc2c37ec006bcc6d1da",
+               "007c5e5b3e59df24a7c355584fc1518d",
+               "",
+               "0e1bde206a07a9c2c1b65300f8c649972b4401346697138c7a4891ee59867d0c",
+       },
+       {
+               "fe47fcce5fc32665d2ae399e4eec72ba",
+               "5adb9609dbaeb58cbd6e7275",
+               "7c0e88c88899a779228465074797cd4c2e1498d259b54390b85e3eef1c02df60e743f1b840382c4bccaf3bafb4ca8429bea063",
+               "88319d6e1d3ffa5f987199166c8a9b56c2aeba5a",
+               "98f4826f05a265e6dd2be82db241c0fbbbf9ffb1c173aa83964b7cf5393043736365253ddbc5db8778371495da76d269e5db3e291ef1982e4defedaa2249f898556b47",
+       },
+       {
+               "ec0c2ba17aa95cd6afffe949da9cc3a8",
+               "296bce5b50b7d66096d627ef",
+               "b85b3753535b825cbe5f632c0b843c741351f18aa484281aebec2f45bb9eea2d79d987b764b9611f6c0f8641843d5d58f3a242",
+               "f8d00f05d22bf68599bcdeb131292ad6e2df5d14",
+               "a7443d31c26bdf2a1c945e29ee4bd344a99cfaf3aa71f8b3f191f83c2adfc7a07162995506fde6309ffc19e716eddf1a828c5a890147971946b627c40016da1ecf3e77",
+       },
+       {
+               "2c1f21cf0f6fb3661943155c3e3d8492",
+               "23cb5ff362e22426984d1907",
+               "42f758836986954db44bf37c6ef5e4ac0adaf38f27252a1b82d02ea949c8a1a2dbc0d68b5615ba7c1220ff6510e259f06655d8",
+               "5d3624879d35e46849953e45a32a624d6a6c536ed9857c613b572b0333e701557a713e3f010ecdf9a6bd6c9e3e44b065208645aff4aabee611b391528514170084ccf587177f4488f33cfb5e979e42b6e1cfc0a60238982a7aec",
+               "81824f0e0d523db30d3da369fdc0d60894c7a0a20646dd015073ad2732bd989b14a222b6ad57af43e1895df9dca2a5344a62cc57a3ee28136e94c74838997ae9823f3a",
+       },
+       {
+               "d9f7d2411091f947b4d6f1e2d1f0fb2e",
+               "e1934f5db57cc983e6b180e7",
+               "73ed042327f70fe9c572a61545eda8b2a0c6e1d6c291ef19248e973aee6c312012f490c2c6f6166f4a59431e182663fcaea05a",
+               "0a8a18a7150e940c3d87b38e73baee9a5c049ee21795663e264b694a949822b639092d0e67015e86363583fcf0ca645af9f43375f05fdb4ce84f411dcbca73c2220dea03a20115d2e51398344b16bee1ed7c499b353d6c597af8",
+               "aaadbd5c92e9151ce3db7210b8714126b73e43436d242677afa50384f2149b831f1d573c7891c2a91fbc48db29967ec9542b2321b51ca862cb637cdd03b99a0f93b134",
+       },
+       {
+               "fe9bb47deb3a61e423c2231841cfd1fb",
+               "4d328eb776f500a2f7fb47aa",
+               "f1cc3818e421876bb6b8bbd6c9",
+               "",
+               "b88c5c1977b35b517b0aeae96743fd4727fe5cdb4b5b42818dea7ef8c9",
+       },
+       {
+               "6703df3701a7f54911ca72e24dca046a",
+               "12823ab601c350ea4bc2488c",
+               "793cd125b0b84a043e3ac67717",
+               "",
+               "b2051c80014f42f08735a7b0cd38e6bcd29962e5f2c13626b85a877101",
+       },
+}
+
+func TestAESGCM(t *testing.T) {
+       for i, test := range aesGCMTests {
+               key, _ := hex.DecodeString(test.key)
+               aes, err := aes.NewCipher(key)
+               if err != nil {
+                       t.Fatal(err)
+               }
+
+               nonce, _ := hex.DecodeString(test.nonce)
+               plaintext, _ := hex.DecodeString(test.plaintext)
+               ad, _ := hex.DecodeString(test.ad)
+               aesgcm, err := cipher.NewGCM(aes)
+               if err != nil {
+                       t.Fatal(err)
+               }
+
+               ct := aesgcm.Seal(nil, nonce, plaintext, ad)
+               if ctHex := hex.EncodeToString(ct); ctHex != test.result {
+                       t.Errorf("#%d: got %s, want %s", i, ctHex, test.result)
+                       continue
+               }
+
+               plaintext2, err := aesgcm.Open(nil, nonce, ct, ad)
+               if err != nil {
+                       t.Errorf("#%d: Open failed", i)
+                       continue
+               }
+
+               if !bytes.Equal(plaintext, plaintext2) {
+                       t.Errorf("#%d: plaintext's don't match: got %x vs %x", i, plaintext2, plaintext)
+                       continue
+               }
+
+               if len(ad) > 0 {
+                       ad[0] ^= 0x80
+                       if _, err := aesgcm.Open(nil, nonce, ct, ad); err == nil {
+                               t.Errorf("#%d: Open was successful after altering additional data", i)
+                       }
+                       ad[0] ^= 0x80
+               }
+
+               nonce[0] ^= 0x80
+               if _, err := aesgcm.Open(nil, nonce, ct, ad); err == nil {
+                       t.Errorf("#%d: Open was successful after altering nonce", i)
+               }
+               nonce[0] ^= 0x80
+
+               ct[0] ^= 0x80
+               if _, err := aesgcm.Open(nil, nonce, ct, ad); err == nil {
+                       t.Errorf("#%d: Open was successful after altering ciphertext", i)
+               }
+               ct[0] ^= 0x80
+       }
+}
+
+func BenchmarkAESGCM(b *testing.B) {
+       buf := make([]byte, 1024)
+       b.SetBytes(int64(len(buf)))
+
+       var key [16]byte
+       var nonce [12]byte
+       aes, _ := aes.NewCipher(key[:])
+       aesgcm, _ := cipher.NewGCM(aes)
+       var out []byte
+
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               out = aesgcm.Seal(out[:0], nonce[:], buf, nonce[:])
+       }
+}
index eb2eb515a52bf053097b87ac0e5f81a3cccf8096..23cbce3652aa4e2b58f55ae2535aa5c647792efe 100644 (file)
@@ -82,8 +82,9 @@ var pkgDeps = map[string][]string{
        // L3 adds reflection and some basic utility packages
        // and interface definitions, but nothing that makes
        // system calls.
-       "crypto":          {"L2", "hash"}, // interfaces
-       "crypto/cipher":   {"L2"},         // interfaces
+       "crypto":          {"L2", "hash"},          // interfaces
+       "crypto/cipher":   {"L2", "crypto/subtle"}, // interfaces
+       "crypto/subtle":   {},
        "encoding/base32": {"L2"},
        "encoding/base64": {"L2"},
        "encoding/binary": {"L2", "reflect"},
@@ -100,6 +101,7 @@ var pkgDeps = map[string][]string{
                "L2",
                "crypto",
                "crypto/cipher",
+               "crypto/subtle",
                "encoding/base32",
                "encoding/base64",
                "encoding/binary",
@@ -248,15 +250,10 @@ var pkgDeps = map[string][]string{
        "net/mail":      {"L4", "NET", "OS"},
        "net/textproto": {"L4", "OS", "net"},
 
-       // Support libraries for crypto that aren't L2.
-       "CRYPTO-SUPPORT": {
-               "crypto/subtle",
-       },
-
        // Core crypto.
        "crypto/aes":    {"L3"},
        "crypto/des":    {"L3"},
-       "crypto/hmac":   {"L3", "CRYPTO-SUPPORT"},
+       "crypto/hmac":   {"L3"},
        "crypto/md5":    {"L3"},
        "crypto/rc4":    {"L3"},
        "crypto/sha1":   {"L3"},
@@ -264,7 +261,6 @@ var pkgDeps = map[string][]string{
        "crypto/sha512": {"L3"},
 
        "CRYPTO": {
-               "CRYPTO-SUPPORT",
                "crypto/aes",
                "crypto/des",
                "crypto/hmac",