class Origami::Encryption::Standard::Dictionary
Class defining a standard encryption dictionary.
Constants
- O
- OE
- Perms
- U
- UE
Public Instance Methods
compute_owner_encryption_key(ownerpassword)
click to toggle source
Computes the key that will be used to encrypt/decrypt the document contents with owner password. Revision 5 and above.
# File lib/origami/encryption.rb, line 1157 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)
click to toggle source
Computes the key that will be used to encrypt/decrypt the document contents with user password.
# File lib/origami/encryption.rb, line 1114 def compute_user_encryption_key(userpassword, fileid) if self.R < 5 padded = pad_password(userpassword) padded.force_encoding('binary') if RUBY_VERSION >= '1.9' padded << self.O padded << [ self.P ].pack("i") padded << fileid encrypt_metadata = self.EncryptMetadata != false padded << [ -1 ].pack("i") if self.R >= 4 and not encrypt_metadata 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)
click to toggle source
Checks owner password. For version 2,3 and 4, salt is the document ID. For version 5, salt is (Owner Key Salt + U)
# File lib/origami/encryption.rb, line 1248 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)
click to toggle source
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.
# File lib/origami/encryption.rb, line 1229 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)
click to toggle source
Retrieve user password from owner password. Cannot be used with revision 5.
# File lib/origami/encryption.rb, line 1266 def retrieve_user_password(ownerpassword) key = compute_owner_key(ownerpassword) if self.R == 2 ARC4.decrypt(key, self.O) elsif self.R == 3 or self.R == 4 user_password = ARC4.decrypt(xor(key, 19), self.O) 19.times { |i| user_password = ARC4.decrypt(xor(key, 18-i), user_password) } user_password end end
set_passwords(ownerpassword, userpassword, salt = nil)
click to toggle source
Set up document passwords.
# File lib/origami/encryption.rb, line 1177 def set_passwords(ownerpassword, userpassword, salt = nil) if self.R < 5 key = compute_owner_key(ownerpassword) upadded = pad_password(userpassword) owner_key = ARC4.encrypt(key, upadded) 19.times { |i| owner_key = ARC4.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
Private Instance Methods
compute_hardened_hash(password, salt, vector = '')
click to toggle source
Computes hardened hash used in revision 6 (extension level 8).
# File lib/origami/encryption.rb, line 1326 def compute_hardened_hash(password, salt, vector = '') block_size = 32 input = Digest::SHA256.digest(password + salt + vector) + "\x00" * 32 key = input[0, 16] iv = input[16, 16] digest, aes, h, x = nil, nil, nil, nil i = 0 while i < 64 or i < x[-1].ord + 32 j = 0 block = input[0, block_size] if Origami::OPTIONS[:use_openssl] aes = OpenSSL::Cipher::Cipher.new("aes-128-cbc").encrypt aes.iv = iv aes.key = key aes.padding = 0 else fail "You need OpenSSL support to encrypt/decrypt documents with this method" end 64.times do |j| x = '' x += aes.update(password) unless password.empty? x += aes.update(block) x += aes.update(vector) unless vector.empty? if j == 0 block_size = 32 + (x.unpack("C16").inject(0) {|a,b| a+b} % 3) * 16 digest = Digest::SHA2.new(block_size << 3) end digest.update(x) end h = digest.digest key = h[0, 16] input[0, block_size] = h[0, block_size] iv = h[16, 16] i = i + 1 end h[0, 32] end