class HexaPDF::Encryption::RubyAES

See: PDF2.0 s7.6.3
This implementation is using AES in Cipher Block Chaining (CBC) mode.
times slower when encrypting than the FastAES version.
For reference: This implementation is about 5000 times slower when decrypting and about 1800
class based on OpenSSL should be used when possible.
Since this algorithm is implemented in pure Ruby, it is not very fast. Therefore the FastAES
Implementation of the general encryption algorithm AES.

def add_round_key(state, round_key)

Performs an XOR operation on the state with the given round key.

AddRoundKey Step
def add_round_key(state, round_key)
  i = 0
  (state[i] ^= round_key[i]; i += 1) while i < state.size
end

def decrypt(state)

Decrypts the +state+ block.
def decrypt(state)
  i = NUMBER_OF_ROUNDS[@key.size]
  add_round_key(state, @expanded_key_blocks[i])
  i -= 1
  while i > 0
    inverse_sub_bytes_and_shift_rows(state)
    add_round_key(state, @expanded_key_blocks[i])
    inverse_mix_columns(state)
    i -= 1
  end
  inverse_sub_bytes_and_shift_rows(state)
  add_round_key(state, @expanded_key_blocks[i])
end

def encrypt(state)

Encrypts the +state+ block.
def encrypt(state)
  add_round_key(state, @expanded_key_blocks[0])
  i = 1
  while i < NUMBER_OF_ROUNDS[@key.size]
    sub_bytes_and_shift_rows(state)
    mix_columns(state)
    add_round_key(state, @expanded_key_blocks[i])
    i += 1
  end
  sub_bytes_and_shift_rows(state)
  add_round_key(state, @expanded_key_blocks[i])
end

def expand_key(key)

algorithm.
Generates NUMBER_OF_ROUNDS + 1 round keys of 128 bit using Rijndael's key scheduling

KeyExpansion step
def expand_key(key)
  key_size = key.size
  nr_bytes = 16 * (NUMBER_OF_ROUNDS[key_size] + 1)
  result = key.bytes
  temp = result[-4, 4]
  while result.size < nr_bytes
    if result.size % key_size == 0
      temp[0] = SBOX[temp[1]] ^ RCON[result.size / key_size]
      temp[1] = SBOX[temp[2]]
      temp[2] = SBOX[temp[3]]
      temp[3] = SBOX[result[-4]] # result[-4] is equal to temp[0]
    elsif key_size == 32 && result.size % key_size == 16
      temp[0] = SBOX[temp[0]]
      temp[1] = SBOX[temp[1]]
      temp[2] = SBOX[temp[2]]
      temp[3] = SBOX[temp[3]]
    end
    result << (temp[0] ^= result[-key_size])
    result << (temp[1] ^= result[-key_size])
    result << (temp[2] ^= result[-key_size])
    result << (temp[3] ^= result[-key_size])
  end
  result.each_slice(16).to_a
end

def initialize(key, iv, mode)

The mode must either be :encrypt or :decrypt.

Creates a new AES object using the given encryption key and initialization vector.
def initialize(key, iv, mode)
  @key = key
  @expanded_key_blocks = expand_key(@key)
  @prev_block = iv.bytes
  @mode = mode
end

def inverse_mix_columns(state)

s[3,c] [11 13 09 14] s[3,c]
s[2,c] = [13 09 14 11] s[2,c]
s[1,c] [09 14 11 13] s[1,c]
s[0,c] [14 11 13 09] s[0,c]

I.e. written as matrix multiplication:

fixed polynomial 3x^3 + x^2 + x + 2 modulo x^4 + 1, which is 11x^3 + 13x^2 + 9x + 14.
Each column is interpreted as polynomial in GF(2^8) and multiplied by the inverse of the

InvMixColumns step
def inverse_mix_columns(state)
  4.times do |c| # each column
    s0, s1, s2, s3 = state[4 * c], state[4 * c + 1], state[4 * c + 2], state[4 * c + 3]
    state[4 * c]     = G14MULT[s0] ^ G11MULT[s1] ^ G13MULT[s2] ^ G9MULT[s3]
    state[4 * c + 1] = G9MULT[s0] ^ G14MULT[s1] ^ G11MULT[s2] ^ G13MULT[s3]
    state[4 * c + 2] = G13MULT[s0] ^ G9MULT[s1] ^ G14MULT[s2] ^ G11MULT[s3]
    state[4 * c + 3] = G11MULT[s0] ^ G13MULT[s1] ^ G9MULT[s2] ^ G14MULT[s3]
  end
end

def inverse_sub_bytes_and_shift_rows(state)

3 7 11 15 7 11 15 3
2 6 10 14 10 14 2 6
1 5 9 13 ---> 13 1 5 9
0 4 8 12 0 4 8 12
places to the left., i.e.
InvShiftRows: Cyclically shifts row two by three, row three by two and row four by one

inverse S-box, using the byte value as index into it
InvSubBytes: Substitutes each byte in the state with the corresponding value from the

InvSubBytes and InvShiftRows steps combined
def inverse_sub_bytes_and_shift_rows(state)
  state[0] = INV_SBOX[state[0]]
  state[4] = INV_SBOX[state[4]]
  state[8] = INV_SBOX[state[8]]
  state[12] = INV_SBOX[state[12]]
  t = INV_SBOX[state[13]]
  state[13] = INV_SBOX[state[9]]
  state[9] = INV_SBOX[state[5]]
  state[5] = INV_SBOX[state[1]]
  state[1] = t
  t = INV_SBOX[state[2]]
  u = INV_SBOX[state[6]]
  state[2] = INV_SBOX[state[10]]
  state[6] = INV_SBOX[state[14]]
  state[10] = t
  state[14] = u
  t = INV_SBOX[state[15]]
  u = INV_SBOX[state[11]]
  v = INV_SBOX[state[7]]
  state[15] = INV_SBOX[state[3]]
  state[11] = t
  state[7] = u
  state[3] = v
end

def mix_columns(state)

s[3,c] [03 01 01 02] s[3,c]
s[2,c] = [01 01 02 03] s[2,c]
s[1,c] [01 02 03 01] s[1,c]
s[0,c] [02 03 01 01] s[0,c]

I.e. written as matrix multiplication:

3x^3 + x^2 + x + 2 modulo x^4 + 1.
Each column is interpreted as polynomial in GF(2^8) and multiplied by the fixed polynomial

MixColumns step
def mix_columns(state)
  4.times do |c| # each column
    s0, s1, s2, s3 = state[4 * c], state[4 * c + 1], state[4 * c + 2], state[4 * c + 3]
    state[4 * c]     = G2MULT[s0] ^ G3MULT[s1] ^ s2 ^ s3
    state[4 * c + 1] = s0 ^ G2MULT[s1] ^ G3MULT[s2] ^ s3
    state[4 * c + 2] = s0 ^ s1 ^ G2MULT[s2] ^ G3MULT[s3]
    state[4 * c + 3] = G3MULT[s0] ^ s1 ^ s2 ^ G2MULT[s3]
  end
end

def process(data)

Encrypts or decrypts the given data whose length must be a multiple of BLOCK_SIZE.
def process(data)
  data = data.bytes
  (data.size / BLOCK_SIZE).times do |i|
    block = data[i * BLOCK_SIZE, BLOCK_SIZE]
    if @mode == :encrypt
      xor_blocks(block, @prev_block) # CBC: XOR plain text block with previous cipher block
      send(@mode, block)
      @prev_block = block
    else
      prev = block.dup
      send(@mode, block)
      xor_blocks(block, @prev_block) # CBC: XOR plain text block with previous cipher block
      @prev_block = prev
    end
    data[i * BLOCK_SIZE, BLOCK_SIZE] = block
  end
  data.pack('C*')
end

def sub_bytes_and_shift_rows(state)

3 7 11 15 15 3 7 11
2 6 10 14 10 14 2 6
1 5 9 13 ---> 5 9 13 1
0 4 8 12 0 4 8 12
to the left, i.e.
ShiftRows: Cyclically shifts row two by one, row three by two and row four by three places

using the byte value as index into the S-box.
SubBytes: Substitutes each byte in the state with the corresponding value from the S-box,

SubBytes and ShiftRows steps combined
def sub_bytes_and_shift_rows(state)
  state[0] = SBOX[state[0]]
  state[4] = SBOX[state[4]]
  state[8] = SBOX[state[8]]
  state[12] = SBOX[state[12]]
  t = SBOX[state[1]]
  state[1] = SBOX[state[5]]
  state[5] = SBOX[state[9]]
  state[9] = SBOX[state[13]]
  state[13] = t
  t = SBOX[state[2]]
  u = SBOX[state[6]]
  state[2] = SBOX[state[10]]
  state[6] = SBOX[state[14]]
  state[10] = t
  state[14] = u
  t = SBOX[state[3]]
  u = SBOX[state[7]]
  v = SBOX[state[11]]
  state[3] = SBOX[state[15]]
  state[7] = t
  state[11] = u
  state[15] = v
end