检测受密码保护的文档

有没有办法知道doc / ppt / xls文件是否在打开文件之前受密码保护?

我创建了一个实用程序方法,尝试检测给定的office文档是否受密码保护。 以下是优势列表:

  • 支持Word,Excel和PowerPoint文档,包括旧版(doc,xls,ppt)和新的OpenXml版本(docx,xl​​sx,pptx)
  • 不依赖于COM或任何其他库
  • 只需要System,System.IO和System.Text命名空间
  • 非常快速和可靠的检测(尊重传统的.doc,.ppt和.xls文件格式)
  • 内存使用率低(最大64KB)

这是代码,希望有人会发现它很有用:

 public static class MsOfficeHelper { ///  /// Detects if a given office document is protected by a password or not. /// Supported formats: Word, Excel and PowerPoint (both legacy and OpenXml). ///  /// Path to an office document. /// True if document is protected by a password, false otherwise. public static bool IsPasswordProtected(string fileName) { using (var stream = File.OpenRead(fileName)) return IsPasswordProtected(stream); } ///  /// Detects if a given office document is protected by a password or not. /// Supported formats: Word, Excel and PowerPoint (both legacy and OpenXml). ///  /// Office document stream. /// True if document is protected by a password, false otherwise. public static bool IsPasswordProtected(Stream stream) { // minimum file size for office file is 4k if (stream.Length < 4096) return false; // read file header stream.Seek(0, SeekOrigin.Begin); var compObjHeader = new byte[0x20]; ReadFromStream(stream, compObjHeader); // check if we have plain zip file if (compObjHeader[0] == 'P' && compObjHeader[1] == 'K') { // this is a plain OpenXml document (not encrypted) return false; } // check compound object magic bytes if (compObjHeader[0] != 0xD0 || compObjHeader[1] != 0xCF) { // unknown document format return false; } int sectionSizePower = compObjHeader[0x1E]; if (sectionSizePower < 8 || sectionSizePower > 16) { // invalid section size return false; } int sectionSize = 2 << (sectionSizePower - 1); const int defaultScanLength = 32768; long scanLength = Math.Min(defaultScanLength, stream.Length); // read header part for scan stream.Seek(0, SeekOrigin.Begin); var header = new byte[scanLength]; ReadFromStream(stream, header); // check if we detected password protection if (ScanForPassword(stream, header, sectionSize)) return true; // if not, try to scan footer as well // read footer part for scan stream.Seek(-scanLength, SeekOrigin.End); var footer = new byte[scanLength]; ReadFromStream(stream, footer); // finally return the result return ScanForPassword(stream, footer, sectionSize); } static void ReadFromStream(Stream stream, byte[] buffer) { int bytesRead, count = buffer.Length; while (count > 0 && (bytesRead = stream.Read(buffer, 0, count)) > 0) count -= bytesRead; if (count > 0) throw new EndOfStreamException(); } static bool ScanForPassword(Stream stream, byte[] buffer, int sectionSize) { const string afterNamePadding = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; try { string bufferString = Encoding.ASCII.GetString(buffer, 0, buffer.Length); // try to detect password protection used in new OpenXml documents // by searching for "EncryptedPackage" or "EncryptedSummary" streams const string encryptedPackageName = "E\0n\0c\0r\0y\0p\0t\0e\0d\0P\0a\0c\0k\0a\0g\0e" + afterNamePadding; const string encryptedSummaryName = "E\0n\0c\0r\0y\0p\0t\0e\0d\0S\0u\0m\0m\0a\0r\0y" + afterNamePadding; if (bufferString.Contains(encryptedPackageName) || bufferString.Contains(encryptedSummaryName)) return true; // try to detect password protection for legacy Office documents const int coBaseOffset = 0x200; const int sectionIdOffset = 0x74; // check for Word header const string wordDocumentName = "W\0o\0r\0d\0D\0o\0c\0u\0m\0e\0n\0t" + afterNamePadding; int headerOffset = bufferString.IndexOf(wordDocumentName, StringComparison.InvariantCulture); int sectionId; if (headerOffset >= 0) { sectionId = BitConverter.ToInt32(buffer, headerOffset + sectionIdOffset); int sectionOffset = coBaseOffset + sectionId * sectionSize; const int fibScanSize = 0x10; if (sectionOffset < 0 || sectionOffset + fibScanSize > stream.Length) return false; // invalid document var fibHeader = new byte[fibScanSize]; stream.Seek(sectionOffset, SeekOrigin.Begin); ReadFromStream(stream, fibHeader); short properties = BitConverter.ToInt16(fibHeader, 0x0A); // check for fEncrypted FIB bit const short fEncryptedBit = 0x0100; return (properties & fEncryptedBit) == fEncryptedBit; } // check for Excel header const string workbookName = "W\0o\0r\0k\0b\0o\0o\0k" + afterNamePadding; headerOffset = bufferString.IndexOf(workbookName, StringComparison.InvariantCulture); if (headerOffset >= 0) { sectionId = BitConverter.ToInt32(buffer, headerOffset + sectionIdOffset); int sectionOffset = coBaseOffset + sectionId * sectionSize; const int streamScanSize = 0x100; if (sectionOffset < 0 || sectionOffset + streamScanSize > stream.Length) return false; // invalid document var workbookStream = new byte[streamScanSize]; stream.Seek(sectionOffset, SeekOrigin.Begin); ReadFromStream(stream, workbookStream); short record = BitConverter.ToInt16(workbookStream, 0); short recordSize = BitConverter.ToInt16(workbookStream, sizeof(short)); const short bofMagic = 0x0809; const short eofMagic = 0x000A; const short filePassMagic = 0x002F; if (record != bofMagic) return false; // invalid BOF // scan for FILEPASS record until the end of the buffer int offset = sizeof(short) * 2 + recordSize; int recordsLeft = 16; // simple infinite loop check just in case do { record = BitConverter.ToInt16(workbookStream, offset); if (record == filePassMagic) return true; recordSize = BitConverter.ToInt16(workbookStream, sizeof(short) + offset); offset += sizeof(short) * 2 + recordSize; recordsLeft--; } while (record != eofMagic && recordsLeft > 0); } // check for PowerPoint user header const string currentUserName = "C\0u\0r\0r\0e\0n\0t\0 \0U\0s\0e\0r" + afterNamePadding; headerOffset = bufferString.IndexOf(currentUserName, StringComparison.InvariantCulture); if (headerOffset >= 0) { sectionId = BitConverter.ToInt32(buffer, headerOffset + sectionIdOffset); int sectionOffset = coBaseOffset + sectionId * sectionSize; const int userAtomScanSize = 0x10; if (sectionOffset < 0 || sectionOffset + userAtomScanSize > stream.Length) return false; // invalid document var userAtom = new byte[userAtomScanSize]; stream.Seek(sectionOffset, SeekOrigin.Begin); ReadFromStream(stream, userAtom); const int headerTokenOffset = 0x0C; uint headerToken = BitConverter.ToUInt32(userAtom, headerTokenOffset); // check for headerToken const uint encryptedToken = 0xF3D1C4DF; return headerToken == encryptedToken; } } catch (Exception ex) { // BitConverter exceptions may be related to document format problems // so we just treat them as "password not detected" result if (ex is ArgumentException) return false; // respect all the rest exceptions throw; } return false; } } 

这是我制作的密码检测器的粗略版本。 不需要打开任何Office对象。

  public static bool IsPassworded(string file) { var bytes = File.ReadAllBytes(file); return IsPassworded(bytes); return false; } public static bool IsPassworded(byte[] bytes) { var prefix = Encoding.Default.GetString(bytes.Take(2).ToArray()); if (prefix == "PK") { //ZIP and not password protected return false; } if (prefix == "ÐÏ") { //Office format. //Flagged with password if (bytes.Skip(0x20c).Take(1).ToArray()[0] == 0x2f) return true; //XLS 2003 if (bytes.Skip(0x214).Take(1).ToArray()[0] == 0x2f) return true; //XLS 2005 if (bytes.Skip(0x20B).Take(1).ToArray()[0] == 0x13) return true; //DOC 2005 if (bytes.Length < 2000) return false; //Guessing false var start = Encoding.Default.GetString(bytes.Take(2000).ToArray()); //DOC/XLS 2007+ start = start.Replace("\0", " "); if (start.Contains("E ncrypted P ackage")) return true; return false; } //Unknown. return false; } 

它可能不是100%。 我通过使用和不使用密码比较几个Excel和Word文档找到的标志。 要添加PowerPoint,请执行相同操作。