///|
// Forward AES for bulk CBC. FIPS 197, sections 4 and 5:
// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf
// Eight byte lanes share each UInt64 operation. Field arithmetic has fixed
// iteration counts and uses neither secret-indexed tables nor secret branches.
priv struct AesEncryptKey {
words : FixedArray[UInt32]
rounds : Int32
}
///|
fn aes_packed_xtime(bytes : UInt64) -> UInt64 {
((bytes & 0x7f7f7f7f7f7f7f7fUL) << 1) ^
(((bytes >> 7) & 0x0101010101010101UL) * 0x1bUL)
}
///|
fn aes_packed_multiply(left : UInt64, right : UInt64) -> UInt64 {
for bit = 0, product = 0UL, left = left, right = right; bit < 8; {
let mask = (right & 0x0101010101010101UL) * 0xffUL
continue bit + 1,
product ^ (left & mask),
aes_packed_xtime(left),
(right >> 1) & 0x7f7f7f7f7f7f7f7fUL
} nobreak {
product
}
}
///|
// Squaring is linear over GF(2). These are the squares of the eight basis
// bits, reduced modulo x^8 + x^4 + x^3 + x + 1.
fn aes_packed_square(bytes : UInt64) -> UInt64 {
(bytes & 0x0101010101010101UL) ^
((bytes & 0x0202020202020202UL) << 1) ^
((bytes & 0x0404040404040404UL) << 2) ^
((bytes & 0x0808080808080808UL) << 3) ^
(((bytes >> 4) & 0x0101010101010101UL) * 0x1bUL) ^
(((bytes >> 5) & 0x0101010101010101UL) * 0x6cUL) ^
(((bytes >> 6) & 0x0101010101010101UL) * 0xabUL) ^
(((bytes >> 7) & 0x0101010101010101UL) * 0x9aUL)
}
///|
fn aes_packed_sub_bytes(bytes : UInt64) -> UInt64 {
// x^254 is the multiplicative inverse (and maps zero to zero). This
// addition chain uses four general products and seven linear squares.
let x2 = aes_packed_square(bytes)
let x3 = aes_packed_multiply(x2, bytes)
let x6 = aes_packed_square(x3)
let x12 = aes_packed_square(x6)
let x15 = aes_packed_multiply(x12, x3)
let x30 = aes_packed_square(x15)
let x60 = aes_packed_square(x30)
let x120 = aes_packed_square(x60)
let x240 = aes_packed_square(x120)
let x252 = aes_packed_multiply(x240, x12)
let inverse = aes_packed_multiply(x252, x2)
// The affine transform rotates within each byte, never across lanes.
inverse ^
(
((inverse << 1) & 0xfefefefefefefefeUL) |
((inverse >> 7) & 0x0101010101010101UL)
) ^
(
((inverse << 2) & 0xfcfcfcfcfcfcfcfcUL) |
((inverse >> 6) & 0x0303030303030303UL)
) ^
(
((inverse << 3) & 0xf8f8f8f8f8f8f8f8UL) |
((inverse >> 5) & 0x0707070707070707UL)
) ^
(
((inverse << 4) & 0xf0f0f0f0f0f0f0f0UL) |
((inverse >> 4) & 0x0f0f0f0f0f0f0f0fUL)
) ^
0x6363636363636363UL
}
///|
fn aes_read_word(bytes : BytesView, offset : Int32) -> UInt32 {
(bytes[offset].to_uint() << 24) |
(bytes[offset + 1].to_uint() << 16) |
(bytes[offset + 2].to_uint() << 8) |
bytes[offset + 3].to_uint()
}
///|
fn AesEncryptKey::new(key : BytesView) -> AesEncryptKey {
let key_words = key.length() / 4
let rounds = key_words + 6
let words = FixedArray::make(4 * (rounds + 1), 0U)
for index in 0..> 24)
word = aes_packed_sub_bytes(UInt64::extend_uint(word)).to_uint() ^
(rcon << 24)
rcon = ((rcon << 1) ^ ((0U - (rcon >> 7)) & 0x1bU)) & 0xffU
} else if key_words == 8 && index % key_words == 4 {
word = aes_packed_sub_bytes(UInt64::extend_uint(word)).to_uint()
}
words[index] = words[index - key_words] ^ word
}
{ words, rounds, }
}
///|
fn aes_mix_word(word : UInt32) -> UInt32 {
let pairs = word ^ ((word << 16) | (word >> 16))
let total = pairs ^ ((pairs << 8) | (pairs >> 24))
let adjacent = word ^ ((word << 8) | (word >> 24))
let doubled = ((adjacent & 0x7f7f7f7fU) << 1) ^
(((adjacent >> 7) & 0x01010101U) * 0x1bU)
word ^ total ^ doubled
}
///|
fn AesEncryptKey::encrypt(
schedule : AesEncryptKey,
state : FixedArray[UInt32],
) -> Unit {
for column in 0..<4 {
state[column] = state[column] ^ schedule.words[column]
}
for round = 1; round <= schedule.rounds; round = round + 1 {
let first_pair = aes_packed_sub_bytes(
(UInt64::extend_uint(state[0]) << 32) | UInt64::extend_uint(state[1]),
)
let second_pair = aes_packed_sub_bytes(
(UInt64::extend_uint(state[2]) << 32) | UInt64::extend_uint(state[3]),
)
let column_0 = (first_pair >> 32).to_uint()
let column_1 = first_pair.to_uint()
let column_2 = (second_pair >> 32).to_uint()
let column_3 = second_pair.to_uint()
// ShiftRows in the column-major state layout specified by FIPS 197.
state[0] = (column_0 & 0xff000000U) |
(column_1 & 0x00ff0000U) |
(column_2 & 0x0000ff00U) |
(column_3 & 0x000000ffU)
state[1] = (column_1 & 0xff000000U) |
(column_2 & 0x00ff0000U) |
(column_3 & 0x0000ff00U) |
(column_0 & 0x000000ffU)
state[2] = (column_2 & 0xff000000U) |
(column_3 & 0x00ff0000U) |
(column_0 & 0x0000ff00U) |
(column_1 & 0x000000ffU)
state[3] = (column_3 & 0xff000000U) |
(column_0 & 0x00ff0000U) |
(column_1 & 0x0000ff00U) |
(column_2 & 0x000000ffU)
for column in 0..<4 {
let word = if round < schedule.rounds {
aes_mix_word(state[column])
} else {
state[column]
}
state[column] = word ^ schedule.words[round * 4 + column]
}
}
}
///|
// The public adapter checks key/IV lengths and block alignment before entry.
fn aes_cbc_encrypt_blocks(
key : BytesView,
iv : BytesView,
data : BytesView,
) -> Bytes {
let schedule = AesEncryptKey::new(key)
let state = FixedArray::makei(4, column => aes_read_word(iv, column * 4))
let output = Array::make(data.length(), b'\x00')
for offset = 0; offset < data.length(); offset = offset + 16 {
for column in 0..<4 {
state[column] = state[column] ^ aes_read_word(data, offset + column * 4)
}
schedule.encrypt(state)
for column in 0..<4 {
let word = state[column]
let start = offset + column * 4
output[start] = (word >> 24).to_byte()
output[start + 1] = (word >> 16).to_byte()
output[start + 2] = (word >> 8).to_byte()
output[start + 3] = word.to_byte()
}
}
Bytes::from_array(output)
}