unxls 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ole/storage'
4
+ require 'openssl'
5
+ require 'set'
6
+ require 'zip'
7
+ require 'awesome_print' if $DEBUG
8
+ require 'pry' if $DEBUG
9
+
10
+ module Unxls
11
+ module Log; end # For easy output of values in different formats
12
+ class BitOps; end # For easy binary operations
13
+
14
+ module Helpers # Too small yet to move to a separate file
15
+ refine Object do
16
+ # Convert String to StringIO unless it's already StringIO
17
+ # @return [StringIO]
18
+ # @raises [RuntimeError] Unless receiver is a String or a StringIO
19
+ def to_sio
20
+ raise "Cannot convert #{self.class} to StringIO" unless self.kind_of?(StringIO) || self.instance_of?(String)
21
+ self.is_a?(StringIO) ? self : StringIO.new(self)
22
+ end
23
+ end
24
+ end
25
+
26
+ module Oshared; end # [MS-OSHARED]: Office Common Data Types and Objects Structures
27
+ module Dtyp; end # [MS-DTYP]: Windows Data Types
28
+ module Offcrypto; end # [MS-OFFCRYPTO]: Office Document Cryptography Structure
29
+
30
+ module Biff8 # Group code by BIFF version
31
+ module Constants; end # Large constants
32
+
33
+ class WorkbookStream; end # Deals with Workbook Stream (2.1.7.20)
34
+
35
+ class Record; end # Recipes for parsing of particular records (2.4)
36
+
37
+ module Structure; end # Deals with structures used in records (2.5)
38
+ module ParsedExpressions; end # Extends Structure. Deals with structures used in formulas (2.5.198). Defined in Structure's file for now.
39
+
40
+ class Browser; end
41
+ end
42
+
43
+ class Parser; end # Opens the compound file and parses the needed storages and streams (2.1.7)
44
+
45
+ end
46
+
47
+ require_relative 'version' unless defined?(Unxls::VERSION)
48
+ require_relative 'log'
49
+ require_relative 'bit_ops'
50
+ # require_relative '../../ext/bit_ops.so'
51
+ require_relative 'biff8/constants'
52
+ require_relative 'biff8/workbook_stream'
53
+ require_relative 'biff8/record'
54
+ require_relative 'biff8/structure'
55
+ require_relative 'biff8/browser'
56
+ require_relative 'oshared'
57
+ require_relative 'dtyp'
58
+ require_relative 'offcrypto'
59
+ require_relative 'parser'
@@ -0,0 +1,423 @@
1
+ # frozen_string_literal: true
2
+
3
+ # [MS-OFFCRYPTO]: Office Document Cryptography Structure
4
+ module Unxls::Offcrypto
5
+ extend self
6
+
7
+ BLOCK_SIZE = 1024
8
+
9
+ DEFAULT_PASSWORD = 'VelvetSweatshop'
10
+
11
+ #
12
+ # XOR obfuscation
13
+ #
14
+
15
+ XOR_PAD = [0xBB, 0xFF, 0xFF, 0xBA, 0xFF, 0xFF, 0xB9, 0x80, 0x00, 0xBE, 0x0F, 0x00, 0xBF, 0x0F, 0x00]
16
+
17
+ XOR_INITIAL_CODE = [
18
+ 0xE1F0, 0x1D0F, 0xCC9C, 0x84C0, 0x110C,
19
+ 0x0E10, 0xF1CE, 0x313E, 0x1872, 0xE139,
20
+ 0xD40F, 0x84F9, 0x280C, 0xA96A, 0x4EC3
21
+ ]
22
+
23
+ XOR_MATRIX = [
24
+ 0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09,
25
+ 0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF,
26
+ 0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0,
27
+ 0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40,
28
+ 0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5,
29
+ 0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A,
30
+ 0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9,
31
+ 0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0,
32
+ 0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC,
33
+ 0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10,
34
+ 0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168,
35
+ 0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C,
36
+ 0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD,
37
+ 0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC,
38
+ 0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4
39
+ ]
40
+
41
+ # 2.3.7.2 Binary Document XOR Array Initialization Method 1
42
+ # FUNCTION CreateXorKey_Method1
43
+ # @param password [String]
44
+ # @return [Integer]
45
+ def _create_xor_key_method1(password)
46
+ ansi_password = _unicode_password_to_ansi(password)
47
+
48
+ xor_key = XOR_INITIAL_CODE[ansi_password.size - 1]
49
+ current_element = 0x68
50
+
51
+ ansi_password.bytes.reverse.each do |c|
52
+ 7.times do
53
+ xor_key ^= XOR_MATRIX[current_element] unless (c & 0x40).zero?
54
+ c <<= 1
55
+ current_element -= 1
56
+ end
57
+ end
58
+
59
+ xor_key
60
+ end
61
+
62
+ # 2.3.7.2 Binary Document XOR Array Initialization Method 1
63
+ # FUNCTION CreateXorArray_Method1
64
+ # @param password [String]
65
+ # @return [Array<Integer>]
66
+ def _create_xor_array_method1(password)
67
+ ansi_password = _unicode_password_to_ansi(password).bytes
68
+ xor_key = _create_xor_key_method1(password)
69
+ index = password.size
70
+ obfuscation_array = Array.new(16) { 0 }
71
+
72
+ xor_key_high = xor_key >> 8
73
+ xor_key_low = xor_key & 0xFF
74
+
75
+ if (index & 1) == 1
76
+ obfuscation_array[index] = _xor_ror(XOR_PAD[0], xor_key_high)
77
+ index -= 1
78
+ obfuscation_array[index] = _xor_ror(ansi_password[-1], xor_key_low)
79
+ end
80
+
81
+ while index > 0
82
+ index -= 1
83
+ obfuscation_array[index] = _xor_ror(ansi_password[index], xor_key_high)
84
+ index -= 1
85
+ obfuscation_array[index] = _xor_ror(ansi_password[index], xor_key_low)
86
+ end
87
+
88
+ index = 15
89
+ pad_index = 15 - password.size
90
+ while pad_index > 0
91
+ obfuscation_array[index] = _xor_ror(XOR_PAD[pad_index], xor_key_high)
92
+ index -= 1
93
+ pad_index -= 1
94
+ obfuscation_array[index] = _xor_ror(XOR_PAD[pad_index], xor_key_low)
95
+ index -= 1
96
+ pad_index -= 1
97
+ end
98
+
99
+ obfuscation_array
100
+ end
101
+
102
+ # 2.3.7.1 Binary Document Password Verifier Derivation Method 1
103
+ # FUNCTION CreatePasswordVerifier_Method1
104
+ # @param password [String]
105
+ # @return [Integer]
106
+ def _xor_password_verifier_method1(password)
107
+ verifier = 0
108
+ ansi_password = _unicode_password_to_ansi(password)
109
+
110
+ ansi_password.bytes.unshift(ansi_password.size).reverse.each do |b|
111
+ int1 = (verifier & 0b0100_0000_0000_0000).zero? ? 0 : 1
112
+ int2 = (verifier << 1) & 0b0111_1111_1111_1111
113
+ int3 = int1 | int2
114
+ verifier = int3 ^ b
115
+ end
116
+
117
+ verifier ^ 0xCE4B
118
+ end
119
+
120
+ # @param password [String]
121
+ # @param verification_bytes [String]
122
+ def _xor_password_match?(password, verification_bytes)
123
+ _xor_password_verifier_method1(password) == verification_bytes
124
+ end
125
+
126
+ # See 2.3.7.4 Binary Document Password Verifier Derivation Method 2 (p. 61)
127
+ # @note This method works reliably only for passwords consisting of ASCII characters
128
+ # @param password [String]
129
+ # @return [String]
130
+ def _unicode_password_to_ansi(password)
131
+ password.encode(Encoding::UTF_16LE).unpack('v*').map do |c|
132
+ low_byte = c & 0xFF
133
+ low_byte.zero? ? ((c >> 8) & 0xFF) : low_byte
134
+ end.pack('C*')
135
+ end
136
+
137
+ def _xor_ror(byte1, byte2)
138
+ Unxls::BitOps.new(byte1 ^ byte2).ror(8, 1)
139
+ end
140
+
141
+ # https://github.com/apache/poi/blob/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java#L445
142
+ # https://github.com/apache/poi/blob/trunk/src/java/org/apache/poi/poifs/crypt/xor/XORDecryptor.java
143
+ # @param byte [Integer]
144
+ # @param byte_index [Integer]
145
+ # @param xor_decryption_array [Array<Integer>]
146
+ # @param record_offset [Integer]
147
+ # @param record_size [Integer]
148
+ # @return [String]
149
+ def _xor_decrypt_byte(byte, byte_index, xor_decryption_array, record_offset, record_size)
150
+ # "The initial value for XorArrayIndex is as follows:
151
+ # XorArrayIndex = (FileOffset + Data.Length) % 16
152
+ # The FileOffset variable in this context is the stream offset into the Workbook stream at
153
+ # the time we are about to write each of the bytes of the record data.
154
+ # This (the value) is then incremented after each byte is written."
155
+ # From: http://social.msdn.microsoft.com/Forums/en-US/3dadbed3-0e68-4f11-8b43-3a2328d9ebd5
156
+ xor_array_index = (record_offset + Unxls::Biff8::Record::HEADER_SIZE + record_size + byte_index) & 0xF
157
+
158
+ byte ^= xor_decryption_array[xor_array_index]
159
+ byte = ((byte << 3) | (byte >> 5)) & 0xFF # rotate right 5 bits
160
+
161
+ [byte].pack('C')
162
+ end
163
+
164
+ #
165
+ # RC4
166
+ #
167
+
168
+ # 2.1.4 Version
169
+ # The Version structure specifies the version of a product or feature. It contains a major and a minor version number.
170
+ # @param data [String]
171
+ # @return [Hash]
172
+ def version(data)
173
+ v_major, v_minor = data.unpack('vv')
174
+
175
+ {
176
+ vMajor: v_major, # vMajor (2 bytes): An unsigned integer that specifies the major version number.
177
+ vMinor: v_minor # vMinor (2 bytes): An unsigned integer that specifies the minor version number.
178
+ }
179
+ end
180
+
181
+ # 2.3.6.1 RC4 Encryption Header
182
+ # @param io [StringIO]
183
+ # @return [Hash]
184
+ def rc4encryptionheader(io)
185
+ {
186
+ EncryptionVersionInfo: version(io.read(4)), # EncryptionVersionInfo (4 bytes): A Version structure (section 2.1.4), where Version.vMajor MUST be 0x0001 and Version.vMinor MUST be 0x0001.
187
+ Salt: io.read(16), # Salt (16 bytes): A randomly generated array of bytes that specifies the salt value used during password hash generation.
188
+ EncryptedVerifier: io.read(16), # EncryptedVerifier (16 bytes): An additional 16-byte verifier encrypted using a 40-bit RC4 cipher initialized as specified in section 2.3.6.2, with a block number of 0x00000000.
189
+ EncryptedVerifierHash: io.read(16) # EncryptedVerifierHash (16 bytes): A 40-bit RC4 encrypted MD5 hash of the verifier used to generate the EncryptedVerifier field.
190
+ }
191
+ end
192
+
193
+ # 2.3.6.2 Encryption Key Derivation
194
+ # @param password [String]
195
+ # @param salt [String]
196
+ # @param block_num [Integer]
197
+ # @return [String]
198
+ def _rc4_make_key(password, salt, block_num)
199
+ h0 = OpenSSL::Digest::MD5.digest(password.encode(Encoding::UTF_16LE))
200
+ buffer = (h0[0..4] + salt) * 16
201
+ h1 = OpenSSL::Digest::MD5.digest(buffer)
202
+ hfin = "#{h1[0..4]}#{[block_num].pack('V')}"
203
+ OpenSSL::Digest::MD5.digest(hfin)
204
+ end
205
+
206
+ # 2.3.6.4 Password Verification
207
+ # @param key [String]
208
+ # @param encr_verifier [String]
209
+ # @param encr_verifier_hash [String]
210
+ def _rc4_password_match?(key, encr_verifier, encr_verifier_hash)
211
+ cipher = OpenSSL::Cipher::RC4.new
212
+ cipher.decrypt
213
+ cipher.key = key
214
+ decrypted_verifier = cipher.update(encr_verifier) + cipher.final
215
+ decrypted_verifier_hash = cipher.update(encr_verifier_hash) + cipher.final
216
+ hashed_verifier = OpenSSL::Digest::MD5.digest(decrypted_verifier)
217
+ hashed_verifier == decrypted_verifier_hash
218
+ end
219
+
220
+ # @param data [String]
221
+ # @param key [String]
222
+ # @return [String]
223
+ def _rc4_decrypt(data, key)
224
+ cipher = OpenSSL::Cipher::RC4.new
225
+ cipher.decrypt
226
+ cipher.key = key
227
+ cipher.update(data) + cipher.final
228
+ end
229
+
230
+ #
231
+ # CryptoAPI
232
+ #
233
+
234
+ # 2.3.5.1 RC4 CryptoAPI Encryption Header
235
+ # @param io [StringIO]
236
+ # @return [Hash]
237
+ def rc4cryptoapiheader(io)
238
+ result = {
239
+ EncryptionVersionInfo: version(io.read(4)), # EncryptionVersionInfo (4 bytes): A Version structure (section 2.1.4) that specifies the encryption version used to create the document and the encryption version required to open the document.
240
+ EncryptionHeaderFlags: encryptionheaderflags(io.read(4).unpack('V').first) # EncryptionHeader.Flags (4 bytes): A copy of the Flags stored in the EncryptionHeader structure (section 2.3.2) that is stored in this stream.
241
+ }
242
+
243
+ encryption_header_size = io.read(4).unpack('V').first
244
+ result[:EncryptionHeaderSize] = encryption_header_size # EncryptionHeaderSize (4 bytes): An unsigned integer that specifies the size, in bytes, of the EncryptionHeader structure.
245
+ result[:EncryptionHeader] = encryptionheader(io) # EncryptionHeader (variable): An EncryptionHeader structure (section 2.3.2) used to encrypt the structure.
246
+ result[:EncryptionVerifier] = encryptionverifier(io) # EncryptionVerifier (variable): An EncryptionVerifier structure as specified in section 2.3.3 that is generated as specified in section 2.3.5.5.
247
+ result[:_encryption_algorithm] = _rc4cryptoapi_encryption_alg(result[:EncryptionHeader])
248
+ result[:_hashing_algorithm] = _rc4cryptoapi_hashing_alg(result[:EncryptionHeader])
249
+
250
+ result
251
+ end
252
+
253
+ # 2.3.2 EncryptionHeader
254
+ # The EncryptionHeader structure is used by ECMA-376 document encryption [ECMA-376] and Office binary document RC4 CryptoAPI encryption, as defined in section 2.3.5, to specify encryption properties for an encrypted stream.
255
+ # @param io [StringIO]
256
+ # @return [Hash]
257
+ def encryptionheader(io)
258
+ result = {
259
+ Flags: encryptionheaderflags(io.read(4).unpack('V').first), # Flags (4 bytes): An EncryptionHeaderFlags structure, as specified in section 2.3.1, that specifies properties of the encryption algorithm used.
260
+ SizeExtra: io.read(4).unpack('V').first, # SizeExtra (4 bytes): A field that is reserved and for which the value MUST be 0x00000000.
261
+ }
262
+
263
+ alg_id = io.read(4).unpack('l<').first # AlgID (4 bytes): A signed integer that specifies the encryption algorithm.
264
+ result[:AlgID] = alg_id
265
+ result[:AlgID_d] = {
266
+ 0x0000 => :'Determined by Flags',
267
+ 0x6801 => :RC4,
268
+ 0x660E => :AES128,
269
+ 0x660F => :AES192,
270
+ 0x6610 => :AES256
271
+ }[alg_id]
272
+
273
+ alg_id_hash, key_size, provider_type, _, _ = io.read(20).unpack('l<VV')
274
+ result[:AlgIDHash] = alg_id_hash # AlgIDHash (4 bytes): A signed integer that specifies the hashing algorithm together with the Flags.fExternal bit.
275
+ result[:KeySize] = key_size # KeySize (4 bytes): An unsigned integer that specifies the number of bits in the encryption key.
276
+ result[:ProviderType] = provider_type # ProviderType (4 bytes): An implementation-specific value that corresponds to constants accepted by the specified CSP.
277
+ # Reserved1 (4 bytes): A value that is undefined and MUST be ignored.
278
+ # Reserved2 (4 bytes): A value that MUST be 0x00000000 and MUST be ignored.
279
+
280
+ result[:CSPName] = Unxls::Oshared._db_zero_terminated(io) # CSPName (variable): A null-terminated Unicode string that specifies the CSP name.
281
+
282
+ result
283
+ end
284
+
285
+ # 2.3.1 EncryptionHeaderFlags
286
+ # The EncryptionHeaderFlags structure specifies properties of the encryption algorithm used.
287
+ # @param value [Integer]
288
+ # @return [Hash]
289
+ def encryptionheaderflags(value)
290
+ attrs = Unxls::BitOps.new(value)
291
+
292
+ {
293
+ # A – Reserved1 (1 bit): A value that MUST be 0 and MUST be ignored.
294
+ # B – Reserved2 (1 bit): A value that MUST be 0 and MUST be ignored.
295
+ fCryptoAPI: attrs.set_at?(2), # C – fCryptoAPI (1 bit): A flag that specifies whether CryptoAPI RC4 or ECMA-376 encryption [ECMA- 376] is used.
296
+ fDocProps: attrs.set_at?(3), # D – fDocProps (1 bit): A value that MUST be 0 if document properties are encrypted. The encryption of document properties is specified in section 2.3.5.4.
297
+ fExternal: attrs.set_at?(4), # E – fExternal (1 bit): A value that MUST be 1 if extensible encryption is used.
298
+ fAES: attrs.set_at?(5), # F – fAES (1 bit): A value that MUST be 1 if the protected content is an ECMA-376 document [ECMA- 376]
299
+ # Unused (26 bits): A value that is undefined and MUST be ignored.
300
+ }
301
+ end
302
+
303
+ # 2.3.3 EncryptionVerifier
304
+ # @param io [StringIO]
305
+ # @return [Hash]
306
+ def encryptionverifier(io)
307
+ {
308
+ SaltSize: io.read(4).unpack('V').first, # SaltSize (4 bytes): An unsigned integer that specifies the size of the Salt field.
309
+ Salt: io.read(16), # Salt (16 bytes): An array of bytes that specifies the salt value used during password hash generation.
310
+ EncryptedVerifier: io.read(16), # EncryptedVerifier (16 bytes): A value that MUST be the randomly generated Verifier value encrypted using the algorithm chosen by the implementation.
311
+ VerifierHashSize: io.read(4).unpack('V').first, # VerifierHashSize (4 bytes): An unsigned integer that specifies the number of bytes needed to contain the hash of the data used to generate the EncryptedVerifier field.
312
+ EncryptedVerifierHash: io.read # EncryptedVerifierHash (variable): An array of bytes that contains the encrypted form of the hash of the randomly generated Verifier value.
313
+ }
314
+ end
315
+
316
+ # See AlgIDHash description, p.33
317
+ # @param encryption_header [Hash]
318
+ # @return [Symbol]
319
+ def _rc4cryptoapi_hashing_alg(encryption_header)
320
+ combination = [
321
+ encryption_header[:AlgIDHash],
322
+ encryption_header[:Flags][:fExternal]
323
+ ]
324
+
325
+ case combination
326
+ when [0x0000, true] then :'Determined by the application'
327
+
328
+ when [0x0000, false],
329
+ [0x8004, false] then :SHA1
330
+
331
+ else raise "Unknown Flags and AlgIDHash combination: #{combination}"
332
+ end
333
+ end
334
+
335
+ # See AlgID description, p.33
336
+ # @param encryption_header [Hash]
337
+ # @return [Symbol]
338
+ def _rc4cryptoapi_encryption_alg(encryption_header)
339
+ combination = [
340
+ encryption_header[:Flags][:fCryptoAPI],
341
+ encryption_header[:Flags][:fAES],
342
+ encryption_header[:Flags][:fExternal],
343
+ encryption_header[:AlgID]
344
+ ]
345
+
346
+ case combination
347
+ when [false, false, true, 0x0000] then :'Determined by the application'
348
+
349
+ when [true, false, false, 0x0000],
350
+ [true, false, false, 0x6801] then :RC4
351
+
352
+ when [true, true, false, 0x0000],
353
+ [true, true, false, 0x660E] then :'AES-128-CBC'
354
+
355
+ when [true, true, false, 0x660F] then :'AES-192-CBC'
356
+
357
+ when [true, true, false, 0x6610] then :'AES-256-CBC'
358
+
359
+ else raise "Unknown Flags and AlgID combination: #{combination}"
360
+ end
361
+ end
362
+
363
+ # 2.3.5.2 RC4 CryptoAPI Encryption Key Generation
364
+ # @param password [String]
365
+ # @param salt [String]
366
+ # @param block_num [Integer]
367
+ # @param key_size [Integer]
368
+ # @param hash_alg [Symbol]
369
+ # @return [String]
370
+ def _rc4cryptoapi_make_key(password, salt, block_num, key_size, hash_alg)
371
+ h0_digest = _openssl_obj(:Digest, hash_alg)
372
+ h0_digest.update(salt)
373
+ h0_digest.update(password.encode(Encoding::UTF_16LE))
374
+ h0 = h0_digest.digest
375
+
376
+ h_digest = _openssl_obj(:Digest, hash_alg)
377
+ h_digest.update(h0)
378
+ h_digest.update([block_num].pack('V'))
379
+ h = h_digest.digest
380
+
381
+ h_fin = h[0..(key_size - 1)]
382
+ h_fin << ("\x00" * (16 - key_size)) if key_size < 16
383
+
384
+ h_fin
385
+ end
386
+
387
+ # 2.3.5.6 Password Verification
388
+ # @param key [String]
389
+ # @param encr_verifier [String]
390
+ # @param encr_verifier_hash [String]
391
+ # @param verifier_hash_size [Integer]
392
+ # @param hash_alg [Symbol]
393
+ # @param encr_alg [Symbol]
394
+ def _rc4cryptoapi_password_match?(key, encr_verifier, encr_verifier_hash, verifier_hash_size, hash_alg, encr_alg)
395
+ cipher = _openssl_obj(:Cipher, encr_alg)
396
+ cipher.decrypt
397
+ cipher.key = key
398
+ decrypted_verifier = cipher.update(encr_verifier) + cipher.final
399
+ decrypted_verifier_hash = cipher.update(encr_verifier_hash) + cipher.final
400
+ decrypted_verifier_hash = decrypted_verifier_hash[0..(verifier_hash_size - 1)]
401
+ hashed_verifier = _openssl_obj(:Digest, hash_alg).digest(decrypted_verifier)
402
+ hashed_verifier == decrypted_verifier_hash
403
+ end
404
+
405
+ # @param data [String]
406
+ # @param key [String]
407
+ # @param encr_alg [Symbol]
408
+ # @return [String]
409
+ def _rc4cryptoapi_decrypt(data, key, encr_alg)
410
+ cipher = _openssl_obj(:Cipher, encr_alg)
411
+ cipher.decrypt
412
+ cipher.key = key
413
+ cipher.update(data) + cipher.final
414
+ end
415
+
416
+ # @param type [Symbol] e.g. :Cipher, :Digest
417
+ # @param name [Symbol] e.g. :RC4, SHA1
418
+ # @return [OpenSSL::Cipher, OpenSSL::Digest] e.g. OpenSSL::Cipher::RC4 instance
419
+ def _openssl_obj(type, name)
420
+ Module.class_eval("OpenSSL::#{type}").new(name.to_s)
421
+ end
422
+
423
+ end