#!/usr/bin/env python

"""
<Program Name>
  pycrypto_keys.py

<Author>
  Vladimir Diaz <vladimir.v.diaz@gmail.com>

<Started>
  October 7, 2013.

<Copyright>
  See LICENSE for licensing information.

<Purpose>
  The goal of this module is to support public-key and general-purpose
  cryptography through the PyCrypto library.  The RSA-related functions provided:
  generate_rsa_public_and_private()
  create_rsa_signature()
  verify_rsa_signature()
  create_rsa_encrypted_pem()
  create_rsa_public_and_private_from_pem()

  The general-purpose functions include:
  encrypt_key()
  decrypt_key()

  PyCrypto (i.e., the 'Crypto' package) performs the actual cryptographic
  operations and the functions listed above can be viewed as the easy-to-use
  public interface.

  https://github.com/dlitz/pycrypto
  https://en.wikipedia.org/wiki/RSA_(algorithm)
  https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
  https://en.wikipedia.org/wiki/3des
  https://en.wikipedia.org/wiki/PBKDF

  TUF key files are encrypted with the AES-256-CTR-Mode symmetric key
  algorithm.  User passwords are strengthened with PBKDF2, currently set to
  100,000 passphrase iterations.  The previous evpy implementation used 1,000
  iterations.

  PEM-encrypted RSA key files use the Triple Data Encryption Algorithm (3DES)
  and Cipher-block chaining (CBC) for the mode of operation.  Password-Based Key
  Derivation Function 1 (PBKF1) + MD5.
 """

# Help with Python 3 compatibility, where the print statement is a function, an
# implicit relative import is invalid, and the '/' operator performs true
# division.  Example:  print 'hello world' raises a 'SyntaxError' exception.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

import os
import binascii
import json

# Crypto.PublicKey (i.e., PyCrypto's public-key cryptography modules) supports
# algorithms like the Digital Signature Algorithm (DSA) and the ElGamal
# encryption system.  'Crypto.PublicKey.RSA' is needed here to generate, sign,
# and verify RSA keys.
import Crypto.PublicKey.RSA

# PyCrypto requires 'Crypto.Hash' hash objects to generate PKCS#1 PSS
# signatures (i.e., Crypto.Signature.PKCS1_PSS).
import Crypto.Hash.SHA256

# RSA's probabilistic signature scheme with appendix (RSASSA-PSS).
# PKCS#1 v1.5 is available for compatibility with existing applications, but
# RSASSA-PSS is encouraged for newer applications.  RSASSA-PSS generates
# a random salt to ensure the signature generated is probabilistic rather than
# deterministic (e.g., PKCS#1 v1.5).
# http://en.wikipedia.org/wiki/RSA-PSS#Schemes
# https://tools.ietf.org/html/rfc3447#section-8.1
import Crypto.Signature.PKCS1_PSS

# Import PyCrypto's Key Derivation Function (KDF) module.  'keys.py' needs this
# module to derive a secret key according to the Password-Based Key Derivation
# Function 2 specification.  The derived key is used as the symmetric key to
# encrypt TUF key information.  PyCrypto's implementation:
# Crypto.Protocol.KDF.PBKDF2().  PKCS#5 v2.0 PBKDF2 specification:
# http://tools.ietf.org/html/rfc2898#section-5.2
import Crypto.Protocol.KDF

# PyCrypto's AES implementation.  AES is a symmetric key algorithm that
# operates on fixed block sizes of 128-bits.
# https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
import Crypto.Cipher.AES

# 'Crypto.Random' is a cryptographically strong version of Python's standard
# "random" module.  Random bits of data is needed for salts and
# initialization vectors suitable for the encryption algorithms used in
# 'pycrypto_keys.py'.
import Crypto.Random

# The mode of operation is presently set to CTR (CounTeR Mode) for symmetric
# block encryption (AES-256, where the symmetric key is 256 bits).  PyCrypto
# provides a callable stateful block counter that can update successive blocks
# when needed.  The initial random block, or initialization vector (IV), can
# be set to begin the process of incrementing the 128-bit blocks and allowing
# the AES algorithm to perform cipher block operations on them.
import Crypto.Util.Counter

import securesystemslib.exceptions
import securesystemslib.hash
import securesystemslib.formats
import securesystemslib.util

# Extract the cryptography library settings.
import securesystemslib.settings

# Recommended RSA key sizes:
# http://www.emc.com/emc-plus/rsa-labs/historical/twirl-and-rsa-key-size.htm#table1
# According to the document above, revised May 6, 2003, RSA keys of
# size 3072 provide security through 2031 and beyond.
_DEFAULT_RSA_KEY_BITS = 3072

# The delimiter symbol used to separate the different sections
# of encrypted files (i.e., salt, iterations, hmac, IV, ciphertext).
# This delimiter is arbitrarily chosen and should not occur in
# the hexadecimal representations of the fields it is separating.
_ENCRYPTION_DELIMITER = '@@@@'

# AES key size.  Default key size = 32 bytes = AES-256.
_AES_KEY_SIZE = 32

# Default salt size, in bytes.  A 128-bit salt (i.e., a random sequence of data
# to protect against attacks that use precomputed rainbow tables to crack
# password hashes) is generated for PBKDF2.
_SALT_SIZE = 16

# Default PBKDF2 passphrase iterations.  The current "good enough" number
# of passphrase iterations.  We recommend that important keys, such as root,
# be kept offline.  'settings.PBKDF2_ITERATIONS' should increase as CPU
# speeds increase, set here at 100,000 iterations by default (in 2013).
# Repository maintainers may opt to modify the default setting according to
# their security needs and computational restrictions.  A strong user password
# is still important.  Modifying the number of iterations will result in a new
# derived key+PBDKF2 combination if the key is loaded and re-saved, overriding
# any previous iteration setting used by the old '<keyid>.key'.
# https://en.wikipedia.org/wiki/PBKDF2
_PBKDF2_ITERATIONS = securesystemslib.settings.PBKDF2_ITERATIONS


def generate_rsa_public_and_private(bits=_DEFAULT_RSA_KEY_BITS):
  """
  <Purpose>
    Generate public and private RSA keys with modulus length 'bits'.  The
    public and private keys returned conform to
    'securesystemslib.formats.PEMRSA_SCHEMA' and have the form:

    '-----BEGIN RSA PUBLIC KEY----- ...'

    or

    '-----BEGIN RSA PRIVATE KEY----- ...'

    The public and private keys are returned as strings in PEM format.

    Although PyCrypto sets a 1024-bit minimum key size,
    generate_rsa_public_and_private() enforces a minimum key size of 2048 bits.
    If 'bits' is unspecified, a 3072-bit RSA key is generated, which is the key
    size recommended by TUF.

    >>> public, private = generate_rsa_public_and_private(2048)
    >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(public)
    True
    >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(private)
    True

  <Arguments>
    bits:
      The key size, or key length, of the RSA key.  'bits' must be 2048, or
      greater, and a multiple of 256.

  <Exceptions>
    securesystemslib.exceptions.FormatError, if 'bits' does not contain the correct
    format.

    ValueError, if an exception occurs in the RSA key generation routine.
    'bits' must be a multiple of 256.  The 'ValueError' exception is raised by
    the PyCrypto key generation function.

  <Side Effects>
    The RSA keys are generated by PyCrypto's Crypto.PublicKey.RSA.generate().

  <Returns>
    A (public, private) tuple containing the RSA keys in PEM format.
  """

  # Does 'bits' have the correct format?
  # This check will ensure 'bits' conforms to
  # 'securesystemslib.formats.RSAKEYBITS_SCHEMA'.  'bits' must be an integer object,
  # with a minimum value of 2048.  Raise 'securesystemslib.exceptions.FormatError'
  # if the check fails.
  securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match(bits)

  # Generate the public and private RSA keys.  The PyCrypto module performs
  # the actual key generation.  Raise 'ValueError' if 'bits' is less than 1024
  # or not a multiple of 256, although a 2048-bit minimum is enforced by
  # securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match().
  rsa_key_object = Crypto.PublicKey.RSA.generate(bits)

  # Extract the public & private halves of the RSA key and generate their
  # PEM-formatted representations.  Return the key pair as a (public, private)
  # tuple, where each RSA is a string in PEM format.
  private = rsa_key_object.exportKey(format='PEM')
  rsa_pubkey = rsa_key_object.publickey()
  public = rsa_pubkey.exportKey(format='PEM')

  return public.decode(), private.decode()





def create_rsa_signature(private_key, data):
  """
  <Purpose>
    Generate an RSASSA-PSS signature.  The signature, and the method (signature
    algorithm) used, is returned as a (signature, method) tuple.

    The signing process will use 'private_key' and 'data' to generate the
    signature.

    RFC3447 - RSASSA-PSS
    http://www.ietf.org/rfc/rfc3447.txt

    >>> public, private = generate_rsa_public_and_private(2048)
    >>> data = 'The quick brown fox jumps over the lazy dog'.encode('utf-8')
    >>> signature, method = create_rsa_signature(private, data)
    >>> securesystemslib.formats.NAME_SCHEMA.matches(method)
    True
    >>> method == 'RSASSA-PSS'
    True
    >>> securesystemslib.formats.PYCRYPTOSIGNATURE_SCHEMA.matches(signature)
    True

  <Arguments>
    private_key:
      The private RSA key, a string in PEM format.

    data:
      Data (string) used by create_rsa_signature() to generate the signature.

  <Exceptions>
    securesystemslib.exceptions.FormatError, if 'private_key' is improperly formatted.

    TypeError, if 'private_key' is unset.

    securesystemslib.exceptions.CryptoError, if the signature cannot be generated.

  <Side Effects>
    PyCrypto's 'Crypto.Signature.PKCS1_PSS' called to generate the signature.

  <Returns>
    A (signature, method) tuple, where the signature is a string and the method
    is 'RSASSA-PSS'.
  """

  # Does 'private_key' have the correct format?
  # This check will ensure 'private_key' conforms to
  # 'securesystemslib.formats.PEMRSA_SCHEMA'.  Raise
  # 'securesystemslib.exceptions.FormatError' if the check fails.
  securesystemslib.formats.PEMRSA_SCHEMA.check_match(private_key)

  # Does 'data' have the correct format?
  securesystemslib.formats.DATA_SCHEMA.check_match(data)

  # Signing the 'data' object requires a private key.
  # The 'RSASSA-PSS' (i.e., PyCrypto module) signing method is the
  # only method currently supported.
  method = 'RSASSA-PSS'
  signature = None

  # Verify the signature, but only if the private key has been set.  The
  # private key is a NULL string if unset.  Although it may be clearer to
  # explicitly check that 'private_key' is not '', we can/should check for a
  # value and not compare identities with the 'is' keyword.  Up to this point
  # 'private_key' has variable size and can be an empty string.
  if len(private_key):
    # Calculate the SHA256 hash of 'data' and generate the hash's PKCS1-PSS
    # signature.

    # PyCrypto's expected exceptions when generating RSA key object:
    # "ValueError/IndexError/TypeError:  When the given key cannot be parsed
    # (possibly because the passphrase is wrong)."
    # If the passphrase is incorrect, PyCrypto returns: "RSA key format is not
    # supported".
    try:
      sha256_object = Crypto.Hash.SHA256.new(data)
      rsa_key_object = Crypto.PublicKey.RSA.importKey(private_key)

    except (ValueError, IndexError, TypeError) as e:
      raise securesystemslib.exceptions.CryptoError('Invalid private key or'
        ' hash data: ' + str(e))

    # Generate RSSA-PSS signature.  Raise 'securesystemslib.exceptions.CryptoError'
    # for the expected PyCrypto exceptions.
    try:
      pkcs1_pss_signer = Crypto.Signature.PKCS1_PSS.new(rsa_key_object)
      signature = pkcs1_pss_signer.sign(sha256_object)

    except ValueError: #pragma: no cover
      raise securesystemslib.exceptions.CryptoError('The RSA key too small for'
        ' given hash algorithm.')

    except TypeError:
      raise securesystemslib.exceptions.CryptoError('Missing required RSA'
        ' private key.')

    except IndexError: # pragma: no cover
      raise securesystemslib.exceptions.CryptoError('An RSA signature cannot'
        ' be generated: ' + str(e))

  else:
    raise ValueError('The required private key is unset.')

  return signature, method





def verify_rsa_signature(signature, signature_method, public_key, data):
  """
  <Purpose>
    Determine whether the corresponding private key of 'public_key' produced
    'signature'.  verify_signature() will use the public key, signature method,
    and 'data' to complete the verification.

    >>> public, private = generate_rsa_public_and_private(2048)
    >>> data = b'The quick brown fox jumps over the lazy dog'
    >>> signature, method = create_rsa_signature(private, data)
    >>> verify_rsa_signature(signature, method, public, data)
    True
    >>> verify_rsa_signature(signature, method, public, b'bad_data')
    False

  <Arguments>
    signature:
      An RSASSA PSS signature as a string.  This is the signature returned
      by create_rsa_signature().

    signature_method:
      A string that indicates the signature algorithm used to generate
      'signature'.  'RSASSA-PSS' is currently supported.

    public_key:
      The RSA public key, a string in PEM format.

    data:
      Data object used by securesystemslib.keys.create_signature() to generate
      'signature'.  'data' is needed here to verify the signature.

  <Exceptions>
    securesystemslib.exceptions.UnknownMethodError.  Raised if the signing method
    used by 'signature' is not one supported by
    securesystemslib.keys.create_signature().

    securesystemslib.exceptions.FormatError. Raised if 'signature',
    'signature_method', or 'public_key' is improperly formatted.

  <Side Effects>
    Crypto.Signature.PKCS1_PSS.verify() called to do the actual verification.

  <Returns>
    Boolean.  True if the signature is valid, False otherwise.
  """

  # Does 'public_key' have the correct format?
  # This check will ensure 'public_key' conforms to
  # 'securesystemslib.formats.PEMRSA_SCHEMA'.  Raise
  # 'securesystemslib.exceptions.FormatError' if the check fails.
  securesystemslib.formats.PEMRSA_SCHEMA.check_match(public_key)

  # Does 'signature_method' have the correct format?
  securesystemslib.formats.NAME_SCHEMA.check_match(signature_method)

  # Does 'signature' have the correct format?
  securesystemslib.formats.PYCRYPTOSIGNATURE_SCHEMA.check_match(signature)

  # Does 'data' have the correct format?
  securesystemslib.formats.DATA_SCHEMA.check_match(data)

  # Verify whether the private key of 'public_key' produced 'signature'.
  # Before returning the 'valid_signature' Boolean result, ensure 'RSASSA-PSS'
  # was used as the signing method.
  valid_signature = False

  # Verify the signature with PyCrypto if the signature method is valid,
  # otherwise raise 'securesystemslib.exceptions.UnknownMethodError'.
  if signature_method == 'RSASSA-PSS':
    try:
      rsa_key_object = Crypto.PublicKey.RSA.importKey(public_key)
      pkcs1_pss_verifier = Crypto.Signature.PKCS1_PSS.new(rsa_key_object)
      sha256_object = Crypto.Hash.SHA256.new(data)
      valid_signature = pkcs1_pss_verifier.verify(sha256_object, signature)

    except (ValueError, IndexError, TypeError):
      raise securesystemslib.exceptions.CryptoError('The RSA signature could not'
        ' be verified.')

  else:
    raise securesystemslib.exceptions.UnknownMethodError(signature_method)

  return valid_signature





def create_rsa_encrypted_pem(private_key, passphrase):
  """
  <Purpose>
    Return a string in PEM format, where the private part of the RSA key is
    encrypted.  The private part of the RSA key is encrypted by the Triple
    Data Encryption Algorithm (3DES) and Cipher-block chaining (CBC) for the
    mode of operation.  Password-Based Key Derivation Function 1 (PBKF1) + MD5
    is used to strengthen 'passphrase'.

    https://en.wikipedia.org/wiki/Triple_DES
    https://en.wikipedia.org/wiki/PBKDF2

    >>> public, private = generate_rsa_public_and_private(2048)
    >>> passphrase = 'secret'
    >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase)
    >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(encrypted_pem)
    True

  <Arguments>
    private_key:
      The private key string in PEM format.

    passphrase:
      The passphrase, or password, to encrypt the private part of the RSA
      key.  'passphrase' is not used directly as the encryption key, a stronger
      encryption key is derived from it.

  <Exceptions>
    securesystemslib.exceptions.FormatError, if the arguments are improperly formatted.

    securesystemslib.exceptions.CryptoError, if an RSA key in encrypted PEM format
    cannot be created.

    TypeError, 'private_key' is unset.

  <Side Effects>
    PyCrypto's Crypto.PublicKey.RSA.exportKey() called to perform the actual
    generation of the PEM-formatted output.

  <Returns>
    A string in PEM format, where the private RSA key is encrypted.
    Conforms to 'securesystemslib.formats.PEMRSA_SCHEMA'.
  """

  # Does 'private_key' have the correct format?
  # This check will ensure 'private_key' has the appropriate number
  # of objects and object types, and that all dict keys are properly named.
  # Raise 'securesystemslib.exceptions.FormatError' if the check fails.
  securesystemslib.formats.PEMRSA_SCHEMA.check_match(private_key)

  # Does 'passphrase' have the correct format?
  securesystemslib.formats.PASSWORD_SCHEMA.check_match(passphrase)

  # 'private_key' is in PEM format and unencrypted.  The extracted key will be
  # imported and converted to PyCrypto's RSA key object (i.e.,
  # Crypto.PublicKey.RSA).  Use PyCrypto's exportKey method, with a passphrase
  # specified, to create the string.  PyCrypto uses PBKDF1+MD5 to strengthen
  # 'passphrase', and 3DES with CBC mode for encryption.  'private_key' may
  # still be a NULL string after the 'securesystemslib.formats.PEMRSA_SCHEMA' (i.e.,
  # 'private_key' has variable size and can be an empty string.

  if len(private_key):
    try:
      rsa_key_object = Crypto.PublicKey.RSA.importKey(private_key)
      encrypted_pem = rsa_key_object.exportKey(format='PEM',
                                               passphrase=passphrase)

    except (ValueError, IndexError, TypeError) as e:
      raise securesystemslib.exceptions.CryptoError('An encrypted RSA key in PEM'
        ' format cannot be generated: ' + str(e))

  else:
    raise TypeError('The required private key is unset.')


  return encrypted_pem.decode()





def create_rsa_public_and_private_from_pem(pem, passphrase=None):
  """
  <Purpose>
    Generate public and private RSA keys from a PEM that can be encrypted.
    The public and private keys returned conform to
    'securesystemslib.formats.PEMRSA_SCHEMA' and have the form:

    '-----BEGIN RSA PUBLIC KEY----- ...'

    or

    '-----BEGIN RSA PRIVATE KEY----- ...'

    The public and private keys are returned as strings in PEM format.

    In case the private key part of 'pem' is encrypted PyCrypto's importKey
    method is passed a passphrase.  PyCrypto uses PBKDF1+MD5 to strengthen
    'passphrase', and 3DES with CBC mode for encryption/decryption.
    Alternatively, key data may be encrypted with AES-CTR-Mode and the
    passphrase strengthened with PBKDF2+SHA256.

    >>> public, private = generate_rsa_public_and_private(2048)
    >>> passphrase = 'secret'
    >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase)
    >>> returned_public, returned_private = \
    create_rsa_public_and_private_from_pem(encrypted_pem, passphrase)
    >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(returned_public)
    True
    >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(returned_private)
    True
    >>> public == returned_public
    True
    >>> private == returned_private
    True

  <Arguments>
    pem:
      A byte string in PEM format, where the private key can be encrypted.
      It has the form:

      '-----BEGIN RSA PRIVATE KEY-----\n
      Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC ...'

    passphrase: (optional)
      The passphrase, or password, to decrypt the private part of the RSA
      key.  'passphrase' is not directly used as the encryption key, instead
      it is used to derive a stronger symmetric key.

  <Exceptions>
    securesystemslib.exceptions.FormatError, if the arguments are improperly
    formatted.

    securesystemslib.exceptions.CryptoError, if the public and private RSA keys
    cannot be generated from 'encrypted_pem', or exported in PEM format.

  <Side Effects>
    PyCrypto's 'Crypto.PublicKey.RSA.importKey()' called to perform the actual
    conversion from an (encrypted) RSA private key.

  <Returns>
    A (public, private) tuple containing the RSA keys in PEM format.
  """

  # Does 'pem' have the correct format?
  # This check will ensure 'pem' has the appropriate number
  # of objects and object types, and that all dict keys are properly named.
  # Raise 'securesystemslib.exceptions.FormatError' if the check fails.
  securesystemslib.formats.PEMRSA_SCHEMA.check_match(pem)

  # Does 'passphrase' have the correct format?
  securesystemslib.formats.PASSWORD_SCHEMA.check_match(passphrase)

  # Generate a PyCrypto key object from 'encrypted_pem'.  The generated
  # PyCrypto key contains the required export methods needed to generate the
  # PEM-formatted representations of the public and private RSA key.
  try:
    rsa_key_object = Crypto.PublicKey.RSA.importKey(pem, passphrase)

  # PyCrypto's expected exceptions:
  # "ValueError/IndexError/TypeError:  When the given key cannot be parsed
  # (possibly because the passphrase is wrong)."
  # If the passphrase is incorrect, PyCrypto returns: "RSA key format is not
  # supported".
  except (ValueError, IndexError, TypeError) as e:
    # Raise 'securesystemslib.exceptions.CryptoError' and PyCrypto's exception
    # message.  Avoid propogating PyCrypto's exception trace to avoid revealing
    # sensitive error.
    raise securesystemslib.exceptions.CryptoError('RSA (public, private) tuple'
      ' cannot be generated from the encrypted PEM string: ' + str(e))

  # Export the public and private halves of the PyCrypto RSA key object.  The
  # (public, private) tuple returned contains the public and private RSA keys
  # in PEM format, as strings.
  try:
    private = rsa_key_object.exportKey(format='PEM')
    rsa_pubkey = rsa_key_object.publickey()
    public = rsa_pubkey.exportKey(format='PEM')

  # PyCrypto raises 'ValueError' if the public or private keys cannot be
  # exported.  See 'Crypto.PublicKey.RSA'.  'ValueError' should not be raised
  # if the 'Crypto.PublicKey.RSA.importKey() call above passed.
  except (ValueError): #pragma: no cover
    raise securesystemslib.exceptions.CryptoError('The public and private keys'
      ' cannot be exported in PEM format.')

  return public.decode(), private.decode()





def encrypt_key(key_object, password):
  """
  <Purpose>
    Return a string containing 'key_object' in encrypted form. Encrypted strings
    may be safely saved to a file.  The corresponding decrypt_key() function can
    be applied to the encrypted string to restore the original key object.
    'key_object' is a TUF key (e.g., RSAKEY_SCHEMA, ED25519KEY_SCHEMA).  This
    function calls the PyCrypto library to perform the encryption and derive
    a suitable encryption key.

    Whereas an encrypted PEM file uses the Triple Data Encryption Algorithm
    (3DES), the Cipher-block chaining (CBC) mode of operation, and the Password
    Based Key Derivation Function 1 (PBKF1) + MD5 to strengthen 'password',
    encrypted TUF keys use AES-256-CTR-Mode and passwords strengthened with
    PBKDF2-HMAC-SHA256 (100K iterations by default, but may be overriden in
    'settings.PBKDF2_ITERATIONS' by the user).

    http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
    http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29
    https://en.wikipedia.org/wiki/PBKDF2

    >>> ed25519_key = {'keytype': 'ed25519', \
                       'keyid': \
          'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d', \
                       'keyval': {'public': \
          '74addb5ad544a4306b34741bc1175a3613a8d7dc69ff64724243efdec0e301ad', \
                                  'private': \
          '1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}}
    >>> passphrase = 'secret'
    >>> encrypted_key = encrypt_key(ed25519_key, passphrase)
    >>> securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key.encode('utf-8'))
    True

  <Arguments>
    key_object:
      The TUF key object that should contain the private portion of the ED25519
      key.

    password:
      The password, or passphrase, to encrypt the private part of the RSA
      key.  'password' is not used directly as the encryption key, a stronger
      encryption key is derived from it.

  <Exceptions>
    securesystemslib.exceptions.FormatError, if any of the arguments are improperly
    formatted or 'key_object' does not contain the private portion of the key.

    securesystemslib.exceptions.CryptoError, if an ED25519 key in encrypted TUF
    format cannot be created.

  <Side Effects>
    PyCrypto cryptographic operations called to perform the actual encryption of
    'key_object'.  'password' used to derive a suitable encryption key.

  <Returns>
    An encrypted string in 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA' format.
  """

  # Do the arguments have the correct format?
  # Ensure the arguments have the appropriate number of objects and object
  # types, and that all dict keys are properly named.
  # Raise 'securesystemslib.exceptions.FormatError' if the check fails.
  securesystemslib.formats.ANYKEY_SCHEMA.check_match(key_object)

  # Does 'password' have the correct format?
  securesystemslib.formats.PASSWORD_SCHEMA.check_match(password)

  # Ensure the private portion of the key is included in 'key_object'.
  if not key_object['keyval']['private']:
    raise securesystemslib.exceptions.FormatError('Key object does not contain'
      ' a private part.')

  # Derive a key (i.e., an appropriate encryption key and not the
  # user's password) from the given 'password'.  Strengthen 'password' with
  # PBKDF2-HMAC-SHA256 (100K iterations by default, but may be overriden in
  # 'settings.PBKDF2_ITERATIONS' by the user).
  salt, iterations, derived_key = _generate_derived_key(password)

  # Store the derived key info in a dictionary, the object expected
  # by the non-public _encrypt() routine.
  derived_key_information = {'salt': salt, 'iterations': iterations,
                             'derived_key': derived_key}

  # Convert the key object to json string format and encrypt it with the
  # derived key.
  encrypted_key = _encrypt(json.dumps(key_object), derived_key_information)

  return encrypted_key





def decrypt_key(encrypted_key, password):
  """
  <Purpose>
    Return a string containing 'encrypted_key' in non-encrypted form.
    The decrypt_key() function can be applied to the encrypted string to restore
    the original key object, a TUF key (e.g., RSAKEY_SCHEMA, ED25519KEY_SCHEMA).
    This function calls the appropriate cryptography module (e.g.,
    pycrypto_keys.py) to perform the decryption.

    Encrypted TUF keys use AES-256-CTR-Mode and passwords strengthened with
    PBKDF2-HMAC-SHA256 (100K iterations be default, but may be overriden in
    'settings.py' by the user).

    http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
    http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29
    https://en.wikipedia.org/wiki/PBKDF2

    >>> ed25519_key = {'keytype': 'ed25519', \
                       'keyid': \
          'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d', \
                       'keyval': {'public': \
          '74addb5ad544a4306b34741bc1175a3613a8d7dc69ff64724243efdec0e301ad', \
                                  'private': \
          '1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}}
    >>> passphrase = 'secret'
    >>> encrypted_key = encrypt_key(ed25519_key, passphrase)
    >>> decrypted_key = decrypt_key(encrypted_key.encode('utf-8'), passphrase)
    >>> securesystemslib.formats.ED25519KEY_SCHEMA.matches(decrypted_key)
    True
    >>> decrypted_key == ed25519_key
    True

  <Arguments>
    encrypted_key:
      An encrypted TUF key (additional data is also included, such as salt,
      number of password iterations used for the derived encryption key, etc)
      of the form 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA'.  'encrypted_key'
      should have been generated with encrypted_key().

    password:
      The password, or passphrase, to encrypt the private part of the RSA
      key.  'password' is not used directly as the encryption key, a stronger
      encryption key is derived from it.

  <Exceptions>
    securesystemslib.exceptions.FormatError, if the arguments are improperly
    formatted.

    securesystemslib.exceptions.CryptoError, if a TUF key cannot be decrypted from
    'encrypted_key'.

    securesystemslib.exceptions.Error, if a valid TUF key object is not found in
    'encrypted_key'.

  <Side Effects>
    The PyCrypto library called to perform the actual decryption of
    'encrypted_key'.  The key derivation data stored in 'encrypted_key' is used
    to re-derive the encryption/decryption key.

  <Returns>
    The decrypted key object in 'securesystemslib.formats.ANYKEY_SCHEMA' format.
  """

  # Do the arguments have the correct format?
  # Ensure the arguments have the appropriate number of objects and object
  # types, and that all dict keys are properly named.
  # Raise 'securesystemslib.exceptions.FormatError' if the check fails.
  securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.check_match(encrypted_key)

  # Does 'password' have the correct format?
  securesystemslib.formats.PASSWORD_SCHEMA.check_match(password)

  # Decrypt 'encrypted_key', using 'password' (and additional key derivation
  # data like salts and password iterations) to re-derive the decryption key.
  json_data = _decrypt(encrypted_key, password)

  # Raise 'securesystemslib.exceptions.Error' if 'json_data' cannot be deserialized
  # to a valid 'securesystemslib.formats.ANYKEY_SCHEMA' key object.
  key_object = securesystemslib.util.load_json_string(json_data.decode())

  return key_object





def _generate_derived_key(password, salt=None, iterations=None):
  """
  Generate a derived key by feeding 'password' to the Password-Based Key
  Derivation Function (PBKDF2).  PyCrypto's PBKDF2 implementation is
  currently used.  'salt' may be specified so that a previous derived key
  may be regenerated.
  """

  if salt is None:
    salt = Crypto.Random.new().read(_SALT_SIZE)

  if iterations is None:
    iterations = _PBKDF2_ITERATIONS


  def pseudorandom_function(password, salt):
    """
    PyCrypto's PBKDF2() expects a callable function for its optional
    'prf' argument.  'prf' is set to HMAC-SHA1 (in PyCrypto's PBKDF2 function)
    by default.  'pseudorandom_function' instead sets 'prf' to HMAC-SHA256.
    """

    return Crypto.Hash.HMAC.new(password, salt, Crypto.Hash.SHA256).digest()


  # 'dkLen' is the desired key length.  'count' is the number of password
  # iterations performed by PBKDF2.  'prf' is a pseudorandom function, which
  # must be callable.
  derived_key = Crypto.Protocol.KDF.PBKDF2(password, salt,
                                           dkLen=_AES_KEY_SIZE,
                                           count=iterations,
                                           prf=pseudorandom_function)

  return salt, iterations, derived_key





def _encrypt(key_data, derived_key_information):
  """
  Encrypt 'key_data' using the Advanced Encryption Standard (AES-256) algorithm.
  'derived_key_information' should contain a key strengthened by PBKDF2.  The
  key size is 256 bits and AES's mode of operation is set to CTR (CounTeR Mode).
  The HMAC of the ciphertext is generated to ensure the ciphertext has not been
  modified.

  'key_data' is the JSON string representation of the key.  In the case
  of RSA keys, this format would be 'securesystemslib.formats.RSAKEY_SCHEMA':
  {'keytype': 'rsa',
   'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
              'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}

  'derived_key_information' is a dictionary of the form:
    {'salt': '...',
     'derived_key': '...',
     'iterations': '...'}

  'securesystemslib.exceptions.CryptoError' raised if the encryption fails.
  """

  # Generate a random initialization vector (IV).  The 'iv' is treated as the
  # initial counter block to a stateful counter block function (i.e.,
  # PyCrypto's 'Crypto.Util.Counter').  The AES block cipher operates on 128-bit
  # blocks, so generate a random 16-byte initialization block.  PyCrypto expects
  # the initial value of the stateful counter to be an integer.
  # Follow the provably secure encrypt-then-MAC approach, which affords the
  # ability to verify ciphertext without needing to decrypt it and preventing
  # an attacker from feeding the block cipher malicious data.  Modes like GCM
  # provide both encryption and authentication, whereas CTR only provides
  # encryption.
  iv = Crypto.Random.new().read(16)
  stateful_counter_128bit_blocks = Crypto.Util.Counter.new(128,
                                      initial_value=int(binascii.hexlify(iv), 16))
  symmetric_key = derived_key_information['derived_key']
  aes_cipher = Crypto.Cipher.AES.new(symmetric_key,
                                     Crypto.Cipher.AES.MODE_CTR,
                                     counter=stateful_counter_128bit_blocks)

  # Use AES-256 to encrypt 'key_data'.  The key size determines how many cycle
  # repetitions are performed by AES, 14 cycles for 256-bit keys.
  try:
    ciphertext = aes_cipher.encrypt(key_data)

  # PyCrypto does not document the exceptions that may be raised or under what
  # circumstances.  PyCrypto example given is to call encrypt() without
  # checking for exceptions.  Avoid propogating the exception trace and only
  # raise 'securesystemslib.exceptions.CryptoError', along with the cause of
  # encryption failure.
  except (ValueError, IndexError, TypeError) as e:
    raise securesystemslib.exceptions.CryptoError('The key data cannot be'
      ' encrypted: ' + str(e))

  # Generate the hmac of the ciphertext to ensure it has not been modified.
  # The decryption routine may verify a ciphertext without having to perform
  # a decryption operation.
  salt = derived_key_information['salt']
  hmac_object = Crypto.Hash.HMAC.new(symmetric_key, ciphertext,
                                     Crypto.Hash.SHA256)
  hmac = hmac_object.hexdigest()

  # Store the number of PBKDF2 iterations used to derive the symmetric key so
  # that the decryption routine can regenerate the symmetric key successfully.
  # The pbkdf2 iterations are allowed to vary for the keys loaded and saved.
  iterations = derived_key_information['iterations']

  # Return the salt, iterations, hmac, initialization vector, and ciphertext
  # as a single string.  These five values are delimited by
  # '_ENCRYPTION_DELIMITER' to make extraction easier.  This delimiter is
  # arbitrarily chosen and should not occur in the hexadecimal representations
  # of the fields it is separating.
  return binascii.hexlify(salt).decode() + _ENCRYPTION_DELIMITER + \
         str(iterations) + _ENCRYPTION_DELIMITER + \
         hmac + _ENCRYPTION_DELIMITER + \
         binascii.hexlify(iv).decode() + _ENCRYPTION_DELIMITER + \
         binascii.hexlify(ciphertext).decode()





def _decrypt(file_contents, password):
  """
  The corresponding decryption routine for _encrypt().

  'securesystemslib.exceptions.CryptoError' raised if the decryption fails.
  """

  # Extract the salt, iterations, hmac, initialization vector, and ciphertext
  # from 'file_contents'.  These five values are delimited by
  # '_ENCRYPTION_DELIMITER'.  This delimiter is arbitrarily chosen and should
  # not occur in the hexadecimal representations of the fields it is
  # separating.  Raise 'securesystemslib.exceptions.CryptoError', if 'file_contents'
  # does not contains the expected data layout.
  try:
    salt, iterations, hmac, iv, ciphertext = \
      file_contents.split(_ENCRYPTION_DELIMITER)

  except ValueError:
    raise securesystemslib.exceptions.CryptoError('Invalid encrypted file.')

  # Ensure we have the expected raw data for the delimited cryptographic data.
  salt = binascii.unhexlify(salt.encode('utf-8'))
  iterations = int(iterations)
  iv = binascii.unhexlify(iv.encode('utf-8'))
  ciphertext = binascii.unhexlify(ciphertext.encode('utf-8'))

  # Generate derived key from 'password'.  The salt and iterations are
  # specified so that the expected derived key is regenerated correctly.
  # Discard the old "salt" and "iterations" values, as we only need the old
  # derived key.
  junk_old_salt, junk_old_iterations, derived_key = \
    _generate_derived_key(password, salt, iterations)

  # Verify the hmac to ensure the ciphertext is valid and has not been altered.
  # See the encryption routine for why we use the encrypt-then-MAC approach.
  generated_hmac_object = Crypto.Hash.HMAC.new(derived_key, ciphertext,
                                               Crypto.Hash.SHA256)
  generated_hmac = generated_hmac_object.hexdigest()

  if not securesystemslib.util.digests_are_equal(generated_hmac, hmac):
    raise securesystemslib.exceptions.CryptoError('Decryption failed.')

  # The following decryption routine assumes 'ciphertext' was encrypted with
  # AES-256.
  stateful_counter_128bit_blocks = Crypto.Util.Counter.new(128,
                                      initial_value=int(binascii.hexlify(iv), 16))
  aes_cipher = Crypto.Cipher.AES.new(derived_key,
                                     Crypto.Cipher.AES.MODE_CTR,
                                     counter=stateful_counter_128bit_blocks)
  try:
    key_plaintext = aes_cipher.decrypt(ciphertext)

  # PyCrypto does not document the exceptions that may be raised or under what
  # circumstances.  PyCrypto example given is to call decrypt() without
  # checking for exceptions.  Avoid propogating the exception trace and only
  # raise 'securesystemslib.exceptions.CryptoError', along with the cause of
  # decryption failure.  Note: decryption failure, due to malicious ciphertext,
  # should not occur here if the hmac check above passed.
  except (ValueError, IndexError, TypeError) as e: # pragma: no cover
    raise securesystemslib.exceptions.CryptoError('Decryption failed: ' + str(e))

  return key_plaintext



if __name__ == '__main__':
  # The interactive sessions of the documentation strings can
  # be tested by running 'pycrypto_keys.py' as a standalone module:
  # $ python pycrypto_keys.py
  import doctest
  doctest.testmod()
