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