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 inherited from Dictionary

#names_cache, #strings_cache, #xref_cache

Attributes included from Object

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

Instance Method Summary collapse

Methods included from StandardObject

#do_type_check, #has_field?, included, #pre_build, #set_default_value, #set_default_values

Methods inherited from Dictionary

#[], #[]=, add_type_info, #cast_to, #copy, #delete, guess_type, hint_type, #initialize, #key?, #map!, #merge, #method_missing, native_type, parse, #to_h, #to_obfuscated_str, #to_s

Methods included from Object

#<=>, #cast_to, #copy, #document, #export, #indirect?, #indirect_parent, #initialize, #logicalize, #logicalize!, native_type, #native_type, 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::Dictionary

Instance Method Details

#compute_owner_encryption_key(ownerpassword) ⇒ Object

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



1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
# File 'lib/origami/encryption.rb', line 1202

def compute_owner_encryption_key(ownerpassword)
    if self.R >= 5
        passwd = password_to_utf8(ownerpassword)

        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
end

#compute_user_encryption_key(userpassword, fileid) ⇒ Object

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



1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
# File 'lib/origami/encryption.rb', line 1160

def compute_user_encryption_key(userpassword, fileid)
    if self.R < 5
        padded = pad_password(userpassword)
        padded.force_encoding('binary')

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

        padded << fileid

         = 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

        if self.R == 2
            key[0, 5]
        elsif self.R >= 3
            key[0, self.Length / 8]
        end
    else
        passwd = password_to_utf8(userpassword)

        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
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:



1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
# File 'lib/origami/encryption.rb', line 1294

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:



1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
# File 'lib/origami/encryption.rb', line 1274

def is_user_password?(pass, salt)

    if self.R == 2
        compute_user_password(pass, salt) == self.U
    elsif self.R == 3 or self.R == 4
        compute_user_password(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(ownerpassword) ⇒ Object

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



1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
# File 'lib/origami/encryption.rb', line 1312

def retrieve_user_password(ownerpassword)

    key = compute_owner_key(ownerpassword)

    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_passwords(ownerpassword, userpassword, salt = nil) ⇒ Object

Set up document passwords.



1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
# File 'lib/origami/encryption.rb', line 1222

def set_passwords(ownerpassword, userpassword, salt = nil)
    if self.R < 5
        key = compute_owner_key(ownerpassword)
        upadded = pad_password(userpassword)

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

        self.O = owner_key
        self.U = compute_user_password(userpassword, salt)

    else
        upass = password_to_utf8(userpassword)
        opass = password_to_utf8(ownerpassword)

        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
end

#version_requiredObject

:nodoc:



1149
1150
1151
1152
1153
1154
1155
# File 'lib/origami/encryption.rb', line 1149

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