You are here

aes.module in AES encryption 7

Same filename and directory in other branches
  1. 8.2 aes.module
  2. 5 aes.module
  3. 6 aes.module

Main file of the AES encryption module.

File

aes.module
View source
<?php

/**
 * @file
 *
 * Main file of the AES encryption module.
 */
define("AES_PASSWORD_MAX_LENGTH", 128);

// Set phpseclib to use its internal implementation even if mcrypt is available.
define("CRYPT_AES_MODE", 1);

/**
 * Implements hook_menu().
 */
function aes_menu() {
  $items = array();
  $items['admin/config/system/aes'] = array(
    'title' => 'AES settings',
    'description' => 'Configure the AES encryption module.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'aes_config',
    ),
    'access arguments' => array(
      'administer aes',
    ),
    'file' => 'aes.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  $items['user/%/password'] = array(
    'title' => 'View password',
    'page callback' => 'aes_get_password',
    'page arguments' => array(
      1,
      TRUE,
    ),
    'access callback' => 'aes_show_password_page',
    'access arguments' => array(
      'view passwords',
      1,
    ),
    'type' => MENU_LOCAL_TASK,
  );
  $items['aes_get_password_callback/%'] = array(
    'title' => 'User\'s password',
    'page callback' => 'aes_ajax_callback',
    'page arguments' => array(
      1,
    ),
    'access arguments' => array(
      'view passwords',
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_permission().
 */
function aes_permission() {
  return array(
    'administer aes' => array(
      'title' => t('Administer AES'),
      'description' => t('Administer AES module.'),
    ),
    'view passwords' => array(
      'title' => t('View passwords'),
      'description' => t('View user password in plain text.'),
      'restrict access' => TRUE,
    ),
  );
}

/**
 * Loads phpseclib.
 *
 * @return bool|int
 *   TRUE on success, negative error code if something went wrong.
 */
function aes_load_phpsec() {

  // Find out where this module is located and set up an include path for the
  // phpsec library.
  if (module_exists('libraries') && libraries_get_path('phpseclib')) {
    $phpsec_include_path = libraries_get_path('phpseclib');
  }
  else {
    $phpsec_include_path = dirname(__FILE__) . '/phpseclib';
  }

  // Include phpsec AES lib.
  if (file_exists($phpsec_include_path . '/Crypt/AES.php') === FALSE) {
    return -2;
  }
  if (is_readable($phpsec_include_path . '/Crypt/AES.php') === FALSE) {
    drupal_set_message(t("It appears that phpseclib is installed in the right location but can't be read. Check that the permissions on the directory and its files allows for reading by the webserver."));
    return -3;
  }
  if (function_exists("set_include_path") == FALSE) {

    // If we don't have set_include_path then we're out of luck.
    return -1;
  }
  set_include_path(get_include_path() . PATH_SEPARATOR . $phpsec_include_path);
  include_once 'Crypt/AES.php';
  return TRUE;
}

/**
 * Access callback for a separate tab page with password.
 *
 * @param $access
 *   Permission name.
 * @param $uid
 *   User ID.
 * @return bool
 *   Is access granted? So should tab page be generated or not.
 */
function aes_show_password_page($access, $uid) {
  $viewing_method = variable_get("aes_viewing_method", "page");
  return user_access($access) && aes_password_exists($uid) && ($viewing_method == "page" || $viewing_method == "both");
}

/**
 * Implements hook_user_view().
 */
function aes_user_view($account, $view_mode, $langcode) {
  $viewing_method = variable_get("aes_viewing_method", "page");
  if (user_access('view passwords') && ($viewing_method == "inplace" || $viewing_method == "both") && variable_get("aes_convert", FALSE)) {
    if (aes_password_exists($account->uid)) {
      drupal_add_library('system', 'drupal.ajax');
      $link = l(t('Show password'), 'aes_get_password_callback/' . $account->uid . '/nojs/', array(
        'attributes' => array(
          'class' => array(
            'use-ajax',
          ),
        ),
      ));
      $account->content['info']['aes_password'] = array(
        '#type' => 'user_profile_item',
        '#title' => t('AES Encryption'),
        '#markup' => '<div id="aes_password">' . $link . '</div>',
      );
    }
    else {
      $account->content['info']['aes_password'] = array(
        '#type' => 'user_profile_item',
        '#title' => t('AES Encryption'),
        '#markup' => t("This user's password does not yet exist in AES encrypted form."),
      );
    }
  }
}

/**
 * Callback for user's password ajax link.
 *
 * Takes different logic paths based on whether Javascript was enabled.
 * If $type == 'ajax', it tells this function that ajax.js has rewritten
 * the URL and thus we are doing an AJAX and can return an array of commands.
 *
 * @param int $uid
 *   User's identifier.
 *
 * @param string $type
 *   Either 'ajax' or 'nojs'.
 *
 * @return string|array
 *   If $type == 'ajax', returns an array of AJAX Commands.
 *   Otherwise, just returns the content, which will end up being a page.
 */
function aes_ajax_callback($uid, $type = 'ajax') {
  $output = aes_get_password($uid, TRUE);
  if ($type != 'ajax') {
    $user = user_load($uid, FALSE);
    drupal_set_title($user->name . '\'s password');
    return $output;
  }
  $commands = array();
  $commands[] = ajax_command_replace('#aes_password', $output);
  $page = array(
    '#type' => 'ajax',
    '#commands' => $commands,
  );
  ajax_deliver($page);
}

/**
 * Implements hook_user_login().
 */
function aes_user_login(&$edit, $account) {
  if (variable_get("aes_convert", FALSE) && aes_password_exists($account->uid) == FALSE && isset($edit['input']) && isset($edit['input']['pass'])) {
    db_insert('aes_passwords')
      ->fields(array(
      'uid' => $account->uid,
      'pass' => aes_encrypt($edit['input']['pass']),
    ))
      ->execute();
  }
}

/**
 * Implements hook_user_insert().
 */
function aes_user_insert(&$edit, $account, $category) {
  aes_user_alter($edit, $account);
}

/**
 * Implements hook_user_update().
 */
function aes_user_update(&$edit, $account, $category) {
  aes_user_alter($edit, $account);
}

/**
 * Implements hook_user_delete().
 */
function aes_user_delete($account) {
  db_delete('aes_passwords')
    ->where("uid = :uid", array(
    ':uid' => $account->uid,
  ))
    ->execute();
}

/**
 * Implements hook_user_alter().
 */
function aes_user_alter(&$edit, $account) {

  // Return immediately if the password is not in $_POST.
  if (!isset($_POST['pass']) || !isset($_POST['pass']['pass1'])) {
    return;
  }

  // Get the password from $_POST here since it's already hashed in $edit.
  $plain_text_password = $_POST['pass']['pass1'];
  if (!empty($plain_text_password) && $account->uid) {
    $password = aes_encrypt($plain_text_password);
    if (strlen($password) > AES_PASSWORD_MAX_LENGTH) {
      drupal_set_message(t("Couldn't update AES password since it's too long."), "error");
    }
    else {

      // If this user doesn't have a password and creation of encrypted
      // passwords is enabled, insert one now.
      if (aes_password_exists($account->uid) == FALSE) {
        if (variable_get("aes_convert", FALSE)) {
          db_insert('aes_passwords')
            ->fields(array(
            'uid' => $account->uid,
            'pass' => $password,
          ))
            ->execute();
        }
      }
      else {
        db_update('aes_passwords')
          ->fields(array(
          'pass' => $password,
        ))
          ->where("uid = :uid", array(
          ':uid' => $account->uid,
        ))
          ->execute();
      }
    }
  }
}

/**
 * Checks if a users password exists.
 * @code
 * // Implements hook_user_view().
 * function mymodule_user_view($account, $view_mode, $langcode) {
 *   if (aes_password_exists($account->uid)) {
 *     // Your custom code.
 *   }
 * }
 * @endcode
 *
 * @param int $uid
 *   The user ID.
 *
 * @see aes_user_view()
 *
 * @return bool
 *   TRUE if encrypted password is exists and decryptable, FALSE otherwise.
 */
function aes_password_exists($uid) {
  return aes_get_password($uid) ? TRUE : FALSE;
}

/**
 * Gets a users password, in plain text, or in it's encrypted form.
 * @code
 * // Implements hook_user_view().
 * function mymodule_user_view($account, $view_mode, $langcode) {
 *   if (aes_password_exists($account->uid)) {
 *     $password = aes_get_password($uid, TRUE);
 *     $account->content['info']['aes_password'] = array(
 *       '#type' => 'user_profile_item',
 *       '#title' => t('User password'),
 *       '#markup' => '<div id="aes_password">' . $password . '</div>',
 *     );
 *   }
 * }
 * @endcode
 *
 * @param int $uid
 *   The user ID.
 * @param bool $decrypt
 *   (optional) Whether to decrypt the password before returning it, or not.
 *   Defaults to FALSE.
 * @see aes_ajax_callback()
 *
 * @return bool|string
 *   The password in plain text on success, FALSE otherwise.
 */
function aes_get_password($uid, $decrypt = FALSE) {
  $password = db_select('aes_passwords', 'p')
    ->fields('p', array(
    'pass',
  ))
    ->condition('uid', $uid)
    ->execute()
    ->fetchField();
  if (empty($password)) {
    return FALSE;
  }
  if ($decrypt) {
    return aes_decrypt($password);
  }
  return $password;
}

/**
 * Gets the current key used for the encryption system. If there's currently
 * no key defined, this function will generate one, store it, and return it.
 *
 * @return string
 *   The key.
 */
function aes_get_key() {
  $storage_method = variable_get("aes_key_storage_method", "Database");
  if ($storage_method == "Database") {
    $key = variable_get("aes_key", FALSE);
    if ($key === FALSE) {
      $key = aes_make_key();
      aes_store_key($key);
      watchdog("aes", "AES module made a new key since one couldn't be found by using the database storage method.");
    }
  }
  if ($storage_method == "File") {
    $key = file_get_contents(variable_get("aes_key_path", ""));
    if ($key === FALSE) {
      $key = aes_make_key();
      aes_store_key($key);
      watchdog("aes", "AES module made a new key since one couldn't be found by using the file storage method.");
    }
  }
  return $key;
}

/**
 * Stores the key given by writing it to the storage method currently used
 * (database or file).
 *
 * @param string $key
 *   The key.
 *
 * @return bool
 *   TRUE on success, FALSE otherwise.
 */
function aes_store_key($key) {
  $storage_method = variable_get("aes_key_storage_method", "Database");
  if ($storage_method == "Database") {
    variable_set("aes_key", $key);
    return TRUE;
  }
  if ($storage_method != "File") {
    throw new Exception('Unknown storage method in AES module: ' . $storage_method);
  }
  $fp = fopen(variable_get("aes_key_path", ""), "w");
  if ($fp === FALSE) {
    drupal_set_message(t("Couldn't write key to file " . variable_get("aes_key_path", "")), "error");
    return FALSE;
  }
  fwrite($fp, $key);
  fclose($fp);
  return TRUE;
}

/**
 * Deletes the encryption system key.
 *
 * @param string $storage_method
 *   The storage method used to store the key. "Database" or "File".
 */
function aes_delete_key($storage_method) {
  if ($storage_method == "Database") {
    variable_del("aes_key");
  }
  if ($storage_method == "File") {
    $result = unlink(variable_get("aes_key_path", ""));
    if ($result === FALSE) {
      drupal_set_message(t("Couldn't delete keyfile!"), "error");
    }
  }
}

/**
 * Generate a random key, containing uppercase, lowercase and digits.
 *
 * @return string
 *   Encryption key.
 */
function aes_make_key() {
  $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
  srand();
  while (TRUE) {
    $key = '';
    while (strlen($key) < 32) {
      $key .= substr($chars, rand(0, strlen($chars)), 1);
    }

    // is there at least one lowercase letter?
    if (!preg_match('/.*[a-z].*/', $key)) {
      continue;
    }

    // is there at least one uppercase letter?
    if (!preg_match('/.*[A-Z].*/', $key)) {
      continue;
    }

    // is there at least one numeric?
    if (!preg_match('/.*[0-9].*/', $key)) {
      continue;
    }
    break;
  }
  return $key;
}

/**
 * Define or set aes_encryption_iv variable.
 *
 * @param bool $ignore_implementation
 *   Set FALSE to protect calling function if using phpseclib, TRUE otherwise.
 */
function aes_make_iv($ignore_implementation = FALSE) {

  // Bail out if using phpseclib
  if (variable_get("aes_implementation", "mcrypt") == "phpseclib" && $ignore_implementation == FALSE) {
    watchdog("aes", "Called aes_make_iv when using phpseclib. This is harmless, but shouldn't happen.", array(), WATCHDOG_WARNING);
    return;
  }
  if (strtoupper(substr(PHP_OS, 0, 3)) === "WIN") {
    $randgen = MCRYPT_RAND;
    srand();
  }
  else {
    $randgen = MCRYPT_DEV_URANDOM;
  }
  $td = mcrypt_module_open_safe(variable_get("aes_cipher", "rijndael-128"), "", MCRYPT_MODE_CBC, "");
  $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), $randgen);
  mcrypt_module_close($td);
  variable_set("aes_encryption_iv", base64_encode($iv));
}

/**
 * Encrypts a string.
 *
 * @param string $string
 *   The string to encrypt.
 * @param bool $base64encode
 *   Whether to return the string base64 encoded (recommended for database
 *   insertion).
 * @param string $custom_key
 *   Use this as the key rather than the stored one for this operation.
 * @param string $custom_cipher
 *   Use this cipher rather than the default one. (only with Mcrypt - ignored
 *   with phpseclib)
 * @param string $custom_iv
 *   Use this initialization vector instead of the default one. Base64-encoded.
 * @param string $custom_implementation
 *   Can be "phpseclib" or "mcrypt". Warning: Does not check if the requested
 *   implementation actually exists.
 *
 * @return bool|string
 *   The encrypted string on success, FALSE on error.
 */
function aes_encrypt($string, $base64encode = TRUE, $custom_key = NULL, $custom_cipher = NULL, $custom_iv = NULL, $custom_implementation = NULL) {

  // Bail out if the passed string is empty.
  if (empty($string)) {
    watchdog("aes", "Tried to encrypt an empty string.", array(), WATCHDOG_WARNING);
    return FALSE;
  }
  if ($custom_cipher != NULL) {
    $cipher = $custom_cipher;
  }
  else {
    $cipher = variable_get("aes_cipher", "rijndael-128");
  }
  if (!empty($custom_key)) {
    $key = $custom_key;
  }
  else {
    $key = aes_get_key();
  }
  if ($custom_implementation == "mcrypt" || $custom_implementation == "phpseclib") {
    $implementation = $custom_implementation;
  }
  else {
    $implementation = variable_get("aes_implementation", "mcrypt");
  }
  if ($implementation == "phpseclib") {

    // Using phpseclib implementation.
    // phpseclib doesn't support custom ciphers.
    if (is_null($custom_cipher) == FALSE) {
      watchdog("aes", "A custom cipher was defined when encrypting a string in the AES module using the phpseclib implementation. This implementation doesn't support custom ciphers therefore the argument was ignored and the encryption was done with the standard cipher.", array(), WATCHDOG_WARNING);
    }
    aes_load_phpsec();
    $phpsec = new Crypt_AES();
    $phpsec
      ->setKey($key);
    if (!is_null($custom_iv)) {
      $phpsec
        ->setIV(base64_decode($custom_iv));
    }
    $encrypted = $phpsec
      ->encrypt($string);
  }
  else {
    if ($implementation == "mcrypt") {

      // Using mcrypt implementation.
      $td = mcrypt_module_open_safe($cipher, "", MCRYPT_MODE_CBC, "");
      if ($custom_iv == NULL) {
        $iv = base64_decode(variable_get("aes_encryption_iv", ""));
      }
      else {
        $iv = base64_decode($custom_iv);
      }
      if (empty($iv)) {
        aes_make_iv();
        $iv = base64_decode(variable_get("aes_encryption_iv", ""));
        watchdog("aes", "No initialization vector found while trying to encrypt! This could be a bit of a pain since you might have to reset all the passwords for all users. I've created a new one now and will try to carry on as normal.", array(), WATCHDOG_WARNING);
      }
      $ks = mcrypt_enc_get_key_size($td);
      $key = substr(sha1($key), 0, $ks);
      mcrypt_generic_init($td, $key, $iv);
      $encrypted = mcrypt_generic($td, $string);
      mcrypt_generic_deinit($td);
      mcrypt_module_close($td);
    }
    else {
      $error_msg = t("Request was sent to encrypt a string with the AES module, but the AES module has no active encryption implementation to work with! Did you forget to run update.php after upgrading this module?");
      if (user_access('administer aes')) {
        drupal_set_message($error_msg, "error");
      }
      watchdog("aes", $error_msg, array(), WATCHDOG_ERROR);
      return FALSE;
    }
  }
  if ($base64encode) {
    return base64_encode($encrypted);
  }
  else {
    return $encrypted;
  }
}

/**
 * Decrypts a string of encrypted data.
 *
 * @param string $string
 *   The string to decrypt.
 * @param bool $base64encoded
 *   Whether this encrypted string is base64 encoded or not.
 * @param string $custom_key
 *   Use this as the key rather than the stored one for this operation.
 * @param string $custom_cipher
 *   Use this cipher rather than the default one. (only with Mcrypt - ignored
 *   with phpseclib)
 * @param string $custom_iv
 *   Use this initialization vector instead of the default one. Base64-encoded.
 * @param string $custom_implementation
 *   Can be "phpseclib" or "mcrypt". Warning: Does not check if the requested
 *   implementation actually exists.
 *
 * @return bool|string
 *   The decrypted string on success, FALSE on error.
 */
function aes_decrypt($string, $base64encoded = TRUE, $custom_key = NULL, $custom_cipher = NULL, $custom_iv = NULL, $custom_implementation = NULL) {
  if ($base64encoded) {
    $string = base64_decode($string);
  }

  // Bail out if the passed string is empty.
  if (empty($string)) {
    watchdog("aes", "Tried to decrypt an empty string.", array(), WATCHDOG_WARNING);
    return FALSE;
  }
  if ($custom_cipher != NULL) {
    $cipher = $custom_cipher;
  }
  else {
    $cipher = variable_get("aes_cipher", "rijndael-128");
  }
  if (!empty($custom_key)) {
    $key = $custom_key;
  }
  else {
    $key = aes_get_key();
  }
  if ($custom_implementation == "mcrypt" || $custom_implementation == "phpseclib") {
    $implementation = $custom_implementation;
  }
  else {
    $implementation = variable_get("aes_implementation", "mcrypt");
  }
  if ($implementation == "phpseclib") {

    // Using phpseclib implementation.
    // phpseclib doesn't support custom ciphers.
    if (is_null($custom_cipher) == FALSE) {
      watchdog("aes", "A custom cipher was defined when decrypting a string in the AES module using the phpseclib implementation. This implementation doesn't support custom ciphers therefore the argument was ignored and the decryption was done with the standard cipher.", array(), WATCHDOG_WARNING);
    }
    aes_load_phpsec();
    $phpsec = new Crypt_AES();
    $phpsec
      ->setKey($key);
    if (!is_null($custom_iv)) {
      $phpsec
        ->setIV(base64_decode($custom_iv));
    }
    $decrypted = $phpsec
      ->decrypt($string);
  }
  else {
    if ($implementation == "mcrypt") {

      // Using mcrypt implementation.
      $td = mcrypt_module_open_safe($cipher, "", MCRYPT_MODE_CBC, "");
      $ks = mcrypt_enc_get_key_size($td);
      if ($custom_iv == NULL) {
        $iv = base64_decode(variable_get("aes_encryption_iv", ""));
      }
      else {
        $iv = base64_decode($custom_iv);
      }
      if (empty($iv)) {
        watchdog("aes", "No initialization vector found while trying to decrypt. Aborting!", array(), WATCHDOG_ERROR);
      }
      $key = substr(sha1($key), 0, $ks);
      mcrypt_generic_init($td, $key, $iv);
      $decrypted = mdecrypt_generic($td, $string);
      mcrypt_generic_deinit($td);
      mcrypt_module_close($td);
    }
    else {
      $error_msg = t("Request was sent to decrypt a string with the AES module, but the AES module has no active encryption implementation to work with! Did you forget to run update.php after upgrading this module?");
      if (user_access('administer aes')) {
        drupal_set_message($error_msg, "error");
      }
      watchdog("aes", $error_msg, array(), WATCHDOG_ERROR);
      return FALSE;
    }
  }
  return trim($decrypted);
}

/**
 * Safe version of mcrypt_module_open function.
 */
function mcrypt_module_open_safe($algorithm, $algorithm_directory, $mode, $mode_directory) {
  if (!function_exists('mcrypt_module_open')) {
    throw new Exception('AES: Mcrypt is selected as encryption implementation but is unavailable.');
  }
  return mcrypt_module_open($algorithm, $algorithm_directory, $mode, $mode_directory);
}

Functions

Namesort descending Description
aes_ajax_callback Callback for user's password ajax link.
aes_decrypt Decrypts a string of encrypted data.
aes_delete_key Deletes the encryption system key.
aes_encrypt Encrypts a string.
aes_get_key Gets the current key used for the encryption system. If there's currently no key defined, this function will generate one, store it, and return it.
aes_get_password Gets a users password, in plain text, or in it's encrypted form.
aes_load_phpsec Loads phpseclib.
aes_make_iv Define or set aes_encryption_iv variable.
aes_make_key Generate a random key, containing uppercase, lowercase and digits.
aes_menu Implements hook_menu().
aes_password_exists Checks if a users password exists.
aes_permission Implements hook_permission().
aes_show_password_page Access callback for a separate tab page with password.
aes_store_key Stores the key given by writing it to the storage method currently used (database or file).
aes_user_alter Implements hook_user_alter().
aes_user_delete Implements hook_user_delete().
aes_user_insert Implements hook_user_insert().
aes_user_login Implements hook_user_login().
aes_user_update Implements hook_user_update().
aes_user_view Implements hook_user_view().
mcrypt_module_open_safe Safe version of mcrypt_module_open function.

Constants