1 | /*******************************************************************************
|
---|
2 | * You may amend and distribute as you like, but don't remove this header!
|
---|
3 | *
|
---|
4 | * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
|
---|
5 | * See http://www.codeplex.com/EPPlus for details.
|
---|
6 | *
|
---|
7 | * Copyright (C) 2011 Jan Källman
|
---|
8 | *
|
---|
9 | * This library is free software; you can redistribute it and/or
|
---|
10 | * modify it under the terms of the GNU Lesser General Public
|
---|
11 | * License as published by the Free Software Foundation; either
|
---|
12 | * version 2.1 of the License, or (at your option) any later version.
|
---|
13 |
|
---|
14 | * This library is distributed in the hope that it will be useful,
|
---|
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
---|
17 | * See the GNU Lesser General Public License for more details.
|
---|
18 | *
|
---|
19 | * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
|
---|
20 | * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
|
---|
21 | *
|
---|
22 | * All code and executables are provided "as is" with no warranty either express or implied.
|
---|
23 | * The author accepts no liability for any damage or loss of business that this product may cause.
|
---|
24 | *
|
---|
25 | * Code change notes:
|
---|
26 | *
|
---|
27 | * Author Change Date
|
---|
28 | * ******************************************************************************
|
---|
29 | * Jan Källman Added 2013-01-05
|
---|
30 | *******************************************************************************/
|
---|
31 | using OfficeOpenXml.Utils;
|
---|
32 | using System;
|
---|
33 | using System.Collections.Generic;
|
---|
34 | using System.IO;
|
---|
35 | using System.Linq;
|
---|
36 | using System.Runtime.InteropServices;
|
---|
37 | using System.Security;
|
---|
38 | using System.Security.Cryptography;
|
---|
39 | using System.Text;
|
---|
40 | using System.Xml;
|
---|
41 | using comTypes = System.Runtime.InteropServices.ComTypes;
|
---|
42 |
|
---|
43 | namespace OfficeOpenXml.Encryption
|
---|
44 | {
|
---|
45 |
|
---|
46 | /// <summary>
|
---|
47 | /// Handels encrypted Excel documents
|
---|
48 | /// </summary>
|
---|
49 | internal class EncryptedPackageHandler
|
---|
50 | {
|
---|
51 | #if !MONO
|
---|
52 | /// <summary>
|
---|
53 | /// Read the package from the OLE document and decrypt it using the supplied password
|
---|
54 | /// </summary>
|
---|
55 | /// <param name="fi">The file</param>
|
---|
56 | /// <param name="encryption"></param>
|
---|
57 | /// <returns></returns>
|
---|
58 | internal MemoryStream DecryptPackage(FileInfo fi, ExcelEncryption encryption)
|
---|
59 | {
|
---|
60 | CompoundDocument doc = new CompoundDocument(fi);
|
---|
61 |
|
---|
62 | MemoryStream ret = null;
|
---|
63 | if (CompoundDocument.IsStorageFile(fi.FullName) == 0)
|
---|
64 | {
|
---|
65 | ret = GetStreamFromPackage(doc, encryption);
|
---|
66 | }
|
---|
67 | else
|
---|
68 | {
|
---|
69 | throw (new InvalidDataException(string.Format("File {0} is not an encrypted package", fi.FullName)));
|
---|
70 | }
|
---|
71 | return ret;
|
---|
72 | }
|
---|
73 |
|
---|
74 | //Helpmethod to output the streams in the storage
|
---|
75 | //private void WriteDoc(CompoundDocument.StoragePart storagePart, string p)
|
---|
76 | //{
|
---|
77 | // foreach (var store in storagePart.SubStorage)
|
---|
78 | // {
|
---|
79 | // string sdir=p + store.Key.Replace((char)6,'x') + "\\";
|
---|
80 | // Directory.CreateDirectory(sdir);
|
---|
81 | // WriteDoc(store.Value, sdir);
|
---|
82 | // }
|
---|
83 | // foreach (var str in storagePart.DataStreams)
|
---|
84 | // {
|
---|
85 | // File.WriteAllBytes(p + str.Key.Replace((char)6, 'x') + ".bin", str.Value);
|
---|
86 | // }
|
---|
87 | //}
|
---|
88 | /// <summary>
|
---|
89 | /// Read the package from the OLE document and decrypt it using the supplied password
|
---|
90 | /// </summary>
|
---|
91 | /// <param name="stream">The memory stream. </param>
|
---|
92 | /// <param name="encryption">The encryption object from the Package</param>
|
---|
93 | /// <returns></returns>
|
---|
94 | [SecuritySafeCritical]
|
---|
95 | internal MemoryStream DecryptPackage(MemoryStream stream, ExcelEncryption encryption)
|
---|
96 | {
|
---|
97 | //Create the lockBytes object.
|
---|
98 | CompoundDocument.ILockBytes lb=null;
|
---|
99 | try
|
---|
100 | {
|
---|
101 | lb = CompoundDocument.GetLockbyte(stream);
|
---|
102 |
|
---|
103 | if (CompoundDocument.IsStorageILockBytes(lb) == 0)
|
---|
104 | {
|
---|
105 | var doc = new CompoundDocument(lb);
|
---|
106 | return GetStreamFromPackage(doc, encryption);
|
---|
107 | }
|
---|
108 | else
|
---|
109 | {
|
---|
110 | Marshal.ReleaseComObject(lb);
|
---|
111 | throw (new InvalidDataException("The stream is not an valid/supported encrypted document."));
|
---|
112 | }
|
---|
113 | }
|
---|
114 | catch// (Exception ex)
|
---|
115 | {
|
---|
116 | throw;
|
---|
117 | }
|
---|
118 | finally
|
---|
119 | {
|
---|
120 | Marshal.ReleaseComObject(lb);
|
---|
121 | lb = null;
|
---|
122 | }
|
---|
123 |
|
---|
124 | }
|
---|
125 | /// <summary>
|
---|
126 | /// Encrypts a package
|
---|
127 | /// </summary>
|
---|
128 | /// <param name="package">The package as a byte array</param>
|
---|
129 | /// <param name="encryption">The encryption info from the workbook</param>
|
---|
130 | /// <returns></returns>
|
---|
131 | internal MemoryStream EncryptPackage(byte[] package, ExcelEncryption encryption)
|
---|
132 | {
|
---|
133 | if (encryption.Version == EncryptionVersion.Standard) //Standard encryption
|
---|
134 | {
|
---|
135 | return EncryptPackageBinary(package, encryption);
|
---|
136 | }
|
---|
137 | else if (encryption.Version == EncryptionVersion.Agile) //Agile encryption
|
---|
138 | {
|
---|
139 | return EncryptPackageAgile(package, encryption);
|
---|
140 | }
|
---|
141 | throw(new ArgumentException("Unsupported encryption version."));
|
---|
142 | }
|
---|
143 |
|
---|
144 | private MemoryStream EncryptPackageAgile(byte[] package, ExcelEncryption encryption)
|
---|
145 | {
|
---|
146 | var xml= "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n";
|
---|
147 | xml += "<encryption xmlns=\"http://schemas.microsoft.com/office/2006/encryption\" xmlns:p=\"http://schemas.microsoft.com/office/2006/keyEncryptor/password\" xmlns:c=\"http://schemas.microsoft.com/office/2006/keyEncryptor/certificate\">";
|
---|
148 | xml += "<keyData saltSize=\"16\" blockSize=\"16\" keyBits=\"256\" hashSize=\"64\" cipherAlgorithm=\"AES\" cipherChaining=\"ChainingModeCBC\" hashAlgorithm=\"SHA512\" saltValue=\"\"/>";
|
---|
149 | xml += "<dataIntegrity encryptedHmacKey=\"\" encryptedHmacValue=\"\"/>";
|
---|
150 | xml += "<keyEncryptors>";
|
---|
151 | xml += "<keyEncryptor uri=\"http://schemas.microsoft.com/office/2006/keyEncryptor/password\">";
|
---|
152 | xml += "<p:encryptedKey spinCount=\"100000\" saltSize=\"16\" blockSize=\"16\" keyBits=\"256\" hashSize=\"64\" cipherAlgorithm=\"AES\" cipherChaining=\"ChainingModeCBC\" hashAlgorithm=\"SHA512\" saltValue=\"\" encryptedVerifierHashInput=\"\" encryptedVerifierHashValue=\"\" encryptedKeyValue=\"\" />";
|
---|
153 | xml += "</keyEncryptor></keyEncryptors></encryption>";
|
---|
154 |
|
---|
155 | var encryptionInfo = new EncryptionInfoAgile();
|
---|
156 | encryptionInfo.ReadFromXml(xml);
|
---|
157 | var encr = encryptionInfo.KeyEncryptors[0];
|
---|
158 | var rnd = RandomNumberGenerator.Create();
|
---|
159 |
|
---|
160 | var s = new byte[16];
|
---|
161 | rnd.GetBytes(s);
|
---|
162 | encryptionInfo.KeyData.SaltValue = s;
|
---|
163 |
|
---|
164 | rnd.GetBytes(s);
|
---|
165 | encr.SaltValue = s;
|
---|
166 |
|
---|
167 | encr.KeyValue = new byte[encr.KeyBits / 8];
|
---|
168 | rnd.GetBytes(encr.KeyValue);
|
---|
169 |
|
---|
170 | //Get the passwork key.
|
---|
171 | var hashProvider = GetHashProvider(encryptionInfo.KeyEncryptors[0]);
|
---|
172 | var baseHash = GetPasswordHash(hashProvider, encr.SaltValue, encryption.Password, encr.SpinCount, encr.HashSize);
|
---|
173 | var hashFinal = GetFinalHash(hashProvider, encr, BlockKey_KeyValue, baseHash);
|
---|
174 | hashFinal = FixHashSize(hashFinal, encr.KeyBits / 8);
|
---|
175 |
|
---|
176 | var encrData = EncryptDataAgile(package, encryptionInfo, hashProvider);
|
---|
177 |
|
---|
178 | /**** Data Integrity ****/
|
---|
179 | var saltHMAC=new byte[64];
|
---|
180 | rnd.GetBytes(saltHMAC);
|
---|
181 |
|
---|
182 | SetHMAC(encryptionInfo,hashProvider,saltHMAC, encrData);
|
---|
183 |
|
---|
184 | /**** Verifier ****/
|
---|
185 | encr.VerifierHashInput = new byte[16];
|
---|
186 | rnd.GetBytes(encr.VerifierHashInput);
|
---|
187 |
|
---|
188 | encr.VerifierHash = hashProvider.ComputeHash(encr.VerifierHashInput);
|
---|
189 |
|
---|
190 | var VerifierInputKey = GetFinalHash(hashProvider, encr, BlockKey_HashInput, baseHash);
|
---|
191 | var VerifierHashKey = GetFinalHash(hashProvider, encr, BlockKey_HashValue, baseHash);
|
---|
192 | var KeyValueKey = GetFinalHash(hashProvider, encr, BlockKey_KeyValue, baseHash);
|
---|
193 |
|
---|
194 | var ms = new MemoryStream();
|
---|
195 | EncryptAgileFromKey(encr, VerifierInputKey, encr.VerifierHashInput, 0, encr.VerifierHashInput.Length, encr.SaltValue, ms);
|
---|
196 | encr.EncryptedVerifierHashInput = ms.ToArray();
|
---|
197 |
|
---|
198 | ms = new MemoryStream();
|
---|
199 | EncryptAgileFromKey(encr, VerifierHashKey, encr.VerifierHash, 0, encr.VerifierHash.Length, encr.SaltValue, ms);
|
---|
200 | encr.EncryptedVerifierHash = ms.ToArray();
|
---|
201 |
|
---|
202 | ms = new MemoryStream();
|
---|
203 | EncryptAgileFromKey(encr, KeyValueKey, encr.KeyValue, 0, encr.KeyValue.Length, encr.SaltValue, ms);
|
---|
204 | encr.EncryptedKeyValue = ms.ToArray();
|
---|
205 |
|
---|
206 | xml = encryptionInfo.Xml.OuterXml;
|
---|
207 |
|
---|
208 | var byXml = Encoding.UTF8.GetBytes(xml);
|
---|
209 |
|
---|
210 | ms = new MemoryStream();
|
---|
211 | ms.Write(BitConverter.GetBytes((ushort)4), 0, 2); //Major Version
|
---|
212 | ms.Write(BitConverter.GetBytes((ushort)4), 0, 2); //Minor Version
|
---|
213 | ms.Write(BitConverter.GetBytes((uint)0x40), 0, 4); //Reserved
|
---|
214 | ms.Write(byXml,0,byXml.Length);
|
---|
215 |
|
---|
216 | var doc = new CompoundDocument();
|
---|
217 |
|
---|
218 | //Add the dataspace streams
|
---|
219 | CreateDataSpaces(doc);
|
---|
220 | //EncryptionInfo...
|
---|
221 | doc.Storage.DataStreams.Add("EncryptionInfo", ms.ToArray());
|
---|
222 | //...and the encrypted package
|
---|
223 | doc.Storage.DataStreams.Add("EncryptedPackage", encrData);
|
---|
224 |
|
---|
225 | ms = new MemoryStream();
|
---|
226 | var e=doc.Save();
|
---|
227 | ms.Write(e,0,e.Length);
|
---|
228 | return ms;
|
---|
229 | }
|
---|
230 |
|
---|
231 | private byte[] EncryptDataAgile(byte[] data, EncryptionInfoAgile encryptionInfo, HashAlgorithm hashProvider)
|
---|
232 | {
|
---|
233 | var ke = encryptionInfo.KeyEncryptors[0];
|
---|
234 | RijndaelManaged aes = new RijndaelManaged();
|
---|
235 | aes.KeySize = ke.KeyBits;
|
---|
236 | aes.Mode = CipherMode.CBC;
|
---|
237 | aes.Padding = PaddingMode.Zeros;
|
---|
238 |
|
---|
239 | int pos=0;
|
---|
240 | int segment=0;
|
---|
241 |
|
---|
242 | //Encrypt the data
|
---|
243 | var ms = new MemoryStream();
|
---|
244 | ms.Write(BitConverter.GetBytes(data.LongLength), 0, 8);
|
---|
245 | while (pos < data.Length)
|
---|
246 | {
|
---|
247 | var segmentSize = (int)(data.Length - pos > 4096 ? 4096 : data.Length - pos);
|
---|
248 |
|
---|
249 | var ivTmp = new byte[4 + encryptionInfo.KeyData.SaltSize];
|
---|
250 | Array.Copy(encryptionInfo.KeyData.SaltValue, 0, ivTmp, 0, encryptionInfo.KeyData.SaltSize);
|
---|
251 | Array.Copy(BitConverter.GetBytes(segment), 0, ivTmp, encryptionInfo.KeyData.SaltSize, 4);
|
---|
252 | var iv=hashProvider.ComputeHash(ivTmp);
|
---|
253 |
|
---|
254 | EncryptAgileFromKey(ke, ke.KeyValue, data, pos, segmentSize, iv, ms);
|
---|
255 | pos += segmentSize;
|
---|
256 | segment++;
|
---|
257 | }
|
---|
258 | ms.Flush();
|
---|
259 | return ms.ToArray();
|
---|
260 | }
|
---|
261 | // Set the dataintegrity
|
---|
262 | private void SetHMAC(EncryptionInfoAgile ei, HashAlgorithm hashProvider, byte[] salt, byte[] data)
|
---|
263 | {
|
---|
264 | var iv = GetFinalHash(hashProvider, ei.KeyEncryptors[0], BlockKey_HmacKey, ei.KeyData.SaltValue);
|
---|
265 | var ms = new MemoryStream();
|
---|
266 | EncryptAgileFromKey(ei.KeyEncryptors[0], ei.KeyEncryptors[0].KeyValue, salt, 0L, salt.LongLength, iv, ms);
|
---|
267 | ei.DataIntegrity.EncryptedHmacKey = ms.ToArray();
|
---|
268 |
|
---|
269 | var h = GetHmacProvider(ei.KeyEncryptors[0], salt);
|
---|
270 | var hmacValue = h.ComputeHash(data);
|
---|
271 |
|
---|
272 | ms = new MemoryStream();
|
---|
273 | iv = GetFinalHash(hashProvider, ei.KeyEncryptors[0], BlockKey_HmacValue, ei.KeyData.SaltValue);
|
---|
274 | EncryptAgileFromKey(ei.KeyEncryptors[0], ei.KeyEncryptors[0].KeyValue, hmacValue, 0L, hmacValue.LongLength, iv, ms);
|
---|
275 | ei.DataIntegrity.EncryptedHmacValue = ms.ToArray();
|
---|
276 | }
|
---|
277 |
|
---|
278 | private HMAC GetHmacProvider(EncryptionInfoAgile.EncryptionKeyEncryptor ei, byte[] salt)
|
---|
279 | {
|
---|
280 | switch (ei.HashAlgorithm)
|
---|
281 | {
|
---|
282 | case eHashAlogorithm.RIPEMD160:
|
---|
283 | return new HMACRIPEMD160(salt);
|
---|
284 | case eHashAlogorithm.MD5:
|
---|
285 | return new HMACMD5(salt);
|
---|
286 | case eHashAlogorithm.SHA1:
|
---|
287 | return new HMACSHA1(salt);
|
---|
288 | case eHashAlogorithm.SHA256:
|
---|
289 | return new HMACSHA256(salt);
|
---|
290 | case eHashAlogorithm.SHA384:
|
---|
291 | return new HMACSHA384(salt);
|
---|
292 | case eHashAlogorithm.SHA512:
|
---|
293 | return new HMACSHA512(salt);
|
---|
294 | default:
|
---|
295 | throw(new NotSupportedException(string.Format("Hash method {0} not supported.",ei.HashAlgorithm)));
|
---|
296 | }
|
---|
297 | }
|
---|
298 |
|
---|
299 | private MemoryStream EncryptPackageBinary(byte[] package, ExcelEncryption encryption)
|
---|
300 | {
|
---|
301 | byte[] encryptionKey;
|
---|
302 | //Create the Encryption Info. This also returns the Encryptionkey
|
---|
303 | var encryptionInfo = CreateEncryptionInfo(encryption.Password,
|
---|
304 | encryption.Algorithm == EncryptionAlgorithm.AES128 ?
|
---|
305 | AlgorithmID.AES128 :
|
---|
306 | encryption.Algorithm == EncryptionAlgorithm.AES192 ?
|
---|
307 | AlgorithmID.AES192 :
|
---|
308 | AlgorithmID.AES256, out encryptionKey);
|
---|
309 |
|
---|
310 | //ILockBytes lb;
|
---|
311 | //var iret = CreateILockBytesOnHGlobal(IntPtr.Zero, true, out lb);
|
---|
312 |
|
---|
313 | //IStorage storage = null;
|
---|
314 | //MemoryStream ret = null;
|
---|
315 |
|
---|
316 | var doc = new CompoundDocument();
|
---|
317 | CreateDataSpaces(doc);
|
---|
318 |
|
---|
319 | doc.Storage.DataStreams.Add("EncryptionInfo", encryptionInfo.WriteBinary());
|
---|
320 |
|
---|
321 | //Encrypt the package
|
---|
322 | byte[] encryptedPackage = EncryptData(encryptionKey, package, false);
|
---|
323 | MemoryStream ms = new MemoryStream();
|
---|
324 | ms.Write(BitConverter.GetBytes((ulong)package.LongLength), 0, 8);
|
---|
325 | ms.Write(encryptedPackage, 0, encryptedPackage.Length);
|
---|
326 | doc.Storage.DataStreams.Add("EncryptedPackage", ms.ToArray());
|
---|
327 |
|
---|
328 | var ret = new MemoryStream();
|
---|
329 | var buffer = doc.Save();
|
---|
330 | ret.Write(buffer, 0, buffer.Length);
|
---|
331 |
|
---|
332 | return ret;
|
---|
333 | }
|
---|
334 | #region "Dataspaces Stream methods"
|
---|
335 | private void CreateDataSpaces(CompoundDocument doc)
|
---|
336 | {
|
---|
337 | var ds = new CompoundDocument.StoragePart();
|
---|
338 | doc.Storage.SubStorage.Add("\x06" + "DataSpaces", ds);
|
---|
339 | var ver=new CompoundDocument.StoragePart();
|
---|
340 | ds.DataStreams.Add("Version", CreateVersionStream());
|
---|
341 | ds.DataStreams.Add("DataSpaceMap", CreateDataSpaceMap());
|
---|
342 |
|
---|
343 | var dsInfo=new CompoundDocument.StoragePart();
|
---|
344 | ds.SubStorage.Add("DataSpaceInfo", dsInfo);
|
---|
345 | dsInfo.DataStreams.Add("StrongEncryptionDataSpace", CreateStrongEncryptionDataSpaceStream());
|
---|
346 |
|
---|
347 | var transInfo=new CompoundDocument.StoragePart();
|
---|
348 | ds.SubStorage.Add("TransformInfo", transInfo);
|
---|
349 |
|
---|
350 | var strEncTrans=new CompoundDocument.StoragePart();
|
---|
351 | transInfo.SubStorage.Add("StrongEncryptionTransform", strEncTrans);
|
---|
352 |
|
---|
353 | strEncTrans.DataStreams.Add("\x06Primary", CreateTransformInfoPrimary());
|
---|
354 | }
|
---|
355 | private byte[] CreateStrongEncryptionDataSpaceStream()
|
---|
356 | {
|
---|
357 | MemoryStream ms = new MemoryStream();
|
---|
358 | BinaryWriter bw = new BinaryWriter(ms);
|
---|
359 |
|
---|
360 | bw.Write((int)8); //HeaderLength
|
---|
361 | bw.Write((int)1); //EntryCount
|
---|
362 |
|
---|
363 | string tr = "StrongEncryptionTransform";
|
---|
364 | bw.Write((int)tr.Length*2);
|
---|
365 | bw.Write(UTF8Encoding.Unicode.GetBytes(tr + "\0")); // end \0 is for padding
|
---|
366 |
|
---|
367 | bw.Flush();
|
---|
368 | return ms.ToArray();
|
---|
369 | }
|
---|
370 | private byte[] CreateVersionStream()
|
---|
371 | {
|
---|
372 | MemoryStream ms = new MemoryStream();
|
---|
373 | BinaryWriter bw = new BinaryWriter(ms);
|
---|
374 |
|
---|
375 | bw.Write((short)0x3C); //Major
|
---|
376 | bw.Write((short)0); //Minor
|
---|
377 | bw.Write(UTF8Encoding.Unicode.GetBytes("Microsoft.Container.DataSpaces"));
|
---|
378 | bw.Write((int)1); //ReaderVersion
|
---|
379 | bw.Write((int)1); //UpdaterVersion
|
---|
380 | bw.Write((int)1); //WriterVersion
|
---|
381 |
|
---|
382 | bw.Flush();
|
---|
383 | return ms.ToArray();
|
---|
384 | }
|
---|
385 | private byte[] CreateDataSpaceMap()
|
---|
386 | {
|
---|
387 | MemoryStream ms = new MemoryStream();
|
---|
388 | BinaryWriter bw = new BinaryWriter(ms);
|
---|
389 |
|
---|
390 | bw.Write((int)8); //HeaderLength
|
---|
391 | bw.Write((int)1); //EntryCount
|
---|
392 | string s1 = "EncryptedPackage";
|
---|
393 | string s2 = "StrongEncryptionDataSpace";
|
---|
394 | bw.Write((int)(s1.Length + s2.Length)*2 + 0x16);
|
---|
395 | bw.Write((int)1); //ReferenceComponentCount
|
---|
396 | bw.Write((int)0); //Stream=0
|
---|
397 | bw.Write((int)s1.Length * 2); //Length s1
|
---|
398 | bw.Write(UTF8Encoding.Unicode.GetBytes(s1));
|
---|
399 | bw.Write((int)(s2.Length * 2)); //Length s2
|
---|
400 | bw.Write(UTF8Encoding.Unicode.GetBytes(s2 + "\0")); // end \0 is for padding
|
---|
401 |
|
---|
402 | bw.Flush();
|
---|
403 | return ms.ToArray();
|
---|
404 | }
|
---|
405 | private byte[] CreateTransformInfoPrimary()
|
---|
406 | {
|
---|
407 | MemoryStream ms = new MemoryStream();
|
---|
408 | BinaryWriter bw = new BinaryWriter(ms);
|
---|
409 | string TransformID = "{FF9A3F03-56EF-4613-BDD5-5A41C1D07246}";
|
---|
410 | string TransformName = "Microsoft.Container.EncryptionTransform";
|
---|
411 | bw.Write(TransformID.Length * 2 + 12);
|
---|
412 | bw.Write((int)1);
|
---|
413 | bw.Write(TransformID.Length * 2);
|
---|
414 | bw.Write(UTF8Encoding.Unicode.GetBytes(TransformID));
|
---|
415 | bw.Write(TransformName.Length * 2);
|
---|
416 | bw.Write(UTF8Encoding.Unicode.GetBytes(TransformName + "\0"));
|
---|
417 | bw.Write((int)1); //ReaderVersion
|
---|
418 | bw.Write((int)1); //UpdaterVersion
|
---|
419 | bw.Write((int)1); //WriterVersion
|
---|
420 |
|
---|
421 | bw.Write((int)0);
|
---|
422 | bw.Write((int)0);
|
---|
423 | bw.Write((int)0); //CipherMode
|
---|
424 | bw.Write((int)4); //Reserved
|
---|
425 |
|
---|
426 | bw.Flush();
|
---|
427 | return ms.ToArray();
|
---|
428 | }
|
---|
429 | #endregion
|
---|
430 | /// <summary>
|
---|
431 | /// Create an EncryptionInfo object to encrypt a workbook
|
---|
432 | /// </summary>
|
---|
433 | /// <param name="password">The password</param>
|
---|
434 | /// <param name="algID"></param>
|
---|
435 | /// <param name="key">The Encryption key</param>
|
---|
436 | /// <returns></returns>
|
---|
437 | private EncryptionInfoBinary CreateEncryptionInfo(string password, AlgorithmID algID, out byte[] key)
|
---|
438 | {
|
---|
439 | if (algID == AlgorithmID.Flags || algID == AlgorithmID.RC4)
|
---|
440 | {
|
---|
441 | throw (new ArgumentException("algID must be AES128, AES192 or AES256"));
|
---|
442 | }
|
---|
443 | var encryptionInfo = new EncryptionInfoBinary();
|
---|
444 | encryptionInfo.MajorVersion = 4;
|
---|
445 | encryptionInfo.MinorVersion = 2;
|
---|
446 | encryptionInfo.Flags = Flags.fAES | Flags.fCryptoAPI;
|
---|
447 |
|
---|
448 | //Header
|
---|
449 | encryptionInfo.Header = new EncryptionHeader();
|
---|
450 | encryptionInfo.Header.AlgID = algID;
|
---|
451 | encryptionInfo.Header.AlgIDHash = AlgorithmHashID.SHA1;
|
---|
452 | encryptionInfo.Header.Flags = encryptionInfo.Flags;
|
---|
453 | encryptionInfo.Header.KeySize =
|
---|
454 | (algID == AlgorithmID.AES128 ? 0x80 : algID == AlgorithmID.AES192 ? 0xC0 : 0x100);
|
---|
455 | encryptionInfo.Header.ProviderType = ProviderType.AES;
|
---|
456 | encryptionInfo.Header.CSPName = "Microsoft Enhanced RSA and AES Cryptographic Provider\0";
|
---|
457 | encryptionInfo.Header.Reserved1 = 0;
|
---|
458 | encryptionInfo.Header.Reserved2 = 0;
|
---|
459 | encryptionInfo.Header.SizeExtra = 0;
|
---|
460 |
|
---|
461 | //Verifier
|
---|
462 | encryptionInfo.Verifier = new EncryptionVerifier();
|
---|
463 | encryptionInfo.Verifier.Salt = new byte[16];
|
---|
464 |
|
---|
465 | var rnd = RandomNumberGenerator.Create();
|
---|
466 | rnd.GetBytes(encryptionInfo.Verifier.Salt);
|
---|
467 | encryptionInfo.Verifier.SaltSize = 0x10;
|
---|
468 |
|
---|
469 | key = GetPasswordHashBinary(password, encryptionInfo);
|
---|
470 |
|
---|
471 | var verifier = new byte[16];
|
---|
472 | rnd.GetBytes(verifier);
|
---|
473 | encryptionInfo.Verifier.EncryptedVerifier = EncryptData(key, verifier, true);
|
---|
474 |
|
---|
475 | //AES = 32 Bits
|
---|
476 | encryptionInfo.Verifier.VerifierHashSize = 0x20;
|
---|
477 | SHA1 sha = new SHA1Managed();
|
---|
478 | var verifierHash = sha.ComputeHash(verifier);
|
---|
479 |
|
---|
480 | encryptionInfo.Verifier.EncryptedVerifierHash = EncryptData(key, verifierHash, false);
|
---|
481 |
|
---|
482 | return encryptionInfo;
|
---|
483 | }
|
---|
484 | private byte[] EncryptData(byte[] key, byte[] data, bool useDataSize)
|
---|
485 | {
|
---|
486 | RijndaelManaged aes = new RijndaelManaged();
|
---|
487 | aes.KeySize = key.Length * 8;
|
---|
488 | aes.Mode = CipherMode.ECB;
|
---|
489 | aes.Padding = PaddingMode.Zeros;
|
---|
490 |
|
---|
491 | //Encrypt the data
|
---|
492 | var crypt = aes.CreateEncryptor(key, null);
|
---|
493 | var ms = new MemoryStream();
|
---|
494 | var cs = new CryptoStream(ms, crypt, CryptoStreamMode.Write);
|
---|
495 | cs.Write(data, 0, data.Length);
|
---|
496 |
|
---|
497 | cs.FlushFinalBlock();
|
---|
498 |
|
---|
499 | byte[] ret;
|
---|
500 | if (useDataSize)
|
---|
501 | {
|
---|
502 | ret = new byte[data.Length];
|
---|
503 | ms.Seek(0, SeekOrigin.Begin);
|
---|
504 | ms.Read(ret, 0, data.Length); //Truncate any padded Zeros
|
---|
505 | return ret;
|
---|
506 | }
|
---|
507 | else
|
---|
508 | {
|
---|
509 | return ms.ToArray();
|
---|
510 | }
|
---|
511 | }
|
---|
512 | private MemoryStream GetStreamFromPackage(CompoundDocument doc, ExcelEncryption encryption)
|
---|
513 | {
|
---|
514 | var ret = new MemoryStream();
|
---|
515 | if(doc.Storage.DataStreams.ContainsKey("EncryptionInfo") ||
|
---|
516 | doc.Storage.DataStreams.ContainsKey("EncryptedPackage"))
|
---|
517 | {
|
---|
518 | var encryptionInfo = EncryptionInfo.ReadBinary(doc.Storage.DataStreams["EncryptionInfo"]);
|
---|
519 |
|
---|
520 | return DecryptDocument(doc.Storage.DataStreams["EncryptedPackage"], encryptionInfo, encryption.Password);
|
---|
521 | }
|
---|
522 | else
|
---|
523 | {
|
---|
524 | throw (new InvalidDataException("Invalid document. EncryptionInfo or EncryptedPackage stream is missing"));
|
---|
525 | }
|
---|
526 | }
|
---|
527 |
|
---|
528 | /// <summary>
|
---|
529 | /// Decrypt a document
|
---|
530 | /// </summary>
|
---|
531 | /// <param name="data">The Encrypted data</param>
|
---|
532 | /// <param name="encryptionInfo">Encryption Info object</param>
|
---|
533 | /// <param name="password">The password</param>
|
---|
534 | /// <returns></returns>
|
---|
535 | private MemoryStream DecryptDocument(byte[] data, EncryptionInfo encryptionInfo, string password)
|
---|
536 | {
|
---|
537 | long size = BitConverter.ToInt64(data, 0);
|
---|
538 |
|
---|
539 | var encryptedData = new byte[data.Length - 8];
|
---|
540 | Array.Copy(data, 8, encryptedData, 0, encryptedData.Length);
|
---|
541 |
|
---|
542 | if (encryptionInfo is EncryptionInfoBinary)
|
---|
543 | {
|
---|
544 | return DecryptBinary((EncryptionInfoBinary)encryptionInfo, password, size, encryptedData);
|
---|
545 | }
|
---|
546 | else
|
---|
547 | {
|
---|
548 | return DecryptAgile((EncryptionInfoAgile)encryptionInfo, password, size, encryptedData, data);
|
---|
549 | }
|
---|
550 |
|
---|
551 | }
|
---|
552 |
|
---|
553 | readonly byte[] BlockKey_HashInput = new byte[] { 0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79 };
|
---|
554 | readonly byte[] BlockKey_HashValue = new byte[] { 0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e };
|
---|
555 | readonly byte[] BlockKey_KeyValue = new byte[] { 0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6 };
|
---|
556 | readonly byte[] BlockKey_HmacKey = new byte[] { 0x5f, 0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, 0xf6 };//MSOFFCRYPTO 2.3.4.14 section 3
|
---|
557 | readonly byte[] BlockKey_HmacValue = new byte[] { 0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, 0x33 };//MSOFFCRYPTO 2.3.4.14 section 5
|
---|
558 |
|
---|
559 | private MemoryStream DecryptAgile(EncryptionInfoAgile encryptionInfo, string password, long size, byte[] encryptedData, byte[] data)
|
---|
560 | {
|
---|
561 | MemoryStream doc = new MemoryStream();
|
---|
562 |
|
---|
563 | if (encryptionInfo.KeyData.CipherAlgorithm == eCipherAlgorithm.AES)
|
---|
564 | {
|
---|
565 | var encr = encryptionInfo.KeyEncryptors[0];
|
---|
566 | var hashProvider = GetHashProvider(encr);
|
---|
567 | var baseHash = GetPasswordHash(hashProvider, encr.SaltValue, password, encr.SpinCount, encr.HashSize);
|
---|
568 |
|
---|
569 | //Get the keys for the verifiers and the key value
|
---|
570 | var valInputKey = GetFinalHash(hashProvider, encr, BlockKey_HashInput, baseHash);
|
---|
571 | var valHashKey = GetFinalHash(hashProvider, encr, BlockKey_HashValue, baseHash);
|
---|
572 | var valKeySizeKey = GetFinalHash(hashProvider, encr, BlockKey_KeyValue, baseHash);
|
---|
573 |
|
---|
574 | //Decrypt
|
---|
575 | encr.VerifierHashInput = DecryptAgileFromKey(encr, valInputKey, encr.EncryptedVerifierHashInput, encr.SaltSize, encr.SaltValue);
|
---|
576 | encr.VerifierHash = DecryptAgileFromKey(encr, valHashKey, encr.EncryptedVerifierHash, encr.HashSize, encr.SaltValue);
|
---|
577 | encr.KeyValue = DecryptAgileFromKey(encr, valKeySizeKey, encr.EncryptedKeyValue, encr.KeyBits / 8, encr.SaltValue);
|
---|
578 |
|
---|
579 | if (IsPasswordValid(hashProvider, encr))
|
---|
580 | {
|
---|
581 | var ivhmac = GetFinalHash(hashProvider, encr, BlockKey_HmacKey, encryptionInfo.KeyData.SaltValue);
|
---|
582 | var key = DecryptAgileFromKey(encr, encr.KeyValue, encryptionInfo.DataIntegrity.EncryptedHmacKey, encryptionInfo.KeyData.HashSize, ivhmac);
|
---|
583 |
|
---|
584 | ivhmac = GetFinalHash(hashProvider, encr, BlockKey_HmacValue, encryptionInfo.KeyData.SaltValue);
|
---|
585 | var value = DecryptAgileFromKey(encr, encr.KeyValue, encryptionInfo.DataIntegrity.EncryptedHmacValue, encryptionInfo.KeyData.HashSize, ivhmac);
|
---|
586 |
|
---|
587 | var hmca = GetHmacProvider(encr, key);
|
---|
588 | var v2 = hmca.ComputeHash(data);
|
---|
589 |
|
---|
590 | for (int i = 0; i < v2.Length; i++)
|
---|
591 | {
|
---|
592 | if (value[i] != v2[i])
|
---|
593 | {
|
---|
594 | throw (new Exception("Dataintegrity key missmatch"));
|
---|
595 | }
|
---|
596 | }
|
---|
597 |
|
---|
598 | int pos = 0;
|
---|
599 | uint segment = 0;
|
---|
600 | while (pos < size)
|
---|
601 | {
|
---|
602 | var segmentSize = (int)(size - pos > 4096 ? 4096 : size - pos);
|
---|
603 | var bufferSize = (int)(encryptedData.Length - pos > 4096 ? 4096 : encryptedData.Length - pos);
|
---|
604 | var ivTmp = new byte[4 + encryptionInfo.KeyData.SaltSize];
|
---|
605 | Array.Copy(encryptionInfo.KeyData.SaltValue, 0, ivTmp, 0, encryptionInfo.KeyData.SaltSize);
|
---|
606 | Array.Copy(BitConverter.GetBytes(segment), 0, ivTmp, encryptionInfo.KeyData.SaltSize, 4);
|
---|
607 | var iv = hashProvider.ComputeHash(ivTmp);
|
---|
608 | var buffer = new byte[bufferSize];
|
---|
609 | Array.Copy(encryptedData, pos, buffer, 0, bufferSize);
|
---|
610 |
|
---|
611 | var b = DecryptAgileFromKey(encr, encr.KeyValue, buffer, segmentSize, iv);
|
---|
612 | doc.Write(b, 0, b.Length);
|
---|
613 | pos += segmentSize;
|
---|
614 | segment++;
|
---|
615 | }
|
---|
616 | doc.Flush();
|
---|
617 | return doc;
|
---|
618 | }
|
---|
619 | else
|
---|
620 | {
|
---|
621 | throw (new SecurityException("Invalid password"));
|
---|
622 | }
|
---|
623 | }
|
---|
624 | return null;
|
---|
625 | }
|
---|
626 | private HashAlgorithm GetHashProvider(EncryptionInfoAgile.EncryptionKeyEncryptor encr)
|
---|
627 | {
|
---|
628 | switch (encr.HashAlgorithm)
|
---|
629 | {
|
---|
630 | case eHashAlogorithm.MD5:
|
---|
631 | return new MD5CryptoServiceProvider();
|
---|
632 | case eHashAlogorithm.RIPEMD160:
|
---|
633 | return new RIPEMD160Managed();
|
---|
634 | case eHashAlogorithm.SHA1:
|
---|
635 | return new SHA1CryptoServiceProvider();
|
---|
636 | case eHashAlogorithm.SHA256:
|
---|
637 | return new SHA256CryptoServiceProvider();
|
---|
638 | case eHashAlogorithm.SHA384:
|
---|
639 | return new SHA384CryptoServiceProvider();
|
---|
640 | case eHashAlogorithm.SHA512:
|
---|
641 | return new SHA512CryptoServiceProvider();
|
---|
642 | default:
|
---|
643 | throw new NotSupportedException(string.Format("Hash provider is unsupported. {0}", encr.HashAlgorithm));
|
---|
644 | }
|
---|
645 | }
|
---|
646 | private MemoryStream DecryptBinary(EncryptionInfoBinary encryptionInfo, string password, long size, byte[] encryptedData)
|
---|
647 | {
|
---|
648 | MemoryStream doc = new MemoryStream();
|
---|
649 |
|
---|
650 | if (encryptionInfo.Header.AlgID == AlgorithmID.AES128 || (encryptionInfo.Header.AlgID == AlgorithmID.Flags && ((encryptionInfo.Flags & (Flags.fAES | Flags.fExternal | Flags.fCryptoAPI)) == (Flags.fAES | Flags.fCryptoAPI)))
|
---|
651 | ||
|
---|
652 | encryptionInfo.Header.AlgID == AlgorithmID.AES192
|
---|
653 | ||
|
---|
654 | encryptionInfo.Header.AlgID == AlgorithmID.AES256
|
---|
655 | )
|
---|
656 | {
|
---|
657 | RijndaelManaged decryptKey = new RijndaelManaged();
|
---|
658 | decryptKey.KeySize = encryptionInfo.Header.KeySize;
|
---|
659 | decryptKey.Mode = CipherMode.ECB;
|
---|
660 | decryptKey.Padding = PaddingMode.None;
|
---|
661 |
|
---|
662 | var key = GetPasswordHashBinary(password, encryptionInfo);
|
---|
663 | if (IsPasswordValid(key, encryptionInfo))
|
---|
664 | {
|
---|
665 | ICryptoTransform decryptor = decryptKey.CreateDecryptor(
|
---|
666 | key,
|
---|
667 | null);
|
---|
668 |
|
---|
669 | var dataStream = new MemoryStream(encryptedData);
|
---|
670 | var cryptoStream = new CryptoStream(dataStream,
|
---|
671 | decryptor,
|
---|
672 | CryptoStreamMode.Read);
|
---|
673 |
|
---|
674 | var decryptedData = new byte[size];
|
---|
675 |
|
---|
676 | cryptoStream.Read(decryptedData, 0, (int)size);
|
---|
677 | doc.Write(decryptedData, 0, (int)size);
|
---|
678 | }
|
---|
679 | else
|
---|
680 | {
|
---|
681 | throw (new UnauthorizedAccessException("Invalid password"));
|
---|
682 | }
|
---|
683 | }
|
---|
684 | return doc;
|
---|
685 | }
|
---|
686 | /// <summary>
|
---|
687 | /// Validate the password
|
---|
688 | /// </summary>
|
---|
689 | /// <param name="key">The encryption key</param>
|
---|
690 | /// <param name="encryptionInfo">The encryption info extracted from the ENCRYPTIOINFO stream inside the OLE document</param>
|
---|
691 | /// <returns></returns>
|
---|
692 | private bool IsPasswordValid(byte[] key, EncryptionInfoBinary encryptionInfo)
|
---|
693 | {
|
---|
694 | RijndaelManaged decryptKey = new RijndaelManaged();
|
---|
695 | decryptKey.KeySize = encryptionInfo.Header.KeySize;
|
---|
696 | decryptKey.Mode = CipherMode.ECB;
|
---|
697 | decryptKey.Padding = PaddingMode.None;
|
---|
698 |
|
---|
699 | ICryptoTransform decryptor = decryptKey.CreateDecryptor(
|
---|
700 | key,
|
---|
701 | null);
|
---|
702 |
|
---|
703 |
|
---|
704 | //Decrypt the verifier
|
---|
705 | MemoryStream dataStream = new MemoryStream(encryptionInfo.Verifier.EncryptedVerifier);
|
---|
706 | CryptoStream cryptoStream = new CryptoStream(dataStream,
|
---|
707 | decryptor,
|
---|
708 | CryptoStreamMode.Read);
|
---|
709 | var decryptedVerifier = new byte[16];
|
---|
710 | cryptoStream.Read(decryptedVerifier, 0, 16);
|
---|
711 |
|
---|
712 | dataStream = new MemoryStream(encryptionInfo.Verifier.EncryptedVerifierHash);
|
---|
713 |
|
---|
714 | cryptoStream = new CryptoStream(dataStream,
|
---|
715 | decryptor,
|
---|
716 | CryptoStreamMode.Read);
|
---|
717 |
|
---|
718 | //Decrypt the verifier hash
|
---|
719 | var decryptedVerifierHash = new byte[16];
|
---|
720 | cryptoStream.Read(decryptedVerifierHash, 0, (int)16);
|
---|
721 |
|
---|
722 | //Get the hash for the decrypted verifier
|
---|
723 | var sha = new SHA1Managed();
|
---|
724 | var hash = sha.ComputeHash(decryptedVerifier);
|
---|
725 |
|
---|
726 | //Equal?
|
---|
727 | for (int i = 0; i < 16; i++)
|
---|
728 | {
|
---|
729 | if (hash[i] != decryptedVerifierHash[i])
|
---|
730 | {
|
---|
731 | return false;
|
---|
732 | }
|
---|
733 | }
|
---|
734 | return true;
|
---|
735 | }
|
---|
736 | /// <summary>
|
---|
737 | /// Validate the password
|
---|
738 | /// </summary>
|
---|
739 | /// <param name="sha">The hash algorithm</param>
|
---|
740 | /// <param name="encr">The encryption info extracted from the ENCRYPTIOINFO stream inside the OLE document</param>
|
---|
741 | /// <returns></returns>
|
---|
742 | private bool IsPasswordValid(HashAlgorithm sha, EncryptionInfoAgile.EncryptionKeyEncryptor encr)
|
---|
743 | {
|
---|
744 | var valHash = sha.ComputeHash(encr.VerifierHashInput);
|
---|
745 |
|
---|
746 | //Equal?
|
---|
747 | for (int i = 0; i < valHash.Length; i++)
|
---|
748 | {
|
---|
749 | if (encr.VerifierHash[i] != valHash[i])
|
---|
750 | {
|
---|
751 | return false;
|
---|
752 | }
|
---|
753 | }
|
---|
754 | return true;
|
---|
755 | }
|
---|
756 |
|
---|
757 | private byte[] DecryptAgileFromKey(EncryptionInfoAgile.EncryptionKeyEncryptor encr, byte[] key, byte[] encryptedData, long size, byte[] iv)
|
---|
758 | {
|
---|
759 | SymmetricAlgorithm decryptKey = GetEncryptionAlgorithm(encr);
|
---|
760 | decryptKey.BlockSize = encr.BlockSize << 3;
|
---|
761 | decryptKey.KeySize = encr.KeyBits;
|
---|
762 | decryptKey.Mode = encr.ChiptherChaining == eChainingMode.ChainingModeCBC ? CipherMode.CBC : CipherMode.CFB;
|
---|
763 | decryptKey.Padding = PaddingMode.Zeros;
|
---|
764 |
|
---|
765 | ICryptoTransform decryptor = decryptKey.CreateDecryptor(
|
---|
766 | FixHashSize(key, encr.KeyBits / 8),
|
---|
767 | FixHashSize(iv, encr.BlockSize, 0x36));
|
---|
768 |
|
---|
769 |
|
---|
770 | MemoryStream dataStream = new MemoryStream(encryptedData);
|
---|
771 |
|
---|
772 | CryptoStream cryptoStream = new CryptoStream(dataStream,
|
---|
773 | decryptor,
|
---|
774 | CryptoStreamMode.Read);
|
---|
775 |
|
---|
776 | var decryptedData = new byte[size];
|
---|
777 |
|
---|
778 | cryptoStream.Read(decryptedData, 0, (int)size);
|
---|
779 | return decryptedData;
|
---|
780 | }
|
---|
781 |
|
---|
782 | private SymmetricAlgorithm GetEncryptionAlgorithm(EncryptionInfoAgile.EncryptionKeyEncryptor encr)
|
---|
783 | {
|
---|
784 | switch (encr.CipherAlgorithm)
|
---|
785 | {
|
---|
786 | case eCipherAlgorithm.AES:
|
---|
787 | return new RijndaelManaged();
|
---|
788 | case eCipherAlgorithm.DES:
|
---|
789 | return new DESCryptoServiceProvider();
|
---|
790 | case eCipherAlgorithm.TRIPLE_DES:
|
---|
791 | case eCipherAlgorithm.TRIPLE_DES_112:
|
---|
792 | return new TripleDESCryptoServiceProvider();
|
---|
793 | case eCipherAlgorithm.RC2:
|
---|
794 | return new RC2CryptoServiceProvider();
|
---|
795 | default:
|
---|
796 | throw(new NotSupportedException(string.Format("Unsupported Cipher Algorithm: {0}", encr.CipherAlgorithm.ToString())));
|
---|
797 | }
|
---|
798 | }
|
---|
799 | private void EncryptAgileFromKey(EncryptionInfoAgile.EncryptionKeyEncryptor encr, byte[] key, byte[] data, long pos, long size, byte[] iv,MemoryStream ms)
|
---|
800 | {
|
---|
801 | var encryptKey = GetEncryptionAlgorithm(encr);
|
---|
802 | encryptKey.BlockSize = encr.BlockSize << 3;
|
---|
803 | encryptKey.KeySize = encr.KeyBits;
|
---|
804 | encryptKey.Mode = encr.ChiptherChaining==eChainingMode.ChainingModeCBC ? CipherMode.CBC : CipherMode.CFB;
|
---|
805 | encryptKey.Padding = PaddingMode.Zeros;
|
---|
806 |
|
---|
807 | ICryptoTransform encryptor = encryptKey.CreateEncryptor(
|
---|
808 | FixHashSize(key, encr.KeyBits / 8),
|
---|
809 | FixHashSize(iv, 16, 0x36));
|
---|
810 |
|
---|
811 |
|
---|
812 | CryptoStream cryptoStream = new CryptoStream(ms,
|
---|
813 | encryptor,
|
---|
814 | CryptoStreamMode.Write);
|
---|
815 |
|
---|
816 | var cryptoSize = size % encr.BlockSize == 0 ? size : (size + (encr.BlockSize - (size % encr.BlockSize)));
|
---|
817 | var buffer = new byte[size];
|
---|
818 | Array.Copy(data, pos, buffer, 0, size);
|
---|
819 | cryptoStream.Write(buffer, 0, (int)size);
|
---|
820 | while (size % encr.BlockSize != 0)
|
---|
821 | {
|
---|
822 | cryptoStream.WriteByte(0);
|
---|
823 | size++;
|
---|
824 | }
|
---|
825 | }
|
---|
826 |
|
---|
827 | /// <summary>
|
---|
828 | /// Create the hash.
|
---|
829 | /// This method is written with the help of Lyquidity library, many thanks for this nice sample
|
---|
830 | /// </summary>
|
---|
831 | /// <param name="password">The password</param>
|
---|
832 | /// <param name="encryptionInfo">The encryption info extracted from the ENCRYPTIOINFO stream inside the OLE document</param>
|
---|
833 | /// <returns>The hash to encrypt the document</returns>
|
---|
834 | private byte[] GetPasswordHashBinary(string password, EncryptionInfoBinary encryptionInfo)
|
---|
835 | {
|
---|
836 | byte[] hash = null;
|
---|
837 | byte[] tempHash = new byte[4 + 20]; //Iterator + prev. hash
|
---|
838 | try
|
---|
839 | {
|
---|
840 | HashAlgorithm hashProvider;
|
---|
841 | if (encryptionInfo.Header.AlgIDHash == AlgorithmHashID.SHA1 || encryptionInfo.Header.AlgIDHash == AlgorithmHashID.App && (encryptionInfo.Flags & Flags.fExternal) == 0)
|
---|
842 | {
|
---|
843 | hashProvider = new SHA1CryptoServiceProvider();
|
---|
844 | }
|
---|
845 | else if (encryptionInfo.Header.KeySize > 0 && encryptionInfo.Header.KeySize < 80)
|
---|
846 | {
|
---|
847 | throw new NotSupportedException("RC4 Hash provider is not supported. Must be SHA1(AlgIDHash == 0x8004)");
|
---|
848 | }
|
---|
849 | else
|
---|
850 | {
|
---|
851 | throw new NotSupportedException("Hash provider is invalid. Must be SHA1(AlgIDHash == 0x8004)");
|
---|
852 | }
|
---|
853 |
|
---|
854 | hash = GetPasswordHash(hashProvider, encryptionInfo.Verifier.Salt, password, 50000, 20);
|
---|
855 |
|
---|
856 | // Append "block" (0)
|
---|
857 | Array.Copy(hash, tempHash, hash.Length);
|
---|
858 | Array.Copy(System.BitConverter.GetBytes(0), 0, tempHash, hash.Length, 4);
|
---|
859 | hash = hashProvider.ComputeHash(tempHash);
|
---|
860 |
|
---|
861 | /***** Now use the derived key algorithm *****/
|
---|
862 | byte[] derivedKey = new byte[64];
|
---|
863 | int keySizeBytes = encryptionInfo.Header.KeySize / 8;
|
---|
864 |
|
---|
865 | //First XOR hash bytes with 0x36 and fill the rest with 0x36
|
---|
866 | for (int i = 0; i < derivedKey.Length; i++)
|
---|
867 | derivedKey[i] = (byte)(i < hash.Length ? 0x36 ^ hash[i] : 0x36);
|
---|
868 |
|
---|
869 |
|
---|
870 | byte[] X1 = hashProvider.ComputeHash(derivedKey);
|
---|
871 |
|
---|
872 | //if verifier size is bigger than the key size we can return X1
|
---|
873 | if ((int)encryptionInfo.Verifier.VerifierHashSize > keySizeBytes)
|
---|
874 | return FixHashSize(X1, keySizeBytes);
|
---|
875 |
|
---|
876 | //Else XOR hash bytes with 0x5C and fill the rest with 0x5C
|
---|
877 | for (int i = 0; i < derivedKey.Length; i++)
|
---|
878 | derivedKey[i] = (byte)(i < hash.Length ? 0x5C ^ hash[i] : 0x5C);
|
---|
879 |
|
---|
880 | byte[] X2 = hashProvider.ComputeHash(derivedKey);
|
---|
881 |
|
---|
882 | //Join the two and return
|
---|
883 | byte[] join = new byte[X1.Length + X2.Length];
|
---|
884 |
|
---|
885 | Array.Copy(X1, 0, join, 0, X1.Length);
|
---|
886 | Array.Copy(X2, 0, join, X1.Length, X2.Length);
|
---|
887 |
|
---|
888 |
|
---|
889 | return FixHashSize(join, keySizeBytes);
|
---|
890 | }
|
---|
891 | catch (Exception ex)
|
---|
892 | {
|
---|
893 | throw (new Exception("An error occured when the encryptionkey was created", ex));
|
---|
894 | }
|
---|
895 | }
|
---|
896 |
|
---|
897 | /// <summary>
|
---|
898 | /// Create the hash.
|
---|
899 | /// This method is written with the help of Lyquidity library, many thanks for this nice sample
|
---|
900 | /// </summary>
|
---|
901 | /// <param name="password">The password</param>
|
---|
902 | /// <param name="encr">The encryption info extracted from the ENCRYPTIOINFO stream inside the OLE document</param>
|
---|
903 | /// <param name="blockKey">The block key appended to the hash to obtain the final hash</param>
|
---|
904 | /// <returns>The hash to encrypt the document</returns>
|
---|
905 | private byte[] GetPasswordHashAgile(string password, EncryptionInfoAgile.EncryptionKeyEncryptor encr, byte[] blockKey)
|
---|
906 | {
|
---|
907 | try
|
---|
908 | {
|
---|
909 | var hashProvider = GetHashProvider(encr);
|
---|
910 | var hash = GetPasswordHash(hashProvider, encr.SaltValue, password, encr.SpinCount, encr.HashSize);
|
---|
911 | var hashFinal = GetFinalHash(hashProvider, encr, blockKey, hash);
|
---|
912 |
|
---|
913 | return FixHashSize(hashFinal, encr.KeyBits / 8);
|
---|
914 | }
|
---|
915 | catch (Exception ex)
|
---|
916 | {
|
---|
917 | throw (new Exception("An error occured when the encryptionkey was created", ex));
|
---|
918 | }
|
---|
919 | }
|
---|
920 | #endif
|
---|
921 | private byte[] GetFinalHash(HashAlgorithm hashProvider, EncryptionInfoAgile.EncryptionKeyEncryptor encr, byte[] blockKey, byte[] hash)
|
---|
922 | {
|
---|
923 | //2.3.4.13 MS-OFFCRYPTO
|
---|
924 | var tempHash = new byte[hash.Length + blockKey.Length];
|
---|
925 | Array.Copy(hash, tempHash, hash.Length);
|
---|
926 | Array.Copy(blockKey, 0, tempHash, hash.Length, blockKey.Length);
|
---|
927 | var hashFinal = hashProvider.ComputeHash(tempHash);
|
---|
928 | return hashFinal;
|
---|
929 | }
|
---|
930 | private byte[] GetPasswordHash(HashAlgorithm hashProvider, byte[] salt, string password, int spinCount, int hashSize)
|
---|
931 | {
|
---|
932 | byte[] hash = null;
|
---|
933 | byte[] tempHash = new byte[4 + hashSize]; //Iterator + prev. hash
|
---|
934 | hash = hashProvider.ComputeHash(CombinePassword(salt, password));
|
---|
935 |
|
---|
936 | //Iterate "spinCount" times, inserting i in first 4 bytes and then the prev. hash in byte 5-24
|
---|
937 | for (int i = 0; i < spinCount; i++)
|
---|
938 | {
|
---|
939 | Array.Copy(BitConverter.GetBytes(i), tempHash, 4);
|
---|
940 | Array.Copy(hash, 0, tempHash, 4, hash.Length);
|
---|
941 |
|
---|
942 | hash = hashProvider.ComputeHash(tempHash);
|
---|
943 | }
|
---|
944 |
|
---|
945 | return hash;
|
---|
946 | }
|
---|
947 | private byte[] FixHashSize(byte[] hash, int size, byte fill=0)
|
---|
948 | {
|
---|
949 | if (hash.Length == size)
|
---|
950 | return hash;
|
---|
951 | else if (hash.Length < size)
|
---|
952 | {
|
---|
953 | byte[] buff = new byte[size];
|
---|
954 | Array.Copy(hash, buff, hash.Length);
|
---|
955 | for (int i = hash.Length; i < size; i++)
|
---|
956 | {
|
---|
957 | buff[i] = fill;
|
---|
958 | }
|
---|
959 | return buff;
|
---|
960 | }
|
---|
961 | else
|
---|
962 | {
|
---|
963 | byte[] buff = new byte[size];
|
---|
964 | Array.Copy(hash, buff, size);
|
---|
965 | return buff;
|
---|
966 | }
|
---|
967 | }
|
---|
968 | private byte[] CombinePassword(byte[] salt, string password)
|
---|
969 | {
|
---|
970 | if (password == "")
|
---|
971 | {
|
---|
972 | password = "VelvetSweatshop"; //Used if Password is blank
|
---|
973 | }
|
---|
974 | // Convert password to unicode...
|
---|
975 | byte[] passwordBuf = UnicodeEncoding.Unicode.GetBytes(password);
|
---|
976 |
|
---|
977 | byte[] inputBuf = new byte[salt.Length + passwordBuf.Length];
|
---|
978 | Array.Copy(salt, inputBuf, salt.Length);
|
---|
979 | Array.Copy(passwordBuf, 0, inputBuf, salt.Length, passwordBuf.Length);
|
---|
980 | return inputBuf;
|
---|
981 | }
|
---|
982 | internal static ushort CalculatePasswordHash(string Password)
|
---|
983 | {
|
---|
984 | //Calculate the hash
|
---|
985 | //Thanks to Kohei Yoshida for the sample http://kohei.us/2008/01/18/excel-sheet-protection-password-hash/
|
---|
986 | ushort hash = 0;
|
---|
987 | for (int i = Password.Length - 1; i >= 0; i--)
|
---|
988 | {
|
---|
989 | hash ^= Password[i];
|
---|
990 | hash = (ushort)(((ushort)((hash >> 14) & 0x01))
|
---|
991 | |
|
---|
992 | ((ushort)((hash << 1) & 0x7FFF))); //Shift 1 to the left. Overflowing bit 15 goes into bit 0
|
---|
993 | }
|
---|
994 |
|
---|
995 | hash ^= (0x8000 | ('N' << 8) | 'K'); //Xor NK with high bit set(0xCE4B)
|
---|
996 | hash ^= (ushort)Password.Length;
|
---|
997 |
|
---|
998 | return hash;
|
---|
999 | }
|
---|
1000 | }
|
---|
1001 | }
|
---|