Class: Axlsx::MsOffCrypto

Inherits:
Object
  • Object
show all
Defined in:
lib/axlsx/util/ms_off_crypto.rb

Overview

The MsOffCrypto class implements ECMA-367 encryption based on the MS-OFF-CRYPTO specification

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file_name, pwd) ⇒ MsOffCrypto

Creates a new MsOffCrypto Object

Parameters:

  • file_name (String)

    the location of the file you want to encrypt

  • pwd (String)

    the password to use when encrypting the file



14
15
16
17
# File 'lib/axlsx/util/ms_off_crypto.rb', line 14

def initialize(file_name, pwd)
  self.password = pwd
  self.file_name = file_name
end

Instance Attribute Details

#file_nameString

retruns the file name of the archive to be encrypted

Returns:

  • (String)


38
39
40
# File 'lib/axlsx/util/ms_off_crypto.rb', line 38

def file_name
  @file_name
end

#passwordString

returns the raw password used in encryption

Returns:

  • (String)


27
28
29
# File 'lib/axlsx/util/ms_off_crypto.rb', line 27

def password
  @password
end

Instance Method Details

#create_encryption_infoString

Generates an encryption info structure

Returns:

  • (String)


105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/axlsx/util/ms_off_crypto.rb', line 105

def create_encryption_info
  header = [3, 0, 2, 0] # version
  # Header flags copy
  header.concat [0x24, 0, 0, 0] #flags -- VERY UNSURE ABOUT THIS STILL
  # header.concat [0, 0, 0, 0] #unused
  header.concat [0xA4, 0, 0, 0] #length
  # Header
  header.concat  [0x24, 0, 0, 0] #flags again
  # header.concat [0, 0, 0, 0] #unused again,
  header.concat [0x0E, 0x66, 0, 0] #alg id
  header.concat [0x04, 0x80, 0, 0] #alg hash id
  header.concat [key.size, 0, 0, 0] #key size
  header.concat [0x18, 0, 0, 0] #provider type
  # header.concat [0, 0, 0, 0] #reserved 1
  # header.concat [0, 0, 0, 0] #reserved 2
  header.concat [0xA0, 0xC7, 0xDC, 0x2, 0, 0, 0, 0]
  header.concat "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)".bytes.to_a.pack('s*').bytes.to_a
  header.concat [0, 0] #null terminator

  #Salt Size
  header.concat [salt.bytes.to_a.size].pack('l').bytes.to_a
  #Salt
  header.concat salt.bytes.to_a.pack('c*').bytes.to_a
  # encryption verifier
  header.concat encrypted_verifier.bytes.to_a.pack('c*').bytes.to_a

  # verifier hash size -- MUST BE 32 bytes
  header.concat [verifier_hash.bytes.to_a.size].pack('l').bytes.to_a

  #encryption verifier hash
  header.concat encrypted_verifier_hash.bytes.to_a.pack('c*').bytes.to_a

  header.flatten!
  header.pack('c*')
end

#create_keyObject

2.3.4.7 ECMA-376 Document Encryption Key Generation (Standard Encryption)



148
149
150
151
152
153
154
155
156
157
158
# File 'lib/axlsx/util/ms_off_crypto.rb', line 148

def create_key
  sha = Digest::SHA1.new() << (salt + @password)
  (0..49999).each { |i| sha.update(i.to_s+sha.to_s) }
  key = sha.update(sha.to_s+'0').digest
  a = key.bytes.each_with_index.map { |item, i|  0x36 ^ item }
  x1 = Digest::SHA1.digest((a.concat Array.new(64 - key.size, 0x36)).to_s)
  a = key.bytes.each_with_index.map { |item, i| 0x5C ^ item }
  x2 = Digest::SHA1.digest( (a.concat Array.new(64 - key.size, 0x5C) ).to_s)
  x3 = x1 + x2
  x3.bytes.to_a[(0..31)].pack('c*')
end

#create_verifier_hashObject

2.3.3



142
143
144
145
# File 'lib/axlsx/util/ms_off_crypto.rb', line 142

def create_verifier_hash
  vh = Digest::SHA1.digest(verifier)
  vh << Array.new(32 - vh.size, 0).join('')
end

#decrypt(data) ⇒ String

dencrypts the data proved

Parameters:

  • data (String)

Returns:

  • (String)

    the dencrypted data



181
182
183
184
185
186
# File 'lib/axlsx/util/ms_off_crypto.rb', line 181

def decrypt(data)
  aes = OpenSSL::Cipher.new("AES-128-ECB")     
  aes.decrypt
  aes.key = key
  aes.update(data) << aes.final
end

#encrypt(data) ⇒ String

encrypts the data proved

Parameters:

  • data (String)

Returns:

  • (String)

    the encrypted data



171
172
173
174
175
176
# File 'lib/axlsx/util/ms_off_crypto.rb', line 171

def encrypt(data)
  aes = OpenSSL::Cipher.new("AES-128-ECB")     
  aes.encrypt
  aes.key = key
  aes.update(data) << aes.final
end

#encrypt_package(file_name) ⇒ Object

size of unencrypted package? concated with encrypted package



97
98
99
100
101
# File 'lib/axlsx/util/ms_off_crypto.rb', line 97

def encrypt_package(file_name)      
  package = File.open(file_name, 'r')
  crypt_pack = encrypt(package.read)      
  [crypt_pack.size].pack('q') + crypt_pack
end

#encrypted_packageString

encrypts and returns the package specified by the file name

Returns:

  • (String)


50
51
52
# File 'lib/axlsx/util/ms_off_crypto.rb', line 50

def encrypted_package
  @encrypted_package ||= encrypt_package(file_name)
end

#encrypted_verifierString

returns the verifier encrytped

Returns:

  • (String)


74
75
76
# File 'lib/axlsx/util/ms_off_crypto.rb', line 74

def encrypted_verifier
  @encrypted_verifier ||= encrypt(verifier)
end

#encrypted_verifier_hashString

returns the verifier hash encrypted

Returns:

  • (String)


80
81
82
# File 'lib/axlsx/util/ms_off_crypto.rb', line 80

def encrypted_verifier_hash
  @encrypted_verifier_hash ||= encrypt(verifier_hash)
end

#encryption_infoString

returns the encryption info for this instance of ms-off-crypto

Returns:

  • (String)


56
57
58
# File 'lib/axlsx/util/ms_off_crypto.rb', line 56

def encryption_info 
  @encryption_info ||= create_encryption_info
end

#keyString

returns an encryption key

Returns:

  • (String)


92
93
94
# File 'lib/axlsx/util/ms_off_crypto.rb', line 92

def key 
  @key ||= create_key
end

#saltString

returns a random salt

Returns:

  • (String)


62
63
64
# File 'lib/axlsx/util/ms_off_crypto.rb', line 62

def salt
  @salt ||= Digest::SHA1.digest(rand(16**16).to_s)
end

#saveObject

Generates a new CBF file based on this instance of ms-off-crypto and overwrites the unencrypted file.



20
21
22
23
# File 'lib/axlsx/util/ms_off_crypto.rb', line 20

def save
  cfb = Cbf.new(self)
  cfb.save
end

#verifierString

returns a random verifier

Returns:

  • (String)


68
69
70
# File 'lib/axlsx/util/ms_off_crypto.rb', line 68

def verifier 
  @verifier ||= rand(16**16).to_s
end

#verifier_hashString

returns a verifier hash

Returns:

  • (String)


86
87
88
# File 'lib/axlsx/util/ms_off_crypto.rb', line 86

def verifier_hash
  @verifier_hash ||= create_verifier_hash
end

#verify_passwordBoolean

ensures that the a hashed decryption of the encryption verifier matches the decrypted verifier hash.

Returns:

  • (Boolean)


162
163
164
165
166
# File 'lib/axlsx/util/ms_off_crypto.rb', line 162

def verify_password
  v = Digest::SHA1.digest decrypt(@encrypted_verifier)
  vh = decrypt(@encrypted_verifier_hash)
  vh[0..15] == v[0..15]
end