| /******************************************************************************* |
| * You may amend and distribute as you like, but don't remove this header! |
| * |
| * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets. |
| * See http://www.codeplex.com/EPPlus for details. |
| * |
| * Copyright (C) 2011 Jan Källman |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| * See the GNU Lesser General Public License for more details. |
| * |
| * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php |
| * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html |
| * |
| * All code and executables are provided "as is" with no warranty either express or implied. |
| * The author accepts no liability for any damage or loss of business that this product may cause. |
| * |
| * Code change notes: |
| * |
| * Author Change Date |
| * ****************************************************************************** |
| * Jan Källman Added 2013-01-05 |
| *******************************************************************************/ |
| using OfficeOpenXml.Utils; |
| using System; |
| using System.Collections.Generic; |
| using System.IO; |
| using System.Linq; |
| using System.Runtime.InteropServices; |
| using System.Security; |
| using System.Security.Cryptography; |
| using System.Text; |
| using System.Xml; |
| using comTypes = System.Runtime.InteropServices.ComTypes; |
| |
| namespace OfficeOpenXml.Encryption |
| { |
| |
| /// <summary> |
| /// Handels encrypted Excel documents |
| /// </summary> |
| internal class EncryptedPackageHandler |
| { |
| #if !MONO |
| /// <summary> |
| /// Read the package from the OLE document and decrypt it using the supplied password |
| /// </summary> |
| /// <param name="fi">The file</param> |
| /// <param name="encryption"></param> |
| /// <returns></returns> |
| internal MemoryStream DecryptPackage(FileInfo fi, ExcelEncryption encryption) |
| { |
| CompoundDocument doc = new CompoundDocument(fi); |
| |
| MemoryStream ret = null; |
| if (CompoundDocument.IsStorageFile(fi.FullName) == 0) |
| { |
| ret = GetStreamFromPackage(doc, encryption); |
| } |
| else |
| { |
| throw (new InvalidDataException(string.Format("File {0} is not an encrypted package", fi.FullName))); |
| } |
| return ret; |
| } |
| |
| //Helpmethod to output the streams in the storage |
| //private void WriteDoc(CompoundDocument.StoragePart storagePart, string p) |
| //{ |
| // foreach (var store in storagePart.SubStorage) |
| // { |
| // string sdir=p + store.Key.Replace((char)6,'x') + "\\"; |
| // Directory.CreateDirectory(sdir); |
| // WriteDoc(store.Value, sdir); |
| // } |
| // foreach (var str in storagePart.DataStreams) |
| // { |
| // File.WriteAllBytes(p + str.Key.Replace((char)6, 'x') + ".bin", str.Value); |
| // } |
| //} |
| /// <summary> |
| /// Read the package from the OLE document and decrypt it using the supplied password |
| /// </summary> |
| /// <param name="stream">The memory stream. </param> |
| /// <param name="encryption">The encryption object from the Package</param> |
| /// <returns></returns> |
| [SecuritySafeCritical] |
| internal MemoryStream DecryptPackage(MemoryStream stream, ExcelEncryption encryption) |
| { |
| //Create the lockBytes object. |
| CompoundDocument.ILockBytes lb=null; |
| try |
| { |
| lb = CompoundDocument.GetLockbyte(stream); |
| |
| if (CompoundDocument.IsStorageILockBytes(lb) == 0) |
| { |
| var doc = new CompoundDocument(lb); |
| return GetStreamFromPackage(doc, encryption); |
| } |
| else |
| { |
| Marshal.ReleaseComObject(lb); |
| throw (new InvalidDataException("The stream is not an valid/supported encrypted document.")); |
| } |
| } |
| catch// (Exception ex) |
| { |
| throw; |
| } |
| finally |
| { |
| Marshal.ReleaseComObject(lb); |
| lb = null; |
| } |
| |
| } |
| /// <summary> |
| /// Encrypts a package |
| /// </summary> |
| /// <param name="package">The package as a byte array</param> |
| /// <param name="encryption">The encryption info from the workbook</param> |
| /// <returns></returns> |
| internal MemoryStream EncryptPackage(byte[] package, ExcelEncryption encryption) |
| { |
| if (encryption.Version == EncryptionVersion.Standard) //Standard encryption |
| { |
| return EncryptPackageBinary(package, encryption); |
| } |
| else if (encryption.Version == EncryptionVersion.Agile) //Agile encryption |
| { |
| return EncryptPackageAgile(package, encryption); |
| } |
| throw(new ArgumentException("Unsupported encryption version.")); |
| } |
| |
| private MemoryStream EncryptPackageAgile(byte[] package, ExcelEncryption encryption) |
| { |
| var xml= "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n"; |
| 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\">"; |
| xml += "<keyData saltSize=\"16\" blockSize=\"16\" keyBits=\"256\" hashSize=\"64\" cipherAlgorithm=\"AES\" cipherChaining=\"ChainingModeCBC\" hashAlgorithm=\"SHA512\" saltValue=\"\"/>"; |
| xml += "<dataIntegrity encryptedHmacKey=\"\" encryptedHmacValue=\"\"/>"; |
| xml += "<keyEncryptors>"; |
| xml += "<keyEncryptor uri=\"http://schemas.microsoft.com/office/2006/keyEncryptor/password\">"; |
| xml += "<p:encryptedKey spinCount=\"100000\" saltSize=\"16\" blockSize=\"16\" keyBits=\"256\" hashSize=\"64\" cipherAlgorithm=\"AES\" cipherChaining=\"ChainingModeCBC\" hashAlgorithm=\"SHA512\" saltValue=\"\" encryptedVerifierHashInput=\"\" encryptedVerifierHashValue=\"\" encryptedKeyValue=\"\" />"; |
| xml += "</keyEncryptor></keyEncryptors></encryption>"; |
| |
| var encryptionInfo = new EncryptionInfoAgile(); |
| encryptionInfo.ReadFromXml(xml); |
| var encr = encryptionInfo.KeyEncryptors[0]; |
| var rnd = RandomNumberGenerator.Create(); |
| |
| var s = new byte[16]; |
| rnd.GetBytes(s); |
| encryptionInfo.KeyData.SaltValue = s; |
| |
| rnd.GetBytes(s); |
| encr.SaltValue = s; |
| |
| encr.KeyValue = new byte[encr.KeyBits / 8]; |
| rnd.GetBytes(encr.KeyValue); |
| |
| //Get the passwork key. |
| var hashProvider = GetHashProvider(encryptionInfo.KeyEncryptors[0]); |
| var baseHash = GetPasswordHash(hashProvider, encr.SaltValue, encryption.Password, encr.SpinCount, encr.HashSize); |
| var hashFinal = GetFinalHash(hashProvider, encr, BlockKey_KeyValue, baseHash); |
| hashFinal = FixHashSize(hashFinal, encr.KeyBits / 8); |
| |
| var encrData = EncryptDataAgile(package, encryptionInfo, hashProvider); |
| |
| /**** Data Integrity ****/ |
| var saltHMAC=new byte[64]; |
| rnd.GetBytes(saltHMAC); |
| |
| SetHMAC(encryptionInfo,hashProvider,saltHMAC, encrData); |
| |
| /**** Verifier ****/ |
| encr.VerifierHashInput = new byte[16]; |
| rnd.GetBytes(encr.VerifierHashInput); |
| |
| encr.VerifierHash = hashProvider.ComputeHash(encr.VerifierHashInput); |
| |
| var VerifierInputKey = GetFinalHash(hashProvider, encr, BlockKey_HashInput, baseHash); |
| var VerifierHashKey = GetFinalHash(hashProvider, encr, BlockKey_HashValue, baseHash); |
| var KeyValueKey = GetFinalHash(hashProvider, encr, BlockKey_KeyValue, baseHash); |
| |
| var ms = new MemoryStream(); |
| EncryptAgileFromKey(encr, VerifierInputKey, encr.VerifierHashInput, 0, encr.VerifierHashInput.Length, encr.SaltValue, ms); |
| encr.EncryptedVerifierHashInput = ms.ToArray(); |
| |
| ms = new MemoryStream(); |
| EncryptAgileFromKey(encr, VerifierHashKey, encr.VerifierHash, 0, encr.VerifierHash.Length, encr.SaltValue, ms); |
| encr.EncryptedVerifierHash = ms.ToArray(); |
| |
| ms = new MemoryStream(); |
| EncryptAgileFromKey(encr, KeyValueKey, encr.KeyValue, 0, encr.KeyValue.Length, encr.SaltValue, ms); |
| encr.EncryptedKeyValue = ms.ToArray(); |
| |
| xml = encryptionInfo.Xml.OuterXml; |
| |
| var byXml = Encoding.UTF8.GetBytes(xml); |
| |
| ms = new MemoryStream(); |
| ms.Write(BitConverter.GetBytes((ushort)4), 0, 2); //Major Version |
| ms.Write(BitConverter.GetBytes((ushort)4), 0, 2); //Minor Version |
| ms.Write(BitConverter.GetBytes((uint)0x40), 0, 4); //Reserved |
| ms.Write(byXml,0,byXml.Length); |
| |
| var doc = new CompoundDocument(); |
| |
| //Add the dataspace streams |
| CreateDataSpaces(doc); |
| //EncryptionInfo... |
| doc.Storage.DataStreams.Add("EncryptionInfo", ms.ToArray()); |
| //...and the encrypted package |
| doc.Storage.DataStreams.Add("EncryptedPackage", encrData); |
| |
| ms = new MemoryStream(); |
| var e=doc.Save(); |
| ms.Write(e,0,e.Length); |
| return ms; |
| } |
| |
| private byte[] EncryptDataAgile(byte[] data, EncryptionInfoAgile encryptionInfo, HashAlgorithm hashProvider) |
| { |
| var ke = encryptionInfo.KeyEncryptors[0]; |
| RijndaelManaged aes = new RijndaelManaged(); |
| aes.KeySize = ke.KeyBits; |
| aes.Mode = CipherMode.CBC; |
| aes.Padding = PaddingMode.Zeros; |
| |
| int pos=0; |
| int segment=0; |
| |
| //Encrypt the data |
| var ms = new MemoryStream(); |
| ms.Write(BitConverter.GetBytes(data.LongLength), 0, 8); |
| while (pos < data.Length) |
| { |
| var segmentSize = (int)(data.Length - pos > 4096 ? 4096 : data.Length - pos); |
| |
| var ivTmp = new byte[4 + encryptionInfo.KeyData.SaltSize]; |
| Array.Copy(encryptionInfo.KeyData.SaltValue, 0, ivTmp, 0, encryptionInfo.KeyData.SaltSize); |
| Array.Copy(BitConverter.GetBytes(segment), 0, ivTmp, encryptionInfo.KeyData.SaltSize, 4); |
| var iv=hashProvider.ComputeHash(ivTmp); |
| |
| EncryptAgileFromKey(ke, ke.KeyValue, data, pos, segmentSize, iv, ms); |
| pos += segmentSize; |
| segment++; |
| } |
| ms.Flush(); |
| return ms.ToArray(); |
| } |
| // Set the dataintegrity |
| private void SetHMAC(EncryptionInfoAgile ei, HashAlgorithm hashProvider, byte[] salt, byte[] data) |
| { |
| var iv = GetFinalHash(hashProvider, ei.KeyEncryptors[0], BlockKey_HmacKey, ei.KeyData.SaltValue); |
| var ms = new MemoryStream(); |
| EncryptAgileFromKey(ei.KeyEncryptors[0], ei.KeyEncryptors[0].KeyValue, salt, 0L, salt.LongLength, iv, ms); |
| ei.DataIntegrity.EncryptedHmacKey = ms.ToArray(); |
| |
| var h = GetHmacProvider(ei.KeyEncryptors[0], salt); |
| var hmacValue = h.ComputeHash(data); |
| |
| ms = new MemoryStream(); |
| iv = GetFinalHash(hashProvider, ei.KeyEncryptors[0], BlockKey_HmacValue, ei.KeyData.SaltValue); |
| EncryptAgileFromKey(ei.KeyEncryptors[0], ei.KeyEncryptors[0].KeyValue, hmacValue, 0L, hmacValue.LongLength, iv, ms); |
| ei.DataIntegrity.EncryptedHmacValue = ms.ToArray(); |
| } |
| |
| private HMAC GetHmacProvider(EncryptionInfoAgile.EncryptionKeyEncryptor ei, byte[] salt) |
| { |
| switch (ei.HashAlgorithm) |
| { |
| case eHashAlogorithm.MD5: |
| return new HMACMD5(salt); |
| case eHashAlogorithm.SHA1: |
| return new HMACSHA1(salt); |
| case eHashAlogorithm.SHA256: |
| return new HMACSHA256(salt); |
| case eHashAlogorithm.SHA384: |
| return new HMACSHA384(salt); |
| case eHashAlogorithm.SHA512: |
| return new HMACSHA512(salt); |
| default: |
| throw(new NotSupportedException(string.Format("Hash method {0} not supported.",ei.HashAlgorithm))); |
| } |
| } |
| |
| private MemoryStream EncryptPackageBinary(byte[] package, ExcelEncryption encryption) |
| { |
| byte[] encryptionKey; |
| //Create the Encryption Info. This also returns the Encryptionkey |
| var encryptionInfo = CreateEncryptionInfo(encryption.Password, |
| encryption.Algorithm == EncryptionAlgorithm.AES128 ? |
| AlgorithmID.AES128 : |
| encryption.Algorithm == EncryptionAlgorithm.AES192 ? |
| AlgorithmID.AES192 : |
| AlgorithmID.AES256, out encryptionKey); |
| |
| //ILockBytes lb; |
| //var iret = CreateILockBytesOnHGlobal(IntPtr.Zero, true, out lb); |
| |
| //IStorage storage = null; |
| //MemoryStream ret = null; |
| |
| var doc = new CompoundDocument(); |
| CreateDataSpaces(doc); |
| |
| doc.Storage.DataStreams.Add("EncryptionInfo", encryptionInfo.WriteBinary()); |
| |
| //Encrypt the package |
| byte[] encryptedPackage = EncryptData(encryptionKey, package, false); |
| MemoryStream ms = new MemoryStream(); |
| ms.Write(BitConverter.GetBytes((ulong)package.LongLength), 0, 8); |
| ms.Write(encryptedPackage, 0, encryptedPackage.Length); |
| doc.Storage.DataStreams.Add("EncryptedPackage", ms.ToArray()); |
| |
| var ret = new MemoryStream(); |
| var buffer = doc.Save(); |
| ret.Write(buffer, 0, buffer.Length); |
| |
| return ret; |
| } |
| #region "Dataspaces Stream methods" |
| private void CreateDataSpaces(CompoundDocument doc) |
| { |
| var ds = new CompoundDocument.StoragePart(); |
| doc.Storage.SubStorage.Add("\x06" + "DataSpaces", ds); |
| var ver=new CompoundDocument.StoragePart(); |
| ds.DataStreams.Add("Version", CreateVersionStream()); |
| ds.DataStreams.Add("DataSpaceMap", CreateDataSpaceMap()); |
| |
| var dsInfo=new CompoundDocument.StoragePart(); |
| ds.SubStorage.Add("DataSpaceInfo", dsInfo); |
| dsInfo.DataStreams.Add("StrongEncryptionDataSpace", CreateStrongEncryptionDataSpaceStream()); |
| |
| var transInfo=new CompoundDocument.StoragePart(); |
| ds.SubStorage.Add("TransformInfo", transInfo); |
| |
| var strEncTrans=new CompoundDocument.StoragePart(); |
| transInfo.SubStorage.Add("StrongEncryptionTransform", strEncTrans); |
| |
| strEncTrans.DataStreams.Add("\x06Primary", CreateTransformInfoPrimary()); |
| } |
| private byte[] CreateStrongEncryptionDataSpaceStream() |
| { |
| MemoryStream ms = new MemoryStream(); |
| BinaryWriter bw = new BinaryWriter(ms); |
| |
| bw.Write((int)8); //HeaderLength |
| bw.Write((int)1); //EntryCount |
| |
| string tr = "StrongEncryptionTransform"; |
| bw.Write((int)tr.Length*2); |
| bw.Write(UTF8Encoding.Unicode.GetBytes(tr + "\0")); // end \0 is for padding |
| |
| bw.Flush(); |
| return ms.ToArray(); |
| } |
| private byte[] CreateVersionStream() |
| { |
| MemoryStream ms = new MemoryStream(); |
| BinaryWriter bw = new BinaryWriter(ms); |
| |
| bw.Write((short)0x3C); //Major |
| bw.Write((short)0); //Minor |
| bw.Write(UTF8Encoding.Unicode.GetBytes("Microsoft.Container.DataSpaces")); |
| bw.Write((int)1); //ReaderVersion |
| bw.Write((int)1); //UpdaterVersion |
| bw.Write((int)1); //WriterVersion |
| |
| bw.Flush(); |
| return ms.ToArray(); |
| } |
| private byte[] CreateDataSpaceMap() |
| { |
| MemoryStream ms = new MemoryStream(); |
| BinaryWriter bw = new BinaryWriter(ms); |
| |
| bw.Write((int)8); //HeaderLength |
| bw.Write((int)1); //EntryCount |
| string s1 = "EncryptedPackage"; |
| string s2 = "StrongEncryptionDataSpace"; |
| bw.Write((int)(s1.Length + s2.Length)*2 + 0x16); |
| bw.Write((int)1); //ReferenceComponentCount |
| bw.Write((int)0); //Stream=0 |
| bw.Write((int)s1.Length * 2); //Length s1 |
| bw.Write(UTF8Encoding.Unicode.GetBytes(s1)); |
| bw.Write((int)(s2.Length * 2)); //Length s2 |
| bw.Write(UTF8Encoding.Unicode.GetBytes(s2 + "\0")); // end \0 is for padding |
| |
| bw.Flush(); |
| return ms.ToArray(); |
| } |
| private byte[] CreateTransformInfoPrimary() |
| { |
| MemoryStream ms = new MemoryStream(); |
| BinaryWriter bw = new BinaryWriter(ms); |
| string TransformID = "{FF9A3F03-56EF-4613-BDD5-5A41C1D07246}"; |
| string TransformName = "Microsoft.Container.EncryptionTransform"; |
| bw.Write(TransformID.Length * 2 + 12); |
| bw.Write((int)1); |
| bw.Write(TransformID.Length * 2); |
| bw.Write(UTF8Encoding.Unicode.GetBytes(TransformID)); |
| bw.Write(TransformName.Length * 2); |
| bw.Write(UTF8Encoding.Unicode.GetBytes(TransformName + "\0")); |
| bw.Write((int)1); //ReaderVersion |
| bw.Write((int)1); //UpdaterVersion |
| bw.Write((int)1); //WriterVersion |
| |
| bw.Write((int)0); |
| bw.Write((int)0); |
| bw.Write((int)0); //CipherMode |
| bw.Write((int)4); //Reserved |
| |
| bw.Flush(); |
| return ms.ToArray(); |
| } |
| #endregion |
| /// <summary> |
| /// Create an EncryptionInfo object to encrypt a workbook |
| /// </summary> |
| /// <param name="password">The password</param> |
| /// <param name="algID"></param> |
| /// <param name="key">The Encryption key</param> |
| /// <returns></returns> |
| private EncryptionInfoBinary CreateEncryptionInfo(string password, AlgorithmID algID, out byte[] key) |
| { |
| if (algID == AlgorithmID.Flags || algID == AlgorithmID.RC4) |
| { |
| throw (new ArgumentException("algID must be AES128, AES192 or AES256")); |
| } |
| var encryptionInfo = new EncryptionInfoBinary(); |
| encryptionInfo.MajorVersion = 4; |
| encryptionInfo.MinorVersion = 2; |
| encryptionInfo.Flags = Flags.fAES | Flags.fCryptoAPI; |
| |
| //Header |
| encryptionInfo.Header = new EncryptionHeader(); |
| encryptionInfo.Header.AlgID = algID; |
| encryptionInfo.Header.AlgIDHash = AlgorithmHashID.SHA1; |
| encryptionInfo.Header.Flags = encryptionInfo.Flags; |
| encryptionInfo.Header.KeySize = |
| (algID == AlgorithmID.AES128 ? 0x80 : algID == AlgorithmID.AES192 ? 0xC0 : 0x100); |
| encryptionInfo.Header.ProviderType = ProviderType.AES; |
| encryptionInfo.Header.CSPName = "Microsoft Enhanced RSA and AES Cryptographic Provider\0"; |
| encryptionInfo.Header.Reserved1 = 0; |
| encryptionInfo.Header.Reserved2 = 0; |
| encryptionInfo.Header.SizeExtra = 0; |
| |
| //Verifier |
| encryptionInfo.Verifier = new EncryptionVerifier(); |
| encryptionInfo.Verifier.Salt = new byte[16]; |
| |
| var rnd = RandomNumberGenerator.Create(); |
| rnd.GetBytes(encryptionInfo.Verifier.Salt); |
| encryptionInfo.Verifier.SaltSize = 0x10; |
| |
| key = GetPasswordHashBinary(password, encryptionInfo); |
| |
| var verifier = new byte[16]; |
| rnd.GetBytes(verifier); |
| encryptionInfo.Verifier.EncryptedVerifier = EncryptData(key, verifier, true); |
| |
| //AES = 32 Bits |
| encryptionInfo.Verifier.VerifierHashSize = 0x20; |
| SHA1 sha = new SHA1Managed(); |
| var verifierHash = sha.ComputeHash(verifier); |
| |
| encryptionInfo.Verifier.EncryptedVerifierHash = EncryptData(key, verifierHash, false); |
| |
| return encryptionInfo; |
| } |
| private byte[] EncryptData(byte[] key, byte[] data, bool useDataSize) |
| { |
| RijndaelManaged aes = new RijndaelManaged(); |
| aes.KeySize = key.Length * 8; |
| aes.Mode = CipherMode.ECB; |
| aes.Padding = PaddingMode.Zeros; |
| |
| //Encrypt the data |
| var crypt = aes.CreateEncryptor(key, null); |
| var ms = new MemoryStream(); |
| var cs = new CryptoStream(ms, crypt, CryptoStreamMode.Write); |
| cs.Write(data, 0, data.Length); |
| |
| cs.FlushFinalBlock(); |
| |
| byte[] ret; |
| if (useDataSize) |
| { |
| ret = new byte[data.Length]; |
| ms.Seek(0, SeekOrigin.Begin); |
| ms.Read(ret, 0, data.Length); //Truncate any padded Zeros |
| return ret; |
| } |
| else |
| { |
| return ms.ToArray(); |
| } |
| } |
| private MemoryStream GetStreamFromPackage(CompoundDocument doc, ExcelEncryption encryption) |
| { |
| var ret = new MemoryStream(); |
| if(doc.Storage.DataStreams.ContainsKey("EncryptionInfo") || |
| doc.Storage.DataStreams.ContainsKey("EncryptedPackage")) |
| { |
| var encryptionInfo = EncryptionInfo.ReadBinary(doc.Storage.DataStreams["EncryptionInfo"]); |
| |
| return DecryptDocument(doc.Storage.DataStreams["EncryptedPackage"], encryptionInfo, encryption.Password); |
| } |
| else |
| { |
| throw (new InvalidDataException("Invalid document. EncryptionInfo or EncryptedPackage stream is missing")); |
| } |
| } |
| |
| /// <summary> |
| /// Decrypt a document |
| /// </summary> |
| /// <param name="data">The Encrypted data</param> |
| /// <param name="encryptionInfo">Encryption Info object</param> |
| /// <param name="password">The password</param> |
| /// <returns></returns> |
| private MemoryStream DecryptDocument(byte[] data, EncryptionInfo encryptionInfo, string password) |
| { |
| long size = BitConverter.ToInt64(data, 0); |
| |
| var encryptedData = new byte[data.Length - 8]; |
| Array.Copy(data, 8, encryptedData, 0, encryptedData.Length); |
| |
| if (encryptionInfo is EncryptionInfoBinary) |
| { |
| return DecryptBinary((EncryptionInfoBinary)encryptionInfo, password, size, encryptedData); |
| } |
| else |
| { |
| return DecryptAgile((EncryptionInfoAgile)encryptionInfo, password, size, encryptedData, data); |
| } |
| |
| } |
| |
| readonly byte[] BlockKey_HashInput = new byte[] { 0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79 }; |
| readonly byte[] BlockKey_HashValue = new byte[] { 0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e }; |
| readonly byte[] BlockKey_KeyValue = new byte[] { 0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6 }; |
| readonly byte[] BlockKey_HmacKey = new byte[] { 0x5f, 0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, 0xf6 };//MSOFFCRYPTO 2.3.4.14 section 3 |
| readonly byte[] BlockKey_HmacValue = new byte[] { 0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, 0x33 };//MSOFFCRYPTO 2.3.4.14 section 5 |
| |
| private MemoryStream DecryptAgile(EncryptionInfoAgile encryptionInfo, string password, long size, byte[] encryptedData, byte[] data) |
| { |
| MemoryStream doc = new MemoryStream(); |
| |
| if (encryptionInfo.KeyData.CipherAlgorithm == eCipherAlgorithm.AES) |
| { |
| var encr = encryptionInfo.KeyEncryptors[0]; |
| var hashProvider = GetHashProvider(encr); |
| var baseHash = GetPasswordHash(hashProvider, encr.SaltValue, password, encr.SpinCount, encr.HashSize); |
| |
| //Get the keys for the verifiers and the key value |
| var valInputKey = GetFinalHash(hashProvider, encr, BlockKey_HashInput, baseHash); |
| var valHashKey = GetFinalHash(hashProvider, encr, BlockKey_HashValue, baseHash); |
| var valKeySizeKey = GetFinalHash(hashProvider, encr, BlockKey_KeyValue, baseHash); |
| |
| //Decrypt |
| encr.VerifierHashInput = DecryptAgileFromKey(encr, valInputKey, encr.EncryptedVerifierHashInput, encr.SaltSize, encr.SaltValue); |
| encr.VerifierHash = DecryptAgileFromKey(encr, valHashKey, encr.EncryptedVerifierHash, encr.HashSize, encr.SaltValue); |
| encr.KeyValue = DecryptAgileFromKey(encr, valKeySizeKey, encr.EncryptedKeyValue, encr.KeyBits / 8, encr.SaltValue); |
| |
| if (IsPasswordValid(hashProvider, encr)) |
| { |
| var ivhmac = GetFinalHash(hashProvider, encr, BlockKey_HmacKey, encryptionInfo.KeyData.SaltValue); |
| var key = DecryptAgileFromKey(encr, encr.KeyValue, encryptionInfo.DataIntegrity.EncryptedHmacKey, encryptionInfo.KeyData.HashSize, ivhmac); |
| |
| ivhmac = GetFinalHash(hashProvider, encr, BlockKey_HmacValue, encryptionInfo.KeyData.SaltValue); |
| var value = DecryptAgileFromKey(encr, encr.KeyValue, encryptionInfo.DataIntegrity.EncryptedHmacValue, encryptionInfo.KeyData.HashSize, ivhmac); |
| |
| var hmca = GetHmacProvider(encr, key); |
| var v2 = hmca.ComputeHash(data); |
| |
| for (int i = 0; i < v2.Length; i++) |
| { |
| if (value[i] != v2[i]) |
| { |
| throw (new Exception("Dataintegrity key missmatch")); |
| } |
| } |
| |
| int pos = 0; |
| uint segment = 0; |
| while (pos < size) |
| { |
| var segmentSize = (int)(size - pos > 4096 ? 4096 : size - pos); |
| var bufferSize = (int)(encryptedData.Length - pos > 4096 ? 4096 : encryptedData.Length - pos); |
| var ivTmp = new byte[4 + encryptionInfo.KeyData.SaltSize]; |
| Array.Copy(encryptionInfo.KeyData.SaltValue, 0, ivTmp, 0, encryptionInfo.KeyData.SaltSize); |
| Array.Copy(BitConverter.GetBytes(segment), 0, ivTmp, encryptionInfo.KeyData.SaltSize, 4); |
| var iv = hashProvider.ComputeHash(ivTmp); |
| var buffer = new byte[bufferSize]; |
| Array.Copy(encryptedData, pos, buffer, 0, bufferSize); |
| |
| var b = DecryptAgileFromKey(encr, encr.KeyValue, buffer, segmentSize, iv); |
| doc.Write(b, 0, b.Length); |
| pos += segmentSize; |
| segment++; |
| } |
| doc.Flush(); |
| return doc; |
| } |
| else |
| { |
| throw (new SecurityException("Invalid password")); |
| } |
| } |
| return null; |
| } |
| private HashAlgorithm GetHashProvider(EncryptionInfoAgile.EncryptionKeyEncryptor encr) |
| { |
| switch (encr.HashAlgorithm) |
| { |
| case eHashAlogorithm.MD5: |
| return new MD5CryptoServiceProvider(); |
| case eHashAlogorithm.SHA1: |
| return new SHA1CryptoServiceProvider(); |
| case eHashAlogorithm.SHA256: |
| return new SHA256CryptoServiceProvider(); |
| case eHashAlogorithm.SHA384: |
| return new SHA384CryptoServiceProvider(); |
| case eHashAlogorithm.SHA512: |
| return new SHA512CryptoServiceProvider(); |
| default: |
| throw new NotSupportedException(string.Format("Hash provider is unsupported. {0}", encr.HashAlgorithm)); |
| } |
| } |
| private MemoryStream DecryptBinary(EncryptionInfoBinary encryptionInfo, string password, long size, byte[] encryptedData) |
| { |
| MemoryStream doc = new MemoryStream(); |
| |
| if (encryptionInfo.Header.AlgID == AlgorithmID.AES128 || (encryptionInfo.Header.AlgID == AlgorithmID.Flags && ((encryptionInfo.Flags & (Flags.fAES | Flags.fExternal | Flags.fCryptoAPI)) == (Flags.fAES | Flags.fCryptoAPI))) |
| || |
| encryptionInfo.Header.AlgID == AlgorithmID.AES192 |
| || |
| encryptionInfo.Header.AlgID == AlgorithmID.AES256 |
| ) |
| { |
| RijndaelManaged decryptKey = new RijndaelManaged(); |
| decryptKey.KeySize = encryptionInfo.Header.KeySize; |
| decryptKey.Mode = CipherMode.ECB; |
| decryptKey.Padding = PaddingMode.None; |
| |
| var key = GetPasswordHashBinary(password, encryptionInfo); |
| if (IsPasswordValid(key, encryptionInfo)) |
| { |
| ICryptoTransform decryptor = decryptKey.CreateDecryptor( |
| key, |
| null); |
| |
| var dataStream = new MemoryStream(encryptedData); |
| var cryptoStream = new CryptoStream(dataStream, |
| decryptor, |
| CryptoStreamMode.Read); |
| |
| var decryptedData = new byte[size]; |
| |
| cryptoStream.Read(decryptedData, 0, (int)size); |
| doc.Write(decryptedData, 0, (int)size); |
| } |
| else |
| { |
| throw (new UnauthorizedAccessException("Invalid password")); |
| } |
| } |
| return doc; |
| } |
| /// <summary> |
| /// Validate the password |
| /// </summary> |
| /// <param name="key">The encryption key</param> |
| /// <param name="encryptionInfo">The encryption info extracted from the ENCRYPTIOINFO stream inside the OLE document</param> |
| /// <returns></returns> |
| private bool IsPasswordValid(byte[] key, EncryptionInfoBinary encryptionInfo) |
| { |
| RijndaelManaged decryptKey = new RijndaelManaged(); |
| decryptKey.KeySize = encryptionInfo.Header.KeySize; |
| decryptKey.Mode = CipherMode.ECB; |
| decryptKey.Padding = PaddingMode.None; |
| |
| ICryptoTransform decryptor = decryptKey.CreateDecryptor( |
| key, |
| null); |
| |
| |
| //Decrypt the verifier |
| MemoryStream dataStream = new MemoryStream(encryptionInfo.Verifier.EncryptedVerifier); |
| CryptoStream cryptoStream = new CryptoStream(dataStream, |
| decryptor, |
| CryptoStreamMode.Read); |
| var decryptedVerifier = new byte[16]; |
| cryptoStream.Read(decryptedVerifier, 0, 16); |
| |
| dataStream = new MemoryStream(encryptionInfo.Verifier.EncryptedVerifierHash); |
| |
| cryptoStream = new CryptoStream(dataStream, |
| decryptor, |
| CryptoStreamMode.Read); |
| |
| //Decrypt the verifier hash |
| var decryptedVerifierHash = new byte[16]; |
| cryptoStream.Read(decryptedVerifierHash, 0, (int)16); |
| |
| //Get the hash for the decrypted verifier |
| var sha = new SHA1Managed(); |
| var hash = sha.ComputeHash(decryptedVerifier); |
| |
| //Equal? |
| for (int i = 0; i < 16; i++) |
| { |
| if (hash[i] != decryptedVerifierHash[i]) |
| { |
| return false; |
| } |
| } |
| return true; |
| } |
| /// <summary> |
| /// Validate the password |
| /// </summary> |
| /// <param name="sha">The hash algorithm</param> |
| /// <param name="encr">The encryption info extracted from the ENCRYPTIOINFO stream inside the OLE document</param> |
| /// <returns></returns> |
| private bool IsPasswordValid(HashAlgorithm sha, EncryptionInfoAgile.EncryptionKeyEncryptor encr) |
| { |
| var valHash = sha.ComputeHash(encr.VerifierHashInput); |
| |
| //Equal? |
| for (int i = 0; i < valHash.Length; i++) |
| { |
| if (encr.VerifierHash[i] != valHash[i]) |
| { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private byte[] DecryptAgileFromKey(EncryptionInfoAgile.EncryptionKeyEncryptor encr, byte[] key, byte[] encryptedData, long size, byte[] iv) |
| { |
| SymmetricAlgorithm decryptKey = GetEncryptionAlgorithm(encr); |
| decryptKey.BlockSize = encr.BlockSize << 3; |
| decryptKey.KeySize = encr.KeyBits; |
| decryptKey.Mode = encr.ChiptherChaining == eChainingMode.ChainingModeCBC ? CipherMode.CBC : CipherMode.CFB; |
| decryptKey.Padding = PaddingMode.Zeros; |
| |
| ICryptoTransform decryptor = decryptKey.CreateDecryptor( |
| FixHashSize(key, encr.KeyBits / 8), |
| FixHashSize(iv, encr.BlockSize, 0x36)); |
| |
| |
| MemoryStream dataStream = new MemoryStream(encryptedData); |
| |
| CryptoStream cryptoStream = new CryptoStream(dataStream, |
| decryptor, |
| CryptoStreamMode.Read); |
| |
| var decryptedData = new byte[size]; |
| |
| cryptoStream.Read(decryptedData, 0, (int)size); |
| return decryptedData; |
| } |
| |
| private SymmetricAlgorithm GetEncryptionAlgorithm(EncryptionInfoAgile.EncryptionKeyEncryptor encr) |
| { |
| switch (encr.CipherAlgorithm) |
| { |
| case eCipherAlgorithm.AES: |
| return new RijndaelManaged(); |
| case eCipherAlgorithm.DES: |
| return new DESCryptoServiceProvider(); |
| case eCipherAlgorithm.TRIPLE_DES: |
| case eCipherAlgorithm.TRIPLE_DES_112: |
| return new TripleDESCryptoServiceProvider(); |
| case eCipherAlgorithm.RC2: |
| return new RC2CryptoServiceProvider(); |
| default: |
| throw(new NotSupportedException(string.Format("Unsupported Cipher Algorithm: {0}", encr.CipherAlgorithm.ToString()))); |
| } |
| } |
| private void EncryptAgileFromKey(EncryptionInfoAgile.EncryptionKeyEncryptor encr, byte[] key, byte[] data, long pos, long size, byte[] iv,MemoryStream ms) |
| { |
| var encryptKey = GetEncryptionAlgorithm(encr); |
| encryptKey.BlockSize = encr.BlockSize << 3; |
| encryptKey.KeySize = encr.KeyBits; |
| encryptKey.Mode = encr.ChiptherChaining==eChainingMode.ChainingModeCBC ? CipherMode.CBC : CipherMode.CFB; |
| encryptKey.Padding = PaddingMode.Zeros; |
| |
| ICryptoTransform encryptor = encryptKey.CreateEncryptor( |
| FixHashSize(key, encr.KeyBits / 8), |
| FixHashSize(iv, 16, 0x36)); |
| |
| |
| CryptoStream cryptoStream = new CryptoStream(ms, |
| encryptor, |
| CryptoStreamMode.Write); |
| |
| var cryptoSize = size % encr.BlockSize == 0 ? size : (size + (encr.BlockSize - (size % encr.BlockSize))); |
| var buffer = new byte[size]; |
| Array.Copy(data, pos, buffer, 0, size); |
| cryptoStream.Write(buffer, 0, (int)size); |
| while (size % encr.BlockSize != 0) |
| { |
| cryptoStream.WriteByte(0); |
| size++; |
| } |
| } |
| |
| /// <summary> |
| /// Create the hash. |
| /// This method is written with the help of Lyquidity library, many thanks for this nice sample |
| /// </summary> |
| /// <param name="password">The password</param> |
| /// <param name="encryptionInfo">The encryption info extracted from the ENCRYPTIOINFO stream inside the OLE document</param> |
| /// <returns>The hash to encrypt the document</returns> |
| private byte[] GetPasswordHashBinary(string password, EncryptionInfoBinary encryptionInfo) |
| { |
| byte[] hash = null; |
| byte[] tempHash = new byte[4 + 20]; //Iterator + prev. hash |
| try |
| { |
| HashAlgorithm hashProvider; |
| if (encryptionInfo.Header.AlgIDHash == AlgorithmHashID.SHA1 || encryptionInfo.Header.AlgIDHash == AlgorithmHashID.App && (encryptionInfo.Flags & Flags.fExternal) == 0) |
| { |
| hashProvider = new SHA1CryptoServiceProvider(); |
| } |
| else if (encryptionInfo.Header.KeySize > 0 && encryptionInfo.Header.KeySize < 80) |
| { |
| throw new NotSupportedException("RC4 Hash provider is not supported. Must be SHA1(AlgIDHash == 0x8004)"); |
| } |
| else |
| { |
| throw new NotSupportedException("Hash provider is invalid. Must be SHA1(AlgIDHash == 0x8004)"); |
| } |
| |
| hash = GetPasswordHash(hashProvider, encryptionInfo.Verifier.Salt, password, 50000, 20); |
| |
| // Append "block" (0) |
| Array.Copy(hash, tempHash, hash.Length); |
| Array.Copy(System.BitConverter.GetBytes(0), 0, tempHash, hash.Length, 4); |
| hash = hashProvider.ComputeHash(tempHash); |
| |
| /***** Now use the derived key algorithm *****/ |
| byte[] derivedKey = new byte[64]; |
| int keySizeBytes = encryptionInfo.Header.KeySize / 8; |
| |
| //First XOR hash bytes with 0x36 and fill the rest with 0x36 |
| for (int i = 0; i < derivedKey.Length; i++) |
| derivedKey[i] = (byte)(i < hash.Length ? 0x36 ^ hash[i] : 0x36); |
| |
| |
| byte[] X1 = hashProvider.ComputeHash(derivedKey); |
| |
| //if verifier size is bigger than the key size we can return X1 |
| if ((int)encryptionInfo.Verifier.VerifierHashSize > keySizeBytes) |
| return FixHashSize(X1, keySizeBytes); |
| |
| //Else XOR hash bytes with 0x5C and fill the rest with 0x5C |
| for (int i = 0; i < derivedKey.Length; i++) |
| derivedKey[i] = (byte)(i < hash.Length ? 0x5C ^ hash[i] : 0x5C); |
| |
| byte[] X2 = hashProvider.ComputeHash(derivedKey); |
| |
| //Join the two and return |
| byte[] join = new byte[X1.Length + X2.Length]; |
| |
| Array.Copy(X1, 0, join, 0, X1.Length); |
| Array.Copy(X2, 0, join, X1.Length, X2.Length); |
| |
| |
| return FixHashSize(join, keySizeBytes); |
| } |
| catch (Exception ex) |
| { |
| throw (new Exception("An error occured when the encryptionkey was created", ex)); |
| } |
| } |
| |
| /// <summary> |
| /// Create the hash. |
| /// This method is written with the help of Lyquidity library, many thanks for this nice sample |
| /// </summary> |
| /// <param name="password">The password</param> |
| /// <param name="encr">The encryption info extracted from the ENCRYPTIOINFO stream inside the OLE document</param> |
| /// <param name="blockKey">The block key appended to the hash to obtain the final hash</param> |
| /// <returns>The hash to encrypt the document</returns> |
| private byte[] GetPasswordHashAgile(string password, EncryptionInfoAgile.EncryptionKeyEncryptor encr, byte[] blockKey) |
| { |
| try |
| { |
| var hashProvider = GetHashProvider(encr); |
| var hash = GetPasswordHash(hashProvider, encr.SaltValue, password, encr.SpinCount, encr.HashSize); |
| var hashFinal = GetFinalHash(hashProvider, encr, blockKey, hash); |
| |
| return FixHashSize(hashFinal, encr.KeyBits / 8); |
| } |
| catch (Exception ex) |
| { |
| throw (new Exception("An error occured when the encryptionkey was created", ex)); |
| } |
| } |
| #endif |
| private byte[] GetFinalHash(HashAlgorithm hashProvider, EncryptionInfoAgile.EncryptionKeyEncryptor encr, byte[] blockKey, byte[] hash) |
| { |
| //2.3.4.13 MS-OFFCRYPTO |
| var tempHash = new byte[hash.Length + blockKey.Length]; |
| Array.Copy(hash, tempHash, hash.Length); |
| Array.Copy(blockKey, 0, tempHash, hash.Length, blockKey.Length); |
| var hashFinal = hashProvider.ComputeHash(tempHash); |
| return hashFinal; |
| } |
| private byte[] GetPasswordHash(HashAlgorithm hashProvider, byte[] salt, string password, int spinCount, int hashSize) |
| { |
| byte[] hash = null; |
| byte[] tempHash = new byte[4 + hashSize]; //Iterator + prev. hash |
| hash = hashProvider.ComputeHash(CombinePassword(salt, password)); |
| |
| //Iterate "spinCount" times, inserting i in first 4 bytes and then the prev. hash in byte 5-24 |
| for (int i = 0; i < spinCount; i++) |
| { |
| Array.Copy(BitConverter.GetBytes(i), tempHash, 4); |
| Array.Copy(hash, 0, tempHash, 4, hash.Length); |
| |
| hash = hashProvider.ComputeHash(tempHash); |
| } |
| |
| return hash; |
| } |
| private byte[] FixHashSize(byte[] hash, int size, byte fill=0) |
| { |
| if (hash.Length == size) |
| return hash; |
| else if (hash.Length < size) |
| { |
| byte[] buff = new byte[size]; |
| Array.Copy(hash, buff, hash.Length); |
| for (int i = hash.Length; i < size; i++) |
| { |
| buff[i] = fill; |
| } |
| return buff; |
| } |
| else |
| { |
| byte[] buff = new byte[size]; |
| Array.Copy(hash, buff, size); |
| return buff; |
| } |
| } |
| private byte[] CombinePassword(byte[] salt, string password) |
| { |
| if (password == "") |
| { |
| password = "VelvetSweatshop"; //Used if Password is blank |
| } |
| // Convert password to unicode... |
| byte[] passwordBuf = UnicodeEncoding.Unicode.GetBytes(password); |
| |
| byte[] inputBuf = new byte[salt.Length + passwordBuf.Length]; |
| Array.Copy(salt, inputBuf, salt.Length); |
| Array.Copy(passwordBuf, 0, inputBuf, salt.Length, passwordBuf.Length); |
| return inputBuf; |
| } |
| internal static ushort CalculatePasswordHash(string Password) |
| { |
| //Calculate the hash |
| //Thanks to Kohei Yoshida for the sample http://kohei.us/2008/01/18/excel-sheet-protection-password-hash/ |
| ushort hash = 0; |
| for (int i = Password.Length - 1; i >= 0; i--) |
| { |
| hash ^= Password[i]; |
| hash = (ushort)(((ushort)((hash >> 14) & 0x01)) |
| | |
| ((ushort)((hash << 1) & 0x7FFF))); //Shift 1 to the left. Overflowing bit 15 goes into bit 0 |
| } |
| |
| hash ^= (0x8000 | ('N' << 8) | 'K'); //Xor NK with high bit set(0xCE4B) |
| hash ^= (ushort)Password.Length; |
| |
| return hash; |
| } |
| } |
| } |