Есть ли способ узнать, защищен ли файл doc
/ppt
/xls
паролем еще до открытия файла?
Обнаружение защищенного паролем документа
Ответы (2)
Я создал служебный метод, который пытается определить, защищен ли данный офисный документ паролем или нет. Вот список преимуществ:
- поддерживает документы Word, Excel и PowerPoint, как устаревшие (doc, xls, ppt), так и новую версию OpenXml (docx, xlsx, pptx)
- не зависит от COM или любой другой библиотеки
- требуются только пространства имен System, System.IO и System.Text
- довольно быстрое и надежное обнаружение (учитывает устаревшие форматы файлов .doc, .ppt и .xls)
- низкое использование памяти (максимум 64 КБ)
Вот код, надеюсь кому-нибудь пригодится:
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, 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;
}
}
person
Funbit
schedule
23.10.2014
Там какая-то приятная спелеология!
- person jschroedl; 21.11.2014
Отличная работа! Подтверждено, что он работает в Word, Excel для форматов 2013 и 97.
- person Martin Murphy; 06.09.2015
Похоже, он не может правильно определить его для 97 версии Powerpoint. Если вы пытаетесь потерпеть неудачу, а не зависнуть. Я предлагаю передать пароль сразу после пути к файлу при открытии. @c:\path\to\file.ppt + ::BadPassword::
- person Martin Murphy; 06.09.2015
Я не совсем понял ваше предложение о неправильном пароле. Не могли бы вы загрузить PPT-файл этого неудачного 97-го? Постараюсь исправить функцию. Спасибо
- person Funbit; 08.09.2015
Шикарный класс, спасибо! У меня работает с защищенными паролем doc/x, xlsx, pptx, НО НЕ с защищенными паролем xls, ppt.
- person Ofer; 27.09.2016
@Ofer Не за что. Существует множество различных версий XLS, поэтому есть вероятность, что некоторые из них не поддерживаются. Тем не менее, функция была протестирована на более чем 10000 различных файлов Excel с 99%-ным правильным обнаружением, по крайней мере. Было бы здорово, если бы вы могли поделиться своим файлом XLS.
- person Funbit; 28.09.2016
Я создал файлы doc, ppt и xls, используя «Сохранить как» с docx, pptx и xlsx соответственно. Может быть, это не поддерживается. Но большинство файлов в старом формате были созданы старыми приложениями Office, так что это не должно быть проблемой. Я бы с радостью поделился файлом, но как?
- person Ofer; 29.09.2016
потрясающая работа, но, как сказал Офер, это не работает для файлов ppt, сохраненных с помощью сохранения, как в более новом офисном костюме (офис 16). Похоже, что в приведенном выше методе ScanForPassword отсутствует раздел кода для проверки пароля PPT.
- person TechnicalSmile; 13.01.2017
@MartinMurphy Я обновил код для поддержки устаревших документов PPT, попробуйте, теперь должно работать (по крайней мере, с документами, сохраненными после Office 2002).
- person Funbit; 04.12.2017
@TechnicalSmile Я обновил код для поддержки устаревших документов PPT, попробуйте.
- person Funbit; 04.12.2017
@Ofer Я обновил код для поддержки устаревших документов PPT, попробуйте.
- person Funbit; 04.12.2017
@Funbit Большое спасибо за ваши усилия. Я уже работаю в другой компании и занимаюсь другими делами, но я уверен, что это будет полезно многим другим.
- person Ofer; 05.12.2017
@Funbit Я попробовал ваше решение для файла Word, оно всегда возвращает false, даже если документ Word защищен паролем. не могли бы вы помочь мне с этим.
- person Manoj Ahuja; 12.06.2018
@Funbit ваше решение работает, если пароль установлен для открытия документа или excel, но если пароль установлен только для изменения, он возвращает false. Не могли бы вы помочь мне определить, установлен ли пароль только для изменения.
- person Manoj Ahuja; 12.06.2018
@ManojAhuja Извините, я не уверен, как применяются пароли редактирования (целью этого кода было определить, можно ли открыть документ для просмотра). Вы можете проверить формат файла XLS (download.microsoft.com/download/1/A/9/), возможно, это просто другой тип записи или флаг для управления редактированием паролей.
- person Funbit; 14.06.2018
@Funbit: Написание тестов для этого сейчас. Если я сохраню excel как xls 5.0/95, это не сработает. А вот для xls 97-2003 работает. Также проверим, есть ли подвох
- person Michael P; 10.12.2020
Похоже, что это возвращает значение true, когда сам файл Excel не защищен паролем, но пользователь выбрал параметр «Защитить структуру книги», чтобы запретить добавление листов и т. д. Как мы можем сделать так, чтобы этот метод возвращал значение true только для полностью защищенных паролем файлов?
- person Chuck Norris; 26.07.2021
Вот грубая версия детектора паролей, который я сделал. Не нужно открывать какие-либо объекты 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, сделайте то же самое.
person
Wolf5
schedule
15.01.2013
здорово. это работает только для офисных документов? как насчет PDF?
- person echo; 15.04.2016
Приведенный выше код предназначен только для офисных документов (Microsoft). PDF-файлы — это продукт Adobe, и у них, вероятно, есть другой способ сделать это. Но может быть так же просто, как сравнить документ PDF до и после того, как он был защищен паролем, чтобы найти флаг (позицию), указывающий, что он защищен паролем. Затем просто создайте код, который реагирует на значение в этом месте.
- person Wolf5; 16.04.2016