检测受密码保护的文档

问题描述

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

解决方法

我创建了一个实用程序方法,该方法尝试检测给定的Office文档是否受密码保护。以下是优势列表: 支持旧版(doc,xls,ppt)和新OpenXml版本(docx,xl​​sx,pptx)的Word,Excel和PowerPoint文档 不依赖于COM或任何其他库 仅需要System,System.IO和System.Text命名空间 快速可靠的检测(使用传统的.doc,.ppt和.xls文件格式) 内存使用率低(最大64KB) 这是代码,希望有人会发现它有用:
public static class MsOfficeHelper
{
    /// <summary>
    /// Detects if a given office document is protected by a password or not.
    /// Supported formats: Word,Excel and PowerPoint (both legacy and OpenXml).
    /// </summary>
    /// <param name=\"fileName\">Path to an office document.</param>
    /// <returns>True if document is protected by a password,false otherwise.</returns>
    public static bool IsPasswordProtected(string fileName)
    {
        using (var stream = File.OpenRead(fileName))
            return IsPasswordProtected(stream);
    }

    /// <summary>
    /// Detects if a given office document is protected by a password or not.
    /// Supported formats: Word,Excel and PowerPoint (both legacy and OpenXml).
    /// </summary>
    /// <param name=\"stream\">Office document stream.</param>
    /// <returns>True if document is protected by a password,false otherwise.</returns>
    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,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,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,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,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,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 n c r y p t e d P a c k a g e\")) return true;
            return false;
        }

        //Unknown.
        return false;
    }
可能不是100%。我通过比较带有和不带有密码的几个Excel和Word文档找到了这些标志。要为PowerPoint添加,只需执行相同操作即可。