Content protection and encryption in PDF

From
Revision as of 17:09, 4 April 2018 by Adminko (talk | contribs) (Certificate Encryption)
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.

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.

Check the code below:

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.SetTranslate(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 <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 to built-in encryption with custom auth, here we'll got one step further and use a custom data encryption algorithm. A variation of Caesar cipher.

Check the code below:

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.SetTranslate(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.

Unecrypted 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 this way the encrypted content gets "wrapped" by the readable non-encrypted PDF document and in case of decoding error the non-encrypted "stub" gets displayed to the user.