/// <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, EncryptionInfo 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; }
private MemoryStream GetStreamFromPackage(IStorage storage, ExcelEncryption encryption) { MemoryStream ret=null; comTypes.STATSTG statstg; storage.Stat(out statstg, (uint)STATFLAG.STATFLAG_DEFAULT); IEnumSTATSTG pIEnumStatStg = null; storage.EnumElements(0, IntPtr.Zero, 0, out pIEnumStatStg); comTypes.STATSTG[] regelt = { statstg }; uint fetched = 0; uint res = pIEnumStatStg.Next(1, regelt, out fetched); //if (regelt[0].pwcsName == "DataSpaces") //{ // PrintStorage(storage, regelt[0],""); //} if (res == 0) { byte[] data; EncryptionInfo encryptionInfo = null; while (res != 1) { switch (statstg.pwcsName) { case "EncryptionInfo": data = GetOleStream(storage, statstg); //File.WriteAllBytes(@"c:\temp\EncInfo1.bin", data); encryptionInfo = new EncryptionInfo(); encryptionInfo.ReadBinary(data); encryption.Algorithm = encryptionInfo.Header.AlgID == AlgorithmID.AES128 ? EncryptionAlgorithm.AES128 : encryptionInfo.Header.AlgID == AlgorithmID.AES192 ? EncryptionAlgorithm.AES192 : EncryptionAlgorithm.AES256; break; case "EncryptedPackage": data = GetOleStream(storage, statstg); ret = DecryptDocument(data, encryptionInfo, encryption.Password); break; } if ((res = pIEnumStatStg.Next(1, regelt, out fetched)) != 1) { statstg = regelt[0]; } } } Marshal.ReleaseComObject(pIEnumStatStg); return ret; }
/// <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[] GetPasswordHash(string password, EncryptionInfo 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 Exception("RC4 Hash provider is not supported. Must be SHA1(AlgIDHash == 0x8004)"); } else { throw new Exception("Hash provider is invalid. Must be SHA1(AlgIDHash == 0x8004)"); } hash = hashProvider.ComputeHash(CombinePassword(encryptionInfo.Verifier.Salt, password)); //Iterate 50 000 times, inserting i in first 4 bytes and then the prev. hash in byte 5-24 for (int i = 0; i < 50000; i++) { Array.Copy(BitConverter.GetBytes(i), tempHash, 4); Array.Copy(hash, 0, tempHash, 4, hash.Length); hash = hashProvider.ComputeHash(tempHash); } // 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 (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)); } }
// Help method to print a storage part binary to c:\temp //private void PrintStorage(IStorage storage, System.Runtime.InteropServices.ComTypes.STATSTG sTATSTG, string topName) //{ // IStorage ds; // if (topName.Length > 0) // { // topName = topName[0] < 'A' ? topName.Substring(1, topName.Length - 1) : topName; // } // storage.OpenStorage(sTATSTG.pwcsName, // null, // (uint)(STGM.DIRECT | STGM.READ | STGM.SHARE_EXCLUSIVE), // IntPtr.Zero, // 0, // out ds); // System.Runtime.InteropServices.ComTypes.STATSTG statstgSub; // ds.Stat(out statstgSub, (uint)STATFLAG.STATFLAG_DEFAULT); // IEnumSTATSTG pIEnumStatStgSub = null; // System.Runtime.InteropServices.ComTypes.STATSTG[] regeltSub = { statstgSub }; // ds.EnumElements(0, IntPtr.Zero, 0, out pIEnumStatStgSub); // uint fetched = 0; // while (pIEnumStatStgSub.Next(1, regeltSub, out fetched) == 0) // { // string sName = regeltSub[0].pwcsName[0] < 'A' ? regeltSub[0].pwcsName.Substring(1, regeltSub[0].pwcsName.Length - 1) : regeltSub[0].pwcsName; // if (regeltSub[0].type == 1) // { // PrintStorage(ds, regeltSub[0], topName + sName + "_"); // } // else if(regeltSub[0].type==2) // { // File.WriteAllBytes(@"c:\temp\" + topName + sName + ".bin", GetOleStream(ds, regeltSub[0])); // } // } //} /// <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) { if (encryptionInfo == null) { throw(new Exception("Invalid document. EncryptionInfo is missing")); } long size = BitConverter.ToInt64(data,0); var encryptedData = new byte[data.Length - 8]; Array.Copy(data, 8, encryptedData, 0, encryptedData.Length); 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=GetPasswordHash(password, encryptionInfo); if (IsPasswordValid(key, encryptionInfo)) { ICryptoTransform decryptor = decryptKey.CreateDecryptor( key, null); MemoryStream dataStream = new MemoryStream(encryptedData); CryptoStream 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> /// 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 EncryptionInfo 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 EncryptionInfo(); 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 = GetPasswordHash(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; }
/// <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[] GetPasswordHash(string password, EncryptionInfo 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 = hashProvider.ComputeHash(CombinePassword(encryptionInfo.Verifier.Salt, password)); //Iterate 50 000 times, inserting i in first 4 bytes and then the prev. hash in byte 5-24 for (int i = 0; i < 50000; i++) { Array.Copy(BitConverter.GetBytes(i), tempHash, 4); Array.Copy(hash, 0, tempHash, 4, hash.Length); hash = hashProvider.ComputeHash(tempHash); } // 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 (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)); } }