ceremonyclient/bedlam/pkg/crypto/cipher/cts/cts.qcl
Cassandra Heart dbd95bd9e9
v2.1.0 (#439)
* v2.1.0 [omit consensus and adjacent] - this commit will be amended with the full release after the file copy is complete

* 2.1.0 main node rollup
2025-09-30 02:48:15 -05:00

136 lines
3.4 KiB
Go

// -*- go -*-
//
// Copyright (c) 2024 Markku Rossi
//
// All rights reserved.
//
// Package cts implements the ciphertext stealing (CTS) mode of
// operation for block ciphers. The implementation uses the CTS-3
// (Kerberos) variant for formatting the cipher text.
package cts
import (
"crypto/aes"
)
// EncryptAES128 encrypts the data in AES-CTS mode. The key specifies
// the AES encryption key and iv is a random initialization vector.
//
// key := []byte{
// 0x63, 0x68, 0x69, 0x63, 0x6b, 0x65, 0x6e, 0x20,
// 0x74, 0x65, 0x72, 0x69, 0x79, 0x61, 0x6b, 0x69,
// }
// var iv [16]byte
// data := []byte{
// 0x49, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20,
// 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65,
// 0x20,
// }
// cipher := cts.EncryptAES128(key, iv, data)
// => c6353568f2bf8cb4d8a580362da7ff7f97
func EncryptAES128(key [16]byte, iv [aes.BlockSize]byte, data []byte) []byte {
numBlocks := len(data) / aes.BlockSize
tail := len(data) % aes.BlockSize
if tail != 0 {
numBlocks++
} else {
tail = aes.BlockSize
}
if numBlocks < 2 {
panic("cts.EncryptAES128: input must be at least 2 block")
}
var block [aes.BlockSize]byte
copy(block, iv)
var plain [aes.BlockSize]byte
var cipher [len(data)]byte
// Standard CBC for the first numBlocks-1 blocks.
for i := 0; i < numBlocks-1; i++ {
copy(plain, data[i*aes.BlockSize:])
for j := 0; j < aes.BlockSize; j++ {
plain[j] ^= block[j]
}
block = aes.EncryptBlock(key, plain)
if i < numBlocks-2 {
// Store standard CBC output block.
copy(cipher[i*aes.BlockSize:], block)
} else {
// Store last ciphertext block.
copy(cipher[(numBlocks-1)*aes.BlockSize:], block)
}
}
// Create last input block.
copy(plain, data[(numBlocks-1)*aes.BlockSize:])
for i := tail; i < aes.BlockSize; i++ {
plain[i] = 0
}
for j := 0; j < aes.BlockSize; j++ {
plain[j] ^= block[j]
}
block = aes.EncryptBlock(key, plain)
copy(cipher[(numBlocks-2)*aes.BlockSize:], block)
return cipher
}
// DecryptAES128 decrypts the data that is encrypted in AES-CTS
// mode. The key specifies the AES encryption key and iv is the random
// initialization vector used in encryption.
func DecryptAES128(key [16]byte, iv [aes.BlockSize]byte, data []byte) []byte {
numBlocks := len(data) / aes.BlockSize
tail := len(data) % aes.BlockSize
if tail != 0 {
numBlocks++
} else {
tail = aes.BlockSize
}
if numBlocks < 2 {
panic("cts.DecryptAES128: input must be at least 2 blocks")
}
var block [aes.BlockSize]byte
var cipher [aes.BlockSize]byte
var tmp2 [aes.BlockSize]byte
var plain [len(data)]byte
// Standard CBC for the first numBlocks-2 blocks.
for i := 0; i < numBlocks-2; i++ {
copy(cipher, data[i*aes.Blocks:])
block = aes.DecryptBlock(key, cipher)
for j := 0; j < aes.BlockSize; j++ {
block[j] ^= iv[j]
}
copy(plain[i*aes.BlockSize:], block)
copy(iv, cipher)
}
// Decrypt second-to-last cipher block.
copy(cipher, data[(numBlocks-2)*aes.BlockSize:])
tmp := aes.DecryptBlock(key, cipher)
// Create padded last cipher block.
copy(tmp2, data[(numBlocks-1)*aes.BlockSize:])
copy(tmp2[tail:], tmp[tail:])
// Decrypt second-to-last block.
block = aes.DecryptBlock(key, tmp2)
for j := 0; j < aes.BlockSize; j++ {
block[j] ^= iv[j]
}
copy(plain[(numBlocks-2)*aes.BlockSize:], block)
copy(iv, tmp2)
// Finalize last block.
for j := 0; j < aes.BlockSize; j++ {
tmp[j] ^= iv[j]
}
copy(plain[(numBlocks-1)*aes.BlockSize:], tmp)
return plain
}