Free cookie consent management tool by TermsFeed Policy Generator

source: branches/ProblemRefactoring/HeuristicLab.ExtLibs/HeuristicLab.EPPlus/4.0.3/EPPlus-4.0.3/Encryption/EncryptionHandler.cs @ 15630

Last change on this file since 15630 was 12074, checked in by sraggl, 9 years ago

#2341: Added EPPlus-4.0.3 to ExtLibs

File size: 45.0 KB
Line 
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 *******************************************************************************/
31using OfficeOpenXml.Utils;
32using System;
33using System.Collections.Generic;
34using System.IO;
35using System.Linq;
36using System.Runtime.InteropServices;
37using System.Security;
38using System.Security.Cryptography;
39using System.Text;
40using System.Xml;
41using comTypes = System.Runtime.InteropServices.ComTypes;
42
43namespace 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}
Note: See TracBrowser for help on using the repository browser.