class Epuber::EncryptionHandler

Constants

ADOBE_OBFUSCATION
IDPF_OBFUSCATION

Attributes

encryption_items[R]

@return [Hash<String, EncryptionItem>] key is abs file path (from root of EPUB), value is EncryptionItem

Public Class Methods

decrypt_data(key, data, algorithm) click to toggle source

Decrypt data with given key and algorithm

@param [String] key @param [String] data @param [String] algorithm

# File lib/epuber/from_file/encryption_handler.rb, line 67
def self.decrypt_data(key, data, algorithm)
  is_adobe = algorithm == ADOBE_OBFUSCATION
  crypt_len = is_adobe ? 1024 : 1040
  crypt = data.byteslice(0, crypt_len)
              .bytes
  key_cycle = key.bytes
                 .cycle
  decrypt = crypt.each_with_object([]) { |x, acc| acc << (x ^ key_cycle.next) }
                 .pack('C*')
  decrypt + data.byteslice(crypt_len..-1)
end
find_and_parse_encryption_key(identifiers) click to toggle source

@param [String] raw_unique_identifier @param [Array<Nokogiri::XML::Node>] identifiers

@return [String, nil]

# File lib/epuber/from_file/encryption_handler.rb, line 95
def self.find_and_parse_encryption_key(identifiers)
  raw_identifier = identifiers.find do |i|
    i['scheme']&.downcase == 'uuid' || i.text.strip.start_with?('urn:uuid:')
  end&.text&.strip
  return nil unless raw_identifier

  uuid_str = raw_identifier.sub(/^urn:uuid:/, '')
  UUIDTools::UUID.parse(uuid_str).raw
end
new(encryption_file, opf) click to toggle source

@param [String] encryption_file contents of META-INF/encryption.xml file @param [Epuber::OpfFile] opf

# File lib/epuber/from_file/encryption_handler.rb, line 47
def initialize(encryption_file, opf)
  @opf = opf
  @encryption_items = _prepare_items(encryption_file)
end
parse_encryption_file(string) click to toggle source

Parse META-INF/encryption.xml file

@return [Array<EncryptionItem>, nil]

# File lib/epuber/from_file/encryption_handler.rb, line 109
def self.parse_encryption_file(string)
  doc = Nokogiri::XML(string)
  doc.remove_namespaces!

  encryption_node = doc.at_css('encryption')
  return nil unless encryption_node

  encryption_node.css('EncryptedData')
                 .map do |encrypted_data_node|
                   algorithm = encrypted_data_node.at_css('EncryptionMethod')['Algorithm']
                   file_path = encrypted_data_node.at_css('CipherData CipherReference')['URI']

                   EncryptionItem.new(algorithm, file_path)
                 end
end
parse_idpf_key(raw_unique_identifier) click to toggle source

Parse IDPF key from unique identifier (main identifier from OPF file)

@param [String] raw_unique_identifier

@return [String, nil]

# File lib/epuber/from_file/encryption_handler.rb, line 85
def self.parse_idpf_key(raw_unique_identifier)
  key = raw_unique_identifier.strip.gsub(/[\u0020\u0009\u000d\u000a]/, '')
  Digest::SHA1.digest(key)
end

Public Instance Methods

_prepare_items(encryption_file) click to toggle source

Prepare encryption items with correct keys

@param [String] encryption_file

@return [Hash<String, EncryptionItem>]

# File lib/epuber/from_file/encryption_handler.rb, line 131
def _prepare_items(encryption_file)
  idpf_key = EncryptionHandler.parse_idpf_key(@opf.raw_unique_identifier)
  adobe_key = EncryptionHandler.find_and_parse_encryption_key(@opf.identifiers)

  items = EncryptionHandler.parse_encryption_file(encryption_file)
  items.each do |i|
    if i.algorithm == EncryptionHandler::IDPF_OBFUSCATION
      i.key = idpf_key
    elsif i.algorithm == EncryptionHandler::ADOBE_OBFUSCATION
      i.key = adobe_key
    end
  end
  items.map { |i| [i.file_path, i] }.to_h
end
process_file(path, data) click to toggle source

@param [String] path @param [String] data

# File lib/epuber/from_file/encryption_handler.rb, line 54
def process_file(path, data)
  enc_item = @encryption_items[path]
  data = EncryptionHandler.decrypt_data(enc_item.key, data, enc_item.algorithm) if enc_item

  data
end