3

Below is my encryption implementation in PHP. It's based on mcrypt, and uses AES-128 in CTR mode along with a RIPEMD-128 HMAC for integrity checking.

For brevity, I've inlined the functions for calculating HMAC, verifying HMACs, directly int encrypt/decrypt functions. On the other hand, I've added more comments below than the original code has, to ensure that what happens can be properly understood by people unfamiliar with PHP1.

function encrypt($data, $key, $hmac_key) {
  // Get the appropriate IV size for AES-128/CTR
  $ivSize = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, 'ctr');

  // Create a random IV.
  $iv = mcrypt_create_iv($ivSize, MCRYPT_RAND);

  // Encrypt with AES-128/CTR with the provided key.
  $encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, 'ctr', $iv);

  // Base HMAC on the contatenated IV, the encrypted data and the cipher identifier.
  $hmac_data = $iv . $encrypted . MCRYPT_RIJNDAEL_128;

  // Calculate HMAC using ripemd128 and the supplied HMAC key.
  $hmac = hash_hmac('ripemd128', $hmac_data, $hmac_key);

  // Concatenate together the IV, encrypted data, and hmac, and base 64 encode them.
  return base64_encode($iv . $encrypted . $hmac);
}

function decrypt($encrypted, $key, $hmac_key) {
  // Decode from base 64.
  $encrypted = base64_decode($encrypted);

  // Get the appropriate IV size for AES-128/CTR
  $ivSize = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, 'ctr');

  // Grab the IV and HMAC from the data.
  $iv = substr($encrypted, 0, $ivSize);
  $hmac = substr($encrypted, -32);

  // Cut out the remaining data, which is the cipher text.
  $encrypted = substr($encrypted, $ivSize, -32);

  // Base HMAC on the contatenated IV, the encrypted data and the cipher identifier.
  $hmac_data = $iv . $encrypted . MCRYPT_RIJNDAEL_128;

  // Calculate verification HMAC using ripemd128 and the supplied HMAC key.
  $verification_hmac = hash_hmac('ripemd128', $hmac_data, $hmac_key);

  $verification = true;
  // Refuse to proceed if the two HMACs are of different length.
  if (strlen($hmac) !== strlen($verification_hmac)) {
    return NULL;
  }

  // Always iterate over the entire length of the HMACs to avoid timing information.
  for($i = 0; $i < strlen($verification_hmac); $i++) {
    // Check for strict equality for each character.
    if($verification_hmac[$i] !== $verification_hmac[$i]) {
      $verification = false;
    }
  }

  // Refuse to proceed if the two HMACs don't match exactly.
  if (!$verification) {
    return NULL;
  }

  $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypted, 'ctr', $iv);

  return $decrypted;
}

1: I'm thinking primarily of the concatenation operator, dot (.), which may not be entirely obvious.

  • 1
    A few other things (aside the RIPEMD128 which was already mentioned by Polynomial) caught my eye. You should be using MCRYPT_DEV_URANDOM as the randomness source, as it is safe for cryptographic use (here it is especially important to have definitely a fresh nonce for every encryption), whereas MCRYPT_RAND is not. Also, the HMAC verification leaks timing information (as === comparison uses branches). As a side note, some other things to consider when implementing crypto (in PHP): timoh6.github.io/2014/06/16/PHP-data-encryption-cheatsheet.html – timoh Sep 5 '14 at 9:22
  • 1
    @timoh Very good link, thank you. I'll fix the randomness source. I don't know how to avoid the timing problem though. Some googling seems to suggest that I need to be using bitwise ops, but I don't understand it enough. Could you shed any more light on the topic? – user50849 Sep 5 '14 at 9:44
  • About constant-time string comparison, check rdist.root.org/2009/05/28/… and for a specific PHP implementation you can use for example github.com/timoh6/TCrypto/blob/master/library/TCrypto/… (if you choose to use constant-time comparison instead of "double HMACing"). – timoh Sep 5 '14 at 10:34
2

Looks fine to me, except your use of RIPEMD128, about which Wikipedia has this to say:

The 128-bit version was intended only as a drop-in replacement for the original RIPEMD, which was also 128-bit, and which had been found to have questionable security.

I suggest you choose a better hash for your authenticity, e.g. SHA256 or at least RIPEMD160.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.