// Package gcm64 seals and opens AES-GCM with the 8-byte tag this protocol uses. package gcm64 import ( "crypto/aes" "crypto/cipher" "crypto/subtle" "encoding/binary" "errors" ) // IV is the 12-byte nonce: a 6-byte prefix, the client id, and the counter. func IV(prefix []byte, clientID uint16, counter uint32) []byte { iv := make([]byte, 12) copy(iv, prefix) binary.BigEndian.PutUint16(iv[6:8], clientID) binary.BigEndian.PutUint32(iv[8:12], counter) return iv } // Seal returns an 8-byte tag and the ciphertext. The tag is the leading 8 // bytes of the ordinary 16-byte GCM tag, because crypto/cipher only builds // tags of 12 to 16 bytes. func Seal(key, nonce, plain, aad []byte) (tag, ct []byte, err error) { g, err := full(key) if err != nil { return nil, nil, err } out := g.Seal(nil, nonce, plain, aad) ct = append([]byte(nil), out[:len(plain)]...) tag = append([]byte(nil), out[len(plain):len(plain)+8]...) return tag, ct, nil } // Open checks the 8-byte tag and returns the plaintext. func Open(key, nonce, tag, ct, aad []byte) ([]byte, error) { if len(tag) != 8 { return nil, errors.New("tag") } g, err := full(key) if err != nil { return nil, err } stream := g.Seal(nil, nonce, make([]byte, len(ct)), aad) plain := make([]byte, len(ct)) subtle.XORBytes(plain, ct, stream[:len(ct)]) check := g.Seal(nil, nonce, plain, aad) var got, want [8]byte copy(got[:], tag) copy(want[:], check[len(plain):len(plain)+8]) if subtle.ConstantTimeCompare(got[:], want[:]) != 1 { return nil, errors.New("tag") } return plain, nil } func full(key []byte) (cipher.AEAD, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } return cipher.NewGCM(block) }