Encrypting Fields on an Entity

Recently, a client had a need to store social security numbers in a Drupal site.  It wasn't the full SSN, just the last 4 digits, but I still wasn't comfortable storing it in plain text in case the database (God forbid!) was ever compromised.  After researching all the encryption methods available through Drupal modules (there is no native way to do two-way encryption in Drupal 7), it appeared that the Encryption module was closest to what I needed.  It encrypts the data for storage and unencrypts it to be read on the site, stores the key outside of the database and site root, and is maintained by a well-respected member of the Drupal security team.  Just one problem...it doesn't encrypt custom fields on custom entity types.  So I had a bit of work to do.

Step 1:

Using hook_form_alter in MYMODULE.module, add the #encrypt property to the field (as per Encryption module instructions):

/**
 * Implements hook_form_form_id_alter().
 *
 **/

function MYMODULE_form_redhen_contact_contact_form_alter (&$form, &$form_state) {
    $form['field_ssn']['#encrypt'] = TRUE;
}

Step 2:

Add this patch to the encrypt module.

Step 3:

Add a field formatter so we can decrypt the field to show it unencrypted to site admins.

/**
 * Implements hook_field_formatter_info().
 * Creating a textfield formatter to decrypt ssn when viewing a contact entity
 * Change MYMODULE_DECRYPT_SSN to match your module and field
 **/

function MYMODULE_field_formatter_info() {
  return array(
    // Machine name of the formatter.
    'MYMODULE_DECRYPT_SSN' => array(
      // Label for the administrative UI.
      'label' => t('Decrypt'),
      // Field types it supports.
      'field types' => array('text'),
    ),
  );
}

/**
 * Implements hook_field_formatter_view().
 *
 **/
function MYMODULE_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  //Load the file for the decrypt function
  module_load_include('module', 'encrypt');

  $output = array();
  // Verify the formatter type.
  if ($display['type'] == 'MYMODULE_DECRYPT_SSN') {
    // Handle multi-valued fields.
    foreach ($items as $delta => $item) {
      // See which option was selected.
      $output[$delta] = array(
        '#markup' => decrypt($item['value'], array('base64' =>
TRUE)),
      );
    }
  }
  return $output;
}

Step 3

I also had a view set up where admins could search for contacts using an exposed filter. The exposed filter consisted of one search box where the admin could search for Name, Email, and SSN fields using a combined exposed filter. If the entered search string was all numbers, it was safe to assume that the admin was searching by SSN. This meant that the SSN the admin was searching for needed to be encrypted before it was compared with the encrypted SSN's in the database. I needed to alter the query being submitted for searching by encrypting the SSN field. Enter hook_views_query_alter. But first, preparing views to be altered in the MYMODULE.module file.

/**
 * Implements hook_views_api().
 *
 **/
// Add support for views alterations
function MYMODULE_views_api() {
  return array(
    'api' => 3,
  );
}

Step 4

Then doing the query alter in a MYMODULE.views.inc file.

/**
 * Implements hook_views_query_alter().
 *
 * Allows views combined field query to search on encrypted SSN.
 **/
function MYMODULE_views_query_alter(&$view, &$query) {
  // (Example assuming a view with an combined exposed filter containing ssn field.)
  // If the input for combined filter is numeric and the ssn field exists in the view,
  // encrypt the ssn before searching the database.
  if (isset($view->exposed_raw_input['combine']) && is_numeric($view->exposed_raw_input['combine'])) {
    // Traverse through the 'where' part of the query.
    foreach ($query->where as &$condition_group) {
      foreach ($condition_group['conditions'] as &$condition) {
         //If this is the part of the query filtering on the combined fields and ssn is one of the combined fields, change the condition to filter on the encrypted ssn.
        if (strpos($condition['field'], 'CONCAT_WS') !== false && strpos($condition['field'], 'field_ssn_value') !== false) {
          $condition = array(
            'field' => $condition['field'],
            'value' => array(':views_combine' => '%' . encrypt($view->exposed_raw_input['combine'], array('base64' => TRUE)) . '%'),
            'operator' => 'formula',
          );
        }
      }
    }
  }
}

Step 5

Install SSL certificate on the server and configure Secure Pages module so that the SSN is also encrypted while traveling to and from the server.

Tags: