64 lines
1.7 KiB
Go
64 lines
1.7 KiB
Go
// 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)
|
|
}
|