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.



820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
# File 'lib/origami/encryption.rb', line 820

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 and not 

    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.



843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
# File 'lib/origami/encryption.rb', line 843

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

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

    if self.R == 5
        okey = Digest::SHA256.digest(passwd + oks + self.U)
    else
        okey = 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.



799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
# File 'lib/origami/encryption.rb', line 799

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]

    if self.R == 5
        ukey = Digest::SHA256.digest(passwd + uks)
    else
        ukey = 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.



780
781
782
783
784
785
786
787
788
789
790
791
792
793
# File 'lib/origami/encryption.rb', line 780

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:



939
940
941
942
943
944
945
946
947
948
949
950
951
# File 'lib/origami/encryption.rb', line 939

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:



919
920
921
922
923
924
925
926
927
928
929
930
931
932
# File 'lib/origami/encryption.rb', line 919

def is_user_password?(pass, salt)

    if self.R == 2
        compute_user_password_hash(pass, salt) == self.U
    elsif self.R == 3 or 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.



957
958
959
960
961
962
963
964
965
966
967
968
969
# File 'lib/origami/encryption.rb', line 957

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 or 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.



903
904
905
906
907
908
909
910
911
912
# File 'lib/origami/encryption.rb', line 903

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.



862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
# File 'lib/origami/encryption.rb', line 862

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:



768
769
770
771
772
773
774
# File 'lib/origami/encryption.rb', line 768

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