/******************************************************************************* * 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 { /// /// Handels encrypted Excel documents /// internal class EncryptedPackageHandler { #if !MONO /// /// Read the package from the OLE document and decrypt it using the supplied password /// /// The file /// /// 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); // } //} /// /// Read the package from the OLE document and decrypt it using the supplied password /// /// The memory stream. /// The encryption object from the Package /// [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; } } /// /// Encrypts a package /// /// The package as a byte array /// The encryption info from the workbook /// 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= "\r\n"; xml += ""; xml += ""; xml += ""; xml += ""; xml += ""; xml += ""; xml += ""; 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.RIPEMD160: return new HMACRIPEMD160(salt); 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 /// /// Create an EncryptionInfo object to encrypt a workbook /// /// The password /// /// The Encryption key /// 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")); } } /// /// Decrypt a document /// /// The Encrypted data /// Encryption Info object /// The password /// 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.RIPEMD160: return new RIPEMD160Managed(); 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; } /// /// Validate the password /// /// The encryption key /// The encryption info extracted from the ENCRYPTIOINFO stream inside the OLE document /// 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; } /// /// Validate the password /// /// The hash algorithm /// The encryption info extracted from the ENCRYPTIOINFO stream inside the OLE document /// 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++; } } /// /// Create the hash. /// This method is written with the help of Lyquidity library, many thanks for this nice sample /// /// The password /// The encryption info extracted from the ENCRYPTIOINFO stream inside the OLE document /// The hash to encrypt the document 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)); } } /// /// Create the hash. /// This method is written with the help of Lyquidity library, many thanks for this nice sample /// /// The password /// The encryption info extracted from the ENCRYPTIOINFO stream inside the OLE document /// The block key appended to the hash to obtain the final hash /// The hash to encrypt the document 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; } } }