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