Passwords and encryption
DocWright opens password-protected .docx files (agile and standard encryption) and legacy .doc files (RC4), and saves .docx with the same AES-256 encryption Word uses. The encryption is implemented with the .NET base class library only.
Open an encrypted document
Pass the password in ConvertOptions. It's ignored for unencrypted files, so you can always set it. The three failures mean different things:
using DocWright;
using DocWright.Core;
using DocWright.Cryptography;
using DocWright.Dom;
// Cheap check, no password needed: is this an encrypted Office package?
using (FileStream probe = File.OpenRead("encrypted.docx"))
{
Console.WriteLine($"Encrypted package: {EncryptedOfficePackage.IsEncryptedPackage(probe)}");
}
foreach (string? password in new[] { null, "wrong", "DocWright!22" })
{
try
{
using FileStream input = File.OpenRead("encrypted.docx");
using WordDocument document = converter.Load(input, FormatDetection.Auto,
new ConvertOptions { Password = password });
Console.WriteLine($"Opened with '{password}': {document.GetText().Split('\n')[0]}");
}
catch (DocumentEncryptedException ex)
{
Console.WriteLine($"No password: {ex.GetType().Name} ({ex.EncryptionScheme})");
}
catch (InvalidPasswordException)
{
Console.WriteLine($"'{password}': wrong password, ask again");
}
catch (DocumentIntegrityException)
{
Console.WriteLine("Right password, but the file was altered or truncated");
}
}
Output (DocWright's AES-256 test document)
Encrypted package: True
No password: DocumentEncryptedException (ECMA-376 standard)
'wrong': wrong password, ask again
Opened with 'DocWright!22': DocWright seed document one: basic paragraphs.
| Exception | Meaning | What to do |
|---|---|---|
DocumentEncryptedException |
No password was given, or the scheme isn't supported. EncryptionScheme says which. |
Ask for a password. |
InvalidPasswordException |
The password is wrong. | Ask again. |
DocumentIntegrityException |
The password was right, but the file was altered or truncated. | Don't ask again: the file is damaged. |
The password is checked before the file's integrity code, so a typo is reported as a typo, not as damage.
Save with encryption
using DocWright;
using DocWright.Cryptography;
using DocWright.Dom;
using (FileStream input = File.OpenRead("encrypted.docx"))
using (WordDocument document = converter.Load(input, FormatDetection.Auto, new ConvertOptions { Password = "DocWright!22" }))
using (FileStream output = File.Create("re-encrypted.docx"))
{
// What Word writes: AES-256, SHA-512, 100,000 spin rounds.
converter.Save(document, output, ".docx", new SaveOptions
{
Encryption = new AgileEncryptionSettings("a new password"),
});
}
Defaults match Word: AES-256, SHA-512 and 100,000 key-derivation rounds. AgileEncryptionSettings lets you change KeyBits (128, 192 or 256), HashAlgorithm, SpinCount and SaltSize. Invalid values throw rather than being quietly corrected.
- Only
.docxcan be encrypted. Asking to encrypt RTF, HTML, Markdown or text throwsUnsupportedFeatureException, rather than writing an unprotected file. - Encrypted output is not byte-reproducible: the salts and keys are random, which is the point. Everything else DocWright writes is reproducible.
- DocWright only writes agile encryption. It reads the older schemes.
Without a converter
DocWright.Cryptography works on the package directly: EncryptedOfficePackage.IsEncryptedPackage(stream) is a cheap check, OfficeDecryptor.Decrypt(package, password) returns the plaintext package as a stream, and OfficeEncryptor.Encrypt(plaintext, output, settings) encrypts one you already have.
Protection passwords are not encryption
"Restrict editing" in Word records a password hash in the document. It is a user-interface convenience, not security: the content isn't encrypted, and DocWright doesn't enforce the restriction. DocumentProtectionHash.VerifyDocumentProtection(protection, password) checks the recorded password, and SetDocumentProtectionPassword sets a new one.
There is no password recovery, cracking or bypass in DocWright, and none will be added. Passwords never appear in diagnostics, exceptions or ToString().
Hostile files
An encrypted file states its own key-derivation round count, so an attacker can ask for ten million rounds and tie up your CPU. ResourceLimits.MaxKeyDerivationSpinCount refuses anything over 1,000,000 by default, before any hashing.