Feeds

Author

Nov 07 2017
Nov 07

8 November 2017

The Enforce Profile Field is a new module which allows editors to enforce the completion of one or more fields in order to access content on a site. It is now available for Drupal 8.

Sometimes you need to collect a variety of profile data for different users. The data may be needed for regulatory compliance or marketing reasons. In some cases you need a single field and in others it may be several. You may also wish to collect the information when a user access certain parts of the site.

The Enforce Profile Field module comes to the rescue in cases such as these, forcing users to complete their profile before being able to move onto the page they want to see. This may sound harsh, however, collecting data as you need it is a more subtle way of collecting data than enforcing it all at registration time.

The implementation consists mainly from a new Field Type called "Enforce profile" and hook_entity_view_alter().

The module works as follows

  1. Site builder defines a “form display” for the user type bundle and specify fields associated with it to collect data.
    1. The fields should not be required, as this allows the user to skip them on registration and profile editing.
    2. In addition the Form Mode Manager module can be used to display the “form display” as a "tab" on a user profile page.
  2. The site builder places an Enforce profile field onto an entity type bundle, such as a node article or page.
  3. The Enforce profile field requires some settings:
    1. A "User's form mode" to be utilized for additional field information extraction (created in the first step).
    2. An "Enforced view modes" that require some profile data to be filled in before being able to access them. You should usually select the "Full content" view mode and rather not include view modes like "Teaser" or "Search".
  4. The editor creates content, an article or page, and can then select which fields need to be enforced.
    1. The editor is provided with multi-select of "User's form mode" fields.
    2. Selecting nothing is equal to no access change, no profile data enforcement.
  5. A new user navigates to the content and is redirected to the profile tab and is informed that they need to complete the fields.
  6. Fields are completed, form submitted and the user redirected back to the content.
    1. In case the user doesn't provide all enforced fields, the profile tab is displayed again with the message what fields need to be filled in.

Why to use the Enforce Profile Field to collect an additional profile data?

  • You may need customer's information to generate a coupon or access token.
  • You may just want to know better with whom you share information.
  • Your users know exactly what content requires their additional profile data input rather than satisfying a wide range of requirements during registration. It just makes it easier for them.
  • The new profile data can be synced to a CRM or other system if required to.

Let us know what you think.
 

Aug 21 2016
Aug 21

Client-side performance can be dramatically improved by removing render-blocking JavaScript with LABjs. No more waiting and no more spinning-wheel icons for users to see. You can get a user speed experience of a simple site with all advantages of complex functionality-rich site.

Have you ever needed to boost up page speed? Has your boss asked you to improve SEO, PageSpeed ranking or client-side performance specifically? Modern websites contains a lot of JavaScript and that is the reason why Google PageSpeed tool provides you with hints such as

“Your page has blocking script resources. This causes a delay in rendering your page.

Followed by description like this

None of the above-the-fold content on your page could be rendered without waiting for the following resources to load. Try to defer or asynchronously load blocking resources, or inline the critical portions of those resources directly in the HTML.

Remove render-blocking JavaScript:

        https://[script1].js
        https://[script2].js
        ...

Google now pays a lot of attention to blocking script resources recently. The reason is user experience. PageSpeed advises you to defer or asynchronously load blocking JavaScript, or inline the critical portions of JavaScript directly in the HTML.

There are a number of options available to those who want to speed the rendering of the page:

However not all of those options have the same effect. Some of them may even behave differently than expected.

When we were faced with this situation we wanted our solution to have a cross-browser support and best client-side performance results possible. We nvestigated the options and ended up with LABjs.

LABjs (Loading And Blocking JavaScript library)

LABjs is a JavaScript loader, used by many large sites such as Twitter, Vimeo, examiner.com. It loads and executes all scripts in parallel as fast as the browser will allow. In other words, it is an on-demand parallel loader for JavaScript with execution order dependencies support.

Kyle Simpson, the author of LABjs puts it this way:

“LABjs is almost 4 years old, and has been stable (no bug fixes/patches) for almost 2 years.”

Quoting from LABjs library github page:

“The defining characteristic of LABjs is the ability to load all JavaScript files in parallel, as fast as the browser will allow, but giving you the option to ensure proper execution order if you have dependencies between files.”

LABjs library can be found on github: https://github.com/getify/LABjsThere is also a Drupal LABjs contrib module, https://www.drupal.org/project/labjs, which helps to integrate LABjs library to Drupal. It is not a big module, so fully comprehending what it does and verifying that it can be trusted wasn't hard.

After LABjs deployment on Morpht website, we received significant PageSpeed and YSlow rating boost and even more obvious user experience improvement. The site feels like it would be a simple website dispite the fact of being build on Drupal 7. 

LABjs contrib module takes care of special cases and glitches like Google Analytics code or Advanced CSS/JS Aggregation module usage.

LABjs usage example

<script src="https://www.morpht.com/blog/remove-render-blocking-javascript-labjs/LAB.js"></script>
<script>
  $LAB
  .script("http://remote.tld/jquery.js").wait()
  .script("/local/plugin1.jquery.js")
  .script("/local/plugin2.jquery.js").wait()
  .script("/local/init.js").wait(function(){
      initMyPage();
  });
</script>

First the LABjs library is loaded, then the global $LAB variable is used to schedule asynchronous defer loading of additional JavaScript files or inline code. All specified resources are downloaded asynchronously and their execution is deferred till the page has finished parsing. The specific order of resources execution can be required in some cases (quite often). The wait() function call expresses an execution order dependency, so all resources specified below the wait() function call will not be executed till the waiting resource has finished its execution.

DOM content load speed cut by 34%

We found a significant speedup in the loading of teh DOM content. The site we tested on was relatively simple and we would expect better results on more complex sites.

Before LABjs

Waterfall without LABjs
  • No asynchronous or defered JavaScript load is incorporated yet.
  • DOMContentLoaded: 413ms
  • Page load: 604 ms

After LABjs

Waterfall with LABjs

Be careful

Asynchronous load or defer execution of JavaScript requires some caution. The usage of LABjs or any other third party library is usually not enough. Additional code changes may be required.

Inline JavaScript code is not allowed to have any dependencies on any asynchronously loaded JavaScript file without proper synchronization. If you have such dependencies, they need to be taken care of first. The above-the-fold content can have JavaScript dependencies, so inlining critical portions of JavaScript resources directly may be required. As an example we can imagine JavaScript code required to render some graphics. More appropriate example could be an order button that needs to be active immediatly after page load, but its action requires JavaScript processing.

Other options

There are other options out there but they do have their shortcomings.

The HTML <script> async attribute can be used to asynchronously load JavaScript. Async attribute make the load of JavaScript to be asynchronous, but it parses and executes the JavaScript immediately after it is loaded. As a result the HTML parsing is being blocked. Plus the order of scripts exectuion is not guaranteed (first loaded is first executed). Full cross-browser support is not guaranteed.

The HTML <script> defer attribute can be used to defer the execution of JavaScript. Defer attribute causes the JavaScript to be loaded asynchronously and executed after the HTML parsing is completed. JavaScript is parsed immediately after it is loaded. Full cross-browser support is not guaranteed.

LABjs is cross-browser reliable. It parses and executes the JavaScripts after the whole HTML DOM is parsed (page loading icon indicates that the page is fully loaded already).

Bryan McQuade showed us how Google Developers eliminate render-blocking JavaScript and CSS in his video presentation.

[embedded content]

Conclusion

You’ve just seen how to use LABjs library to turn the render-blocking JavaScripts into asynchronously loaded JavaScripts with deferred parsing and execution. As a real-life example the / website was presented. Its DOM content load speed was cut by 34%. That’s a good result for a site that uses JavaScript just sparingly. Imagine what could be accomplished on a complex website with a lot of JavaScript in use.

Remember that inlining just a critical portions of JavaScript resources directly in HTML and loading the rest asynchronously with postponed execution gives not only client-side performance boost, but also SEO boost that can make a difference.

My takeaway tip is to pay close attention to JavaScript code modularity so it can be easily separated based on the needs and use-cases required. Having good JavaScript means not only the functionality that’s needed, but also better user experience and better SEO ranking.

Helpful analytical tools

GTmetrix

  • https://gtmetrix.com/
  • Provides PageSpeed & YSlow reports together with Waterfall chart, page load time, total page size, number of request done and similar information.

PageSpeed

YSlow

Appendix

There are terms, expressions and constructs that should be known before any action is taken to remove render-blocking JavaScript.

Client-side

Client-side refers to operations that are performed by the client in a client–server relationship. In web application context it is a web browser, that runs on a user's local computer.

Client-side behavior forming user’s experience is significantly influenced by content received (HTML DOM), time interval between requesting and displaying page and code that needs to be parsed and executed (like JavaScript and CSS).

Server-side

Server-side refers to operations that are performed by the server in a client–server relationship. In web application context it is a web server, that runs on a remote server, reachable from a user's local computer. 

Server-side is responsible for http response content. Most of the web-application logic usually resides on server-side. The speed and amount of processing required on the server-side together with the server’s connection speed contribute to the overall responsiveness of the web application.

Client-side performance

Page load time is a sum of time required to load page content with its all external resources and time required to parse and finally execute internal and external resources (like HTML, CSS, JavaScript). Most of current pages are using JavaScript. The degree of overall JavaScript usage on the site influences page load time.

Performance is a ratio of work that has been done and time it took. Client-side performance can be considered as a ratio of data size downloaded combined with all operations performed and page load time. Quite often client-side performance notion is being simplified to just a a page load time. It is quite fair from user experience point of view, so this simplification works fine.

The way how JavaScript is loaded, parsed and executed is one of the important factors that influences client-side performance as it significantly influences page load time.

Above-the-fold content

When you land on a website, “above-the-fold” is any content you immediately see without scrolling the page. The “fold” is where the bottom of the screen is. Anything you have to scroll down to see is “below-the-fold”.

“Above-the-fold” is a term typically used for Newspapers, content above where the paper is folded horizontally.

Asynchronous JavaScript loading

By default, JavaScript execution is “parser blocking”: when the browser encounters an inline script in the document or scripts included via a script tag it must pause DOM construction, hand over the control to the JavaScript runtime and let the script execute before proceeding with DOM construction.

However, in the case of an external JavaScript file the browser will also have to pause and wait for the script to be fetched from disk, cache, or a remote server, which can add tens to thousands of milliseconds of delay to the critical rendering path.

Inlining the relevant external JavaScripts can help to avoid extra network roundtrips. 

Defer loading of JavaScript

The loading and execution of scripts that are not necessary for the initial page render may be deferred until after the initial render or other critical parts of the page have finished loading. Doing so can help reduce resource load burden and improve performance.

Quite often defer loading can mean that the resource is loaded asynchronously but the parsing and execution of that resource is deferred till the HTML DOM is loaded or page has finished parsing.

Inlining the critical portions of JavaScript

As a critical portion of JavaScript can be considered any JavaScript code that is required to make the above-the-fold page content to look and behave as intended, immediatelly after the page is loaded. According to the principal of inlining, the amount of critical portion of JavaScript should be the bare minimum.

Extracting bare minimum of JavaScript to satisfy page needs immediatelly after load seems to not be a common practice yet. It is going to change and new approaches in JavaScripts development can help with that (components, typesafty, more structure and code separation, TDD, new JavaScript compilers like TypeScript and others: https://github.com/jashkenas/coffeescript/wiki/list-of-languages-that-co...).

Client-side vs Server-side rendering

Client-side rendering means JavaScript running in the browser produces HTML or manipulates the DOM.

Server-side rendering means when the browser fetches the page over HTTP, it immediately gets back HTML describing the page. It maintains the idea that pages are documents, and if you ask a server for a document by URL, you get back the text of the document rather than a program that generates that text using a complicated API.

Client-side JavaScript code should be optimized to be lightweight. The rule is to do as much on your server-side as you possible can. Such optimization can lead to significant improvement of page load performance.

It is even possible to render JavaScript templates on the server to deliver fast first render, and then use client-side templating once the page is loaded.

Exceptions can be found of course. Some calculations can be put to client-side JavaScript deliberately. The intention maybe to reduce server load for heavy operations that can be easily handled by JavaScript on background asynchronously to not impair page load.

HTML <script> async attribute

The definition of async attribute on w3schools.com is incorrect. It says:

“When present, it specifies that the script will be executed asynchronously as soon as it is available.”

But in fact what happen is that the async attribute tells the browser to not block the DOM construction while it waits for the script to become available. It means that the script is loaded/downloaded asynchronously, but when the loading is finished the HTML parser is paused to execute the script synchronously. This is still a huge performance win though.

There are few downsides

  • Internet Explorer supports async attribute only from IE10 up.
  • Ordering is not preserved when the async attribute is used.
  • Any dependencies must be designed carefully. Synchronization could be required, as the exact timeline of operations and events is unknown upfront.

Example:

<script src="https://www.morpht.com/blog/remove-render-blocking-javascript-labjs/demo_async.js" async></script>

HTML <script> defer attribute

When the defer attribute is present, it specifies that the script is executed when the page has finished parsing.

Defer scripts are also guaranteed to execute in the order that they appear in the document.

Example:

<script src="https://www.morpht.com/blog/remove-render-blocking-javascript-labjs/demo_defer.js" defer></script>

Related videos

  • "Optimizing the Critical Rendering Path for Instant Mobile Websites - Velocity SC - 2013" by Ilya Grigorik

[embedded content]

Feb 23 2015
Feb 23

One of the trickier aspects of any data migration is migrating users and ensuring that the authentication details which worked on the legacy site also continue to work on the new Drupal site. Neither Drupal nor probably the legacy system actually stores the password in plain text. A one way hash, often salted will be used instead. This level of security is essential for ensuring that user passwords cannot be easily captured, however, it does provide a challenge when the method of hashing is unknown.

This article will take a look at how Drupal handles authentication and how it can be extended to handle new methods, such as those used by a legacy system. We will then go on to take a look at the hashing algorithm used on a .Net site and how we were able to implement some Drupal code to ensure that the hashes could be understood by Drupal. This took a fair bit of detective work and the main point of the article is to document how we did it.

Authentication in Drupal

Drupal contains the logic for user authentication in /includes/password.inc. An important function is user_check_password() where the first three characters of the $stored_hash are used to define the $type of password. A Drupal 7 password is denoted as '$S$' as can be seen from the code below.

/includes/password.inc

function user_check_password($password, $account) {
  if (substr($account->pass, 0, 2) == 'U$') {
    // This may be an updated password from user_update_7000(). Such hashes
    // have 'U' added as the first character and need an extra md5().
    $stored_hash = substr($account->pass, 1);
    $password = md5($password);
  }
  else {
    $stored_hash = $account->pass;
  }

  $type = substr($stored_hash, 0, 3);
  switch ($type) {
    case '$S$':
      // A normal Drupal 7 password using sha512.
      $hash = _password_crypt('sha512', $password, $stored_hash);
      break;
    case '$H$':
      // phpBB3 uses "$H$" for the same thing as "$P$".
    case '$P$':
      // A phpass password generated using md5.  This is an
      // imported password or from an earlier Drupal version.
      $hash = _password_crypt('md5', $password, $stored_hash);
      break;
    default:
      return FALSE;
  }
  return ($hash && $stored_hash == $hash);
}

The secret to defining your own hashing algorithm is to replace this function with another, and this can be done with a small module which swaps out the password.inc file. By implementing your own "myauthmodule" you can swap out the password.inc file and include your own logic as desired.

/sites/all/modules/myauthmodule/myauthmodule.install


/**
 * @file
 * Supply alternate authentication mechanism.
 */

/**
 * Implements hook_enable().
 */
function myauthmodule_enable() {
  variable_set('password_inc', drupal_get_path('module', 'myauthmodule') . '/password.inc');
}

/**
 * Implements hook_disable().
 */
function myauthmodule_disable() {
  variable_set('password_inc', 'includes/password.inc');
}

As you can see we use our own custom password.inc when the modules is enabled, and revert back to the old one when the module is disabled.

This is what our new user_check_password() looks like.

/sites/all/modules/myauthmodule/password.inc

/**
 * @file
 * Alternate authentication mechanism implementation.
 */

function user_check_password($password, $account) {
  if (substr($account->pass, 0, 2) == 'U$') {
    // This may be an updated password from user_update_7000(). Such hashes
    // have 'U' added as the first character and need an extra md5().
    $stored_hash = substr($account->pass, 1);
    $password = md5($password);
  }
  else {
    $stored_hash = $account->pass;
  }

  $type = substr($stored_hash, 0, 3);
  switch ($type) {
    case '$S$':
      // A normal Drupal 7 password using sha512.
      $hash = _password_crypt('sha512', $password, $stored_hash);
      break;
    case '$H$':
      // phpBB3 uses "$H$" for the same thing as "$P$".
    case '$P$':
      // A phpass password generated using md5.  This is an
      // imported password or from an earlier Drupal version.
      $hash = _password_crypt('md5', $password, $stored_hash);
      break;
    case '$X$':
      // The legacy .Net method
      $hash = _myauthmodule_crypt($password, $stored_hash);
      break;
    default:
      return FALSE;
  }
  return ($hash && $stored_hash == $hash);
}

In this case, if the hash starts with '$X$' our custom algorithm will kick in and check the $password as entered against the $stored_hash. It's up to you to define the correct algorithm so that the $password is transformed into something which can be compared against the $stored_hash. If there is a match, the user will be authenticated.

.Net hashing algorithm

We will now go on to examine the hashing algorithm used in the .Net web application. The most difficult piece of the puzzle was working out exactly what algorithm was being used for the hash. After a lot of poking around we discovered that the .Net application was using SHA1 with 1000 iterations (RFC 2898). We also have to pick apart the string we were given by base64 decoding the string and then pulling the salt off the front of it. 

Playing with .Net hashing algorithm, working out what was what and then how to implement it was my job. This Stack Overflow article held the clues for how to solve it.

/sites/all/modules/myauthmodule/crypt.inc

/**
 * @file
 * Hash algorithm based on .Net hashing algorithm.
 */

/**
 * Legacy hashing constants.
 */
define('LEGACY_HASH_SUBKEY_LENGTH', 32);  
define('LEGACY_HASH_ALGORITHM', 'sha1');
define('LEGACY_HASH_ITERATIONS_NUMBER', 1000);
define('LEGACY_HASH_PREFIX', '$X$');

/**
 * Returns a hash for the password as per the .Net legacy mechanism.
 */
function _myauthmodule_crypt($password, $stored_hash) {
  // Remove the prefix.
  $legacy_hash = substr($stored_hash, strlen(LEGACY_HASH_PREFIX));
  // Calculate the hash.
  $legacy_hash_decoded = base64_decode($legacy_hash);
  $legacy_salt = substr($legacy_hash_decoded, 1, 16);
  $subkey = hash_pbkdf2(LEGACY_HASH_ALGORITHM, $password, $legacy_salt, LEGACY_HASH_ITERATIONS_NUMBER, LEGACY_HASH_SUBKEY_LENGTH, TRUE);
  // Hash = null char + salt + subkey.
  $hash = chr(0x00) . $legacy_salt . $subkey;
  return LEGACY_HASH_PREFIX . base64_encode($hash);
}

As we were unable to rely on the hash_pbkdf2() function existing in PHP (supported from PHP 5 >= 5.5.0) we had to code our own as a fallback. The PHP code for the hash algorithm was taken from comment posted on PHP hash_pbkdf2 manual page here.

/sites/all/modules/myauthmodule/hash_pbkdf2_fallback.inc (this file is not needed if you are using PHP 5 >= 5.5.0)


/**
 * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
 * $algorithm - The hash algorithm to use. Recommended: SHA256
 * $password - The password.
 * $salt - A salt that is unique to the password.
 * $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
 * $key_length - The length of the derived key in bytes.
 * $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
 * Returns: A $key_length-byte key derived from the password and salt.
 */
if (!function_exists("hash_pbkdf2")) {

  class pbkdf2 {

    public $algorithm;
    public $password;
    public $salt;
    public $count;
    public $key_length;
    public $raw_output;
    private $hash_length;
    private $output = "";

    public function __construct($data = null) {
      if ($data != null) {
        $this->init($data);
      }
    }

    public function init($data) {
      $this->algorithm = $data["algorithm"];
      $this->password = $data["password"];
      $this->salt = $data["salt"];
      $this->count = $data["count"];
      $this->key_length = $data["key_length"];
      $this->raw_output = $data["raw_output"];
    }

    public function hash() {
      $this->algorithm = strtolower($this->algorithm);
      if (!in_array($this->algorithm, hash_algos(), true))
        throw new Exception('PBKDF2 ERROR: Invalid hash algorithm.');

      if ($this->count <= 0 || $this->key_length <= 0)
        throw new Exception('PBKDF2 ERROR: Invalid parameters.');

      $this->hash_length = strlen(hash($this->algorithm, "", true));
      $block_count = ceil($this->key_length / $this->hash_length);
      for ($i = 1; $i <= $block_count; $i++) {
        // $i encoded as 4 bytes, big endian.
        $last = $this->salt . pack("N", $i);
        // first iteration
        $last = $xorsum = hash_hmac($this->algorithm, $last, $this->password, true);
        // perform the other $this->count - 1 iterations
        for ($j = 1; $j < $this->count; $j++) {
          $xorsum ^= ($last = hash_hmac($this->algorithm, $last, $this->password, true));
        }
        $this->output .= $xorsum;
      }

      if ($this->raw_output) {
        return substr($this->output, 0, $this->key_length);
      }
      else {
        return bin2hex(substr($this->output, 0, $this->key_length));
      }
    }

  }

  function hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false) {
    $data = array('algorithm' => $algorithm, 'password' => $password, 'salt' => $salt, 'count' => $count, 'key_length' => $key_length, 'raw_output' => $raw_output);
    try {
      $pbkdf2 = new pbkdf2($data);
      return $pbkdf2->hash();
    }
    catch (Exception $e) {
      throw $e;
    }
  }

}

Getting it all to work

Now that our custom code is in place we can give it a spin with some real live data. The first step of the process is to import the data into your users table. The string you write into the pass column must include the following concatenated items:

  • the hash type, in this case '$X$',
  • the hash

In our case the hash string was base64 encoded and was concatenation of 3 parts:

  • null character (8 bits)
  • 16 characters of salt (128 bits)
  • 32 characters of subkey (256 bits)

The hash string, as extracted from the legacy database looked similar to the following:

AFnR63Ykym/kDXLFEM5tlL450Y+drbfdwRGhsOCOMlcR273QYod3QZdKwhiKHKHjXw==

After it was written to the password column in the users table it looked like:

$X$AFnR63Ykym/kDXLFEM5tlL450Y+drbfdwRGhsOCOMlcR273QYod3QZdKwhiKHKHjXw==

With that knowledge (we had to learn and investigate to acquire them in first place) we were able to extract the legacy .Net salt and use it for calculation of our new subkey. The subkey is generated by hash_pbkdf2() function mentioned before, but to get the right subkey we need to provide correct settings and inputs (the order is exactly the same as the hash_pbkdf2() function requires):

  • the hash algorithm to be used (in our case sha1)
  • a password provided by a user, the legacy .Net salt
  • the number of iterations for the key derivation process (in our case 1000)
  • the length of the derived key in bytes (in our case 32)
  • the raw output set to TRUE to get our new subkey in raw binary format

To see clearly how the mechanism of getting .Net legacy hash is working, here's the code again:

/**
 * Legacy hashing constants.
 */
define('LEGACY_HASH_SUBKEY_LENGTH', 32);  
define('LEGACY_HASH_ALGORITHM', 'sha1');
define('LEGACY_HASH_ITERATIONS_NUMBER', 1000);
define('LEGACY_HASH_PREFIX', '$X$');


/**
 * Returns a hash for the password as per the .Net legacy mechanism.
 */
function _myauthmodule_crypt($password, $stored_hash) {
  // Remove the prefix.
  $legacy_hash = substr($stored_hash, strlen(LEGACY_HASH_PREFIX));
  // Calculate the hash.
  $legacy_hash_decoded = base64_decode($legacy_hash);
  $legacy_salt = substr($legacy_hash_decoded, 1, 16);
  $subkey = hash_pbkdf2(LEGACY_HASH_ALGORITHM, $password, $legacy_salt, LEGACY_HASH_ITERATIONS_NUMBER, LEGACY_HASH_SUBKEY_LENGTH, TRUE);
  // Hash = null char + salt + subkey.
  $hash = chr(0x00) . $legacy_salt . $subkey;
  return LEGACY_HASH_PREFIX . base64_encode($hash);
}

The _myauthmodule_crypt() function returns a newly calculated hash based on the password provided and the salt we extracted from the stored hash. This is combined with the prefix and the whole result is returned back to the calling function where it is compared with the stored hash.

The hash construction by .Net:

To make the hash construction more clear, we can look at it from .Net perspective. The .Net web has its own specific salt. It's represented in hexadecimal format, let it be: 59d1eb7624ca6fe40d72c510ce6d94be (32 chars). User has provided some password, let it beUserPassword . After the necessary operations are done we have the hash: AFnR63Ykym/kDXLFEM5tlL450Y+drbfdwRGhsOCOMlcR273QYod3QZdKwhiKHKHjXw== . So now we need to understand what are the necessary operations that creates hash from salt and user password inputs. Below is PHP code with hash_example() function that provides step by step example:

/sites/all/modules/myauthmodule/doc/hash_example.inc

require_once 'hex2bin_fallback.inc';

/**
 * Legacy hashing constants.
 */
define('LEGACY_HASH_SALT', '59d1eb7624ca6fe40d72c510ce6d94be'); // 32 chars in hexadecimal form
define('LEGACY_HASH_SUBKEY_LENGTH', 32);
define('LEGACY_HASH_ALGORITHM', 'sha1');
define('LEGACY_HASH_ITERATIONS_NUMBER', 1000);

function hash_example() {
// Convert the salt into binary form.
$salt_binary = hex2bin(LEGACY_HASH_SALT); // 16 chars in binary form
// Get the password from user.
$password = 'UserPassword';

$hash = legacy_hash($password, $salt_binary);
// $hash = AFnR63Ykym/kDXLFEM5tlL450Y+drbfdwRGhsOCOMlcR273QYod3QZdKwhiKHKHjXw==

return $hash;
}

/**
 * Legacy hash construction based on .Net procedure.
 */
function legacy_hash($password, $salt) {
  $subkey = hash_pbkdf2(LEGACY_HASH_ALGORITHM, $password, $salt, LEGACY_HASH_ITERATIONS_NUMBER, LEGACY_HASH_SUBKEY_LENGTH, TRUE);
  // Hash = null char + salt + subkey.
  $hash = chr(0x00) . $salt . $subkey;
  return base64_encode($hash);
}

As we are unable to rely on the hex2bin() function existing in PHP (supported from PHP 5 >= 5.4.0) we had to code our own as a fallback. The PHP code for the hash algorithm was taken from comment posted on PHP hex2bin manual page here.

/sites/all/modules/myauthmodule/doc/hex2bin_fallback.inc

/**
 * hex2bin() decodes a hexadecimally encoded binary string.
 * http://php.net/manual/en/function.hex2bin.php
 * (PHP >= 5.4.0)
 */
if (!function_exists('hex2bin')) {

  function hex2bin($str) {
    $sbin = "";
    $len = strlen($str);
    for ($i = 0; $i < $len; $i += 2) {
      $sbin .= pack("H*", substr($str, $i, 2));
    }
    return $sbin;
  }

}

Conclusion

This kind of approach is used for all migrations from legacy systems where users need to be migrated. Generally it is a fairly simple approach as the hashing algorithm is either simple or well documented. In this case we had to do a fair deal of sleuthing to work out how to do it. We hope that this article will be of help to other developers who are neck deep in salts and hashes.

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web