vendor: import KillerPDF 1.7.5 source (GPL-3.0) as the base for MMD PDF

This commit is contained in:
2026-08-27 06:58:22 +02:00
commit 532485a830
577 changed files with 149058 additions and 0 deletions
+323
View File
@@ -0,0 +1,323 @@
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace PdfSharpCore.Pdf.Security
{
internal class AESEncryptor : RC4Encryptor
{
public override void InitEncryptionKey(string password)
{
if (rValue == 5)
{
InitVersion5(password);
return;
}
if (rValue == 6)
{
// http://esec-lab.sogeti.com/post/The-undocumented-password-validation-algorithm-of-Adobe-Reader-X
InitVersion6(password);
return;
}
base.InitEncryptionKey(password);
}
/// <summary>
/// Pdf Reference 1.7 Extension Level 3, Chapter 3.5.2, Algorithm 3.2a
/// </summary>
/// <param name="password"></param>
protected void InitVersion5(string password)
{
var pwdBytes = Encoding.UTF8.GetBytes(password);
if (pwdBytes.Length > 127)
pwdBytes = pwdBytes.Take(127).ToArray();
// split O and U into their components
var oHash = new byte[32];
var oValidation = new byte[8];
var oSalt = new byte[8];
var uHash = new byte[32];
var uValidation = new byte[8];
var uSalt = new byte[8];
Array.Copy(ownerValue, oHash, 32);
Array.Copy(ownerValue, 32, oValidation, 0, 8);
Array.Copy(ownerValue, 40, oSalt, 0, 8);
Array.Copy(userValue, uHash, 32);
Array.Copy(userValue, 32, uValidation, 0, 8);
Array.Copy(userValue, 40, uSalt, 0, 8);
computedOwnerValue = new byte[32];
computedUserValue = new byte[32];
var oKeyBytes = new byte[pwdBytes.Length + 8 + 48];
Array.Copy(pwdBytes, oKeyBytes, pwdBytes.Length);
Array.Copy(oValidation, 0, oKeyBytes, pwdBytes.Length, 8);
Array.Copy(userValue, 0, oKeyBytes, pwdBytes.Length + 8, 48);
HaveOwnerPermission = PasswordMatchR5(oKeyBytes, ownerValue);
if (HaveOwnerPermission)
{
PasswordValid = true;
Array.Copy(ownerValue, computedOwnerValue, 32);
CreateEncryptionKeyR5(oeValue, pwdBytes, oSalt, userValue);
}
else
{
oKeyBytes = new byte[pwdBytes.Length + 8];
Array.Copy(pwdBytes, oKeyBytes, pwdBytes.Length);
Array.Copy(uValidation, 0, oKeyBytes, pwdBytes.Length, 8);
// if the result matches the first 32 bytes of userValue, we have the user password
PasswordValid = PasswordMatchR5(oKeyBytes, userValue);
if (PasswordValid)
{
Array.Copy(userValue, computedUserValue, 32);
CreateEncryptionKeyR5(ueValue, pwdBytes, uSalt, null);
}
}
}
private void CreateEncryptionKeyR5(byte[] encryptedValue, byte[] password, byte[] salt, byte[] uservalue)
{
var sha = SHA256.Create();
var aes256Cbc = Aes.Create();
aes256Cbc.KeySize = 256;
aes256Cbc.Mode = CipherMode.CBC;
aes256Cbc.Padding = PaddingMode.None;
var bufLen = password.Length + salt.Length + (uservalue != null ? 48 : 0);
var buf = new byte[bufLen];
Array.Copy(password, buf, password.Length);
Array.Copy(salt, 0, buf, password.Length, salt.Length);
if (uservalue != null)
Array.Copy(uservalue, 0, buf, password.Length + salt.Length, 48);
var shaKey = sha.ComputeHash(buf);
using (var decryptor = aes256Cbc.CreateDecryptor(shaKey, new byte[16]))
{
using (var ms = new MemoryStream(encryptedValue))
{
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
encryptionKey = new byte[32];
cs.Read(encryptionKey, 0, 32);
}
}
}
aes256Cbc.Clear();
}
private void InitVersion6(string password)
{
// split O and U into their components
var oSalt = new byte[8];
var uSalt = new byte[8];
var uKeySalt = new byte[8];
var oKeySalt = new byte[8];
var oKey = new byte[48];
Array.Copy(ownerValue, 32, oSalt, 0, 8);
Array.Copy(userValue, 32, uSalt, 0, 8);
Array.Copy(userValue, 40, uKeySalt, 0, 8);
Array.Copy(ownerValue, 40, oKeySalt, 0, 8);
Array.Copy(userValue, oKey, 48);
computedUserValue = new byte[32];
computedOwnerValue = new byte[32];
ValidateVersion6(password, uSalt, null, computedUserValue);
ValidateVersion6(password, oSalt, oKey, computedOwnerValue);
byte[] keyToDecrypt = null;
byte[] salt = null;
byte[] hashKey = null;
if (CompareArrays(computedOwnerValue, ownerValue, 32))
{
keyToDecrypt = oeValue;
salt = oKeySalt;
hashKey = oKey;
PasswordValid = true;
HaveOwnerPermission = true;
}
else if (CompareArrays(computedUserValue, userValue, 32))
{
keyToDecrypt = ueValue;
salt = uKeySalt;
PasswordValid = true;
}
if (keyToDecrypt != null)
{
encryptionKey = new byte[32];
var hash = new byte[32];
var iv = new byte[16];
ValidateVersion6(password, salt, hashKey, hash);
using (var aes256 = Aes.Create())
{
aes256.KeySize = 256;
aes256.Mode = CipherMode.CBC;
aes256.Padding = PaddingMode.None;
using (var decryptor = aes256.CreateDecryptor(hash, iv))
{
decryptor.TransformBlock(keyToDecrypt, 0, 32, encryptionKey, 0);
}
}
}
}
private static void ValidateVersion6(string password, byte[] salt, byte[] ownerKey, byte[] hash)
{
var data = new byte[(128 + 64 + 48) * 64];
var block = new byte[64];
var blockSize = 32;
var dataLen = 0;
int i, j, sum;
using (var aes128 = Aes.Create())
{
aes128.BlockSize = 16 * 8;
aes128.Mode = CipherMode.CBC;
var pwdBytes = Encoding.UTF8.GetBytes(password);
var iv = new byte[16];
var aesKey = new byte[16];
/* Step 1: calculate initial data block */
using (var sha256 = SHA256.Create())
{
sha256.TransformBlock(pwdBytes, 0, pwdBytes.Length, pwdBytes, 0);
sha256.TransformBlock(salt, 0, salt.Length, salt, 0);
if (ownerKey != null)
sha256.TransformBlock(ownerKey, 0, ownerKey.Length, ownerKey, 0);
sha256.TransformFinalBlock(salt, 0, 0);
Array.Copy(sha256.Hash, block, sha256.HashSize / 8);
}
for (i = 0; i < 64 || i < data[dataLen * 64 - 1] + 32; i++)
{
/* Step 2: repeat password and data block 64 times */
Array.Copy(pwdBytes, data, pwdBytes.Length);
Array.Copy(block, 0, data, pwdBytes.Length, blockSize);
if (ownerKey != null)
Array.Copy(ownerKey, 0, data, pwdBytes.Length + blockSize, 48);
dataLen = pwdBytes.Length + blockSize + (ownerKey != null ? 48 : 0);
for (j = 1; j < 64; j++)
Array.Copy(data, 0, data, j * dataLen, dataLen);
/* Step 3: encrypt data using data block as key and iv */
Array.Copy(block, 16, iv, 0, 16);
Array.Copy(block, 0, aesKey, 0, 16);
using (var aesEnc = aes128.CreateEncryptor(aesKey, iv))
{
aesEnc.TransformBlock(data, 0, dataLen * 64, data, 0);
/* Step 4: determine SHA-2 hash size for this round */
for (j = 0, sum = 0; j < 16; j++)
sum += data[j];
/* Step 5: calculate data block for next round */
blockSize = 32 + sum % 3 * 16;
HashAlgorithm hashAlg = null;
switch (blockSize)
{
case 32:
hashAlg = SHA256.Create();
break;
case 48:
hashAlg = SHA384.Create();
break;
case 64:
hashAlg = SHA512.Create();
break;
}
hashAlg.TransformBlock(data, 0, dataLen * 64, data, 0);
hashAlg.TransformFinalBlock(data, 0, 0);
Array.Copy(hashAlg.Hash, block, hashAlg.HashSize / 8);
hashAlg.Dispose();
}
}
}
Array.Copy(block, hash, 32);
}
private static bool PasswordMatchR5(byte[] key, byte[] comparand)
{
var sha = SHA256.Create();
var hash = sha.ComputeHash(key);
for (var i = 0; i < 32; i++)
{
if (hash[i] != comparand[i])
return false;
}
return true;
}
/// <summary>
/// Pdf Reference 1.7, Chapter 7.6.2, Algorithm #1
/// </summary>
/// <param name="id"></param>
public override void CreateHashKey(PdfObjectID id)
{
if (rValue >= 5)
{
if (key == null || key.Length != encryptionKey.Length)
key = new byte[encryptionKey.Length];
Array.Copy(encryptionKey, key, encryptionKey.Length);
return;
}
var objectId = new byte[5];
md5.Initialize();
// Split the object number and generation
objectId[0] = (byte)id.ObjectNumber;
objectId[1] = (byte)(id.ObjectNumber >> 8);
objectId[2] = (byte)(id.ObjectNumber >> 16);
objectId[3] = (byte)id.GenerationNumber;
objectId[4] = (byte)(id.GenerationNumber >> 8);
var salt = new byte[] { 0x73, 0x41, 0x6C, 0x54 };
var k = new byte[encryptionKey.Length + 9];
Array.Copy(encryptionKey, k, encryptionKey.Length);
Array.Copy(objectId, 0, k, encryptionKey.Length, objectId.Length);
Array.Copy(salt, 0, k, encryptionKey.Length + objectId.Length, salt.Length);
key = md5.ComputeHash(k);
md5.Initialize();
keySize = encryptionKey.Length + 5;
if (keySize > 16)
keySize = 16;
}
/// <summary>
/// Decrypts a block of data
/// </summary>
/// <param name="bytes">Bytes to decrypt</param>
/// <returns></returns>
public override byte[] Encrypt(byte[] bytes)
{
// first 16 bytes should be an initialization vector for the encryption
if (bytes.Length <= 16)
return bytes;
var iv = new byte[16];
Array.Copy(bytes, iv, 16);
// Pdf Reference 1.7, Section 7.6.2 :
// "Strings and streams encrypted with AES shall use a padding scheme that is described in Internet RFC 2898, PKCS #5"
var output = new byte[bytes.Length - 16];
int dataLength;
using (var aes = Aes.Create())
{
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
var decryptor = aes.CreateDecryptor(key, iv);
try
{
var offset = decryptor.TransformBlock(bytes, 16, bytes.Length - 16, output, 0);
var suffix = decryptor.TransformFinalBlock(bytes, 0, 0);
Array.Copy(suffix, 0, output, offset, suffix.Length);
dataLength = offset + suffix.Length;
}
catch
{
// return unmodified
// (encountered documents that were "partly" encrypted, i.e. everything was encrypted except object-streams)
return bytes;
}
}
return output.Take(dataLength).ToArray();
}
}
}
+150
View File
@@ -0,0 +1,150 @@
using PdfSharpCore.Pdf.Internal;
using System;
using System.Security.Cryptography;
namespace PdfSharpCore.Pdf.Security
{
internal abstract class EncryptorBase
{
protected readonly MD5 md5 = MD5.Create();
/// <summary>
/// The encryption key for the owner.
/// </summary>
protected byte[] ownerKey = new byte[32];
/// <summary>
/// The encryption key for the user.
/// </summary>
protected byte[] userKey = new byte[32];
/// <summary>
/// The encryption key for a particular object/generation.
/// </summary>
protected byte[] key;
/// <summary>
/// The global encryption key.
/// </summary>
protected byte[] encryptionKey;
/// <summary>
/// The /O value as read from the input document
/// </summary>
protected byte[] ownerValue;
/// <summary>
/// The /U value as read from the input document
/// </summary>
protected byte[] userValue;
protected byte[] computedOwnerValue;
protected byte[] computedUserValue;
protected byte[] documentId;
protected byte[] oeValue;
protected byte[] ueValue;
protected byte[] permsValue;
protected int pValue;
protected int rValue;
protected int vValue;
protected bool encryptMetadata;
protected PdfDictionary cf;
protected string stmF;
protected string strF;
protected int keyLength;
protected static readonly byte[] passwordPadding = new byte[] // 32 bytes password padding defined by Adobe
{
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
};
/// <summary>
/// The encryption key length for a particular object/generation.
/// </summary>
protected int keySize;
protected PdfDocument doc;
protected PdfDictionary encryptionDict;
public bool PasswordValid { get; protected set; }
public bool HaveOwnerPermission { get; protected set; }
public void Initialize(PdfDocument document, PdfDictionary encryptionDictionary)
{
doc = document;
encryptionDict = encryptionDictionary;
documentId = PdfEncoders.RawEncoding.GetBytes(doc.Internals.FirstDocumentID);
ownerValue = PdfEncoders.RawEncoding.GetBytes(encryptionDict.Elements.GetString(PdfStandardSecurityHandler.Keys.O));
userValue = PdfEncoders.RawEncoding.GetBytes(encryptionDict.Elements.GetString(PdfStandardSecurityHandler.Keys.U));
oeValue = PdfEncoders.RawEncoding.GetBytes(encryptionDict.Elements.GetString(PdfStandardSecurityHandler.Keys.OE));
ueValue = PdfEncoders.RawEncoding.GetBytes(encryptionDict.Elements.GetString(PdfStandardSecurityHandler.Keys.UE));
permsValue = PdfEncoders.RawEncoding.GetBytes(encryptionDict.Elements.GetString(PdfStandardSecurityHandler.Keys.Perms));
pValue = encryptionDict.Elements.GetInteger(PdfStandardSecurityHandler.Keys.P);
rValue = encryptionDict.Elements.GetInteger(PdfStandardSecurityHandler.Keys.R);
vValue = encryptionDict.Elements.GetInteger(PdfSecurityHandler.Keys.V);
encryptMetadata = !encryptionDict.Elements.ContainsKey(PdfStandardSecurityHandler.Keys.EncryptMetadata) || encryptionDict.Elements.GetBoolean(PdfStandardSecurityHandler.Keys.EncryptMetadata);
cf = encryptionDict.Elements.GetDictionary(PdfSecurityHandler.Keys.CF);
stmF = encryptionDict.Elements.GetString(PdfSecurityHandler.Keys.StmF);
strF = encryptionDict.Elements.GetString(PdfSecurityHandler.Keys.StrF);
keyLength = encryptionDict.Elements.GetInteger(PdfSecurityHandler.Keys.Length) / 8; // specified in Bits
// Length may be absent, use default of 40 bits (see 7.6.1 Table 20, "V" entry)
if (keyLength <= 0)
keyLength = 5;
keySize = keyLength;
}
public void SetEncryptionKey(byte[] encKey)
{
encryptionKey = encKey;
}
/// <summary>
/// Pads a password to a 32 byte array.
/// </summary>
protected static byte[] PadPassword(string password)
{
var padded = new byte[32];
if (password == null)
Array.Copy(passwordPadding, 0, padded, 0, 32);
else
{
int length = password.Length;
Array.Copy(PdfEncoders.RawEncoding.GetBytes(password), 0, padded, 0, Math.Min(length, 32));
if (length < 32)
Array.Copy(passwordPadding, 0, padded, length, 32 - length);
}
return padded;
}
/// <summary>
/// Compares the first 'length' bytes of two byte-arrays
/// </summary>
/// <returns></returns>
protected static bool CompareArrays(byte[] left, byte[] right, int length)
{
for (var i = 0; i < length; i++)
{
if (left[i] != right[i])
return false;
}
return true;
}
}
}
@@ -0,0 +1,63 @@
using PdfSharpCore.Pdf.IO;
namespace PdfSharpCore.Pdf.Security
{
internal class EncryptorFactory
{
public static void Create(PdfDocument doc, PdfDictionary dict, out IEncryptor stringEncryptor, out IEncryptor streamEncryptor)
{
stringEncryptor = streamEncryptor = null;
var filter = dict.Elements.GetName(PdfSecurityHandler.Keys.Filter);
var v = dict.Elements.GetInteger(PdfSecurityHandler.Keys.V);
var maxSupportedVersion = 5;
if (filter != "/Standard" || !(v >= 1 && v <= maxSupportedVersion))
throw new PdfReaderException(PSSR.UnknownEncryption);
foreach (var keyName in new []{"/StrF", "/StmF"})
{
IEncryptor encryptor = null;
if (v >= 4)
{
var cf = dict.Elements.GetDictionary(PdfSecurityHandler.Keys.CF);
if (cf != null)
{
var filterName = dict.Elements.GetName(keyName);
if (!string.IsNullOrEmpty(filterName))
{
/*
* Pdf Reference 1.7 Chapter 7.6.5 (Crypt Filters)
*
* None: The application shall not decrypt data but shall direct the input stream to the security handler for decryption.
* V2: The application shall ask the security handler for the encryption key and shall implicitly decrypt data with
* "Algorithm 1: Encryption of data using the RC4 or AES algorithms", using the RC4 algorithm.
* AESV2: (PDF 1.6)The application shall ask the security handler for the encryption key and shall implicitly decrypt data with
* "Algorithm 1: Encryption of data using the RC4 or AES algorithms", using the AES algorithm in Cipher Block
* Chaining (CBC) mode with a 16-byte block size and an initialization vector that shall be randomly generated and
* placed as the first 16 bytes in the stream or string.
* AESV3: (PDF 1.7, ExtensionLevel 3) The application asks the security handler for the encryption key and implicitly decrypts data with
* Algorithm 3.1a, using the AES-256 algorithm in Cipher Block Chaining (CBC) with padding mode with a 16-byte block size and an
* initialization vector that is randomly generated and placed as the first 16 bytes in the stream or string.
* The key size (Length) shall be 256 bits.
*/
var filterDict = cf.Elements.GetDictionary(filterName);
if (filterDict != null)
{
var cfm = filterDict.Elements.GetName(PdfSecurityHandler.Keys.CFM);
if (!string.IsNullOrEmpty(cfm) && cfm.StartsWith("/AESV")) // AESV2(PDF 1.6), AESV3(PDF 1.7, ExtensionLevel 3)
encryptor = new AESEncryptor();
}
}
}
}
// default to RC4 encryption
if (encryptor == null)
encryptor = new RC4Encryptor();
encryptor.Initialize(doc, dict);
if (keyName == "/StrF")
stringEncryptor = encryptor;
else
streamEncryptor = encryptor;
}
}
}
}
+21
View File
@@ -0,0 +1,21 @@
namespace PdfSharpCore.Pdf.Security
{
internal interface IEncryptor
{
bool PasswordValid { get; }
bool HaveOwnerPermission { get; }
void Initialize(PdfDocument document, PdfDictionary encryptionDict);
void InitEncryptionKey(string password);
bool ValidatePassword(string password);
void SetEncryptionKey(byte[] key);
void CreateHashKey(PdfObjectID objectId);
byte[] Encrypt(byte[] bytes);
}
}
@@ -0,0 +1,174 @@
#region PDFsharp - A .NET library for processing PDF
//
// Authors:
// Stefan Lange
//
// Copyright (c) 2005-2016 empira Software GmbH, Cologne Area (Germany)
//
// http://www.PdfSharp.com
// http://sourceforge.net/projects/pdfsharp
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#endregion
// ReSharper disable InconsistentNaming
namespace PdfSharpCore.Pdf.Security
{
/// <summary>
/// Represents the base of all security handlers.
/// </summary>
public abstract class PdfSecurityHandler : PdfDictionary
{
internal PdfSecurityHandler(PdfDocument document)
: base(document)
{ }
internal PdfSecurityHandler(PdfDictionary dict)
: base(dict)
{ }
/// <summary>
/// Predefined keys of this dictionary.
/// </summary>
internal class Keys : KeysBase
{
/// <summary>
/// (Required) The name of the preferred security handler for this document. Typically,
/// it is the name of the security handler that was used to encrypt the document. If
/// SubFilter is not present, only this security handler should be used when opening
/// the document. If it is present, consumer applications can use any security handler
/// that implements the format specified by SubFilter.
/// Standard is the name of the built-in password-based security handler. Names for other
/// security handlers can be registered by using the procedure described in Appendix E.
/// </summary>
[KeyInfo(KeyType.Name | KeyType.Required)]
public const string Filter = "/Filter";
/// <summary>
/// (Optional; PDF 1.3) A name that completely specifies the format and interpretation of
/// the contents of the encryption dictionary. It is needed to allow security handlers other
/// than the one specified by Filter to decrypt the document. If this entry is absent, other
/// security handlers should not be allowed to decrypt the document.
/// </summary>
[KeyInfo("1.3", KeyType.Name | KeyType.Optional)]
public const string SubFilter = "/SubFilter";
/// <summary>
/// (Optional but strongly recommended) A code specifying the algorithm to be used in encrypting
/// and decrypting the document:
/// 0 An algorithm that is undocumented and no longer supported, and whose use is strongly discouraged.
/// 1 Algorithm 3.1, with an encryption key length of 40 bits.
/// 2 (PDF 1.4) Algorithm 3.1, but permitting encryption key lengths greater than 40 bits.
/// 3 (PDF 1.4) An unpublished algorithm that permits encryption key lengths ranging from 40 to 128 bits.
/// 4 (PDF 1.5) The security handler defines the use of encryption and
/// decryption in the document, using the rules specified by the CF,
/// StmF, and StrF entries using algorithm 3.1 with a key length of 128 bits.
/// 5 (ExtensionLevel 3) The security handler defines the use of
/// encryption and decryption in the document, using the rules
/// specified by the CF, StmF, and StrF entries using algorithm 3.1a with a key length of 256 bits
/// The default value if this entry is omitted is 0, but a value of 1 or greater is strongly recommended.
/// </summary>
[KeyInfo(KeyType.Integer | KeyType.Optional)]
public const string V = "/V";
/// <summary>
/// (Optional; PDF 1.4; only if V is 2 or 3) The length of the encryption key, in bits.
/// The value must be a multiple of 8, in the range 40 to 256. Default value: 40.
/// Note: Security handlers can define their own use of the Length entry
/// but are encouraged to use it to define the bit length of the encryption key.
/// </summary>
[KeyInfo("1.4", KeyType.Integer | KeyType.Optional)]
public const string Length = "/Length";
/// <summary>
/// (Optional; meaningful only when the value of V is 4; PDF 1.5)
/// A dictionary whose keys are crypt filter names and whose values are the corresponding
/// crypt filter dictionaries. Every crypt filter used in the document must have an entry
/// in this dictionary, except for the standard crypt filter names.
/// </summary>
[KeyInfo(KeyType.Dictionary | KeyType.Optional)]
public const string CF = "/CF";
/// <summary>
/// (Optional) The method used, if any, by the consumer application to decrypt data.
/// The following values are supported:
/// • None The application does not decrypt data but directs the input
/// stream to the security handler for decryption. (See implementation
/// note 30 in Appendix H.)
/// • V2 The application asks the security handler for the encryption key
/// and implicitly decrypts data with Algorithm 3.1, using the RC4 algorithm.
/// • AESV2(PDF 1.6) The application asks the security handler for the
/// encryption key and implicitly decrypts data with Algorithm 3.1, using
/// the AES-128 algorithm in Cipher Block Chaining(CBC) with padding
/// mode with a 16-byte block size and an initialization vector that is
/// randomly generated and placed as the first 16 bytes in the stream or
/// string. The key size(Length) shall be 128 bits.
/// • AESV3(ExtensionLevel 3) The application asks the security handler
/// for the encryption key and implicitly decrypts data with
/// Algorithm 3.1a, using the AES-256 algorithm in Cipher Block
/// Chaining(CBC) with padding mode with a 16-byte block size and an
/// initialization vector that is randomly generated and placed as the
/// first 16 bytes in the stream or string. The key size(Length) shall be 256 bits.
/// When the value is V2, AESV2, or AESV3, the application may ask once for
/// this encryption key and cache the key for subsequent use for streams
/// that use the same crypt filter.Therefore, there must be a one-to-one
/// relationship between a crypt filter name and the corresponding encryption key.
/// Only the values listed here are supported. Applications that encounter
/// other values should report that the file is encrypted with an unsupported algorithm.
/// Default value: None.
/// </summary>
[KeyInfo(KeyType.String | KeyType.Optional)]
public const string CFM = "/CFM";
/// <summary>
/// (Optional; meaningful only when the value of V is 4 or 5; PDF 1.5)
/// The name of the crypt filter that is used by default when decrypting streams.
/// The name must be a key in the CF dictionary or a standard crypt filter name. All streams
/// in the document, except for cross-reference streams or streams that have a Crypt entry in
/// their Filter array, are decrypted by the security handler, using this crypt filter.
/// Default value: Identity.
/// </summary>
[KeyInfo("1.5", KeyType.Name | KeyType.Optional)]
public const string StmF = "/StmF";
/// <summary>
/// (Optional; meaningful only when the value of V is 4 or 5; PDF 1.)
/// The name of the crypt filter that is used when decrypting all strings in the document.
/// The name must be a key in the CF dictionary or a standard crypt filter name.
/// Default value: Identity.
/// </summary>
[KeyInfo("1.5", KeyType.Name | KeyType.Optional)]
public const string StrF = "/StrF";
/// <summary>
/// (Optional; meaningful only when the value of V is 4 or 5; PDF 1.6)
/// The name of the crypt filter that should be used by default when encrypting embedded
/// file streams; it must correspond to a key in the CF dictionary or a standard crypt
/// filter name. This entry is provided by the security handler. Applications should respect
/// this value when encrypting embedded files, except for embedded file streams that have
/// their own crypt filter specifier. If this entry is not present, and the embedded file
/// stream does not contain a crypt filter specifier, the stream should be encrypted using
/// the default stream crypt filter specified by StmF.
/// </summary>
[KeyInfo("1.6", KeyType.Name | KeyType.Optional)]
public const string EFF = "/EFF";
}
}
}
@@ -0,0 +1,253 @@
#region PDFsharp - A .NET library for processing PDF
//
// Authors:
// Stefan Lange
//
// Copyright (c) 2005-2016 empira Software GmbH, Cologne Area (Germany)
//
// http://www.PdfSharpCore.com
// http://sourceforge.net/projects/pdfsharp
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#endregion
using System;
namespace PdfSharpCore.Pdf.Security
{
/// <summary>
/// Encapsulates access to the security settings of a PDF document.
/// </summary>
public sealed class PdfSecuritySettings
{
internal PdfSecuritySettings(PdfDocument document)
{
_document = document;
}
readonly PdfDocument _document;
/// <summary>
/// Indicates whether the granted access to the document is 'owner permission'. Returns true if the document
/// is unprotected or was opened with the owner password. Returns false if the document was opened with the
/// user password.
/// </summary>
public bool HasOwnerPermissions
{
get { return _hasOwnerPermissions; }
}
internal bool _hasOwnerPermissions = true;
/// <summary>
/// Gets or sets the document security level. If you set the security level to anything but PdfDocumentSecurityLevel.None
/// you must also set a user and/or an owner password. Otherwise saving the document will fail.
/// </summary>
public PdfDocumentSecurityLevel DocumentSecurityLevel
{
get { return _documentSecurityLevel; }
set { _documentSecurityLevel = value; }
}
PdfDocumentSecurityLevel _documentSecurityLevel;
/// <summary>
/// Sets the user password of the document. Setting a password automatically sets the
/// PdfDocumentSecurityLevel to PdfDocumentSecurityLevel.Encrypted128Bit if its current
/// value is PdfDocumentSecurityLevel.None.
/// </summary>
public string UserPassword
{
set { SecurityHandler.UserPassword = value; }
}
/// <summary>
/// Sets the owner password of the document. Setting a password automatically sets the
/// PdfDocumentSecurityLevel to PdfDocumentSecurityLevel.Encrypted128Bit if its current
/// value is PdfDocumentSecurityLevel.None.
/// </summary>
public string OwnerPassword
{
set { SecurityHandler.OwnerPassword = value; }
}
/// <summary>
/// Determines whether the document can be saved.
/// </summary>
internal bool CanSave(ref string message)
{
if (_documentSecurityLevel != PdfDocumentSecurityLevel.None)
{
if (String.IsNullOrEmpty(SecurityHandler._userPassword) && String.IsNullOrEmpty(SecurityHandler._ownerPassword))
{
message = PSSR.UserOrOwnerPasswordRequired;
return false;
}
}
return true;
}
#region Permissions
//TODO: Use documentation from our English Acrobat 6.0 version.
/// <summary>
/// Permits printing the document. Should be used in conjunction with PermitFullQualityPrint.
/// </summary>
public bool PermitPrint
{
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitPrint) != 0; }
set
{
PdfUserAccessPermission permission = SecurityHandler.Permission;
if (value)
permission |= PdfUserAccessPermission.PermitPrint;
else
permission &= ~PdfUserAccessPermission.PermitPrint;
SecurityHandler.Permission = permission;
}
}
/// <summary>
/// Permits modifying the document.
/// </summary>
public bool PermitModifyDocument
{
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitModifyDocument) != 0; }
set
{
PdfUserAccessPermission permission = SecurityHandler.Permission;
if (value)
permission |= PdfUserAccessPermission.PermitModifyDocument;
else
permission &= ~PdfUserAccessPermission.PermitModifyDocument;
SecurityHandler.Permission = permission;
}
}
/// <summary>
/// Permits content copying or extraction.
/// </summary>
public bool PermitExtractContent
{
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitExtractContent) != 0; }
set
{
PdfUserAccessPermission permission = SecurityHandler.Permission;
if (value)
permission |= PdfUserAccessPermission.PermitExtractContent;
else
permission &= ~PdfUserAccessPermission.PermitExtractContent;
SecurityHandler.Permission = permission;
}
}
/// <summary>
/// Permits commenting the document.
/// </summary>
public bool PermitAnnotations
{
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitAnnotations) != 0; }
set
{
PdfUserAccessPermission permission = SecurityHandler.Permission;
if (value)
permission |= PdfUserAccessPermission.PermitAnnotations;
else
permission &= ~PdfUserAccessPermission.PermitAnnotations;
SecurityHandler.Permission = permission;
}
}
/// <summary>
/// Permits filling of form fields.
/// </summary>
public bool PermitFormsFill
{
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitFormsFill) != 0; }
set
{
PdfUserAccessPermission permission = SecurityHandler.Permission;
if (value)
permission |= PdfUserAccessPermission.PermitFormsFill;
else
permission &= ~PdfUserAccessPermission.PermitFormsFill;
SecurityHandler.Permission = permission;
}
}
/// <summary>
/// Permits content extraction for accessibility.
/// </summary>
public bool PermitAccessibilityExtractContent
{
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitAccessibilityExtractContent) != 0; }
set
{
PdfUserAccessPermission permission = SecurityHandler.Permission;
if (value)
permission |= PdfUserAccessPermission.PermitAccessibilityExtractContent;
else
permission &= ~PdfUserAccessPermission.PermitAccessibilityExtractContent;
SecurityHandler.Permission = permission;
}
}
/// <summary>
/// Permits to insert, rotate, or delete pages and create bookmarks or thumbnail images even if
/// PermitModifyDocument is not set.
/// </summary>
public bool PermitAssembleDocument
{
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitAssembleDocument) != 0; }
set
{
PdfUserAccessPermission permission = SecurityHandler.Permission;
if (value)
permission |= PdfUserAccessPermission.PermitAssembleDocument;
else
permission &= ~PdfUserAccessPermission.PermitAssembleDocument;
SecurityHandler.Permission = permission;
}
}
/// <summary>
/// Permits to print in high quality. insert, rotate, or delete pages and create bookmarks or thumbnail images
/// even if PermitModifyDocument is not set.
/// </summary>
public bool PermitFullQualityPrint
{
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitFullQualityPrint) != 0; }
set
{
PdfUserAccessPermission permission = SecurityHandler.Permission;
if (value)
permission |= PdfUserAccessPermission.PermitFullQualityPrint;
else
permission &= ~PdfUserAccessPermission.PermitFullQualityPrint;
SecurityHandler.Permission = permission;
}
}
#endregion
/// <summary>
/// PdfStandardSecurityHandler is the only implemented handler.
/// </summary>
internal PdfStandardSecurityHandler SecurityHandler
{
get { return _document._trailer.SecurityHandler; }
}
}
}
@@ -0,0 +1,736 @@
#region PDFsharp - A .NET library for processing PDF
//
// Authors:
// Stefan Lange (mailto:Stefan.Lange@pdfsharp.com)
//
// Copyright (c) 2005-2016 empira Software GmbH, Cologne (Germany)
//
// http://www.pdfsharp.com
// http://sourceforge.net/projects/pdfsharp
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.Cryptography;
using PdfSharpCore.Pdf.IO;
using PdfSharpCore.Pdf.Advanced;
using PdfSharpCore.Pdf.Internal;
#pragma warning disable 0169
#pragma warning disable 0649
namespace PdfSharpCore.Pdf.Security
{
/// <summary>
/// Represents the standard PDF security handler.
/// </summary>
public sealed class PdfStandardSecurityHandler : PdfSecurityHandler
{
internal PdfStandardSecurityHandler(PdfDocument document)
: base(document)
{ }
internal PdfStandardSecurityHandler(PdfDictionary dict)
: base(dict)
{ }
/// <summary>
/// Sets the user password of the document. Setting a password automatically sets the
/// PdfDocumentSecurityLevel to PdfDocumentSecurityLevel.Encrypted128Bit if its current
/// value is PdfDocumentSecurityLevel.None.
/// </summary>
public string UserPassword
{
set
{
if (_document._securitySettings.DocumentSecurityLevel == PdfDocumentSecurityLevel.None)
_document._securitySettings.DocumentSecurityLevel = PdfDocumentSecurityLevel.Encrypted128Bit;
_userPassword = value;
}
}
internal string _userPassword;
/// <summary>
/// Sets the owner password of the document. Setting a password automatically sets the
/// PdfDocumentSecurityLevel to PdfDocumentSecurityLevel.Encrypted128Bit if its current
/// value is PdfDocumentSecurityLevel.None.
/// </summary>
public string OwnerPassword
{
set
{
if (_document._securitySettings.DocumentSecurityLevel == PdfDocumentSecurityLevel.None)
_document._securitySettings.DocumentSecurityLevel = PdfDocumentSecurityLevel.Encrypted128Bit;
_ownerPassword = value;
}
}
internal string _ownerPassword;
/// <summary>
/// Gets or sets the user access permission represented as an integer in the P key.
/// </summary>
internal PdfUserAccessPermission Permission
{
get
{
PdfUserAccessPermission permission = (PdfUserAccessPermission)Elements.GetInteger(Keys.P);
if ((int)permission == 0)
permission = PdfUserAccessPermission.PermitAll;
return permission;
}
set { Elements.SetInteger(Keys.P, (int)value); }
}
/// <summary>
/// Encrypts the whole document.
/// </summary>
public void EncryptDocument()
{
foreach (PdfReference iref in _document._irefTable.AllReferences)
{
if (!ReferenceEquals(iref.Value, this))
EncryptObject(iref.Value);
}
}
/// <summary>
/// Encrypts an indirect object.
/// </summary>
internal void EncryptObject(PdfObject value)
{
Debug.Assert(value.Reference != null);
stringEncryptor.CreateHashKey(value.ObjectID);
#if DEBUG
if (value.ObjectID.ObjectNumber == 10)
GetType();
#endif
PdfDictionary dict;
PdfArray array;
PdfStringObject str;
if ((dict = value as PdfDictionary) != null)
EncryptDictionary(dict);
else if ((array = value as PdfArray) != null)
EncryptArray(array);
else if ((str = value as PdfStringObject) != null)
{
if (str.Length != 0)
{
byte[] bytes = str.EncryptionValue;
bytes = stringEncryptor.Encrypt(bytes);
str.EncryptionValue = bytes;
}
}
}
/// <summary>
/// Encrypts a dictionary.
/// </summary>
void EncryptDictionary(PdfDictionary dict)
{
// Pdf Reference 1.7, Chapter 7.5.8.2: The cross-reference stream shall not be encrypted
// Pdf Reference 1.7, Chapter 7.6.1: Strings in the Encryption-Dictionary shall not be encrypted
if (dict.Elements.GetName("/Type") == "/XRef"
|| dict.ObjectNumber == ObjectNumber)
return;
foreach (KeyValuePair<string, PdfItem> item in dict.Elements)
{
PdfString value1;
PdfDictionary value2;
PdfArray value3;
if ((value1 = item.Value as PdfString) != null)
EncryptString(value1);
else if ((value2 = item.Value as PdfDictionary) != null)
EncryptDictionary(value2);
else if ((value3 = item.Value as PdfArray) != null)
EncryptArray(value3);
}
if (dict.Stream != null)
{
byte[] bytes = dict.Stream.Value;
if (bytes.Length != 0)
{
streamEncryptor.CreateHashKey(dict.ObjectID);
bytes = streamEncryptor.Encrypt(bytes);
dict.Stream.Value = bytes;
}
}
}
/// <summary>
/// Encrypts an array.
/// </summary>
void EncryptArray(PdfArray array)
{
int count = array.Elements.Count;
for (int idx = 0; idx < count; idx++)
{
PdfItem item = array.Elements[idx];
PdfString value1;
PdfDictionary value2;
PdfArray value3;
if ((value1 = item as PdfString) != null)
{
stringEncryptor.CreateHashKey(array.ObjectID);
EncryptString(value1);
}
else if ((value2 = item as PdfDictionary) != null)
EncryptDictionary(value2);
else if ((value3 = item as PdfArray) != null)
EncryptArray(value3);
}
}
/// <summary>
/// Encrypts a string.
/// </summary>
void EncryptString(PdfString value)
{
if (value.Length != 0)
{
byte[] bytes = value.EncryptionValue;
bytes = stringEncryptor.Encrypt(bytes);
value.EncryptionValue = bytes;
}
}
/// <summary>
/// Encrypts an array.
/// </summary>
internal byte[] EncryptBytes(byte[] bytes)
{
if (bytes != null && bytes.Length != 0)
{
PrepareKey();
EncryptRC4(bytes);
}
return bytes;
}
#region Encryption Algorithms
/// <summary>
/// Checks the password.
/// </summary>
/// <param name="inputPassword">Password or null if no password is provided.</param>
public PasswordValidity ValidatePassword(string inputPassword)
{
// We can handle 40 and 128 bit standard encryption.
string filter = Elements.GetName(PdfSecurityHandler.Keys.Filter);
int v = Elements.GetInteger(PdfSecurityHandler.Keys.V);
if (filter != "/Standard" || !(v >= 1 && v <= 5))
throw new PdfReaderException(PSSR.UnknownEncryption);
if (inputPassword == null)
inputPassword = "";
EncryptorFactory.Create(_document, this, out stringEncryptor, out streamEncryptor);
stringEncryptor.InitEncryptionKey(inputPassword);
streamEncryptor.InitEncryptionKey(inputPassword);
stringEncryptor.ValidatePassword(inputPassword);
if (stringEncryptor.PasswordValid && stringEncryptor.HaveOwnerPermission)
return PasswordValidity.OwnerPassword;
if (stringEncryptor.PasswordValid)
return PasswordValidity.UserPassword;
return PasswordValidity.Invalid;
}
[Conditional("DEBUG")]
static void DumpBytes(string tag, byte[] bytes)
{
string dump = tag + ": ";
for (int idx = 0; idx < bytes.Length; idx++)
dump += String.Format("{0:X2}", bytes[idx]);
Debug.WriteLine(dump);
}
/// <summary>
/// Pads a password to a 32 byte array.
/// </summary>
static byte[] PadPassword(string password)
{
byte[] padded = new byte[32];
if (password == null)
Array.Copy(PasswordPadding, 0, padded, 0, 32);
else
{
int length = password.Length;
Array.Copy(PdfEncoders.RawEncoding.GetBytes(password), 0, padded, 0, Math.Min(length, 32));
if (length < 32)
Array.Copy(PasswordPadding, 0, padded, length, 32 - length);
}
return padded;
}
static readonly byte[] PasswordPadding = // 32 bytes password padding defined by Adobe
{
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
};
/// <summary>
/// Generates the user key based on the padded user password.
/// </summary>
void InitWithUserPassword(byte[] documentID, string userPassword, byte[] ownerKey, int permissions, bool strongEncryption)
{
InitEncryptionKey(documentID, PadPassword(userPassword), ownerKey, permissions, strongEncryption);
SetupUserKey(documentID);
}
/// <summary>
/// Generates the user key based on the padded owner password.
/// </summary>
void InitWithOwnerPassword(byte[] documentID, string ownerPassword, byte[] ownerKey, int permissions, bool strongEncryption)
{
byte[] userPad = ComputeOwnerKey(ownerKey, PadPassword(ownerPassword), strongEncryption);
InitEncryptionKey(documentID, userPad, ownerKey, permissions, strongEncryption);
SetupUserKey(documentID);
}
/// <summary>
/// Computes the padded user password from the padded owner password.
/// </summary>
byte[] ComputeOwnerKey(byte[] userPad, byte[] ownerPad, bool strongEncryption)
{
byte[] ownerKey = new byte[32];
//#if !SILVERLIGHT
byte[] digest = _md5.ComputeHash(ownerPad);
if (strongEncryption)
{
byte[] mkey = new byte[16];
// Hash the pad 50 times
for (int idx = 0; idx < 50; idx++)
digest = _md5.ComputeHash(digest);
Array.Copy(userPad, 0, ownerKey, 0, 32);
// Encrypt the key
for (int i = 0; i < 20; i++)
{
for (int j = 0; j < mkey.Length; ++j)
mkey[j] = (byte)(digest[j] ^ i);
PrepareRC4Key(mkey);
EncryptRC4(ownerKey);
}
}
else
{
PrepareRC4Key(digest, 0, 5);
EncryptRC4(userPad, ownerKey);
}
//#endif
return ownerKey;
}
/// <summary>
/// Computes the encryption key.
/// </summary>
void InitEncryptionKey(byte[] documentID, byte[] userPad, byte[] ownerKey, int permissions, bool strongEncryption)
{
_ownerKey = ownerKey;
_encryptionKey = new byte[strongEncryption ? 16 : 5];
_md5.Initialize();
_md5.TransformBlock(userPad, 0, userPad.Length, userPad, 0);
_md5.TransformBlock(ownerKey, 0, ownerKey.Length, ownerKey, 0);
// Split permission into 4 bytes
byte[] permission = new byte[4];
permission[0] = (byte)permissions;
permission[1] = (byte)(permissions >> 8);
permission[2] = (byte)(permissions >> 16);
permission[3] = (byte)(permissions >> 24);
_md5.TransformBlock(permission, 0, 4, permission, 0);
_md5.TransformBlock(documentID, 0, documentID.Length, documentID, 0);
_md5.TransformFinalBlock(permission, 0, 0);
byte[] digest = _md5.Hash;
_md5.Initialize();
// Create the hash 50 times (only for 128 bit)
if (_encryptionKey.Length == 16)
{
for (int idx = 0; idx < 50; idx++)
{
digest = _md5.ComputeHash(digest);
_md5.Initialize();
}
}
Array.Copy(digest, 0, _encryptionKey, 0, _encryptionKey.Length);
}
/// <summary>
/// Computes the user key.
/// </summary>
void SetupUserKey(byte[] documentID)
{
if (_encryptionKey.Length == 16)
{
_md5.TransformBlock(PasswordPadding, 0, PasswordPadding.Length, PasswordPadding, 0);
_md5.TransformFinalBlock(documentID, 0, documentID.Length);
byte[] digest = _md5.Hash;
_md5.Initialize();
Array.Copy(digest, 0, _userKey, 0, 16);
for (int idx = 16; idx < 32; idx++)
_userKey[idx] = 0;
//Encrypt the key
for (int i = 0; i < 20; i++)
{
for (int j = 0; j < _encryptionKey.Length; j++)
digest[j] = (byte)(_encryptionKey[j] ^ i);
PrepareRC4Key(digest, 0, _encryptionKey.Length);
EncryptRC4(_userKey, 0, 16);
}
}
else
{
PrepareRC4Key(_encryptionKey);
EncryptRC4(PasswordPadding, _userKey);
}
}
/// <summary>
/// Prepare the encryption key.
/// </summary>
void PrepareKey()
{
PrepareRC4Key(_key, 0, _keySize);
}
/// <summary>
/// Prepare the encryption key.
/// </summary>
void PrepareRC4Key(byte[] key)
{
PrepareRC4Key(key, 0, key.Length);
}
/// <summary>
/// Prepare the encryption key.
/// </summary>
void PrepareRC4Key(byte[] key, int offset, int length)
{
int idx1 = 0;
int idx2 = 0;
for (int idx = 0; idx < 256; idx++)
_state[idx] = (byte)idx;
byte tmp;
for (int idx = 0; idx < 256; idx++)
{
idx2 = (key[idx1 + offset] + _state[idx] + idx2) & 255;
tmp = _state[idx];
_state[idx] = _state[idx2];
_state[idx2] = tmp;
idx1 = (idx1 + 1) % length;
}
}
/// <summary>
/// Encrypts the data.
/// </summary>
// ReSharper disable InconsistentNaming
void EncryptRC4(byte[] data)
// ReSharper restore InconsistentNaming
{
EncryptRC4(data, 0, data.Length, data);
}
/// <summary>
/// Encrypts the data.
/// </summary>
// ReSharper disable once InconsistentNaming
void EncryptRC4(byte[] data, int offset, int length)
{
EncryptRC4(data, offset, length, data);
}
/// <summary>
/// Encrypts the data.
/// </summary>
// ReSharper disable once InconsistentNaming
void EncryptRC4(byte[] inputData, byte[] outputData)
{
EncryptRC4(inputData, 0, inputData.Length, outputData);
}
/// <summary>
/// Encrypts the data.
/// </summary>
// ReSharper disable once InconsistentNaming
void EncryptRC4(byte[] inputData, int offset, int length, byte[] outputData)
{
length += offset;
int x = 0, y = 0;
byte b;
for (int idx = offset; idx < length; idx++)
{
x = (x + 1) & 255;
y = (_state[x] + y) & 255;
b = _state[x];
_state[x] = _state[y];
_state[y] = b;
outputData[idx] = (byte)(inputData[idx] ^ _state[(_state[x] + _state[y]) & 255]);
}
}
/// <summary>
/// Checks whether the calculated key correct.
/// </summary>
bool EqualsKey(byte[] value, int length)
{
for (int idx = 0; idx < length; idx++)
{
if (_userKey[idx] != value[idx])
return false;
}
return true;
}
/// <summary>
/// Set the hash key for the specified object.
/// </summary>
internal void SetHashKey(PdfObjectID id)
{
byte[] objectId = new byte[5];
_md5.Initialize();
// Split the object number and generation
objectId[0] = (byte)id.ObjectNumber;
objectId[1] = (byte)(id.ObjectNumber >> 8);
objectId[2] = (byte)(id.ObjectNumber >> 16);
objectId[3] = (byte)id.GenerationNumber;
objectId[4] = (byte)(id.GenerationNumber >> 8);
_md5.TransformBlock(_encryptionKey, 0, _encryptionKey.Length, _encryptionKey, 0);
_md5.TransformFinalBlock(objectId, 0, objectId.Length);
_key = _md5.Hash;
_md5.Initialize();
_keySize = _encryptionKey.Length + 5;
if (_keySize > 16)
_keySize = 16;
}
/// <summary>
/// Prepares the security handler for encrypting the document.
/// </summary>
public void PrepareEncryption()
{
//#if !SILVERLIGHT
Debug.Assert(_document._securitySettings.DocumentSecurityLevel != PdfDocumentSecurityLevel.None);
int permissions = (int)Permission;
bool strongEncryption = _document._securitySettings.DocumentSecurityLevel == PdfDocumentSecurityLevel.Encrypted128Bit;
PdfInteger vValue;
PdfInteger length;
PdfInteger rValue;
if (strongEncryption)
{
vValue = new PdfInteger(2);
length = new PdfInteger(128);
rValue = new PdfInteger(3);
}
else
{
vValue = new PdfInteger(1);
length = new PdfInteger(40);
rValue = new PdfInteger(2);
}
if (String.IsNullOrEmpty(_userPassword))
_userPassword = "";
// Use user password twice if no owner password provided.
if (String.IsNullOrEmpty(_ownerPassword))
_ownerPassword = _userPassword;
// Correct permission bits
permissions |= (int)(strongEncryption ? (uint)0xfffff0c0 : (uint)0xffffffc0);
permissions &= unchecked((int)0xfffffffc);
PdfInteger pValue = new PdfInteger(permissions);
Debug.Assert(_ownerPassword.Length > 0, "Empty owner password.");
byte[] userPad = PadPassword(_userPassword);
byte[] ownerPad = PadPassword(_ownerPassword);
_md5.Initialize();
_ownerKey = ComputeOwnerKey(userPad, ownerPad, strongEncryption);
byte[] documentID = PdfEncoders.RawEncoding.GetBytes(_document.Internals.FirstDocumentID);
InitWithUserPassword(documentID, _userPassword, _ownerKey, permissions, strongEncryption);
PdfString oValue = new PdfString(PdfEncoders.RawEncoding.GetString(_ownerKey, 0, _ownerKey.Length));
PdfString uValue = new PdfString(PdfEncoders.RawEncoding.GetString(_userKey, 0, _userKey.Length));
Elements[Keys.Filter] = new PdfName("/Standard");
Elements[Keys.V] = vValue;
Elements[Keys.Length] = length;
Elements[Keys.R] = rValue;
Elements[Keys.O] = oValue;
Elements[Keys.U] = uValue;
Elements[Keys.P] = pValue;
//#endif
}
/// <summary>
/// The global encryption key.
/// </summary>
byte[] _encryptionKey;
readonly MD5 _md5 = MD5.Create();
/// <summary>
/// Bytes used for RC4 encryption.
/// </summary>
readonly byte[] _state = new byte[256];
/// <summary>
/// The encryption key for the owner.
/// </summary>
byte[] _ownerKey = new byte[32];
/// <summary>
/// The encryption key for the user.
/// </summary>
readonly byte[] _userKey = new byte[32];
/// <summary>
/// The encryption key for a particular object/generation.
/// </summary>
byte[] _key;
/// <summary>
/// The encryption key length for a particular object/generation.
/// </summary>
int _keySize;
private IEncryptor stringEncryptor;
private IEncryptor streamEncryptor;
#endregion
internal override void WriteObject(PdfWriter writer)
{
// Don't encrypt myself.
PdfStandardSecurityHandler securityHandler = writer.SecurityHandler;
writer.SecurityHandler = null;
base.WriteObject(writer);
writer.SecurityHandler = securityHandler;
}
#region Keys
/// <summary>
/// Predefined keys of this dictionary.
/// </summary>
internal sealed new class Keys : PdfSecurityHandler.Keys
{
/// <summary>
/// (Required) A number specifying which revision of the standard security handler
/// should be used to interpret this dictionary:
/// • 2 if the document is encrypted with a V value less than 2 and does not have any of
/// the access permissions set (by means of the P entry, below) that are designated
/// "Revision 3 or greater".
/// • 3 if the document is encrypted with a V value of 2 or 3, or has any "Revision 3 or
/// greater" access permissions set.
/// • 4 if the document is encrypted with a V value of 4
/// • 5 (ExtensionLevel 3) if the document is encrypted with a V value of 5
/// </summary>
[KeyInfo(KeyType.Integer | KeyType.Required)]
public const string R = "/R";
/// <summary>
/// (Required) A string used in computing the encryption key.
/// The value of the string depends on the value of the
/// revision number, the R entry described above.
/// • The value of R is 4 or less: A 32-byte string, based on both the owner and user passwords, that is used in
/// computing the encryption key and in determining whether a valid owner password was entered.
/// • The value for R is 5: (ExtensionLevel 3) A 48-byte string, based on the owner and user passwords, that is used in
/// computing the encryption key and in determining whether a valid owner password was entered.
/// </summary>
[KeyInfo(KeyType.String | KeyType.Required)]
public const string O = "/O";
/// <summary>
/// (Required) A string based on the user password. The value
/// of the string depends on the value of the revision number, the R entry described above.
/// • The value of R is 4 or less: A 32-byte string, based on the user password, that is used in determining
/// whether to prompt the user for a password and, if so, whether a valid user or owner password was entered.
/// • The value for R is 5: (ExtensionLevel 3) A 48-byte string, based on the user password, that is used in
/// determining whether to prompt the user for a password and, if so, whether a valid user password was entered.
/// </summary>
[KeyInfo(KeyType.String | KeyType.Required)]
public const string U = "/U";
/// <summary>
/// (Required) A set of flags specifying which operations are permitted when the document
/// is opened with user access.
/// </summary>
[KeyInfo(KeyType.Integer | KeyType.Required)]
public const string P = "/P";
/// <summary>
/// (ExtensionLevel 3; required if R is 5)
/// A 32-byte string, based on the owner and user passwords, that is used in computing the encryption key.
/// </summary>
[KeyInfo(KeyType.Integer | KeyType.Optional)]
public const string OE = "/OE";
/// <summary>
/// (ExtensionLevel 3; required if R is 5)
/// A 32-byte string, based on the user password, that is used in computing the encryption key.
/// </summary>
[KeyInfo(KeyType.Integer | KeyType.Optional)]
public const string UE = "/UE";
/// <summary>
/// (ExtensionLevel 3; required if R is 5)
/// A 16-byte string, encrypted with the file encryption key, that contains an encrypted copy of the permission flags.
/// </summary>
[KeyInfo(KeyType.Integer | KeyType.Optional)]
public const string Perms = "/Perms";
/// <summary>
/// (Optional; meaningful only when the value of V is 4 or 5; PDF 1.5) Indicates whether
/// the document-level metadata stream is to be encrypted. Applications should respect this value.
/// Default value: true.
/// </summary>
[KeyInfo(KeyType.Boolean | KeyType.Optional)]
public const string EncryptMetadata = "/EncryptMetadata";
/// <summary>
/// Gets the KeysMeta for these keys.
/// </summary>
public static DictionaryMeta Meta
{
get { return _meta ?? (_meta = CreateMeta(typeof(Keys))); }
}
static DictionaryMeta _meta;
}
/// <summary>
/// Gets the KeysMeta of this dictionary type.
/// </summary>
internal override DictionaryMeta Meta
{
get { return Keys.Meta; }
}
#endregion
}
}
+275
View File
@@ -0,0 +1,275 @@
using PdfSharpCore.Pdf.Internal;
using System;
namespace PdfSharpCore.Pdf.Security
{
class RC4Encryptor : EncryptorBase, IEncryptor
{
/// <summary>
/// Bytes used for RC4 encryption.
/// </summary>
readonly byte[] state = new byte[256];
/// <summary>
/// Creates the encryption Key.
/// Based on algorithm #2 (3.2 in Extension Level 3) in the Pdf 1.7 reference (7.6.3.3)
/// </summary>
public virtual void InitEncryptionKey(string password)
{
var userPad = PadPassword(password);
md5.Initialize();
md5.TransformBlock(userPad, 0, userPad.Length, userPad, 0);
md5.TransformBlock(ownerValue, 0, ownerValue.Length, ownerValue, 0);
var permission = new byte[4];
permission[0] = (byte)pValue;
permission[1] = (byte)(pValue >> 8);
permission[2] = (byte)(pValue >> 16);
permission[3] = (byte)(pValue >> 24);
md5.TransformBlock(permission, 0, 4, permission, 0);
md5.TransformBlock(documentId, 0, documentId.Length, documentId, 0);
if (rValue >= 4 && !encryptMetadata)
{
var ff = new byte[] { 0xff, 0xff, 0xff, 0xff };
md5.TransformBlock(ff, 0, ff.Length, ff, 0);
}
md5.TransformFinalBlock(permission, 0, 0);
var hash = md5.Hash;
if (rValue >= 3)
{
for (var i = 0; i < 50; i++)
{
md5.Initialize();
hash = md5.ComputeHash(hash, 0, keyLength);
}
}
encryptionKey = new byte[keyLength];
Array.Copy(hash, encryptionKey, keyLength);
}
public bool ValidatePassword(string password)
{
// AESEncryptor sets these
if (HaveOwnerPermission || PasswordValid)
return true;
ValidateOwnerPassword(password);
if (!PasswordValid)
ValidateUserPassword(password);
return PasswordValid;
}
private void ValidateUserPassword(string password)
{
CreateUserKey(password);
PasswordValid = CompareArrays(computedUserValue, userValue, 16);
}
private void ValidateOwnerPassword(string password)
{
var pwdPad = PadPassword(password);
md5.Initialize();
var pwdKey = md5.ComputeHash(pwdPad);
if (rValue >= 3)
{
for (var i = 0; i < 50; i++)
{
pwdKey = md5.ComputeHash(pwdKey, 0, keyLength);
}
}
var n = rValue <= 2 ? 5 : keyLength;
var rc4Input = new byte[n];
Array.Copy(pwdKey, rc4Input, n);
var ov = new byte[ownerValue.Length];
Array.Copy(ownerValue, ov, ov.Length);
if (rValue < 3)
{
PrepareRC4Key(rc4Input, 0, n);
EncryptRC4(ov);
}
else
{
var xor = new byte[n];
for (var i = 0; i < 20; i++)
{
for (var j = 0; j < n; j++)
xor[j] = (byte)(rc4Input[j] ^ (19 - i));
PrepareRC4Key(xor, 0, n);
EncryptRC4(ov);
}
}
var userPass = PdfEncoders.RawEncoding.GetString(ov);
ValidateUserPassword(userPass);
if (PasswordValid)
HaveOwnerPermission = true;
}
/// <summary>
/// Pdf Reference 1.7, Chapter 7.6.3.4, Algorithm #3
/// </summary>
public void CreateOwnerKey(string password)
{
var pwdPad = PadPassword(password);
md5.Initialize();
var pwdKey = md5.ComputeHash(pwdPad);
if (rValue >= 3)
{
for (var i = 0; i < 50; i++)
pwdKey = md5.ComputeHash(pwdKey);
}
var n = rValue <= 2 ? 5 : keyLength;
var rc4Input = new byte[n];
Array.Copy(pwdKey, rc4Input, n);
if (rValue >= 3)
{
for (var i = 0; i < 20; i++)
{
for (var j = 0; j < rc4Input.Length; j++)
rc4Input[j] = (byte)(rc4Input[j] ^ i);
PrepareRC4Key(rc4Input);
EncryptRC4(pwdPad);
}
}
computedOwnerValue = new byte[n];
Array.Copy(pwdPad, computedOwnerValue, n);
}
/// <summary>
/// Pdf Reference 1.7, Chapter 7.6.3.4, Algorithm #4 and #5
/// </summary>
public void CreateUserKey(string password)
{
InitEncryptionKey(password);
if (rValue == 2)
{
var data = new byte[passwordPadding.Length];
Array.Copy(passwordPadding, data, data.Length);
PrepareRC4Key(encryptionKey);
EncryptRC4(data);
computedUserValue = new byte[data.Length];
Array.Copy(data, computedUserValue, data.Length);
}
else
{
computedUserValue = new byte[32];
md5.Initialize();
md5.TransformBlock(passwordPadding, 0, passwordPadding.Length, passwordPadding, 0);
md5.TransformFinalBlock(documentId, 0, documentId.Length);
var mkey = md5.Hash;
Array.Copy(mkey, computedUserValue, mkey.Length);
for (var i = 0; i < 20; i++)
{
for (var j = 0; j < mkey.Length; j++)
mkey[j] = (byte)(encryptionKey[j] ^ i);
PrepareRC4Key(mkey);
EncryptRC4(computedUserValue, 0, 16);
}
for (var i = 16; i < 32; i++)
computedUserValue[i] = 0;
}
}
/// <summary>
/// Pdf Reference 1.7, Chapter 7.6.2, Algorithm #1
/// </summary>
/// <param name="id"></param>
public virtual void CreateHashKey(PdfObjectID id)
{
var objectId = new byte[5];
md5.Initialize();
// Split the object number and generation
objectId[0] = (byte)id.ObjectNumber;
objectId[1] = (byte)(id.ObjectNumber >> 8);
objectId[2] = (byte)(id.ObjectNumber >> 16);
objectId[3] = (byte)id.GenerationNumber;
objectId[4] = (byte)(id.GenerationNumber >> 8);
md5.TransformBlock(encryptionKey, 0, encryptionKey.Length, encryptionKey, 0); // ?? incomplete
md5.TransformFinalBlock(objectId, 0, objectId.Length);
key = md5.Hash;
md5.Initialize();
keySize = encryptionKey.Length + 5;
if (keySize > 16)
keySize = 16;
}
public virtual byte[] Encrypt(byte[] bytes)
{
PrepareRC4Key(key);
EncryptRC4(bytes);
return bytes;
}
/// <summary>
/// Prepare the encryption key.
/// </summary>
protected void PrepareRC4Key(byte[] key)
{
PrepareRC4Key(key, 0, keySize);
}
/// <summary>
/// Prepare the encryption key.
/// </summary>
protected void PrepareRC4Key(byte[] key, int offset, int length)
{
int idx1 = 0;
int idx2 = 0;
for (int idx = 0; idx < 256; idx++)
this.state[idx] = (byte)idx;
byte tmp;
for (int idx = 0; idx < 256; idx++)
{
idx2 = (key[idx1 + offset] + this.state[idx] + idx2) & 255;
tmp = this.state[idx];
this.state[idx] = this.state[idx2];
this.state[idx2] = tmp;
idx1 = (idx1 + 1) % length;
}
}
/// <summary>
/// Encrypts the data.
/// </summary>
protected void EncryptRC4(byte[] data)
{
EncryptRC4(data, 0, data.Length, data);
}
/// <summary>
/// Encrypts the data.
/// </summary>
protected void EncryptRC4(byte[] data, int offset, int length)
{
EncryptRC4(data, offset, length, data);
}
/// <summary>
/// Encrypts the data.
/// </summary>
protected void EncryptRC4(byte[] inputData, byte[] outputData)
{
EncryptRC4(inputData, 0, inputData.Length, outputData);
}
/// <summary>
/// Encrypts the data.
/// </summary>
protected void EncryptRC4(byte[] inputData, int offset, int length, byte[] outputData)
{
length += offset;
int x = 0, y = 0;
byte b;
for (int idx = offset; idx < length; idx++)
{
x = (x + 1) & 255;
y = (this.state[x] + y) & 255;
b = this.state[x];
this.state[x] = this.state[y];
this.state[y] = b;
outputData[idx] = (byte)(inputData[idx] ^ state[(this.state[x] + this.state[y]) & 255]);
}
}
}
}
@@ -0,0 +1,53 @@
#region PDFsharp - A .NET library for processing PDF
//
// Authors:
// Stefan Lange
//
// Copyright (c) 2005-2016 empira Software GmbH, Cologne Area (Germany)
//
// http://www.PdfSharpCore.com
// http://sourceforge.net/projects/pdfsharp
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#endregion
namespace PdfSharpCore.Pdf.Security
{
/// <summary>
/// Specifies the security level of the PDF document.
/// </summary>
public enum PdfDocumentSecurityLevel
{
/// <summary>
/// Document is not protected.
/// </summary>
None,
/// <summary>
/// Document is protected with 40-bit security. This option is for compatibility with
/// Acrobat 3 and 4 only. Use Encrypted128Bit whenever possible.
/// </summary>
Encrypted40Bit,
/// <summary>
/// Document is protected with 128-bit security.
/// </summary>
Encrypted128Bit,
}
}
@@ -0,0 +1,92 @@
#region PDFsharp - A .NET library for processing PDF
//
// Authors:
// Stefan Lange
//
// Copyright (c) 2005-2016 empira Software GmbH, Cologne Area (Germany)
//
// http://www.PdfSharpCore.com
// http://sourceforge.net/projects/pdfsharp
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#endregion
using System;
namespace PdfSharpCore.Pdf.Security
{
/// <summary>
/// Specifies which operations are permitted when the document is opened with user access.
/// </summary>
[Flags]
internal enum PdfUserAccessPermission
{
/// <summary>
/// Permits everything. This is the default value.
/// </summary>
PermitAll = -3, // = 0xFFFFFFFC,
// Bit 12 Reserved; must be 0.
// Bit 3 (Revision 2) Print the document.
// (Revision 3 or greater) Print the document (possibly not at the highest
// quality level, depending on whether bit 12 is also set).
PermitPrint = 0x00000004, //1 << (3 - 1),
// Bit 4 Modify the contents of the document by operations other than
// those controlled by bits 6, 9, and 11.
PermitModifyDocument = 0x00000008, //1 << (4 - 1),
// Bit 5 (Revision 2) Copy or otherwise extract text and graphics from the
// document, including extracting text and graphics (in support of accessibility
// to users with disabilities or for other purposes).
// (Revision 3 or greater) Copy or otherwise extract text and graphics
// from the document by operations other than that controlled by bit 10.
PermitExtractContent = 0x00000010, //1 << (5 - 1),
// Bit 6 Add or modify text annotations, fill in interactive form fields, and,
// if bit 4 is also set, create or modify interactive form fields (including
// signature fields).
PermitAnnotations = 0x00000020, //1 << (6 - 1),
// Bit 78 Reserved; must be 1.
// 9 (Revision 3 or greater) Fill in existing interactive form fields (including
// signature fields), even if bit 6 is clear.
PermitFormsFill = 0x00000100, //1 << (9 - 1),
// Bit 10 (Revision 3 or greater) Extract text and graphics (in support of accessibility
// to users with disabilities or for other purposes).
PermitAccessibilityExtractContent = 0x00000200, //1 << (10 - 1),
// Bit 11 (Revision 3 or greater) Assemble the document (insert, rotate, or delete
// pages and create bookmarks or thumbnail images), even if bit 4
// is clear.
PermitAssembleDocument = 0x00000400, //1 << (11 - 1),
// Bit 12 (Revision 3 or greater) Print the document to a representation from
// which a faithful digital copy of the PDF content could be generated.
// When this bit is clear (and bit 3 is set), printing is limited to a lowlevel
// representation of the appearance, possibly of degraded quality.
// (See implementation note 24 in Appendix H.)
PermitFullQualityPrint = 0x00000800, //1 << (12 - 1),
//Bit 1332 (Revision 3 or greater) Reserved; must be 1.
}
}