ceremonyclient/bedlam/pkg/crypto/cipher/gcm/gcm.qcl
Cassandra Heart e51992f3e8
OT
2025-03-23 21:11:16 -05:00

232 lines
4.7 KiB
Go

// -*- go -*-
//
// Copyright (c) 2021-2024 Markku Rossi
//
// All rights reserved.
//
// Package gcm implements the Galois/Counter Mode of Operation (GCM)
// for block ciphers.
package gcm
import (
"bytes"
"crypto/aes"
)
// NonceSize specifies the nonce size in bytes.
const NonceSize = 12
// TagSize specifies the tag size in bytes.
const TagSize = 16
// EncryptAES128 encrypts the plaintext in AES-GCM mode. The key
// specifies the AES encryption key and nonce is an unique
// initialization vector; the nonce must not be reused for the same
// encryption key. The additionalData specifies additional data that
// is authenticated but not encrypted. The input plaintext can be of
// any length i.e. it don't have to be padded to cipher block size.
func EncryptAES128(key [16]byte, nonce [NonceSize]byte,
plaintext, additionalData []byte) []byte {
var counter [aes.BlockSize]byte
copy(counter, nonce)
counter = incr(counter)
e0 := byteToUint128(aes.Block128(key, counter))
var block [aes.BlockSize]byte
// h = E(k, 0^128)
h := byteToUint128(aes.Block128(key, block))
// Auth data.
var x uint128 = 0
// Add additionalData to auth data.
for i := 0; i < len(additionalData); i += aes.BlockSize {
for j := 0; j < aes.BlockSize; j++ {
if i+j < len(additionalData) {
block[j] = additionalData[i+j]
} else {
block[j] = 0
}
}
x ^= byteToUint128(block)
x = multGF2Pow128(x, h)
}
var cipher [len(plaintext) + 16]byte
var cipherBlock [aes.BlockSize]byte
for i := 0; i < len(plaintext); i += aes.BlockSize {
counter = incr(counter)
block = aes.Block128(key, counter)
for j := 0; j < aes.BlockSize; j++ {
if i+j < len(plaintext) {
cipher[i+j] = plaintext[i+j] ^ block[j]
cipherBlock[j] = cipher[i+j]
} else {
cipherBlock[j] = 0
}
}
// Auth data.
x ^= byteToUint128(cipherBlock)
x = multGF2Pow128(x, h)
}
// len(A) || len(C)
var l uint128
l |= uint128(len(additionalData)*8) << 64
l |= uint128(len(plaintext) * 8)
x ^= l
x = multGF2Pow128(x, h)
x ^= e0
tag := uint128ToByte(x)
for i := 0; i < 16; i++ {
cipher[len(plaintext)+i] = tag[i]
}
return cipher
}
// DecryptAES128 decrypts the ciphertext in AES-GCM mode. They key
// specifies the AES encryption key and nonce is an unique
// initialization vector; the nonce must not be reused for the same
// encryption key. The additionalData specifies additional data that
// is was authenticated but not encrypted when the ciphertext was
// created.
func DecryptAES128(key [16]byte, nonce [NonceSize]byte,
ciphertext, additionalData []byte) ([]byte, bool) {
var counter [aes.BlockSize]byte
copy(counter, nonce)
counter = incr(counter)
e0 := byteToUint128(aes.Block128(key, counter))
var block [aes.BlockSize]byte
// h = E(k, 0^128)
h := byteToUint128(aes.Block128(key, block))
// Auth data.
var x uint128 = 0
// Add additionalData to auth data.
for i := 0; i < len(additionalData); i += aes.BlockSize {
for j := 0; j < aes.BlockSize; j++ {
if i+j < len(additionalData) {
block[j] = additionalData[i+j]
} else {
block[j] = 0
}
}
x ^= byteToUint128(block)
x = multGF2Pow128(x, h)
}
if len(ciphertext) < TagSize {
return ciphertext[:0], false
}
cipherLen := len(ciphertext) - TagSize
cipher := ciphertext[0:cipherLen]
tag := ciphertext[cipherLen:]
var plain [cipherLen]byte
var cipherBlock [aes.BlockSize]byte
for i := 0; i < len(cipher); i += aes.BlockSize {
counter = incr(counter)
block = aes.Block128(key, counter)
for j := 0; j < aes.BlockSize; j++ {
if i+j < len(cipher) {
cipherBlock[j] = cipher[i+j]
plain[i+j] = cipher[i+j] ^ block[j]
} else {
cipherBlock[j] = 0
}
}
// Auth data.
x ^= byteToUint128(cipherBlock)
x = multGF2Pow128(x, h)
}
// len(A) || len(C)
var l uint128 = 0
l |= uint128(len(additionalData)*8) << 64
l |= uint128(len(plain) * 8)
x ^= l
x = multGF2Pow128(x, h)
x ^= e0
computedTag := uint128ToByte(x)
if bytes.Compare(tag, computedTag) != 0 {
return cipher, false
}
return plain, true
}
func byteToUint128(x [16]byte) uint128 {
var r uint128
for i := 0; i < len(x); i++ {
r <<= 8
r |= uint128(x[i])
}
return r
}
func uint128ToByte(x uint128) [16]byte {
var r [16]byte
for i := 0; i < 16; i++ {
r[15-i] = byte(x & 0xff)
x >>= 8
}
return r
}
func incr(counter [aes.BlockSize]byte) [aes.BlockSize]byte {
var c uint32
for i := 0; i < 4; i++ {
c <<= 8
c |= uint32(counter[12+i])
}
c++
for i := 0; i < 4; i++ {
counter[15-i] = byte(c & 0xff)
c >>= 8
}
return counter
}
func multGF2Pow128(x, y uint128) uint128 {
var z uint128 = 0
r := uint128(0b11100001)
r <<= 120
for i := 127; i >= 0; i-- {
if (y>>i)&1 == 1 {
z ^= x
}
if x&1 == 0 {
x >>= 1
} else {
x >>= 1
x ^= r
}
}
return z
}