Free cookie consent management tool by TermsFeed Policy Generator

source: branches/ExportSymbolicDataAnalysisSolutions/HeuristicLab.ExtLibs/HeuristicLab.EPPlus/3.1.3/EPPlus-3.1.3/Utils/EncryptedPackageHandler.cs @ 11072

Last change on this file since 11072 was 9580, checked in by sforsten, 12 years ago

#1730:

  • added SymbolicDataAnalysisExpressionExcelFormatter
  • changed modifiers in SymbolicExpressionTreeChart of methods SaveImageAsBitmap and SaveImageAsEmf to public
  • added menu item ExportSymbolicSolutionToExcelMenuItem to export a symbolic solution to an excel file
  • added EPPlus-3.1.3 to ExtLibs
File size: 46.7 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 * This class is created with the help of the MS-OFFCRYPTO PDF documentation... http://msdn.microsoft.com/en-us/library/cc313071(office.12).aspx
26 * Decryption library for Office Open XML files(Lyquidity) and Sminks very nice example
27 * on "Reading compound documents in c#" on Stackoverflow. Many thanks!
28 ***************************************************************************************
29 * Code change notes:
30 *
31 * Author             Change            Date
32 *******************************************************************************
33 * Jan Källman    Added   10-AUG-2010
34 * Jan Källman    License changed GPL-->LGPL 2011-12-16
35 *******************************************************************************/
36using System;
37using System.Collections.Generic;
38using System.Text;
39using System.Runtime.InteropServices;
40using comTypes=System.Runtime.InteropServices.ComTypes;
41using System.IO;
42using System.Security.Cryptography;
43using System.IO.Packaging;
44namespace OfficeOpenXml.Utils
45{
46    /// <summary>
47    /// Handles the EncryptionInfo stream
48    /// </summary>
49    internal class EncryptionInfo
50    {
51        internal short MajorVersion;
52        internal short MinorVersion;
53        internal Flags Flags;
54        internal uint HeaderSize;
55        internal EncryptionHeader Header;
56        internal EncryptionVerifier Verifier;
57        internal void ReadBinary(byte[] data)
58        {
59            MajorVersion = BitConverter.ToInt16(data, 0);
60            MinorVersion = BitConverter.ToInt16(data, 2);
61
62            Flags = (Flags)BitConverter.ToInt32(data, 4);
63            HeaderSize = (uint)BitConverter.ToInt32(data, 8);
64
65            /**** EncryptionHeader ****/
66            Header = new EncryptionHeader();
67            Header.Flags = (Flags)BitConverter.ToInt32(data, 12);
68            Header.SizeExtra = BitConverter.ToInt32(data, 16);
69            Header.AlgID = (AlgorithmID)BitConverter.ToInt32(data, 20);
70            Header.AlgIDHash = (AlgorithmHashID)BitConverter.ToInt32(data, 24);
71            Header.KeySize = BitConverter.ToInt32(data, 28);
72            Header.ProviderType = (ProviderType)BitConverter.ToInt32(data, 32);
73            Header.Reserved1 = BitConverter.ToInt32(data, 36);
74            Header.Reserved2 = BitConverter.ToInt32(data, 40);
75
76            byte[] text = new byte[(int)HeaderSize - 34];
77            Array.Copy(data, 44, text, 0, (int)HeaderSize - 34);
78            Header.CSPName = UTF8Encoding.Unicode.GetString(text);
79
80            int pos = (int)HeaderSize + 12;
81
82            /**** EncryptionVerifier ****/
83            Verifier = new EncryptionVerifier();
84            Verifier.SaltSize = (uint)BitConverter.ToInt32(data, pos);
85            Verifier.Salt = new byte[Verifier.SaltSize];
86
87            Array.Copy(data, pos + 4, Verifier.Salt, 0, Verifier.SaltSize);
88
89            Verifier.EncryptedVerifier = new byte[16];
90            Array.Copy(data, pos + 20, Verifier.EncryptedVerifier, 0, 16);
91
92            Verifier.VerifierHashSize = (uint)BitConverter.ToInt32(data, pos + 36);
93            Verifier.EncryptedVerifierHash = new byte[Verifier.VerifierHashSize];
94            Array.Copy(data, pos + 40, Verifier.EncryptedVerifierHash, 0, Verifier.VerifierHashSize);
95        }
96        internal byte[] WriteBinary()
97        {
98            MemoryStream ms=new MemoryStream();
99            BinaryWriter bw = new BinaryWriter(ms);
100
101            bw.Write(MajorVersion);
102            bw.Write(MinorVersion);
103            bw.Write((int)Flags);
104            byte[] header = Header.WriteBinary();
105            bw.Write((uint)header.Length);
106            bw.Write(header);
107            bw.Write(Verifier.WriteBinary());
108
109            bw.Flush();
110            return ms.ToArray();
111        }
112
113    }
114    /// <summary>
115    /// Encryption Header inside the EncryptionInfo stream
116    /// </summary>
117    internal class EncryptionHeader
118    {
119        internal Flags Flags;
120        internal int SizeExtra;             //MUST be 0x00000000.
121        internal AlgorithmID AlgID;         //MUST be 0x0000660E (AES-128), 0x0000660F (AES-192), or 0x00006610 (AES-256).
122        internal AlgorithmHashID AlgIDHash; //MUST be 0x00008004 (SHA-1).
123        internal int KeySize;               //MUST be 0x00000080 (AES-128), 0x000000C0 (AES-192), or 0x00000100 (AES-256).
124        internal ProviderType ProviderType; //SHOULD<10> be 0x00000018 (AES).
125        internal int Reserved1;             //Undefined and MUST be ignored.
126        internal int Reserved2;             //MUST be 0x00000000 and MUST be ignored.
127        internal string CSPName;            //SHOULD<11> be set to either "Microsoft Enhanced RSA and AES Cryptographic Provider" or "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)" as a null-terminated Unicode string.
128        internal byte[] WriteBinary()
129        {
130            MemoryStream ms = new MemoryStream();
131            BinaryWriter bw = new BinaryWriter(ms);
132
133            bw.Write((int)Flags);
134            bw.Write(SizeExtra);
135            bw.Write((int)AlgID);
136            bw.Write((int)AlgIDHash);
137            bw.Write((int)KeySize);
138            bw.Write((int)ProviderType);
139            bw.Write(Reserved1);
140            bw.Write(Reserved2);
141            bw.Write(Encoding.Unicode.GetBytes(CSPName));
142
143            bw.Flush();
144            return ms.ToArray();
145        }
146    }
147    /// <summary>
148    /// Encryption verifier inside the EncryptionInfo stream
149    /// </summary>
150    internal class EncryptionVerifier
151    {
152        internal uint SaltSize;              // An unsigned integer that specifies the size of the Salt field. It MUST be 0x00000010.
153        internal byte[] Salt;                //(16 bytes): An array of bytes that specifies the salt value used during password hash generation. It MUST NOT be the same data used for the verifier stored encrypted in the EncryptedVerifier field.
154        internal byte[] EncryptedVerifier;   //(16 bytes): MUST be the randomly generated Verifier value encrypted using the algorithm chosen by the implementation.
155        internal uint VerifierHashSize;      //(4 bytes): An unsigned integer that specifies the number of bytes needed to contain the hash of the data used to generate the EncryptedVerifier field.
156        internal byte[] EncryptedVerifierHash; //(variable): An array of bytes that contains the encrypted form of the hash of the randomly generated Verifier value. The length of the array MUST be the size of the encryption block size multiplied by the number of blocks needed to encrypt the hash of the Verifier. If the encryption algorithm is RC4, the length MUST be 20 bytes. If the encryption algorithm is AES, the length MUST be 32 bytes.
157        internal byte[] WriteBinary()
158        {
159            MemoryStream ms = new MemoryStream();
160            BinaryWriter bw = new BinaryWriter(ms);
161
162            bw.Write(SaltSize);
163            bw.Write(Salt);
164            bw.Write(EncryptedVerifier);
165            bw.Write(0x14);                 //Sha1 is 20 bytes  (Encrypted is 32)
166            bw.Write(EncryptedVerifierHash);
167
168            bw.Flush();
169            return ms.ToArray();
170        }
171    }
172    [Flags]
173    internal enum Flags
174    {
175        Reserved1 = 1,   // (1 bit): MUST be set to zero, and MUST be ignored.
176        Reserved2 = 2,   // (1 bit): MUST be set to zero, and MUST be ignored.
177        fCryptoAPI= 4,   // (1 bit): A flag that specifies whether CryptoAPI RC4 or [ECMA-376] encryption is used. It MUST be set to 1 unless fExternal is 1. If fExternal is set to 1, it MUST be set to zero.       
178        fDocProps = 8,   // (1 bit): MUST be set to zero if document properties are encrypted. Otherwise, it MUST be set to 1. Encryption of document properties is specified in section 2.3.5.4.
179        fExternal = 16,  // (1 bit): If extensible encryption is used, it MUST be set to 1. Otherwise, it MUST be set to zero. If this field is set to 1, all other fields in this structure MUST be set to zero.
180        fAES      = 32   //(1 bit): If the protected content is an [ECMA-376] document, it MUST be set to 1. Otherwise, it MUST be set to zero. If the fAES bit is set to 1, the fCryptoAPI bit MUST also be set to 1
181    }
182    internal enum AlgorithmID
183    {
184        Flags   = 0x00000000,   // Determined by Flags
185        RC4     = 0x00006801,   // RC4
186        AES128  = 0x0000660E,   // 128-bit AES
187        AES192  = 0x0000660F,   // 192-bit AES
188        AES256  = 0x00006610    // 256-bit AES
189    }
190    internal enum AlgorithmHashID
191    {
192        App =  0x00000000,
193        SHA1 = 0x00008004,
194    }
195    internal enum ProviderType
196    {
197        Flags=0x00000000,//Determined by Flags
198        RC4=0x00000001,
199        AES=0x00000018,
200    }
201    /// <summary>
202    /// Handels encrypted Excel documents
203    /// </summary>
204    internal class EncryptedPackageHandler
205    {       
206        [DllImport("ole32.dll")]
207        private static extern int StgIsStorageFile(
208            [MarshalAs(UnmanagedType.LPWStr)] string pwcsName);
209        [DllImport("ole32.dll")]
210        private static extern int StgIsStorageILockBytes(
211            ILockBytes plkbyt);
212
213
214        [DllImport("ole32.dll")]
215        static extern int StgOpenStorage(
216            [MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
217            IStorage pstgPriority,
218            STGM grfMode,
219            IntPtr snbExclude,
220            uint reserved,
221            out IStorage ppstgOpen);
222
223        [DllImport("ole32.dll")]
224        static extern int StgOpenStorageOnILockBytes(
225            ILockBytes plkbyt,
226            IStorage pStgPriority,
227            STGM grfMode,
228            IntPtr snbEnclude,
229            uint reserved,
230            out IStorage ppstgOpen);
231        [DllImport("ole32.dll")]
232        static extern int CreateILockBytesOnHGlobal(
233            IntPtr hGlobal,
234            bool fDeleteOnRelease,
235            out ILockBytes ppLkbyt);
236
237        [DllImport("ole32.dll")]
238        static extern int StgCreateDocfileOnILockBytes(ILockBytes plkbyt, STGM grfMode, int reserved, out IStorage ppstgOpen);
239        internal static int IsStorageFile(string Name)
240        {
241            return StgIsStorageFile(Name);
242        }
243        internal static int IsStorageILockBytes(ILockBytes lb)
244        {
245            return StgIsStorageILockBytes(lb);
246        }
247        /// <summary>
248        /// Read the package from the OLE document and decrypt it using the supplied password
249        /// </summary>
250        /// <param name="fi">The file</param>
251        /// <param name="encryption"></param>
252        /// <returns></returns>
253        internal MemoryStream DecryptPackage(FileInfo fi, ExcelEncryption encryption)
254        {
255            MemoryStream ret = null;
256            if (StgIsStorageFile(fi.FullName) == 0)
257            {
258                IStorage storage = null;
259                if (StgOpenStorage(
260                    fi.FullName,
261                    null,
262                    STGM.DIRECT | STGM.READ | STGM.SHARE_EXCLUSIVE,
263                    IntPtr.Zero,
264                    0,
265                    out storage) == 0)
266                {
267                    ret = GetStreamFromPackage(storage, encryption);                   
268                    Marshal.ReleaseComObject(storage);
269                }
270            }
271            else
272            {
273                throw(new InvalidDataException(string.Format("File {0} is not an encrypted package",fi.FullName)));
274            }
275            return ret;
276        }
277        /// <summary>
278        /// Read the package from the OLE document and decrypt it using the supplied password
279        /// </summary>
280        /// <param name="stream">The memory stream. </param>
281        /// <param name="encryption">The encryption object from the Package</param>
282        /// <returns></returns>
283        internal MemoryStream DecryptPackage(MemoryStream stream, ExcelEncryption encryption)
284        {
285            //Create the lockBytes object.
286            ILockBytes lb = GetLockbyte(stream);
287
288            MemoryStream ret = null;
289
290            if (StgIsStorageILockBytes(lb) == 0)
291            {
292                IStorage storage = null;
293                if (StgOpenStorageOnILockBytes(
294                        lb,
295                        null,
296                        STGM.DIRECT | STGM.READ | STGM.SHARE_EXCLUSIVE,
297                        IntPtr.Zero,
298                        0,
299                        out storage) == 0)
300                {
301                    ret = GetStreamFromPackage(storage, encryption);
302                }
303                Marshal.ReleaseComObject(storage);
304            }
305            else
306            {
307                throw (new InvalidDataException("The stream is not an encrypted package"));
308            }
309            Marshal.ReleaseComObject(lb);
310
311            return ret;
312        }
313        internal ILockBytes GetLockbyte(MemoryStream stream)
314        {
315            ILockBytes lb;
316            var iret = CreateILockBytesOnHGlobal(IntPtr.Zero, true, out lb);
317            byte[] docArray = stream.GetBuffer();
318            IntPtr buffer = Marshal.AllocHGlobal(docArray.Length);
319            Marshal.Copy(docArray, 0, buffer, docArray.Length);
320            UIntPtr readSize;
321            lb.WriteAt(0, buffer, docArray.Length, out readSize);
322            Marshal.FreeHGlobal(buffer);
323            return lb;
324        }
325        /// <summary>
326        /// Encrypts a package
327        /// </summary>
328        /// <param name="package">The package as a byte array</param>
329        /// <param name="encryption">The encryption info from the workbook</param>
330        /// <returns></returns>
331        internal MemoryStream EncryptPackage(byte[] package, ExcelEncryption encryption)
332        {
333            byte[] encryptionKey;
334            //Create the Encryption Info. This also returns the Encryptionkey
335            var encryptionInfo = CreateEncryptionInfo(encryption.Password,
336                    encryption.Algorithm == EncryptionAlgorithm.AES128 ?
337                        AlgorithmID.AES128 :
338                    encryption.Algorithm == EncryptionAlgorithm.AES192 ?
339                        AlgorithmID.AES192 :
340                        AlgorithmID.AES256, out encryptionKey);
341
342            ILockBytes lb;
343            var iret = CreateILockBytesOnHGlobal(IntPtr.Zero, true, out lb);
344
345            IStorage storage = null;
346            MemoryStream ret = null;
347
348            //Create the document in-memory
349            if (StgCreateDocfileOnILockBytes(lb,
350                    STGM.CREATE | STGM.READWRITE | STGM.SHARE_EXCLUSIVE | STGM.TRANSACTED,
351                    0,
352                    out storage)==0)
353            {
354                //First create the dataspace storage
355                CreateDataSpaces(storage);
356
357                //Create the Encryption info Stream
358                comTypes.IStream stream;
359                storage.CreateStream("EncryptionInfo", (uint)(STGM.CREATE | STGM.WRITE | STGM.DIRECT | STGM.SHARE_EXCLUSIVE), (uint)0, (uint)0, out stream);
360                byte[] ei=encryptionInfo.WriteBinary();
361                stream.Write(ei, ei.Length, IntPtr.Zero);
362                stream = null;
363
364                //Encrypt the package
365                byte[] encryptedPackage=EncryptData(encryptionKey, package, false);
366
367                storage.CreateStream("EncryptedPackage", (uint)(STGM.CREATE | STGM.WRITE | STGM.DIRECT | STGM.SHARE_EXCLUSIVE), (uint)0, (uint)0, out stream);
368
369                //Write Size here
370                MemoryStream ms = new MemoryStream();
371                BinaryWriter bw = new BinaryWriter(ms);
372                bw.Write((ulong)package.LongLength);
373                bw.Flush();
374                byte[] length = ms.ToArray();
375                //Write Encrypted data length first as an unsigned long
376                stream.Write(length, length.Length, IntPtr.Zero);
377                //And now the Encrypted data
378                stream.Write(encryptedPackage, encryptedPackage.Length, IntPtr.Zero);
379                stream = null;
380                storage.Commit(0);
381                lb.Flush();
382
383                //Now copy the unmanaged stream to a byte array --> memory stream
384                var statstg = new comTypes.STATSTG();
385                lb.Stat(out statstg, 0);
386                int size = (int)statstg.cbSize;
387                IntPtr buffer = Marshal.AllocHGlobal(size);
388                UIntPtr readSize;
389                byte[] pack=new byte[size];
390                lb.ReadAt(0, buffer, size, out readSize);
391                Marshal.Copy(buffer, pack, 0, size);
392                Marshal.FreeHGlobal(buffer);
393
394                ret = new MemoryStream();
395                ret.Write(pack, 0, size);
396            }
397            Marshal.ReleaseComObject(storage);
398            Marshal.ReleaseComObject(lb);
399            return ret;
400        }
401        #region "Dataspaces Stream methods"
402        private void CreateDataSpaces(IStorage storage)
403        {
404            IStorage dataSpaces;
405            storage.CreateStorage("\x06" + "DataSpaces", (uint)(STGM.CREATE | STGM.WRITE | STGM.DIRECT | STGM.SHARE_EXCLUSIVE), 0, 0, out dataSpaces);
406            storage.Commit(0);
407
408            //Version Stream
409            comTypes.IStream versionStream;
410            dataSpaces.CreateStream("Version", (uint)(STGM.CREATE | STGM.WRITE | STGM.DIRECT | STGM.SHARE_EXCLUSIVE), 0, 0, out versionStream);
411            byte[] version = CreateVersionStream();
412            versionStream.Write(version,version.Length, IntPtr.Zero);
413
414            //DataSpaceMap
415            comTypes.IStream dataSpaceMapStream;
416            dataSpaces.CreateStream("DataSpaceMap", (uint)(STGM.CREATE | STGM.WRITE | STGM.DIRECT | STGM.SHARE_EXCLUSIVE), 0, 0, out dataSpaceMapStream);
417            byte[] dataSpaceMap = CreateDataSpaceMap();
418            dataSpaceMapStream.Write(dataSpaceMap, dataSpaceMap.Length, IntPtr.Zero);
419
420            //DataSpaceInfo
421            IStorage dataSpaceInfo;
422            dataSpaces.CreateStorage("DataSpaceInfo", (uint)(STGM.CREATE | STGM.WRITE | STGM.DIRECT | STGM.SHARE_EXCLUSIVE), 0, 0, out dataSpaceInfo);
423
424            comTypes.IStream strongEncryptionDataSpaceStream;
425            dataSpaceInfo.CreateStream("StrongEncryptionDataSpace", (uint)(STGM.CREATE | STGM.WRITE | STGM.DIRECT | STGM.SHARE_EXCLUSIVE), 0, 0, out strongEncryptionDataSpaceStream);
426            byte[] strongEncryptionDataSpace = CreateStrongEncryptionDataSpaceStream();
427            strongEncryptionDataSpaceStream.Write(strongEncryptionDataSpace, strongEncryptionDataSpace.Length, IntPtr.Zero);
428            dataSpaceInfo.Commit(0);
429
430            //TransformInfo
431            IStorage tranformInfo;
432            dataSpaces.CreateStorage("TransformInfo", (uint)(STGM.CREATE | STGM.WRITE | STGM.DIRECT | STGM.SHARE_EXCLUSIVE), 0, 0, out tranformInfo);
433
434            IStorage strongEncryptionTransform;
435            tranformInfo.CreateStorage("StrongEncryptionTransform", (uint)(STGM.CREATE | STGM.WRITE | STGM.DIRECT | STGM.SHARE_EXCLUSIVE), 0, 0, out strongEncryptionTransform);
436
437            comTypes.IStream primaryStream;
438            strongEncryptionTransform.CreateStream("\x06Primary", (uint)(STGM.CREATE | STGM.WRITE | STGM.DIRECT | STGM.SHARE_EXCLUSIVE), 0, 0, out primaryStream);
439            byte[] primary = CreateTransformInfoPrimary();
440            primaryStream.Write(primary, primary.Length, IntPtr.Zero);
441            tranformInfo.Commit(0);
442            dataSpaces.Commit(0);
443        }
444        private byte[] CreateStrongEncryptionDataSpaceStream()
445        {
446            MemoryStream ms = new MemoryStream();
447            BinaryWriter bw = new BinaryWriter(ms);
448
449            bw.Write((int)8);       //HeaderLength
450            bw.Write((int)1);       //EntryCount
451
452            string tr = "StrongEncryptionTransform";   
453            bw.Write((int)tr.Length);
454            bw.Write(UTF8Encoding.Unicode.GetBytes(tr + "\0")); // end \0 is for padding
455           
456            bw.Flush();
457            return ms.ToArray();
458        }
459        private byte[] CreateVersionStream()
460        {
461            MemoryStream ms = new MemoryStream();
462            BinaryWriter bw = new BinaryWriter(ms);
463
464            bw.Write((short)0x3C);  //Major
465            bw.Write((short)0);     //Minor
466            bw.Write(UTF8Encoding.Unicode.GetBytes("Microsoft.Container.DataSpaces"));
467            bw.Write((int)1);       //ReaderVersion
468            bw.Write((int)1);       //UpdaterVersion
469            bw.Write((int)1);       //WriterVersion
470       
471            bw.Flush();
472            return ms.ToArray();
473        }
474        private byte[] CreateDataSpaceMap()
475        {
476            MemoryStream ms = new MemoryStream();
477            BinaryWriter bw = new BinaryWriter(ms);
478
479            bw.Write((int)8);       //HeaderLength
480            bw.Write((int)1);       //EntryCount
481            string s1 = "EncryptedPackage";
482            string s2 = "StrongEncryptionDataSpace";
483            bw.Write((int)s1.Length + s2.Length+0x14); 
484            bw.Write((int)1);       //ReferenceComponentCount
485            bw.Write((int)0);       //Stream=0
486            bw.Write((int)s1.Length*2); //Length s1
487            bw.Write(UTF8Encoding.Unicode.GetBytes(s1));
488            bw.Write((int)(s2.Length-1) * 2);   //Length s2
489            bw.Write(UTF8Encoding.Unicode.GetBytes(s2 + "\0"));   // end \0 is for padding
490
491            bw.Flush();
492            return ms.ToArray();
493        }
494        private byte[] CreateTransformInfoPrimary()
495        {
496            MemoryStream ms = new MemoryStream();
497            BinaryWriter bw = new BinaryWriter(ms);
498            string TransformID="{FF9A3F03-56EF-4613-BDD5-5A41C1D07246}";
499            string TransformName = "Microsoft.Container.EncryptionTransform";
500            bw.Write(TransformID.Length * 2 + 12);
501            bw.Write((int)1);
502            bw.Write(TransformID.Length * 2);
503            bw.Write(UTF8Encoding.Unicode.GetBytes(TransformID));
504            bw.Write(TransformName.Length * 2);
505            bw.Write(UTF8Encoding.Unicode.GetBytes(TransformName+"\0"));
506            bw.Write((int)1);   //ReaderVersion
507            bw.Write((int)1);   //UpdaterVersion
508            bw.Write((int)1);   //WriterVersion
509
510            bw.Write((int)0);
511            bw.Write((int)0);
512            bw.Write((int)0);       //CipherMode
513            bw.Write((int)4);       //Reserved
514
515            bw.Flush();
516            return ms.ToArray();
517        }
518        #endregion
519        /// <summary>
520        /// Create an EncryptionInfo object to encrypt a workbook
521        /// </summary>
522        /// <param name="password">The password</param>
523        /// <param name="algID"></param>
524        /// <param name="key">The Encryption key</param>
525        /// <returns></returns>
526        private EncryptionInfo CreateEncryptionInfo(string password, AlgorithmID algID, out byte[] key)
527        {
528            if (algID == AlgorithmID.Flags || algID == AlgorithmID.RC4)
529            {
530                throw(new ArgumentException("algID must be AES128, AES192 or AES256"));
531            }
532            var encryptionInfo = new EncryptionInfo();
533            encryptionInfo.MajorVersion = 4;
534            encryptionInfo.MinorVersion = 2;
535            encryptionInfo.Flags = Flags.fAES | Flags.fCryptoAPI;
536           
537            //Header
538            encryptionInfo.Header = new EncryptionHeader();
539            encryptionInfo.Header.AlgID = algID;
540            encryptionInfo.Header.AlgIDHash = AlgorithmHashID.SHA1;
541            encryptionInfo.Header.Flags = encryptionInfo.Flags;
542            encryptionInfo.Header.KeySize =
543                (algID == AlgorithmID.AES128 ? 0x80 : algID == AlgorithmID.AES192 ? 0xC0 : 0x100);
544            encryptionInfo.Header.ProviderType = ProviderType.AES;
545            encryptionInfo.Header.CSPName = "Microsoft Enhanced RSA and AES Cryptographic Provider\0";
546            encryptionInfo.Header.Reserved1 = 0;
547            encryptionInfo.Header.Reserved2 = 0;
548            encryptionInfo.Header.SizeExtra = 0;
549           
550            //Verifier
551            encryptionInfo.Verifier = new EncryptionVerifier();
552            encryptionInfo.Verifier.Salt = new byte[16];
553
554            var rnd = RandomNumberGenerator.Create();
555            rnd.GetBytes(encryptionInfo.Verifier.Salt);
556            encryptionInfo.Verifier.SaltSize = 0x10;
557
558            key = GetPasswordHash(password, encryptionInfo);
559           
560            var verifier = new byte[16];
561            rnd.GetBytes(verifier);
562            encryptionInfo.Verifier.EncryptedVerifier = EncryptData(key, verifier,true);
563
564            //AES = 32 Bits
565            encryptionInfo.Verifier.VerifierHashSize = 0x20;
566            SHA1 sha= new SHA1Managed();
567            var verifierHash = sha.ComputeHash(verifier);
568
569            encryptionInfo.Verifier.EncryptedVerifierHash = EncryptData(key, verifierHash, false);
570
571            return encryptionInfo;
572        }
573        private byte[] EncryptData(byte[] key, byte[] data, bool useDataSize)
574        {
575            RijndaelManaged aes = new RijndaelManaged();
576            aes.KeySize = key.Length*8;
577            aes.Mode = CipherMode.ECB;
578            aes.Padding = PaddingMode.Zeros;
579
580            //Encrypt the data
581            var crypt = aes.CreateEncryptor(key, null);
582            var ms = new MemoryStream();
583            var cs = new CryptoStream(ms, crypt, CryptoStreamMode.Write);
584            cs.Write(data, 0, data.Length);
585           
586            cs.FlushFinalBlock();
587           
588            byte[] ret;
589            if (useDataSize)
590            {
591                ret = new byte[data.Length];
592                ms.Seek(0, SeekOrigin.Begin);
593                ms.Read(ret, 0, data.Length);  //Truncate any padded Zeros
594                return ret;
595            }
596            else
597            {
598                return ms.ToArray();
599            }
600        }
601
602        private MemoryStream GetStreamFromPackage(IStorage storage, ExcelEncryption encryption)
603        {
604            MemoryStream ret=null;       
605            comTypes.STATSTG statstg;
606           
607            storage.Stat(out statstg, (uint)STATFLAG.STATFLAG_DEFAULT);
608
609            IEnumSTATSTG pIEnumStatStg = null;
610            storage.EnumElements(0, IntPtr.Zero, 0, out pIEnumStatStg);
611
612            comTypes.STATSTG[] regelt = { statstg };
613            uint fetched = 0;
614            uint res = pIEnumStatStg.Next(1, regelt, out fetched);
615
616            //if (regelt[0].pwcsName == "DataSpaces")
617            //{
618            //    PrintStorage(storage, regelt[0],"");
619            //}
620            if (res == 0)
621            {
622                byte[] data;
623                EncryptionInfo encryptionInfo = null;
624                while (res != 1)
625                {
626                    switch (statstg.pwcsName)
627                    {
628                        case "EncryptionInfo":
629                            data = GetOleStream(storage, statstg);
630                            //File.WriteAllBytes(@"c:\temp\EncInfo1.bin", data);
631                            encryptionInfo = new EncryptionInfo();
632                            encryptionInfo.ReadBinary(data);
633
634                            encryption.Algorithm = encryptionInfo.Header.AlgID == AlgorithmID.AES128 ?
635                                EncryptionAlgorithm.AES128 :
636                            encryptionInfo.Header.AlgID == AlgorithmID.AES192 ?
637                                EncryptionAlgorithm.AES192 :
638                                EncryptionAlgorithm.AES256;
639                            break;
640                        case "EncryptedPackage":
641                            data = GetOleStream(storage, statstg);
642                            ret = DecryptDocument(data, encryptionInfo, encryption.Password);
643                            break;
644                    }
645
646                    if ((res = pIEnumStatStg.Next(1, regelt, out fetched)) != 1)
647                    {
648                        statstg = regelt[0];
649                    }
650                }
651            }
652            Marshal.ReleaseComObject(pIEnumStatStg);
653            return ret;
654        }
655        // Help method to print a storage part binary to c:\temp
656        //private void PrintStorage(IStorage storage, System.Runtime.InteropServices.ComTypes.STATSTG sTATSTG, string topName)
657        //{
658        //    IStorage ds;
659        //    if (topName.Length > 0)
660        //    {
661        //        topName = topName[0] < 'A' ? topName.Substring(1, topName.Length - 1) : topName;
662        //    }
663        //    storage.OpenStorage(sTATSTG.pwcsName,
664        //        null,
665        //        (uint)(STGM.DIRECT | STGM.READ | STGM.SHARE_EXCLUSIVE),
666        //        IntPtr.Zero,
667        //        0,
668        //        out ds);
669
670        //    System.Runtime.InteropServices.ComTypes.STATSTG statstgSub;
671        //    ds.Stat(out statstgSub, (uint)STATFLAG.STATFLAG_DEFAULT);
672
673        //    IEnumSTATSTG pIEnumStatStgSub = null;
674        //    System.Runtime.InteropServices.ComTypes.STATSTG[] regeltSub = { statstgSub };
675        //    ds.EnumElements(0, IntPtr.Zero, 0, out pIEnumStatStgSub);
676
677        //    uint fetched = 0;
678        //    while (pIEnumStatStgSub.Next(1, regeltSub, out fetched) == 0)
679        //    {
680        //        string sName = regeltSub[0].pwcsName[0] < 'A' ? regeltSub[0].pwcsName.Substring(1, regeltSub[0].pwcsName.Length - 1) : regeltSub[0].pwcsName;
681        //        if (regeltSub[0].type == 1)
682        //        {
683        //            PrintStorage(ds, regeltSub[0], topName + sName + "_");
684        //        }
685        //        else if(regeltSub[0].type==2)
686        //        {
687        //            File.WriteAllBytes(@"c:\temp\" + topName + sName + ".bin", GetOleStream(ds, regeltSub[0]));
688        //        }
689        //    }
690        //}
691
692        /// <summary>
693        /// Decrypt a document
694        /// </summary>
695        /// <param name="data">The Encrypted data</param>
696        /// <param name="encryptionInfo">Encryption Info object</param>
697        /// <param name="password">The password</param>
698        /// <returns></returns>
699        private MemoryStream DecryptDocument(byte[] data, EncryptionInfo encryptionInfo, string password)
700        {
701            if (encryptionInfo == null)
702            {
703                throw(new InvalidDataException("Invalid document. EncryptionInfo is missing"));
704            }
705            long size = BitConverter.ToInt64(data,0);
706
707            var encryptedData = new byte[data.Length - 8];
708            Array.Copy(data, 8, encryptedData, 0, encryptedData.Length);
709           
710            MemoryStream doc = new MemoryStream();
711
712            if (encryptionInfo.Header.AlgID == AlgorithmID.AES128 || (encryptionInfo.Header.AlgID == AlgorithmID.Flags  && ((encryptionInfo.Flags & (Flags.fAES | Flags.fExternal | Flags.fCryptoAPI)) == (Flags.fAES | Flags.fCryptoAPI)))
713                ||
714                encryptionInfo.Header.AlgID == AlgorithmID.AES192
715                ||
716                encryptionInfo.Header.AlgID == AlgorithmID.AES256
717                )
718            {
719                RijndaelManaged decryptKey = new RijndaelManaged();
720                decryptKey.KeySize = encryptionInfo.Header.KeySize;
721                decryptKey.Mode = CipherMode.ECB;
722                decryptKey.Padding = PaddingMode.None;
723
724                var key=GetPasswordHash(password, encryptionInfo);
725
726                if (IsPasswordValid(key, encryptionInfo))
727                {
728                    ICryptoTransform decryptor = decryptKey.CreateDecryptor(
729                                                             key,
730                                                             null);
731
732
733                    MemoryStream dataStream = new MemoryStream(encryptedData);
734
735                    CryptoStream cryptoStream = new CryptoStream(dataStream,
736                                                                  decryptor,
737                                                                  CryptoStreamMode.Read);
738
739                    var decryptedData = new byte[size];
740
741                    cryptoStream.Read(decryptedData, 0, (int)size);
742                    doc.Write(decryptedData, 0, (int)size);
743                }
744                else
745                {
746                    throw(new UnauthorizedAccessException("Invalid password"));
747                }
748            }
749            return doc;
750        }
751        /// <summary>
752        /// Validate the password
753        /// </summary>
754        /// <param name="key">The encryption key</param>
755        /// <param name="encryptionInfo">The encryption info extracted from the ENCRYPTIOINFO stream inside the OLE document</param>
756        /// <returns></returns>
757        private bool IsPasswordValid(byte[] key, EncryptionInfo encryptionInfo)
758        {
759            RijndaelManaged decryptKey = new RijndaelManaged();
760            decryptKey.KeySize = encryptionInfo.Header.KeySize;
761            decryptKey.Mode = CipherMode.ECB;
762            decryptKey.Padding = PaddingMode.None;
763
764            ICryptoTransform decryptor = decryptKey.CreateDecryptor(
765                                                     key,
766                                                     null);
767
768
769            //Decrypt the verifier
770            MemoryStream dataStream = new MemoryStream(encryptionInfo.Verifier.EncryptedVerifier);
771            CryptoStream cryptoStream = new CryptoStream(dataStream,
772                                                          decryptor,
773                                                          CryptoStreamMode.Read);
774            var decryptedVerifier = new byte[16];
775            cryptoStream.Read(decryptedVerifier, 0, 16);
776
777            dataStream = new MemoryStream(encryptionInfo.Verifier.EncryptedVerifierHash);
778
779            cryptoStream = new CryptoStream(    dataStream,
780                                                decryptor,
781                                                CryptoStreamMode.Read);
782
783            //Decrypt the verifier hash
784            var decryptedVerifierHash = new byte[16];
785            cryptoStream.Read(decryptedVerifierHash, 0, (int)16);
786
787            //Get the hash for the decrypted verifier
788            var sha = new SHA1Managed();
789            var hash = sha.ComputeHash(decryptedVerifier);
790
791            //Equal?
792            for (int i = 0; i < 16; i++)
793            {
794                if (hash[i] != decryptedVerifierHash[i])
795                {
796                    return false;
797                }
798            }
799            return true;
800        }
801
802        /// <summary>
803        /// Read the stream and return it as a byte-array
804        /// </summary>
805        /// <param name="storage"></param>
806        /// <param name="statstg"></param>
807        /// <returns></returns>
808        private byte[] GetOleStream(IStorage storage, comTypes.STATSTG statstg)
809        {
810            comTypes.IStream pIStream;
811            storage.OpenStream(statstg.pwcsName,
812               IntPtr.Zero,
813               (uint)(STGM.READ | STGM.SHARE_EXCLUSIVE),
814               0,
815               out pIStream);
816
817            byte[] data = new byte[statstg.cbSize];
818            pIStream.Read(data, (int)statstg.cbSize, IntPtr.Zero);
819            Marshal.ReleaseComObject(pIStream);
820
821            return data;
822        }
823        /// <summary>
824        /// Create the hash.
825        /// This method is written with the help of Lyquidity library, many thanks for this nice sample
826        /// </summary>
827        /// <param name="password">The password</param>
828        /// <param name="encryptionInfo">The encryption info extracted from the ENCRYPTIOINFO stream inside the OLE document</param>
829        /// <returns>The hash to encrypt the document</returns>
830        private byte[] GetPasswordHash(string password, EncryptionInfo encryptionInfo)
831        {
832            byte[] hash = null;
833            byte[] tempHash = new byte[4+20];    //Iterator + prev. hash
834            try
835            {
836                HashAlgorithm hashProvider;
837                if (encryptionInfo.Header.AlgIDHash == AlgorithmHashID.SHA1 || encryptionInfo.Header.AlgIDHash == AlgorithmHashID.App && (encryptionInfo.Flags & Flags.fExternal) == 0)
838                {
839                    hashProvider = new SHA1CryptoServiceProvider();
840                }
841                else if (encryptionInfo.Header.KeySize > 0 && encryptionInfo.Header.KeySize < 80)
842                {
843                    throw new NotSupportedException("RC4 Hash provider is not supported. Must be SHA1(AlgIDHash == 0x8004)");
844                }
845                else
846                {
847                    throw new NotSupportedException("Hash provider is invalid. Must be SHA1(AlgIDHash == 0x8004)");
848                }
849
850                hash = hashProvider.ComputeHash(CombinePassword(encryptionInfo.Verifier.Salt, password));
851
852                //Iterate 50 000 times, inserting i in first 4 bytes and then the prev. hash in byte 5-24
853                for (int i = 0; i < 50000; i++)
854                {
855                    Array.Copy(BitConverter.GetBytes(i), tempHash, 4);
856                    Array.Copy(hash, 0, tempHash, 4, hash.Length);     
857               
858                    hash = hashProvider.ComputeHash(tempHash);
859                }
860
861                // Append "block" (0)
862                Array.Copy(hash, tempHash, hash.Length);
863                Array.Copy(System.BitConverter.GetBytes(0), 0, tempHash, hash.Length, 4);
864                hash = hashProvider.ComputeHash(tempHash);
865
866                /***** Now use the derived key algorithm *****/
867                byte[] derivedKey = new byte[64];
868                int keySizeBytes = encryptionInfo.Header.KeySize / 8;
869
870                //First XOR hash bytes with 0x36 and fill the rest with 0x36
871                for (int i = 0; i < derivedKey.Length; i++)
872                    derivedKey[i] = (byte)(i < hash.Length ? 0x36 ^ hash[i] : 0x36);
873               
874
875                byte[] X1 = hashProvider.ComputeHash(derivedKey);
876
877                //if verifier size is bigger than the key size we can return X1
878                if (encryptionInfo.Verifier.VerifierHashSize > keySizeBytes)
879                    return FixHashSize(X1,keySizeBytes);
880
881                //Else XOR hash bytes with 0x5C and fill the rest with 0x5C
882                for (int i = 0; i < derivedKey.Length; i++)
883                    derivedKey[i] = (byte)(i < hash.Length ? 0x5C ^ hash[i] : 0x5C);
884
885                byte[] X2 = hashProvider.ComputeHash(derivedKey);
886
887                //Join the two and return
888                byte[] join = new byte[X1.Length + X2.Length];
889
890                Array.Copy(X1, 0, join, 0, X1.Length);
891                Array.Copy(X2, 0, join, X1.Length, X2.Length);
892
893                return FixHashSize(join,keySizeBytes);
894            }
895            catch (Exception ex)
896            {
897                throw (new Exception("An error occured when the encryptionkey was created", ex));
898            }
899        }
900
901        private byte[] FixHashSize(byte[] hash, int size)
902        {
903            byte[] buff = new byte[size];
904            Array.Copy(hash, buff, size);
905            return buff;
906        }
907        private byte[] CombinePassword(byte[] salt, string password)
908        {
909            if (password == "")
910            {
911                password = "VelvetSweatshop";   //Used if Password is blank
912            }
913            // Convert password to unicode...
914            byte[] passwordBuf = UnicodeEncoding.Unicode.GetBytes(password);
915           
916            byte[] inputBuf = new byte[salt.Length + passwordBuf.Length];
917            Array.Copy(salt, inputBuf, salt.Length);
918            Array.Copy(passwordBuf, 0, inputBuf, salt.Length, passwordBuf.Length);           
919            return inputBuf;
920        }
921        internal static ushort CalculatePasswordHash(string Password)
922        {
923            //Calculate the hash
924            //Thanks to Kohei Yoshida for the sample http://kohei.us/2008/01/18/excel-sheet-protection-password-hash/
925            ushort hash = 0;
926            for (int i = Password.Length - 1; i >= 0; i--)
927            {
928                hash ^= Password[i];
929                hash = (ushort)(((ushort)((hash >> 14) & 0x01))
930                                |
931                                ((ushort)((hash << 1) & 0x7FFF)));  //Shift 1 to the left. Overflowing bit 15 goes into bit 0
932            }
933
934            hash ^= (0x8000 | ('N' << 8) | 'K'); //Xor NK with high bit set(0xCE4B)
935            hash ^= (ushort)Password.Length;
936
937            return hash;
938        }
939    }
940    [ComImport]
941    [Guid("0000000d-0000-0000-C000-000000000046")]
942    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
943    internal interface IEnumSTATSTG
944    {
945        // The user needs to allocate an STATSTG array whose size is celt.
946        [PreserveSig]
947        uint Next(
948            uint celt,
949            [MarshalAs(UnmanagedType.LPArray), Out]
950            System.Runtime.InteropServices.ComTypes.STATSTG[] rgelt,
951            out uint pceltFetched
952        );
953 
954        void Skip(uint celt);
955 
956        void Reset();
957 
958        [return: MarshalAs(UnmanagedType.Interface)]
959        IEnumSTATSTG Clone();
960    }
961 
962    [ComImport]
963    [Guid("0000000b-0000-0000-C000-000000000046")]
964    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
965    interface IStorage
966    {
967        void CreateStream(
968            /* [string][in] */ string pwcsName,
969            /* [in] */ uint grfMode,
970            /* [in] */ uint reserved1,
971            /* [in] */ uint reserved2,
972            /* [out] */ out comTypes.IStream ppstm);
973       
974        void OpenStream(
975            /* [string][in] */ string pwcsName,
976            /* [unique][in] */ IntPtr reserved1,
977            /* [in] */ uint grfMode,
978            /* [in] */ uint reserved2,
979            /* [out] */ out comTypes.IStream ppstm);
980 
981        void CreateStorage(
982            /* [string][in] */ string pwcsName,
983            /* [in] */ uint grfMode,
984            /* [in] */ uint reserved1,
985            /* [in] */ uint reserved2,
986            /* [out] */ out IStorage ppstg);
987 
988        void OpenStorage(
989            /* [string][unique][in] */ string pwcsName,
990            /* [unique][in] */ IStorage pstgPriority,
991            /* [in] */ uint grfMode,
992            /* [unique][in] */ IntPtr snbExclude,
993            /* [in] */ uint reserved,
994            /* [out] */ out IStorage ppstg);
995
996        void CopyTo(
997            [InAttribute] uint ciidExclude,
998            [InAttribute] Guid[] rgiidExclude,
999            [InAttribute] IntPtr snbExclude,
1000            [InAttribute] IStorage pstgDest
1001        );
1002 
1003        void MoveElementTo(
1004            /* [string][in] */ string pwcsName,
1005            /* [unique][in] */ IStorage pstgDest,
1006            /* [string][in] */ string pwcsNewName,
1007            /* [in] */ uint grfFlags);
1008 
1009        void Commit(
1010            /* [in] */ uint grfCommitFlags);
1011 
1012        void Revert();
1013 
1014        void EnumElements(
1015            /* [in] */ uint reserved1,
1016            /* [size_is][unique][in] */ IntPtr reserved2,
1017            /* [in] */ uint reserved3,
1018            /* [out] */ out IEnumSTATSTG ppenum);
1019 
1020        void DestroyElement(
1021            /* [string][in] */ string pwcsName);
1022 
1023        void RenameElement(
1024            /* [string][in] */ string pwcsOldName,
1025            /* [string][in] */ string pwcsNewName);
1026 
1027        void SetElementTimes(
1028            /* [string][unique][in] */ string pwcsName,
1029            /* [unique][in] */ System.Runtime.InteropServices.ComTypes.FILETIME pctime,
1030            /* [unique][in] */ System.Runtime.InteropServices.ComTypes.FILETIME patime,
1031            /* [unique][in] */ System.Runtime.InteropServices.ComTypes.FILETIME pmtime);
1032 
1033        void SetClass(
1034            /* [in] */ Guid clsid);
1035 
1036        void SetStateBits(
1037            /* [in] */ uint grfStateBits,
1038            /* [in] */ uint grfMask);
1039 
1040        void Stat(
1041            /* [out] */ out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg,
1042            /* [in] */ uint grfStatFlag);
1043 
1044    }
1045    [ComVisible(false)]
1046    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("0000000A-0000-0000-C000-000000000046")]
1047    internal interface ILockBytes
1048    {
1049        void ReadAt(long ulOffset, System.IntPtr pv, int cb, out UIntPtr pcbRead);
1050        void WriteAt(long ulOffset, System.IntPtr pv, int cb, out UIntPtr pcbWritten);
1051        void Flush();
1052        void SetSize(long cb);
1053        void LockRegion(long libOffset, long cb, int dwLockType);
1054        void UnlockRegion(long libOffset, long cb, int dwLockType);
1055        void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag);
1056    }
1057    [Flags]
1058    internal enum STGM : int
1059    {
1060        DIRECT = 0x00000000,
1061        TRANSACTED = 0x00010000,
1062        SIMPLE = 0x08000000,
1063        READ = 0x00000000,
1064        WRITE = 0x00000001,
1065        READWRITE = 0x00000002,
1066        SHARE_DENY_NONE = 0x00000040,
1067        SHARE_DENY_READ = 0x00000030,
1068        SHARE_DENY_WRITE = 0x00000020,
1069        SHARE_EXCLUSIVE = 0x00000010,
1070        PRIORITY = 0x00040000,
1071        DELETEONRELEASE = 0x04000000,
1072        NOSCRATCH = 0x00100000,
1073        CREATE = 0x00001000,
1074        CONVERT = 0x00020000,
1075        FAILIFTHERE = 0x00000000,
1076        NOSNAPSHOT = 0x00200000,
1077        DIRECT_SWMR = 0x00400000,
1078    }
1079 
1080    internal enum STATFLAG : uint
1081    {
1082        STATFLAG_DEFAULT = 0,
1083        STATFLAG_NONAME = 1,
1084        STATFLAG_NOOPEN = 2
1085    }
1086 
1087    internal enum STGTY : int
1088    {
1089        STGTY_STORAGE = 1,
1090        STGTY_STREAM = 2,
1091        STGTY_LOCKBYTES = 3,
1092        STGTY_PROPERTY = 4
1093    }
1094}
Note: See TracBrowser for help on using the repository browser.