//
// Cryptographic support functions for PDFio.
//
// Copyright © 2021-2026 by Michael R Sweet.
//
// Licensed under Apache License v2.0.  See the file "LICENSE" for more
// information.
//

#include "pdfio-private.h"
#if _WIN32
#  include <windows.h>
#  include <bcrypt.h>
#  include <sys/types.h>
#  include <sys/timeb.h>
#else
#  include <sys/time.h>
#endif // _WIN32
#ifdef __has_include
#  if __has_include(<sys/random.h>)
#    define HAVE_GETRANDOM 1
#    include <sys/random.h>
#  endif // __has_include(<sys/random.h>)
#endif // __has_include


//
// PDF files can use one of several methods to encrypt a PDF file.  There is
// an owner password that controls/unlocks full editing/usage permissions and a
// user password that unlocks limited usage of the PDF.  Permissions are set
// using bits for copy, print, etc. (see the `pdfio_permission_t` enumeration).
// Passwords can be up to 32 bytes in length, with a well-known padding string
// that is applied if the string is less than 32 bytes or there is no password.
//
// > Note: PDF encryption has several design weaknesses which limit the
// > protection offered.  The V2 and V4 security handlers depend on the obsolete
// > MD5 and RC4 algorithms for key generation, and Cipher Block Chaining (CBC)
// > further weakens AES support.  Enforcement of usage permissions depends on
// > the consuming software honoring them, so if the password is known or (more
// > commonly) the user password is blank, it is possible to bypass usage
// > permissions completely.
//
// PDFio supports the following:
//
// - The original 40-bit RC4 (V2+R2) encryption for reading only
// - 128-bit RC4 (V2+R3) encryption for reading and writing
// - 128-bit AES (V4+R4) encryption for reading and writing
// - TODO: 256-bit AES (V6+R6) encryption for reading and writing
//
// Common values:
//
// - "F" is the file encryption key (40 to 256 bits/5 to 32 bytes)
// - "Fid" is the file ID string (stored in PDF file, 32 bytes)
// - "O" is the owner key (stored in PDF file, 32 bytes)
// - "Opad" is the padded owner password (32 bytes)
// - "P" is the permissions integer (stored in PDF file)
// - "P4" is the permissions integer as a 32-bit little-endian value
// - "U" is the user key (stored in PDF file, 32 bytes)
// - "Upad" is the padded user password (32 bytes)
//
// V2+R2 handler:
//
//   F = md5(Upad+O+P4+Fid)
//   O = rc4(Upad, md5(Opad))
//       (unlock with owner password)
//       Upad = rc4(O, md5(Opad))
//   U = rc4(md5(Upad+Fid)+0[16], F)
//
// V2+R3/V4+R4 handler:
//
//   F = md5(md5(Upad+O+P4+Fid))^50
//   O = rc4(Upad, md5(md5(Opad))^50)^20
//       (unlock with owner password)
//       Upad = rc4(O, md5(md5(Opad))^50)^20
//   U = rc4(md5(Upad+Fid)+0[16], F)^20
//
// V6+R6 handler:
//
//   TODO: document V6+R6 handler
//

//
// Local globals...
//

static uint8_t pdf_passpad[32] =	// Padding for passwords
{
  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
};


//
// Local functions...
//

static void	decrypt_ou_key(pdfio_encryption_t encryption, const uint8_t *file_key, size_t file_keylen, uint8_t ou_key[32]);
static void	encrypt_ou_key(pdfio_encryption_t encryption, const uint8_t *file_key, size_t file_keylen, uint8_t ou_key[32]);
static void	make_file_key(pdfio_encryption_t encryption, size_t file_keylen, pdfio_permission_t permissions, const unsigned char *file_id, size_t file_idlen, const uint8_t *user_pad, const uint8_t *owner_key, bool encrypt_metadata, uint8_t file_key[16]);
static void	make_owner_key(pdfio_encryption_t encryption, size_t file_keylen, const uint8_t *owner_pad, const uint8_t *user_pad, uint8_t owner_key[32]);
static void	make_password_key(pdfio_encryption_t encryption, size_t file_keylen, const uint8_t *pad, uint8_t *key);
static void	make_user_key(const unsigned char *file_id, size_t file_idlen, uint8_t user_key[32]);
static void	pad_password(const char *password, uint8_t pad[32]);
static bool	test_password(pdfio_encryption_t encryption, size_t file_keylen, pdfio_permission_t permisions,const unsigned char *file_id, size_t file_idlen, const uint8_t *user_pad, const uint8_t *user_key, const uint8_t *owner_key, bool encrypt_metadata, uint8_t file_key[16]);


//
// '_pdfioCryptoLock()' - Lock a PDF file by generating the encryption object and keys.
//

bool					// O - `true` on success, `false` otherwise
_pdfioCryptoLock(
    pdfio_file_t       *pdf,		// I - PDF file
    pdfio_permission_t permissions,	// I - Use permissions
    pdfio_encryption_t encryption,	// I - Type of encryption to use
    const char         *owner_password,	// I - Owner password, if any
    const char         *user_password)	// I - User password, if any
{
  pdfio_dict_t	*dict;			// Encryption dictionary
  uint8_t	owner_pad[32],		// Padded owner password
		user_pad[32],		// Padded user password
		*file_id;		// File ID bytes
  size_t	file_idlen;		// Length of file ID
  pdfio_dict_t	*cf_dict,		// CF dictionary
		*filter_dict;		// CryptFilter dictionary


  if ((dict = pdfioDictCreate(pdf)) == NULL)
  {
    _pdfioFileError(pdf, "Unable to create encryption dictionary.");
    return (false);
  }

  pdfioDictSetName(dict, "Filter", "Standard");

  switch (encryption)
  {
    case PDFIO_ENCRYPTION_RC4_128 :
    case PDFIO_ENCRYPTION_AES_128 :
        // Create the 128-bit encryption keys...
        pad_password(user_password, user_pad);
	pdf->file_keylen = 16;

        if (!owner_password && user_password && *user_password)
        {
	  // Generate a random owner password...
	  _pdfioCryptoMakeRandom(owner_pad, sizeof(owner_pad));
	}
	else
	{
	  // Use supplied owner password
	  pad_password(owner_password, owner_pad);
	}

        // Compute the owner key...
        make_owner_key(encryption, pdf->file_keylen, owner_pad, user_pad, pdf->owner_key);
	pdf->owner_keylen = 32;

        // Generate the encryption key
        file_id = pdfioArrayGetBinary(pdf->id_array, 0, &file_idlen);

        make_file_key(encryption, pdf->file_keylen, permissions, file_id, file_idlen, user_pad, pdf->owner_key, pdf->encrypt_metadata, pdf->file_key);

	// Generate the user key...
	make_user_key(file_id, file_idlen, pdf->user_key);
	encrypt_ou_key(encryption, pdf->file_key, pdf->file_keylen, pdf->user_key);
	pdf->user_keylen = 32;

	// Save everything in the dictionary...
	pdfioDictSetNumber(dict, "Length", 128);
	pdfioDictSetBinary(dict, "O", pdf->owner_key, sizeof(pdf->owner_key));
	pdfioDictSetNumber(dict, "P", (int)permissions);
	pdfioDictSetNumber(dict, "R", encryption == PDFIO_ENCRYPTION_RC4_128 ? 3 : 4);
	pdfioDictSetNumber(dict, "V", encryption == PDFIO_ENCRYPTION_RC4_128 ? 2 : 4);
	pdfioDictSetBinary(dict, "U", pdf->user_key, sizeof(pdf->user_key));

        if (encryption == PDFIO_ENCRYPTION_AES_128)
        {
	  if ((cf_dict = pdfioDictCreate(pdf)) == NULL)
	  {
	    _pdfioFileError(pdf, "Unable to create Encryption CF dictionary.");
	    return (false);
	  }

	  if ((filter_dict = pdfioDictCreate(pdf)) == NULL)
	  {
	    _pdfioFileError(pdf, "Unable to create Encryption CryptFilter dictionary.");
	    return (false);
	  }

	  pdfioDictSetName(filter_dict, "Type", "CryptFilter");
	  pdfioDictSetName(filter_dict, "CFM", "AESV2");
	  pdfioDictSetDict(cf_dict, "StdCF", filter_dict);
	  pdfioDictSetDict(dict, "CF", cf_dict);
	  pdfioDictSetName(dict, "StmF", "StdCF");
	  pdfioDictSetName(dict, "StrF", "StdCF");
	  pdfioDictSetBoolean(dict, "EncryptMetadata", true);
	}
	break;

    case PDFIO_ENCRYPTION_AES_256 :
        // TODO: Implement AES-256 (/V 6 /R 6)

    default :
        _pdfioFileError(pdf, "Encryption mode %d not supported for writing.", (int)encryption);
	return (false);
  }

  if ((pdf->encrypt_obj = pdfioFileCreateObj(pdf, dict)) == NULL)
  {
    _pdfioFileError(pdf, "Unable to create encryption object.");
    return (false);
  }

  pdfioObjClose(pdf->encrypt_obj);

  pdf->encrypt_dict = dict;
  pdf->encryption   = encryption;
  pdf->permissions  = permissions;

  return (true);
}


//
// '_pdfioCryptoMakeRandom()' - Fill a buffer with good random numbers.
//

void
_pdfioCryptoMakeRandom(uint8_t *buffer,	// I - Buffer
                       size_t  bytes)	// I - Number of bytes
{
#ifdef __APPLE__
  // macOS/iOS provide the arc4random function which is seeded with entropy
  // from the system...
  while (bytes > 0)
  {
    // Just collect 8 bits from each call to fill the buffer...
    *buffer++ = (uint8_t)arc4random();
    bytes --;
  }

#else
#  if _WIN32
  // Windows provides the CryptGenRandom function...
  HCRYPTPROV	prov;			// Cryptographic provider

  if (CryptAcquireContextA(&prov, NULL, NULL, PROV_RSA_FULL, 0))
  {
    // Got the default crypto provider, try to get random data...
    BOOL success = CryptGenRandom(prov, (DWORD)bytes, buffer);

    // Release the crypto provider and return on success...
    CryptReleaseContext(prov, 0);

    if (success)
      return;
  }

#  elif HAVE_GETRANDOM
  // Linux provides a system call called getrandom that uses system entropy ...
  ssize_t	rbytes;			// Bytes read

  while (bytes > 0)
  {
    if ((rbytes = getrandom(buffer, bytes, 0)) < 0)
    {
      if (errno != EINTR && errno != EAGAIN)
	break;
    }
    bytes -= (size_t)rbytes;
    buffer += rbytes;
  }

  if (bytes == 0)
    return;

#  else
  // Other UNIX-y systems have /dev/urandom...
  int		fd;			// Random number file
  ssize_t	rbytes;			// Bytes read


  // Fall back on /dev/urandom...
  if ((fd = open("/dev/urandom", O_RDONLY)) >= 0)
  {
    while (bytes > 0)
    {
      if ((rbytes = read(fd, buffer, bytes)) < 0)
      {
        if (errno != EINTR && errno != EAGAIN)
          break;
      }
      bytes -= (size_t)rbytes;
      buffer += rbytes;
    }

    close(fd);

    if (bytes == 0)
      return;
  }
#  endif // _WIN32

  // If we get here then we were unable to get enough random data or the local
  // system doesn't have enough entropy.  Make some up...
  uint32_t	i,			// Looping var
		mt_state[624],		// Mersenne twister state
		mt_index,		// Mersenne twister index
		temp;			// Temporary value
#  if _WIN32
  struct _timeb curtime;		// Current time

  _ftime(&curtime);
  mt_state[0] = (uint32_t)(curtime.time + curtime.millitm);

#  else
  struct timeval curtime;		// Current time

  gettimeofday(&curtime, NULL);
  mt_state[0] = (uint32_t)(curtime.tv_sec + curtime.tv_usec);
#  endif // _WIN32

  // Seed the random number state...
  mt_index = 0;

  for (i = 1; i < 624; i ++)
    mt_state[i] = (uint32_t)((1812433253 * (mt_state[i - 1] ^ (mt_state[i - 1] >> 30))) + i);

  // Fill the buffer with random numbers...
  while (bytes > 0)
  {
    if (mt_index == 0)
    {
      // Generate a sequence of random numbers...
      uint32_t i1 = 1, i397 = 397;	// Looping vars

      for (i = 0; i < 624; i ++)
      {
	temp        = (mt_state[i] & 0x80000000) + (mt_state[i1] & 0x7fffffff);
	mt_state[i] = mt_state[i397] ^ (temp >> 1);

	if (temp & 1)
	  mt_state[i] ^= 2567483615u;

	i1 ++;
	i397 ++;

	if (i1 == 624)
	  i1 = 0;

	if (i397 == 624)
	  i397 = 0;
      }
    }

    // Pull 32-bits of random data...
    temp = mt_state[mt_index ++];
    temp ^= temp >> 11;
    temp ^= (temp << 7) & 2636928640u;
    temp ^= (temp << 15) & 4022730752u;
    temp ^= temp >> 18;

    if (mt_index == 624)
      mt_index = 0;

    // Copy to the buffer...
    switch (bytes)
    {
      case 1 :
          *buffer++ = (uint8_t)(temp >> 24);
          bytes --;
          break;
      case 2 :
          *buffer++ = (uint8_t)(temp >> 24);
          *buffer++ = (uint8_t)(temp >> 16);
          bytes -= 2;
          break;
      case 3 :
          *buffer++ = (uint8_t)(temp >> 24);
          *buffer++ = (uint8_t)(temp >> 16);
          *buffer++ = (uint8_t)(temp >> 8);
          bytes -= 3;
          break;
      default :
          *buffer++ = (uint8_t)(temp >> 24);
          *buffer++ = (uint8_t)(temp >> 16);
          *buffer++ = (uint8_t)(temp >> 8);
          *buffer++ = (uint8_t)temp;
          bytes -= 4;
          break;
    }
  }
#endif // __APPLE__
}


//
// '_pdfioCryptoMakeReader()' - Setup a cryptographic context and callback for reading.
//

_pdfio_crypto_cb_t			// O  - Decryption callback or `NULL` for none
_pdfioCryptoMakeReader(
     pdfio_file_t        *pdf,		// I  - PDF file
     pdfio_obj_t         *obj,		// I  - PDF object
     _pdfio_crypto_ctx_t *ctx,		// I  - Pointer to crypto context
     uint8_t             *iv,		// I  - Buffer for initialization vector
     size_t              *ivlen)	// IO - Size of initialization vector
{
  uint8_t	data[21];		// Key data
  _pdfio_md5_t	md5;			// MD5 state
  uint8_t	digest[16];		// MD5 digest value


  PDFIO_DEBUG("_pdfioCryptoMakeReader(pdf=%p, obj=%p(%d), ctx=%p, iv=%p, ivlen=%p(%d))\n", (void *)pdf, (void *)obj, (int)obj->number, (void *)ctx, (void *)iv, (void *)ivlen, (int)*ivlen);

  // Range check input...
  if (!pdf)
  {
    *ivlen = 0;
    return (NULL);
  }

  switch (pdf->encryption)
  {
    default :
        _pdfioFileError(pdf, "Unsupported encryption algorithm.");
        *ivlen = 0;
        return (NULL);

    case PDFIO_ENCRYPTION_RC4_40 :
	// Copy the key data for the MD5 hash.
	memcpy(data, pdf->file_key, 5);
	data[5] = (uint8_t)obj->number;
	data[6] = (uint8_t)(obj->number >> 8);
	data[7] = (uint8_t)(obj->number >> 16);
	data[8] = (uint8_t)obj->generation;
	data[9] = (uint8_t)(obj->generation >> 8);

        // Hash it...
        _pdfioCryptoMD5Init(&md5);
	_pdfioCryptoMD5Append(&md5, data, 10);
	_pdfioCryptoMD5Finish(&md5, digest);

        // Initialize the RC4 context using 80 bits of the digest...
	_pdfioCryptoRC4Init(&ctx->rc4, digest, 10);
	*ivlen = 0;
	return ((_pdfio_crypto_cb_t)_pdfioCryptoRC4Crypt);

    case PDFIO_ENCRYPTION_AES_128 :
        if (*ivlen < 16)
        {
          *ivlen = 0;
          _pdfioFileError(pdf, "Value too short for AES encryption.");
          return (NULL);
        }

#ifdef __APPLE__
        __attribute__((fallthrough));
#endif // __APPLE__

    case PDFIO_ENCRYPTION_RC4_128 :
	// Copy the key data for the MD5 hash.
	memcpy(data, pdf->file_key, 16);
	data[16] = (uint8_t)obj->number;
	data[17] = (uint8_t)(obj->number >> 8);
	data[18] = (uint8_t)(obj->number >> 16);
	data[19] = (uint8_t)obj->generation;
	data[20] = (uint8_t)(obj->generation >> 8);

        // Hash it...
        _pdfioCryptoMD5Init(&md5);
	_pdfioCryptoMD5Append(&md5, data, sizeof(data));
	if (pdf->encryption == PDFIO_ENCRYPTION_AES_128)
	  _pdfioCryptoMD5Append(&md5, (const uint8_t *)"sAlT", 4);
	_pdfioCryptoMD5Finish(&md5, digest);

        // Initialize the RC4/AES context using the digest...
        if (pdf->encryption == PDFIO_ENCRYPTION_RC4_128)
        {
	  *ivlen = 0;
          _pdfioCryptoRC4Init(&ctx->rc4, digest, sizeof(digest));
          return ((_pdfio_crypto_cb_t)_pdfioCryptoRC4Crypt);
	}
	else
	{
	  *ivlen = 16;
          _pdfioCryptoAESInit(&ctx->aes, digest, sizeof(digest), iv);
          return ((_pdfio_crypto_cb_t)_pdfioCryptoAESDecrypt);
	}
  }
}


//
// '_pdfioCryptoMakeWriter()' - Setup a cryptographic context and callback for writing.
//

_pdfio_crypto_cb_t			// O  - Encryption callback or `NULL` for none
_pdfioCryptoMakeWriter(
     pdfio_file_t        *pdf,		// I  - PDF file
     pdfio_obj_t         *obj,		// I  - PDF object
     _pdfio_crypto_ctx_t *ctx,		// I  - Pointer to crypto context
     uint8_t             *iv,		// I  - Buffer for initialization vector
     size_t              *ivlen)	// IO - Size of initialization vector
{
  uint8_t	data[21];		/* Key data */
  _pdfio_md5_t	md5;			/* MD5 state */
  uint8_t	digest[16];		/* MD5 digest value */


  PDFIO_DEBUG("_pdfioCryptoMakeWriter(pdf=%p, obj=%p(%d), ctx=%p, iv=%p, ivlen=%p(%d))\n", (void *)pdf, (void *)obj, (int)obj->number, (void *)ctx, (void *)iv, (void *)ivlen, (int)*ivlen);

  // Range check input...
  if (!pdf)
  {
    *ivlen = 0;
    return (NULL);
  }

  switch (pdf->encryption)
  {
    default :
        *ivlen = 0;
        return (NULL);

    case PDFIO_ENCRYPTION_RC4_128 :
    case PDFIO_ENCRYPTION_AES_128 :
	// Copy the key data for the MD5 hash.
	memcpy(data, pdf->file_key, sizeof(pdf->file_key));
	data[16] = (uint8_t)obj->number;
	data[17] = (uint8_t)(obj->number >> 8);
	data[18] = (uint8_t)(obj->number >> 16);
	data[19] = (uint8_t)obj->generation;
	data[20] = (uint8_t)(obj->generation >> 8);

        // Hash it...
        _pdfioCryptoMD5Init(&md5);
	_pdfioCryptoMD5Append(&md5, data, sizeof(data));
	if (pdf->encryption == PDFIO_ENCRYPTION_AES_128)
	  _pdfioCryptoMD5Append(&md5, (const uint8_t *)"sAlT", 4);
	_pdfioCryptoMD5Finish(&md5, digest);

        // Initialize the RC4/AES context using the digest...
        if (pdf->encryption == PDFIO_ENCRYPTION_RC4_128)
        {
	  *ivlen = 0;
          _pdfioCryptoRC4Init(&ctx->rc4, digest, sizeof(digest));
          return ((_pdfio_crypto_cb_t)_pdfioCryptoRC4Crypt);
	}
	else
	{
	  *ivlen = 16;
	  _pdfioCryptoMakeRandom(iv, *ivlen);
          _pdfioCryptoAESInit(&ctx->aes, digest, sizeof(digest), iv);
          return ((_pdfio_crypto_cb_t)_pdfioCryptoAESEncrypt);
	}
  }
}


//
// '_pdfioCryptoUnlock()' - Unlock an encrypted PDF.
//

bool					// O - `true` on success, `false` otherwise
_pdfioCryptoUnlock(
    pdfio_file_t        *pdf,		// I - PDF file
    pdfio_password_cb_t password_cb,	// I - Password callback or `NULL` for none
    void                *password_data)	// I - Password callback data, if any
{
  int		tries;			// Number of tries
  const char	*password = NULL;	// Password to try
  int		version,		// Version value
		revision,		// Revision value
		length;			// Key length value
  const char	*handler,		// Security handler name
		*stream_filter,		// Stream encryption filter
		*string_filter;		// String encryption filter
  pdfio_dict_t	*cf_dict;		// CryptFilters dictionary
  unsigned char	*owner_key,		// Owner key
		*user_key,		// User key
		*file_id;		// File ID value
  size_t	owner_keylen,		// Length of owner key
		user_keylen,		// Length of user key
		file_idlen;		// Length of file ID
  _pdfio_md5_t	md5;			// MD5 context
  uint8_t	file_digest[16];	// MD5 digest of file ID and pad
  double	p;			// Permissions value as a double
  _pdfio_value_t *value;		// Encrypt dictionary value, if any


  // See if we support the type of encryption specified by the Encrypt
  // dictionary...
  handler  = pdfioDictGetName(pdf->encrypt_dict, "Filter");
  version  = (int)pdfioDictGetNumber(pdf->encrypt_dict, "V");
  revision = (int)pdfioDictGetNumber(pdf->encrypt_dict, "R");
  length   = (int)pdfioDictGetNumber(pdf->encrypt_dict, "Length");

  if ((value = _pdfioDictGetValue(pdf->encrypt_dict, "EncryptMetadata")) != NULL && value->type == PDFIO_VALTYPE_BOOLEAN)
    pdf->encrypt_metadata = value->value.boolean;
  else
    pdf->encrypt_metadata = true;

  PDFIO_DEBUG("_pdfioCryptoUnlock: handler=%p(%s), version=%d, revision=%d, length=%d, encrypt_metadata=%s\n", (void *)handler, handler ? handler : "(null)", version, revision, length, pdf->encrypt_metadata ? "true" : "false");

  if (!handler || strcmp(handler, "Standard"))
  {
    _pdfioFileError(pdf, "Unsupported security handler '%s'.", handler ? handler : "(null)");
    return (false);
  }

  if (version == 4 && revision == 4)
  {
    // Lookup crypt filter to see if we support it...
    pdfio_dict_t *filter;		// Crypt Filter
    const char	*cfm;			// Crypt filter method

    stream_filter = pdfioDictGetName(pdf->encrypt_dict, "StmF");
    string_filter = pdfioDictGetName(pdf->encrypt_dict, "StrF");
    cf_dict       = pdfioDictGetDict(pdf->encrypt_dict, "CF");

    if (!cf_dict)
    {
      _pdfioFileError(pdf, "Missing encryption filter dictionary.");
      return (false);
    }
    else if (!stream_filter)
    {
      _pdfioFileError(pdf, "Missing stream encryption filter.");
      return (false);
    }
    else if (!string_filter)
    {
      _pdfioFileError(pdf, "Missing string encryption filter.");
      return (false);
    }
    else if (strcmp(stream_filter, string_filter))
    {
      _pdfioFileError(pdf, "Different stream and string encryption filters - not supported.");
      return (false);
    }
    else if ((filter = pdfioDictGetDict(cf_dict, stream_filter)) == NULL)
    {
      _pdfioFileError(pdf, "Missing stream encryption filter '%s'.", stream_filter);
      return (false);
    }
    else if ((cfm = pdfioDictGetName(filter, "CFM")) == NULL)
    {
      _pdfioFileError(pdf, "Missing encryption filter method.");
      return (false);
    }
    else
    {
      PDFIO_DEBUG("_pdfioCryptoUnlock: CFM=\"%s\"\n", cfm);

      if (length < 40 || length > 128)
	length = 128;			// Default to 128 bits

      if (!strcmp(cfm, "V2"))
        pdf->encryption = PDFIO_ENCRYPTION_RC4_128;
      else if (!strcmp(cfm, "AESV2"))
        pdf->encryption = PDFIO_ENCRYPTION_AES_128;
    }
  }
  else if (version == 1 || version == 2)
  {
    if (revision == 2)
    {
      pdf->encryption = PDFIO_ENCRYPTION_RC4_40;
      length = 40;
    }
    else if (revision == 3)
    {
      pdf->encryption = PDFIO_ENCRYPTION_RC4_128;
      if (length < 40 || length > 128)
        length = 128;
    }
  }
  else if (version == 6 && revision == 6)
  {
    // TODO: Implement AES-256 - V6 R6
    pdf->encryption = PDFIO_ENCRYPTION_AES_256;
    length          = 256;
  }

  PDFIO_DEBUG("_pdfioCryptoUnlock: encryption=%d, length=%d\n", pdf->encryption, length);

  if (pdf->encryption == PDFIO_ENCRYPTION_NONE)
  {
    _pdfioFileError(pdf, "Unsupported encryption V%d R%d.", version, revision);
    return (false);
  }
  else if (length < 40 || length > 128)
  {
    _pdfioFileError(pdf, "Unsupported key length %d.", length);
    return (false);
  }

  // Grab the remaining values we need to unlock the PDF...
  pdf->file_keylen = (size_t)(length / 8);

  p = pdfioDictGetNumber(pdf->encrypt_dict, "P");
  PDFIO_DEBUG("_pdfioCryptoUnlock: P=%.0f\n", p);
  if (p < 0x7fffffff)			// Handle integers > 2^31-1
    pdf->permissions = (pdfio_permission_t)p;
  else
    pdf->permissions = (pdfio_permission_t)(p - 4294967296.0);
  PDFIO_DEBUG("_pdfioCryptoUnlock: permissions=%d\n", pdf->permissions);

  owner_key = pdfioDictGetBinary(pdf->encrypt_dict, "O", &owner_keylen);
  user_key  = pdfioDictGetBinary(pdf->encrypt_dict, "U", &user_keylen);

  if (!owner_key)
  {
    _pdfioFileError(pdf, "Missing owner key, unable to unlock file.");
    return (false);
  }
  else if (owner_keylen < 32 || owner_keylen > sizeof(pdf->owner_key))
  {
    _pdfioFileError(pdf, "Bad %d bytes owner key, unable to unlock file.", (int)owner_keylen);
    return (false);
  }

  PDFIO_DEBUG("_pdfioCryptoUnlock: owner_key[%d]=%02X%02X%02X%02X...%02X%02X%02X%02X\n", (int)owner_keylen, owner_key[0], owner_key[1], owner_key[2], owner_key[3], owner_key[28], owner_key[29], owner_key[30], owner_key[31]);

  memcpy(pdf->owner_key, owner_key, owner_keylen);
  pdf->owner_keylen = owner_keylen;

  if (!user_key)
  {
    _pdfioFileError(pdf, "Missing user key, unable to unlock file.");
    return (false);
  }
  else if (user_keylen < 32 || user_keylen > sizeof(pdf->user_key))
  {
    _pdfioFileError(pdf, "Bad %d byte user key, unable to unlock file.", (int)user_keylen);
    return (false);
  }

  PDFIO_DEBUG("_pdfioCryptoUnlock: user_key[%d]=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", (int)user_keylen, user_key[0], user_key[1], user_key[2], user_key[3], user_key[28], user_key[29], user_key[30], user_key[31]);

  memcpy(pdf->user_key, user_key, user_keylen);
  pdf->user_keylen = user_keylen;

  if ((file_id = pdfioArrayGetBinary(pdf->id_array, 0, &file_idlen)) == NULL || file_idlen < 16)
  {
    _pdfioFileError(pdf, "Missing or bad file ID, unable to unlock file.");
    return (false);
  }

  PDFIO_DEBUG("_pdfioCryptoUnlock: P=%d\n", pdf->permissions);
  PDFIO_DEBUG("_pdfioCryptoUnlock: file_id(%d)=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", (int)file_idlen, file_id[0], file_id[1], file_id[2], file_id[3], file_id[12], file_id[13], file_id[14], file_id[15]);

  // Generate a base hash from known values...
  _pdfioCryptoMD5Init(&md5);
  _pdfioCryptoMD5Append(&md5, pdf_passpad, 32);
  _pdfioCryptoMD5Append(&md5, file_id, file_idlen);
  _pdfioCryptoMD5Finish(&md5, file_digest);

  // Now try to unlock the PDF...
  for (tries = 0; tries < 4; tries ++)
  {
    if (pdf->encryption <= PDFIO_ENCRYPTION_AES_128)
    {
      // RC4 and AES-128 encryption...
      uint8_t	pad[32],		// Padded password
		padkey[16],		// Password key
		file_key[16];		// File key

      // Pad the supplied password, if any...
      pad_password(password, pad);

      // Test the user password...
      PDFIO_DEBUG("_pdfioCryptoUnlock: Trying <%02X%02X%02X%02X...%02X%02X%02X%02X>\n", pad[0], pad[1], pad[2], pad[3], pad[28], pad[29], pad[30], pad[31]);

      if (test_password(pdf->encryption, pdf->file_keylen, pdf->permissions, file_id, file_idlen, pad, pdf->user_key, pdf->owner_key, pdf->encrypt_metadata, file_key))
      {
        memcpy(pdf->file_key, file_key, pdf->file_keylen);
        memcpy(pdf->password, pad, sizeof(pdf->password));

        return (true);
      }

      // Test the owner password...
      make_password_key(pdf->encryption, pdf->file_keylen, pad, padkey);

      memcpy(pad, pdf->owner_key, sizeof(pad));
      decrypt_ou_key(pdf->encryption, padkey, pdf->file_keylen, pad);

      if (test_password(pdf->encryption, pdf->file_keylen, pdf->permissions, file_id, file_idlen, pad, pdf->user_key, pdf->owner_key, pdf->encrypt_metadata, file_key))
      {
        memcpy(pdf->file_key, file_key, pdf->file_keylen);
        memcpy(pdf->password, pad, sizeof(pdf->password));

        return (true);
      }
    }
    else
    {
      // TODO: Implement AES-256 security handler
      _pdfioFileError(pdf, "Unable to unlock AES-256 encrypted file at this time.");
      return (false);
    }

    // If we get here we need to try another password...
    if (password_cb)
      password = (password_cb)(password_data, pdf->filename);

    if (!password)
      break;
  }

  _pdfioFileError(pdf, "Unable to unlock PDF file.");

  return (false);
}


//
// 'decrypt_ou_key()' - Decrypt the user key.
//

static void
decrypt_ou_key(
    pdfio_encryption_t encryption,	// I  - Type of encryption
    const uint8_t      *file_key,	// I  - File encryption key
    size_t             file_keylen,	// I  - Length of file encryption key
    uint8_t            ou_key[32])	// IO - Owner/User key
{
  size_t	i, j;			// Looping vars
  _pdfio_rc4_t	rc4;			// RC4 encryption context


  if (encryption == PDFIO_ENCRYPTION_RC4_40)
  {
    // Encrypt the result once...
    _pdfioCryptoRC4Init(&rc4, file_key, 5);
    _pdfioCryptoRC4Crypt(&rc4, ou_key, ou_key, 32, /*last*/true);
  }
  else
  {
    // Encrypt the result 20 times...
    uint8_t	key[16];		// Current encryption key

    for (i = 20; i > 0;)
    {
      // XOR each byte in the key with the loop counter...
      i --;

      for (j = 0; j < file_keylen; j ++)
	key[j] = (uint8_t)(file_key[j] ^ i);

      _pdfioCryptoRC4Init(&rc4, key, file_keylen);
      _pdfioCryptoRC4Crypt(&rc4, ou_key, ou_key, 32, /*last*/true);
    }
  }
}


//
// 'encrypt_ou_key()' - Encrypt the owner/user key.
//

static void
encrypt_ou_key(
    pdfio_encryption_t encryption,	// I  - Type of encryption
    const uint8_t      *file_key,	// I  - File encryption key
    size_t             file_keylen,	// I  - Length of file encryption key
    uint8_t            ou_key[32])	// IO - Owner/User key
{
  size_t	i, j;			// Looping vars
  _pdfio_rc4_t	rc4;			// RC4 encryption context


  if (encryption == PDFIO_ENCRYPTION_RC4_40)
  {
    // Encrypt the result once...
    _pdfioCryptoRC4Init(&rc4, file_key, 5);
    _pdfioCryptoRC4Crypt(&rc4, ou_key, ou_key, 32, /*last*/true);
  }
  else
  {
    // Encrypt the result 20 times...
    uint8_t	key[16];		// Current encryption key

    for (i = 0; i < 20; i ++)
    {
      // XOR each byte in the key with the loop counter...
      for (j = 0; j < file_keylen; j ++)
	key[j] = (uint8_t)(file_key[j] ^ i);

      _pdfioCryptoRC4Init(&rc4, key, file_keylen);
      _pdfioCryptoRC4Crypt(&rc4, ou_key, ou_key, 32, /*last*/true);
    }
  }
}


//
// 'make_file_key()' - Make the file encryption key.
//

static void
make_file_key(
    pdfio_encryption_t  encryption,	// I - Type of encryption
    size_t              file_keylen,	// I - Encryption key length
    pdfio_permission_t  permissions,	// I - File permissions
    const unsigned char *file_id,	// I - File ID value
    size_t              file_idlen,	// I - Length of file ID
    const uint8_t       *user_pad,	// I - Padded user password
    const uint8_t       *owner_key,	// I - Owner key
    bool                encrypt_metadata,
					// I - Encrypt metadata?
    uint8_t             file_key[16])	// O - Encryption key
{
  size_t	i;			// Looping var
  uint8_t	perm_bytes[4];		// Permissions bytes
  _pdfio_md5_t	md5;			// MD5 context
  uint8_t	digest[16];		// 128-bit MD5 digest


  perm_bytes[0] = (uint8_t)permissions;
  perm_bytes[1] = (uint8_t)(permissions >> 8);
  perm_bytes[2] = (uint8_t)(permissions >> 16);
  perm_bytes[3] = (uint8_t)(permissions >> 24);

  PDFIO_DEBUG("make_file_key: user_pad[32]=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", user_pad[0], user_pad[1], user_pad[2], user_pad[3], user_pad[28], user_pad[29], user_pad[30], user_pad[31]);
  PDFIO_DEBUG("make_file_key: owner_key[32]=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", owner_key[0], owner_key[1], owner_key[2], owner_key[3], owner_key[28], owner_key[29], owner_key[30], owner_key[31]);
  PDFIO_DEBUG("make_file_key: permissions(%d)=<%02X%02X%02X%02X>\n", permissions, perm_bytes[0], perm_bytes[1], perm_bytes[2], perm_bytes[3]);

  _pdfioCryptoMD5Init(&md5);
  _pdfioCryptoMD5Append(&md5, user_pad, 32);
  _pdfioCryptoMD5Append(&md5, owner_key, 32);
  _pdfioCryptoMD5Append(&md5, perm_bytes, 4);
  _pdfioCryptoMD5Append(&md5, file_id, file_idlen);
  if (!encrypt_metadata)
  {
    uint8_t meta_bytes[4] = { 0xff, 0xff, 0xff, 0xff };
					// Metadata bytes
    _pdfioCryptoMD5Append(&md5, meta_bytes, 4);
  }
  _pdfioCryptoMD5Finish(&md5, digest);

  PDFIO_DEBUG("make_file_key: first md5=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", digest[0], digest[1], digest[2], digest[3], digest[12], digest[13], digest[14], digest[15]);

  if (encryption != PDFIO_ENCRYPTION_RC4_40)
  {
    // MD5 the result 50 times..
    for (i = 0; i < 50; i ++)
    {
      _pdfioCryptoMD5Init(&md5);
      _pdfioCryptoMD5Append(&md5, digest, file_keylen);
      _pdfioCryptoMD5Finish(&md5, digest);
    }
  }

  PDFIO_DEBUG("make_file_key: file_key[16]=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", digest[0], digest[1], digest[2], digest[3], digest[12], digest[13], digest[14], digest[15]);

  memcpy(file_key, digest, file_keylen);
}


//
// 'make_owner_key()' - Generate the (encrypted) owner key...
//

static void
make_owner_key(
    pdfio_encryption_t encryption,	// I - Type of encryption
    size_t             file_keylen,	// I - Length of encryption key
    const uint8_t      *owner_pad,	// I - Padded owner password
    const uint8_t      *user_pad,	// I - Padded user password
    uint8_t            owner_key[32])	// O - Owner key value
{
  uint8_t	key[16];		// Base encryption key


  // Hash the owner password...
  make_password_key(encryption, file_keylen, owner_pad, key);

  // Copy and encrypt the padded user password...
  memcpy(owner_key, user_pad, 32);
  encrypt_ou_key(encryption, key, file_keylen, owner_key);
}


//
// 'make_password_key()' - Make a password key.
//

static void
make_password_key(
    pdfio_encryption_t encryption,	// I - Type of encryption
    size_t             file_keylen,	// I - Length of encryption key
    const uint8_t      *pad,		// I - Padded password
    uint8_t            *key)		// O - Key data
{
  size_t	i;			// Looping var
  _pdfio_md5_t	md5;			// MD5 context
  uint8_t	digest[16];		// 128-bit MD5 digest


  // Hash the padded password...
  _pdfioCryptoMD5Init(&md5);
  _pdfioCryptoMD5Append(&md5, pad, 32);
  _pdfioCryptoMD5Finish(&md5, digest);

  if (encryption != PDFIO_ENCRYPTION_RC4_40)
  {
    // Hash the hash another 50 times...
    for (i = 0; i < 50; i ++)
    {
      _pdfioCryptoMD5Init(&md5);
      _pdfioCryptoMD5Append(&md5, digest, file_keylen);
      _pdfioCryptoMD5Finish(&md5, digest);
    }
  }

  // Copy the key portion of the hashed password to the key...
  memcpy(key, digest, file_keylen);
}


//
// 'make_user_key()' - Make the user key.
//

static void
make_user_key(
    const unsigned char *file_id,	// I - File ID value
    size_t              file_idlen,	// I - Length of file ID
    uint8_t             user_key[32])	// O - User key
{
  _pdfio_md5_t	md5;			// MD5 context


  // Generate a base hash from known values...
  _pdfioCryptoMD5Init(&md5);
  _pdfioCryptoMD5Append(&md5, pdf_passpad, 32);
  _pdfioCryptoMD5Append(&md5, file_id, file_idlen);
  _pdfioCryptoMD5Finish(&md5, user_key);

  memset(user_key + 16, 0, 16);
}


//
// 'pad_password()' - Generate a padded password.
//

static void
pad_password(const char *password,	// I - Password string or `NULL`
             uint8_t    pad[32])	// O - Padded password
{
  size_t	len;			// Length of password


  if (password)
  {
    // Use the specified password
    if ((len = strlen(password)) > 32)
      len = 32;
  }
  else
  {
    // No password
    len = 0;
  }

  if (len > 0)
    memcpy(pad, password, len);
  if (len < 32)
    memcpy(pad + len, pdf_passpad, 32 - len);
}


//
// 'test_password()' - Test a user password...
//

static bool				// O - `true` if password is correct, `false` otherwise
test_password(
    pdfio_encryption_t  encryption,	// I - Type of encryption
    size_t              file_keylen,	// I - Length of encryption key
    pdfio_permission_t  permissions,	// I - File permissions
    const unsigned char *file_id,	// I - File ID value
    size_t              file_idlen,	// I - Length of file ID
    const uint8_t       *user_pad,	// I - Padded user password
    const uint8_t	*user_key,	// I - User key
    const uint8_t       *owner_key,	// I - Owner key
    bool                encrypt_metadata,
					// I - Encrypt metadata?
    uint8_t             file_key[16])	// O - Encryption key
{
  uint8_t	pdf_user_key[32];	// Decrypted user key


  // Make the file key...
  make_file_key(encryption, file_keylen, permissions, file_id, file_idlen, user_pad, owner_key, encrypt_metadata, file_key);

  // Decrypt the user key using the file key...
  memcpy(pdf_user_key, user_key, sizeof(pdf_user_key));
  decrypt_ou_key(encryption, file_key, file_keylen, pdf_user_key);
  PDFIO_DEBUG("test_password: pdf_user_key[32]=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", pdf_user_key[0], pdf_user_key[1], pdf_user_key[2], pdf_user_key[3], pdf_user_key[28], pdf_user_key[29], pdf_user_key[30], pdf_user_key[31]);

  if (encryption == PDFIO_ENCRYPTION_RC4_40 && !memcmp(pdf_user_key, pdf_passpad, 32))
  {
    // 40-bit encryption just compares the eecrypted user key matches...
    return (true);
  }
  else
  {
    // 128-bit encryption needs to match the calculated user key...
    uint8_t	own_user_key[32];	// Calculated user key

    // Calculate what the user key should be...
    make_user_key(file_id, file_idlen, own_user_key);
    PDFIO_DEBUG("test_password: own_user_key[32]=<%02X%02X%02X%02X...%02X%02X%02X%02X>\n", own_user_key[0], own_user_key[1], own_user_key[2], own_user_key[3], own_user_key[28], own_user_key[29], own_user_key[30], own_user_key[31]);

    // Return whether they match...
    return (!memcmp(pdf_user_key, own_user_key, 16));
  }
}
