Class: Origami::Encryption::Standard::Dictionary

Inherits:
EncryptionDictionary show all
Defined in:
lib/origami/encryption.rb

Overview

Class defining a standard encryption dictionary.

Constant Summary

Constants included from StandardObject

StandardObject::DEFAULT_ATTRIBUTES

Constants inherited from Dictionary

Dictionary::TOKENS

Constants included from Object

Object::TOKENS

Instance Attribute Summary

Attributes included from ObjectCache

#names_cache, #strings_cache, #xref_cache

Attributes included from Object

#file_offset, #generation, #no, #objstm_offset, #parent

Instance Method Summary collapse

Methods inherited from EncryptionDictionary

#encryption_cipher, #stream_encryption_cipher, #string_encryption_cipher

Methods included from StandardObject

included, #pre_build

Methods inherited from Dictionary

#[], #[]=, hint_type, #initialize, #merge, parse, #to_h, #to_obfuscated_str, #to_s, #transform_values, #transform_values!

Methods included from TypeGuessing

#guess_type

Methods included from FieldAccessor

#method_missing, #respond_to_missing?

Methods included from CompoundObject

#copy, #delete, #include?, #update_values, #update_values!

Methods included from ObjectCache

#initialize, #rebuild_caches

Methods included from Object

#cast_to, #copy, #document, #export, included, #indirect?, #indirect_parent, #initialize, #logicalize, #logicalize!, #native_type, #numbered?, parse, #post_build, #pre_build, #reference, #set_document, #set_indirect, skip_until_next_obj, #solve, #to_o, #to_s, #type, typeof, #xrefs

Constructor Details

This class inherits a constructor from Origami::Dictionary

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Origami::FieldAccessor

Instance Method Details

#compute_legacy_user_encryption_key(user_password, file_id) ⇒ Object

Computes the key that will be used to encrypt/decrypt the document contents. Only for Revision 4 and less.



871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
# File 'lib/origami/encryption.rb', line 871

def compute_legacy_user_encryption_key(user_password, file_id)
  padded = pad_password(user_password)
  padded.force_encoding('binary')

  padded << self.O
  padded << [self.P].pack("i")

  padded << file_id

   = self.EncryptMetadata != false
  padded << [-1].pack("i") if (self.R >= 4) && !

  key = Digest::MD5.digest(padded)

  50.times { key = Digest::MD5.digest(key[0, self.Length / 8]) } if self.R >= 3

  truncate_key(key)
end

#compute_owner_encryption_key(owner_password) ⇒ Object

Computes the key that will be used to encrypt/decrypt the document contents with owner password. Revision 5 and above.



894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
# File 'lib/origami/encryption.rb', line 894

def compute_owner_encryption_key(owner_password)
  return if self.R < 5

  passwd = password_to_utf8(owner_password)
  oks = self.O[40, 8]

  okey = if self.R == 5
    Digest::SHA256.digest(passwd + oks + self.U)
  else
    compute_hardened_hash(passwd, oks, self.U)
  end

  iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
  AES.new(okey, nil, false).decrypt(iv + self.OE.value)
end

#compute_user_encryption_key(user_password, file_id) ⇒ Object

Computes the key that will be used to encrypt/decrypt the document contents with user password. Called at all revisions.



850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
# File 'lib/origami/encryption.rb', line 850

def compute_user_encryption_key(user_password, file_id)
  return compute_legacy_user_encryption_key(user_password, file_id) if self.R < 5

  passwd = password_to_utf8(user_password)

  uks = self.U[40, 8]

  ukey = if self.R == 5
    Digest::SHA256.digest(passwd + uks)
  else
    compute_hardened_hash(passwd, uks)
  end

  iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
  AES.new(ukey, nil, false).decrypt(iv + self.UE.value)
end

#derive_encryption_key(passwd, doc_id) ⇒ Object

Checks the given password and derives the document encryption key. Raises EncryptionInvalidPasswordError on invalid password.



831
832
833
834
835
836
837
838
839
840
841
842
843
844
# File 'lib/origami/encryption.rb', line 831

def derive_encryption_key(passwd, doc_id)
  if is_user_password?(passwd, doc_id)
    compute_user_encryption_key(passwd, doc_id)
  elsif is_owner_password?(passwd, doc_id)
    if self.V.to_i < 5
      user_passwd = retrieve_user_password(passwd)
      compute_user_encryption_key(user_passwd, doc_id)
    else
      compute_owner_encryption_key(passwd)
    end
  else
    raise EncryptionInvalidPasswordError
  end
end

#is_owner_password?(pass, salt) ⇒ Boolean

Checks owner password. For version 2,3 and 4, salt is the document ID. For version 5, salt is (Owner Key Salt + U)

Returns:



989
990
991
992
993
994
995
996
997
998
999
1000
# File 'lib/origami/encryption.rb', line 989

def is_owner_password?(pass, salt)
  if self.R < 5
    user_password = retrieve_user_password(pass)
    is_user_password?(user_password, salt)
  elsif self.R == 5
    ovs = self.O[32, 8]
    Digest::SHA256.digest(password_to_utf8(pass) + ovs + self.U) == self.O[0, 32]
  elsif self.R == 6
    ovs = self.O[32, 8]
    compute_hardened_hash(password_to_utf8(pass), ovs, self.U[0, 48]) == self.O[0, 32]
  end
end

#is_user_password?(pass, salt) ⇒ Boolean

Checks user password. For version 2, 3 and 4, salt is the document ID. For version 5 and 6, salt is the User Key Salt.

Returns:



970
971
972
973
974
975
976
977
978
979
980
981
982
# File 'lib/origami/encryption.rb', line 970

def is_user_password?(pass, salt)
  if self.R == 2
    compute_user_password_hash(pass, salt) == self.U
  elsif (self.R == 3) || (self.R == 4)
    compute_user_password_hash(pass, salt)[0, 16] == self.U[0, 16]
  elsif self.R == 5
    uvs = self.U[32, 8]
    Digest::SHA256.digest(password_to_utf8(pass) + uvs) == self.U[0, 32]
  elsif self.R == 6
    uvs = self.U[32, 8]
    compute_hardened_hash(password_to_utf8(pass), uvs) == self.U[0, 32]
  end
end

#retrieve_user_password(owner_password) ⇒ Object

Retrieve user password from owner password. Cannot be used with revision 5.



1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
# File 'lib/origami/encryption.rb', line 1006

def retrieve_user_password(owner_password)
  key = compute_owner_key(owner_password)

  if self.R == 2
    RC4.decrypt(key, self.O)
  elsif (self.R == 3) || (self.R == 4)
    user_password = RC4.decrypt(xor(key, 19), self.O)
    19.times { |i| user_password = RC4.decrypt(xor(key, 18 - i), user_password) }

    user_password
  end
end

#set_legacy_passwords(owner_password, user_password, salt) ⇒ Object

Set up document passwords. Only for Revision 4 and less.



954
955
956
957
958
959
960
961
962
963
# File 'lib/origami/encryption.rb', line 954

def set_legacy_passwords(owner_password, user_password, salt)
  owner_key = compute_owner_key(owner_password)
  upadded = pad_password(user_password)

  owner_key_hash = RC4.encrypt(owner_key, upadded)
  19.times { |i| owner_key_hash = RC4.encrypt(xor(owner_key, i + 1), owner_key_hash) } if self.R >= 3

  self.O = owner_key_hash
  self.U = compute_user_password_hash(user_password, salt)
end

#set_passwords(owner_password, user_password, salt = nil) ⇒ Object

Set up document passwords.



913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
# File 'lib/origami/encryption.rb', line 913

def set_passwords(owner_password, user_password, salt = nil)
  return set_legacy_passwords(owner_password, user_password, salt) if self.R < 5

  upass = password_to_utf8(user_password)
  opass = password_to_utf8(owner_password)

  uvs, uks, ovs, oks = ::Array.new(4) { Encryption.rand_bytes(8) }
  file_key = Encryption.strong_rand_bytes(32)
  iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")

  if self.R == 5
    self.U = Digest::SHA256.digest(upass + uvs) + uvs + uks
    self.O = Digest::SHA256.digest(opass + ovs + self.U) + ovs + oks
    ukey = Digest::SHA256.digest(upass + uks)
    okey = Digest::SHA256.digest(opass + oks + self.U)
  else
    self.U = compute_hardened_hash(upass, uvs) + uvs + uks
    self.O = compute_hardened_hash(opass, ovs, self.U) + ovs + oks
    ukey = compute_hardened_hash(upass, uks)
    okey = compute_hardened_hash(opass, oks, self.U)
  end

  self.UE = AES.new(ukey, iv, false).encrypt(file_key)[iv.size, 32]
  self.OE = AES.new(okey, iv, false).encrypt(file_key)[iv.size, 32]

  perms =
    [self.P].pack("V") +                              # 0-3
    [-1].pack("V") +                                  # 4-7
    ((self.EncryptMetadata == true) ? "T" : "F") +        # 8
    "adb" +                                             # 9-11
    [0].pack("V")                                     # 12-15

  self.Perms = AES.new(file_key, iv, false).encrypt(perms)[iv.size, 16]

  file_key
end

#version_requiredObject

:nodoc:



819
820
821
822
823
824
825
# File 'lib/origami/encryption.rb', line 819

def version_required # :nodoc:
  if self.R > 5
    ['1.7', 8]
  else
    super
  end
end