Има ли някакъв начин да разберете дали файл doc
/ppt
/xls
е защитен с парола дори преди да отворите файла?
Откриване на документ, защитен с парола
Отговори (2)
Създадох помощен метод, който се опитва да открие дали даден офис документ е защитен с парола или не. Ето списъка с предимствата:
- поддържа Word, Excel и PowerPoint документи, както наследени (doc, xls, ppt), така и нова версия на OpenXml (docx, xlsx, pptx)
- не зависи от 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, 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 версии, така че има вероятност някои от тях да не се поддържат. Функцията обаче беше тествана на повече от 10 000 различни Excel файла с най-малко 99% правилно откриване. Би било чудесно, ако можете да споделите своя XLS файл.
- person Funbit; 28.09.2016
Създадох doc, ppt и xls файлове, използвайки съответно save as с docx, pptx и xlsx. Може това да не се поддържа. Но повечето от файловете в стария формат са създадени от стари приложения на Office, така че не би трябвало да е истински проблем. С удоволствие бих споделил файла, но как?
- person Ofer; 29.09.2016
невероятна работа, но подобно на това, което каза Офер, това не работи за ppt файлове, записани чрез запазване като в по-нов офис костюм (офис 16). Изглежда, че кодовата секция за проверка на паролата на PPT липсва в метода ScanForPassword по-горе.
- 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