Class: Epuber::EncryptionHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/epuber/from_file/encryption_handler.rb

Defined Under Namespace

Classes: EncryptionItem

Constant Summary collapse

ADOBE_OBFUSCATION =
'http://ns.adobe.com/pdf/enc#RC'
IDPF_OBFUSCATION =
'http://www.idpf.org/2008/embedding'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(encryption_file, opf) ⇒ EncryptionHandler

Returns a new instance of EncryptionHandler.

Parameters:

  • encryption_file (String)

    contents of META-INF/encryption.xml file

  • opf (Epuber::OpfFile)


47
48
49
50
# File 'lib/epuber/from_file/encryption_handler.rb', line 47

def initialize(encryption_file, opf)
  @opf = opf
  @encryption_items = _prepare_items(encryption_file)
end

Instance Attribute Details

#encryption_itemsHash<String, EncryptionItem> (readonly)

Returns key is abs file path (from root of EPUB), value is EncryptionItem.

Returns:

  • (Hash<String, EncryptionItem>)

    key is abs file path (from root of EPUB), value is EncryptionItem



42
43
44
# File 'lib/epuber/from_file/encryption_handler.rb', line 42

def encryption_items
  @encryption_items
end

Class Method Details

.decrypt_data(key, data, algorithm) ⇒ Object

Decrypt data with given key and algorithm

Parameters:

  • key (String)
  • data (String)
  • algorithm (String)


67
68
69
70
71
72
73
74
75
76
77
# 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) ⇒ String?

Parameters:

Returns:

  • (String, nil)


95
96
97
98
99
100
101
102
103
# 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

.parse_encryption_file(string) ⇒ Array<EncryptionItem>?

Parse META-INF/encryption.xml file

Returns:



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# 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) ⇒ String?

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

Parameters:

  • raw_unique_identifier (String)

Returns:

  • (String, nil)


85
86
87
88
# 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

Instance Method Details

#_prepare_items(encryption_file) ⇒ Hash<String, EncryptionItem>

Prepare encryption items with correct keys

Parameters:

  • encryption_file (String)

Returns:



131
132
133
134
135
136
137
138
139
140
141
142
143
144
# 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) ⇒ Object

Parameters:

  • path (String)
  • data (String)


54
55
56
57
58
59
# 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