module HexaPDF::Encryption::AES::ClassMethods

Convenience methods for decryption and encryption that operate according to the PDF specification.

These methods will be available on the class object that prepends the AES module.

Public Instance Methods

decrypt(key, data) click to toggle source

Decrypts the given data using the key.

It is assumed that the initialization vector is included in the first BLOCK_SIZE bytes of the data. After the decryption the PKCS#5 padding is removed.

See: PDF1.7 s7.6.2.

# File lib/hexapdf/encryption/aes.rb, line 115
def decrypt(key, data)
  if data.length % BLOCK_SIZE != 0 || data.length < 2 * BLOCK_SIZE
    raise HexaPDF::EncryptionError, "Invalid data for decryption, need 32 + 16*n bytes"
  end
  unpad(new(key, data.slice!(0, BLOCK_SIZE), :decrypt).process(data))
end
decryption_fiber(key, source) click to toggle source

Returns a Fiber object that decrypts the data from the given source fiber with the key.

Padding and the initialization vector are handled like in decrypt.

# File lib/hexapdf/encryption/aes.rb, line 126
def decryption_fiber(key, source)
  Fiber.new do
    data = ''.b
    while data.length < BLOCK_SIZE && source.alive? && (new_data = source.resume)
      data << new_data
    end

    algorithm = new(key, data.slice!(0, BLOCK_SIZE), :decrypt)

    while source.alive? && (new_data = source.resume)
      data << new_data
      next if data.length < 2 * BLOCK_SIZE
      new_data = data.slice!(0, data.length - BLOCK_SIZE - data.length % BLOCK_SIZE)
      Fiber.yield(algorithm.process(new_data))
    end

    if data.length < BLOCK_SIZE || data.length % BLOCK_SIZE != 0
      raise HexaPDF::EncryptionError, "Invalid data for decryption, need 32 + 16*n bytes"
    end

    unpad(algorithm.process(data))
  end
end
encrypt(key, data) click to toggle source

Encrypts the given data using the key and a randomly generated initialization vector.

The data is padded using the PKCS#5 padding scheme and the initialization vector is prepended to the encrypted data,

See: PDF1.7 s7.6.2.

# File lib/hexapdf/encryption/aes.rb, line 82
def encrypt(key, data)
  iv = random_bytes(BLOCK_SIZE)
  iv << new(key, iv, :encrypt).process(pad(data))
end
encryption_fiber(key, source) click to toggle source

Returns a Fiber object that encrypts the data from the given source fiber with the key.

Padding and the initialization vector are handled like in encrypt.

# File lib/hexapdf/encryption/aes.rb, line 91
def encryption_fiber(key, source)
  Fiber.new do
    data = random_bytes(BLOCK_SIZE)
    algorithm = new(key, data, :encrypt)
    Fiber.yield(data)

    data = ''.b
    while source.alive? && (new_data = source.resume)
      data << new_data
      next if data.length < BLOCK_SIZE
      new_data = data.slice!(0, data.length - data.length % BLOCK_SIZE)
      Fiber.yield(algorithm.process(new_data))
    end

    algorithm.process(pad(data))
  end
end
random_bytes(n) click to toggle source

Returns a string of n random bytes.

The specific AES algorithm class can override this class method to provide another method for generating random bytes.

# File lib/hexapdf/encryption/aes.rb, line 154
def random_bytes(n)
  SecureRandom.random_bytes(n)
end

Private Instance Methods

pad(data) click to toggle source

Pads the data to a muliple of BLOCK_SIZE using the PKCS#5 padding scheme and returns the result.

See: PDF1.7 s7.6.2

# File lib/hexapdf/encryption/aes.rb, line 164
def pad(data)
  padding_length = BLOCK_SIZE - data.size % BLOCK_SIZE
  data + padding_length.chr * padding_length
end
unpad(data) click to toggle source

Removes the padding from the data according to the PKCS#5 padding scheme and returns the result.

In case the padding is not correct as per the specification, it is assumed that there is no padding and the input is returned as is.

See: PDF1.7 s7.6.2

# File lib/hexapdf/encryption/aes.rb, line 176
def unpad(data)
  padding_length = data.getbyte(-1)
  if padding_length > BLOCK_SIZE || padding_length == 0 ||
      data[-padding_length, padding_length].each_byte.any? {|byte| byte != padding_length }
    data
  else
    data[0...-padding_length]
  end
end