Content protection and encryption in PDF

From
Jump to: navigation, search

The PDF specification describes several techniques that allow the content creator to protect the document's content from unauthorized access and usage, e.g. it's possible to set owner's password limiting the access to the encrypted content and assign usage permissions which the conforming reader should respect while opening the document. But encryption and content protection in PDF go beyond than just that, and there are advanced techniques which make it even more protected and secure. Below you can find an overwiew of all available approaches which are illustrated with the code samples created using the Apitron PDF Kit and its API.

Standard Encryption

Allows the document's author to set owner and user passwords as well as define which actions would be allowed for the document. Expand to check the code sample below.

//Static class that wraps the enryption code.
public static class StandardEncryption
{
    /// <summary>
    /// Creates the pdf file using Standard Encryption.
    /// </summary>
    /// <param name="fileName">Name of the file.</param>
    /// <param name="ownerPassword">The owner's password.</param>
    /// <param name="userPassword">The user's password.</param>
    public static void CreateEncryptedFile(string fileName, string userPassword, string ownerPassword)
    {
        using (Stream stream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite))
        {
            using (FixedDocument document = new FixedDocument())
            {
                // Create Standard Security settings, disallow actions on this document
                document.SecuritySettings = new StandardSecurity(userPassword, ownerPassword, 
                    Permissions.DisallowAllPermissions, 
                    EncryptionSpecialization.EncryptionSpecialization.AllDocumentExceptMetadata);

                // Create page and add simple content
                Page page = new Page();
                document.Pages.Add(page);

                page.Content.SetTranslate(100, 750);
                TextObject text = new TextObject(StandardFonts.HelveticaBold, 20);
                text.AppendText("This document uses Standard Encryption");

                page.Content.AppendText(text);

                // Save the document
                document.Save(stream);
            }
        }
    }
}
 

This code creates a password-protected PDF document with all usage permissions disabled. Check the screenshot below.

Password-protected PDF document with custom usage permissions

Certificate Encryption

It is possible to encrypt the document's content using the custom certificate(so-called public key encryption), in this case you provide the certificate and will only be able to read the document if the same certiticate can be presented to the conforming PDF reader application. Expand to see the code below.

// This class wraps the cert encryption code
public static class CertificateEncryption
{
    /// <summary>
    /// Creates the pdf file using certificate encryption.
    /// </summary>
    /// <param name="fileName">Name of the file.</param>
    /// <param name="certificateFileName">Name of the certificate file.</param>
    /// <param name="certificatePassword">The certificate password.</param>
    public static void CreateFile(string fileName, string certificateFileName, string certificatePassword)
    {         
        using (Stream stream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite))
        {
            using (FixedDocument document = new FixedDocument())
            {
                // Create Standard Security
                CertificateSecurity certificateSecurity = new CertificateSecurity(
                    new RecipientsGroup[] 
                        {
                            new RecipientsGroup(Permissions.AllowAllPermissions, 
                            new string[] { certificateFileName }, 
                            new string[] { certificatePassword }) 
                        });
                certificateSecurity.EncryptionLevel = EncryptionLevel.AES_256bit;
                document.SecuritySettings = certificateSecurity;

                // Create page and add simple content
                Page page = new Page();
                document.Pages.Add(page);

                page.Content.SetTranslate(40, 750);
                TextObject text = new TextObject(StandardFonts.HelveticaBold, 20);
                text.AppendText("This document uses Public-Key(Certificate) Encryption");

                page.Content.AppendText(text);

                // Save the document
                document.Save(stream);
            }
        }
    }
}
 

The resulting document will encrypted using the given certificate and you'll be asked to provide it on opening.

Custom Encryption

If standard or certificate security are not enough, it is possible to implement a custom encryption scheme making the use of the custom security handlers for both encrypting and decrypting the data. This way you'll be able to control the exact way of how the user gets authorized and how the data is processed. Authentication and encryption can be implemented separately, thus you're free to use any of the standard encryption algorithms or code one yourself. Both approaches are explained in details below.

Custom authentication with standard encryption algorithm

Let's start with custom authentication, what if you just want to make sure the right person accesses the document, but are not so keen about implementing the complete encryption stack. Luckily, it's possible to use the built-in encryption support of the Apitron PDF Kit implementing only the auth part yourself. Expand the section to see the code sample.

public static class CustomEncryption
{
    /// <summary>
    /// Creates the pdf file using custom encryption.
    /// </summary>
    /// <param name="fileName">Name of the file.</param>
    /// <param name="pincode">The pincode to authorize the user.</param>
    public static void CreateFile(string fileName, string pincode)
    {
        using (Stream stream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite))
        {
            using (FixedDocument document = new FixedDocument())
            {
                // Create the CustomSecurity object implementing standard IDocumentSecurity interface,
                // set the security handler implementation responsible for the auth and encryption-related tasks
                CustomSecurity customSecurity = new CustomSecurity(new KitPincodeSecurityHandler(pincode));
                document.SecuritySettings = customSecurity;

                // Create page and add simple content
                Page page = new Page();
                document.Pages.Add(page);

                page.Content.SetTranslation(100, 750);
                TextObject text = new TextObject(StandardFonts.HelveticaBold, 20);
                text.AppendText("This document uses Custom Encryption");

                page.Content.AppendText(text);

                // Save the document
                document.Save(stream);
            }
        }
    }
}

/// <summary>
/// This class represents a simple pincode security handler.
/// </summary>
public class KitPincodeSecurityHandler : ICustomSecurityHandler
{
    private byte[] pin;

    /// <summary>
    /// Initializes a new instance of the <see cref="KitPincodeSecurityHandler" /> class.
    /// </summary>
    public KitPincodeSecurityHandler() : this(string.Empty)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="KitPincodeSecurityHandler" /> class.
    /// </summary>
    /// <param name="initialPinCode">The initial pin code.</param>
    public KitPincodeSecurityHandler(string initialPinCode)
    {
        // We use 32 bytes array to hold the pin, to match the 256 bit AES key length
        // as we will use the pin as the encryption key later. So if the initialPinCode will be
        // longer than that, it will produce an error here.
        this.pin = new byte[32];
        for (int i = 0; i < initialPinCode.Length; i++)
        {
            this.pin[i] = (byte)initialPinCode[i];
        }
    }

    /// <summary>
    /// Gets or sets the encryption level.
    /// </summary>
    /// <value>
    /// The encryption level.
    /// </value>
    public EncryptionLevel EncryptionLevel
    {
        get
        {
            return EncryptionLevel.AES_256bit;
        }

        set
        {
        }
    }

    /// <summary>
    /// Gets or sets the name of the filter. It's needed to distinguish the filters and be able to choose the correct implementation
    /// later when you read the file.
    /// </summary>
    /// <value>
    /// The name of the filter.
    /// </value>
    public string FilterName
    {
        get
        {
            return "PinCode.Simple";
        }
        set
        {
        }
    }

    /// <summary>
    /// Gets or sets the name of the security handler.
    /// </summary>
    /// <value>
    /// The name of the handler.
    /// </value>
    public string Name
    {
        get
        {
            return "Apitron.Security";
        }
        set
        {
        }
    }

    /// <summary>
    /// Authenticates the user and returns the encryption key if succeeds.
    /// This method is invoked when authentication information is being received.
    /// </summary>
    /// <param name="args">The <see cref="T:Apitron.PDF.Kit.Security.AuthEventArgs" /> instance containing 
    /// the event data that has been filled during 
    /// the <see cref="T:Apitron.PDF.Kit.Security.AuthEventHandler" /> call.
    /// </param>
    /// <param name="privateData">The private data. This data may be used as an additional information to authentificate.</param>
    /// <param name="filterName">Name of the filter.</param>
    /// <returns>
    /// The encryption key that will be used for encryption, returns null if the authentification fails.
    /// </returns>
    public byte[] Authenticate(UserDefinedAuthEventArgs args, IDictionary<string, object> privateData, string filterName)
    {
        string initialPinCode = (string)args.Properties["PINCODE"];
        this.pin = new byte[32];
        for (int i = 0; i < initialPinCode.Length; i++)
        {
            this.pin[i] = (byte)initialPinCode[i];
        }
        return (byte[])this.pin;
    }
    /// <summary>
    /// This method is invoked to initialize the encryption process.
    /// </summary>
    /// <param name="privateData">The private data. This data may be used as an additional information for authentication.</param>
    /// <param name="filterName">Name of the encryption filter.</param>
    /// <returns>
    /// The encryption key that will be used for encryption.
    /// </returns>
    public byte[] Initialize(IDictionary<string, object> privateData, string filterName)
    {
        return (byte[])this.pin;
    }
}
 

When you save this file you'll be only able to open it using the application supporting your custom encryption filter. We'll show how to create one below.

Custom authentication with own encryption algorithm

The previous article demonstrated how to use built-in encryption with custom auth, here we'll go one step further and use a custom data encryption algorithm. A variation of Caesar cipher. Expand the section to see the code sample.

public static class CustomEncryption
{
    /// <summary>
    /// Creates the pdf file using custom encryption.
    /// </summary>
    /// <param name="fileName">Name of the file.</param>
    /// <param name="pincode">The pincode to authorize the user.</param>
    public static void CreateFile(string fileName, string pincode)
    {
        using (Stream stream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite))
        {
            using (FixedDocument document = new FixedDocument())
            {
                // Create the CustomSecurity object implementing standard IDocumentSecurity interface,
                // set the security handler implementation responsible for the auth and encryption-related tasks
                CustomSecurity customSecurity = new CustomSecurity(new KitExtentedPincodeSecurityHandler(pincode));
                document.SecuritySettings = customSecurity;

                // Create page and add simple content
                Page page = new Page();
                document.Pages.Add(page);

                page.Content.SetTranslation(100, 750);
                TextObject text = new TextObject(StandardFonts.HelveticaBold, 20);
                text.AppendText("This document uses Extented Custom Encryption (Caesar cipher)");

                page.Content.AppendText(text);

                // Save the document
                document.Save(stream);
            }
        }
    }
}

/// <summary>
/// This class represents an extended pincode security handler.
/// </summary>
public class KitExtendedPincodeSecurityHandler : IExtendedCustomSecurityHandler
{
    byte[] pin;

    /// <summary>
    /// Initializes a new instance of the <see cref="KitExtendedPincodeSecurityHandler" /> class.
    /// </summary>
    /// <param name="initialPinCode">The initial pin code.</param>
    public KitExtendedPincodeSecurityHandler(string initialPinCode)
    {
        this.pin = new byte[initialPinCode.Length];
        for (int i = 0; i < initialPinCode.Length; i++)
        {
            this.pin[i] = (byte)initialPinCode[i];
        }
    }

    /// <summary>
    /// Gets or sets the encryption level. We should return None if we're using our own implementation.
    /// </summary>
    /// <value>
    /// The encryption level.
    /// </value>
    public EncryptionLevel EncryptionLevel
    {
        get
        {
            return EncryptionLevel.None;
        }

        set
        {
        }
    }

    /// <summary>
    /// Gets or sets the name of the filter. It's needed to distinguish the filters and be able to choose the correct implementation
    /// later when you read the file.
    /// </summary>
    /// <value>
    /// The name of the filter.
    /// </value>
    public string FilterName
    {
        get
        {
            return "PinCode.Extended";
        }

        set
        {
        }
    }

    /// <summary>
    /// Gets or sets the name.
    /// </summary>
    /// <value>
    /// The name.
    /// </value>
    public string Name
    {
        get
        {
            return "Apitron.Security";
        }

        set
        {
        }
    }

    /// <summary>
    /// Decrypts the specified data.
    /// </summary>
    /// <param name="data">The data.</param>
    /// <returns></returns>
    public byte[] Decrypt(byte[] data)
    {
        int length = data.Length;
        byte[] decrypted = new byte[length];
        Array.Copy(data, decrypted, length);
        for (int j = 0; j < this.pin.Length; j++)
        {
            byte shift = pin[j];
            for (int i = 0; i < length; i++)
            {
                byte current = decrypted[i];
                int shifted = current + shift;
                if (shifted > 255)
                {
                    current = (byte)(shifted - 256);
                }
                else if (shifted < 0)
                {
                    current = (byte)(256 + shifted);
                }
                else
                {
                    current = (byte)shifted;
                }
                decrypted[i] = current;
            }
        }

        return decrypted;
    }

    /// <summary>
    /// Encrypts the specified data.
    /// </summary>
    /// <param name="data">The data.</param>
    /// <returns></returns>
    public byte[] Encrypt(byte[] data)
    {
        int length = data.Length;
        byte[] encrypted = new byte[length];
        Array.Copy(data, encrypted, length);
        for (int j = this.pin.Length - 1; j >= 0; j--)
        {
            int shift = -pin[j];
            for (int i = 0; i < length; i++)
            {
                byte current = encrypted[i];
                int shifted = current + shift;
                if (shifted > 255)
                {
                    current = (byte)(shifted - 256);
                }
                else if (shifted < 0)
                {
                    current = (byte)(256 + shifted);
                }
                else
                {
                    current = (byte)shifted;
                }
                encrypted[i] = current;
            }
        }

        return encrypted;
    }

    /// <summary>
    /// Gets the encryption key. This method is invoked when auth information is being received.
    /// </summary>
    /// <param name="args">The <see cref="T:Apitron.PDF.Kit.Security.AuthEventArgs" /> instance containing the event data 
    /// that has been filled during 
    /// the <see cref="T:Apitron.PDF.Kit.Security.AuthEventHandler" /> call.
    /// </param>
    /// <param name="privateData">The private data. This data may be used as an additional information for authentication.</param>
    /// <param name="filterName">Name of the filter.</param>
    /// <returns>
    /// The encryption key that will be used for encryption, returns null if the authentification fails.
    /// </returns>
    public byte[] Authenticate(UserDefinedAuthEventArgs args, IDictionary<string, object> privateData, string filterName)
    {
        string initialPinCode = (string)args.Properties["PINCODE"];
        this.pin = new byte[initialPinCode.Length];
        for (int i = 0; i < initialPinCode.Length; i++)
        {
            this.pin[i] = (byte)initialPinCode[i];
        }
        return new byte[0];
    }

    /// <summary>
    /// This method is invoked to initialize the encryption process.
    /// </summary>
    /// <param name="privateData">The private data. This data may be used as an additional information for authentication.</param>
    /// <param name="filterName">Name of the filter.</param>
    /// <returns>
    /// The encryption key that will be used for encryption.
    /// </returns>
    public byte[] Initialize(IDictionary<string, object> privateData, string filterName)
    {
        return new byte[0];
    }
}
 

When you save this file you'll be only able to open it using the application supporting your custom encryption filter. We'll show how to create one below.

Unencrypted Wrapper

It's not possible to open the documents protected using the custom encryption handlers unless the reader application has the same implementation or module supporting the algorithm the data was encrypted with. While generally an attempt to open such document would produce an error message in conventional PDF readers, there is a way to avoid that and display the informative message to the person trying to open the document, so that he or she would be aware of the right way to open it.

This feature is called Unencrypted wrapper, and as its name suggests the encrypted content gets "wrapped" by the readable non-encrypted PDF document, so in case of decoding error the non-encrypted "stub" gets displayed to the user. Expand the section to see the code sample.

public static class UnencryptedWrapper
{
    /// <summary>
    /// Creates the unencrypted wrapper pdf document using custom encryption.
    /// </summary>
    /// <param name="fileName">Name of the file.</param>
    /// <param name="pincode">The pincode.</param>
    /// <param name="isExtended">if set to <c>true</c>  extended custom encryption will be used.</param>
    public static void CreateFile(string fileName, string pincode, bool isExtended)
    {
        using (Stream stream = new FileStream("wrapped_" + fileName, FileMode.Create, FileAccess.ReadWrite))
        {
            using (FixedDocument document = new FixedDocument())
            {
                // Create Standard Security
                CustomSecurity customSecurity = isExtended ? new CustomSecurity(new KitExtendedPincodeSecurityHandler(pincode)) : 
                    new CustomSecurity(new KitPincodeSecurityHandler(pincode));
                document.SecuritySettings = customSecurity;

                // Create page and add simple content
                Page page = new Page();
                document.Pages.Add(page);

                page.Content.SetTranslation(100, 750);
                TextObject text = new TextObject(StandardFonts.HelveticaBold, 20);
                text.AppendText(isExtended ? "This document uses Extended Custom Encryption" : "This document uses Custom Encryption");

                page.Content.AppendText(text);

                // Save the document
                document.Save(stream);
            }
        }

        using (Stream stream = File.Create(fileName))
        {
            using (FixedDocument wrapper = new FixedDocument(PdfVersion.PDF20))
            {
                Page page = new Page();
                wrapper.Pages.Add(page);

                page.Content.SetTranslation(30, 800);
                TextObject text = new TextObject(StandardFonts.HelveticaBold, 16);
                text.SetTextLeading(20);
                text.AppendTextLine("This document is an Unencrypted Wrapper please use the reader");
                text.AppendTextLine("supporting custom pin code security handler");
                page.Content.AppendText(text);

                EmbeddedFile file = new EmbeddedFile("wrapped_" + fileName, 
                    "(This embedded file is encrypted using the Apitron.Security PinCode filter)", "application/pdf");
                file.Relationship = AssociatedFileRelationship.EncryptedPayload;

                EncryptedPayload payload = new EncryptedPayload("PinCode");
                file.EncryptedPayload = payload;

                wrapper.Collection.Folder.Files.Add(file);
                wrapper.Collection.InitialDocument = file;
                wrapper.Collection.View = CollectionViewMode.Hidden;
                wrapper.AssociatedFiles.Items.Add(file);

                wrapper.Save(stream);
            }
        }
    }
}
 

Resulting document looks as follows when opened in PDF reader:

Unencrypted wrapper pdf document

How to open and read encrypted pdf documents

Let's say you have to read and process a protected document, it can be password-protected, cert-protected or even encrypted using custom security handler. In order to read it you have to pass an authentication handler implementation while creating the FixedDocument object. If you have to render the PDF document, then Apitron PDF Rasterizer library and its Document class can be used respectively to handle this task as it provides the full support for all encryption techniques defined in the PDF specification. Expand the section to see the code sample.

The code sample below uses the same custom security handlers as used in the article demonstrating the custom encryption techniques.

public static class EncryptedDocumentReader
{
    /// <summary>
    /// Opens the encrypted file and writes its pages one by one as text to console.
    /// </summary>
    /// <param name="fileName">Name of the file.</param>
    public static void ProcessFile(string fileName)
    {
        try
        {
            using (Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                using (FixedDocument document = new FixedDocument(stream, OnAuth))
                {
                    for (int i = 0; i < document.Pages.Count; i++)
                    {
                        Page page = document.Pages[i];

                        Console.WriteLine(string.Format("========== Page: {0} ==========", i));
                        Console.WriteLine(page.ExtractText(Apitron.PDF.Kit.Extraction.TextExtractionOptions.FormattedText));
                    }
                }
            }
        }
        catch (Exception e)
        {
            Console.Write(e.Message);
        }

        Console.ReadLine();
    }

    private static void OnAuth(object sender, AuthEventArgs args)
    {
        switch (args.AuthType)
        {
            case AuthType.Password:
                PasswordAuthEventArgs passwordArgs = (PasswordAuthEventArgs)args;
                Console.Write("Enter password: ");
                string password = Console.ReadLine();
                passwordArgs.Password = password;
                break;
            case AuthType.Certificate:
                CertificateAuthEventArgs certificateArgs = (CertificateAuthEventArgs)args;

                Console.WriteLine("Please see the allowed certificates to authenticate:");
                for (int i = 0; i < certificateArgs.AllowedCertificates.Length; i++)
                {
                    Console.WriteLine(certificateArgs.AllowedCertificates[i]);
                }
                Console.WriteLine();

                Console.Write("Certificate file name: ");
                string certificateFileName = Console.ReadLine();

                Console.Write("Certificate password: ");
                string certificatePassword = Console.ReadLine();

                certificateArgs.CertificateData = File.ReadAllBytes(certificateFileName);
                certificateArgs.Password = certificatePassword;
                break;
            case AuthType.UserDefined:
                UserDefinedAuthEventArgs userDefinedArgs = (UserDefinedAuthEventArgs)args;
                Console.WriteLine(string.Format("The file is encrypted using: HandlerName:{0} FilterName:{1}", 
                    userDefinedArgs.HandlerName, userDefinedArgs.FilterName));
                    
                if (userDefinedArgs.HandlerName == "Apitron.Security")
                {
                    if (userDefinedArgs.FilterName == "PinCode.Extended")
                    {
                        Console.Write("Authentification PINCODE: ");
                        string pincode = Console.ReadLine();
                        userDefinedArgs.Properties.Add("PINCODE", pincode);
                        userDefinedArgs.CustomHandler = new KitExtendedPincodeSecurityHandler(pincode);
                    }
                    else
                    {
                        Console.Write("Authentification PINCODE: ");
                        string pincode = Console.ReadLine();
                        userDefinedArgs.Properties.Add("PINCODE", pincode);
                        userDefinedArgs.CustomHandler = new KitPincodeSecurityHandler(pincode);
                    }
                }
                break;
        }
    }
}