Author Topic: PHP command-line and server-side version  (Read 11695 times)

Offline Miquel 'Fire' Burns

  • Administrator
  • *****
  • Posts: 1157
  • Programmer
PHP command-line and server-side version
« on: October 08, 2005, 01:52:15 AM »
Because of my own needs, I converted PasswordMaker's backend code to PHP. On my first go, it didn't go well because PHP doesn't have the >>> operator, so some bit shifting wasn't able to work (and there might be a chance that if there's an overflow with the 32-bit intergers, PHP converts to float might have something to do with it as well)

Anyway, while using Mr. Venkman, I noticed that the return value for the hash functions before the character set was applied was in binary. Now PHP have mhash and I notice that it returns in binary, so as a test, I converted just the rstr2any function and did a test with the hex string '0123456789abcdef', you know the one you use if you want to create passwords like in 0.6, and compared the mhash function through rstr2any with PHP's builtin MD5 and SHA1 functions, and it worked, so it was downhill from there since I didn't need to deal with >>> at all!

The classes:
Code: [Select]
<?php
define('LEET_NONE', 0);
define('LEET_BEFORE', 1);
define('LEET_AFTER', 2);
define('LEET_BOTH', 3);
function generatepassword($hashAlgorithm, $key, $data, $whereToUseL33t, $l33tLevel, $passwordLength, $charset, $prefix, $suffix) {
    global $PasswordMaker_l33t, $PasswordMaker_SHA256, $PasswordMaker_SHA1, $PasswordMaker_MD4, $PasswordMaker_MD5, $PasswordMaker_RIPEMD160;
    // Never *ever, ever* allow the charset's length<2 else
    // the hash algorithms will run indefinitely
    if (strlen($charset) < 2)
  return "";
    
    // for non-hmac algorithms, the key is master pw and url concatenated
    $usingHMAC = strpos($hashAlgorithm, "hmac") !== false;
    if (!$usingHMAC)
  $key .= $data;
    
    // apply l33t before the algorithm?
    if ($whereToUseL33t == LEET_BOTH || $whereToUseL33t == LEET_BEFORE) {
  $key = $PasswordMaker_l33t->convert($l33tLevel, $key);
  if ($usingHMAC) {
      $data = $PasswordMaker_l33t->convert($l33tLevel, $data); // new for 0.3; 0.2 didn't apply l33t to _data_ for HMAC algorithms
  }
    }
    
    // apply the algorithm
    $password = '';
    /**/
    switch($hashAlgorithm) {
  case "sha256":
      $password = $PasswordMaker_SHA256->any_sha256($key, $charset);
      break;
  case "hmac-sha256":
      $password = $PasswordMaker_SHA256->any_hmac_sha256($key, $data, $charset);
      break;
  case "sha1":
      $password = $PasswordMaker_SHA1->any_sha1($key, $charset);
      break;
  case "hmac-sha1":
      $password = $PasswordMaker_SHA1->any_hmac_sha1($key, $data, $charset);
      break;
  case "md4":
      $password = $PasswordMaker_MD4->any_md4($key, $charset);
      break;
  case "hmac-md4":
      $password = $PasswordMaker_MD4->any_hmac_md4($key, $data, $charset);
      break;
  case "md5":
      $password = $PasswordMaker_MD5->any_md5($key, $charset);
      break;
  case "hmac-md5":
      $password = $PasswordMaker_MD5->any_hmac_md5($key, $data, $charset);
      break;
  case "rmd160":
      $password = $PasswordMaker_RIPEMD160->any_rmd160($key, $charset);
      break;
  case "hmac-rmd160":
      $password = $PasswordMaker_RIPEMD160->any_hmac_rmd160($key, $data, $charset);
      break;
    }
    /**/
    // apply l33t after the algorithm?
    if ($whereToUseL33t == LEET_BOTH || $whereToUseL33t == LEET_AFTER)
  $password = $PasswordMaker_l33t->convert($l33tLevel, $password);/**/
    if ($prefix)
  $password = $prefix . $password;
    if ($suffix)
  $password = substr($password, 0, $passwordLength-strlen($suffix)) . $suffix;
    return substr($password, 0, $passwordLength);
}

class PasswordMaker_l33t {
    var $alphabet, $levels;
    function PasswordMaker_l33t() {
  for($i = 0; $i < 26; $i++) {
      $t = chr(ord('a') + $i);
      $t = "/$t/";
      $this->alphabet[] = $t;
  }
  $this->levels = Array(
      Array("4", "b", "c", "d", "3", "f", "g", "h", "i", "j", "k", "1", "m", "n", "0", "p", "9", "r", "s", "7", "u", "v", "w", "x", "y", "z"),
      Array("4", "b", "c", "d", "3", "f", "g", "h", "1", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "y", "2"),
      Array("4", "8", "c", "d", "3", "f", "6", "h", "'", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "'/", "2"),
      Array("@", "8", "c", "d", "3", "f", "6", "h", "'", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "'/", "2"),
      Array("@", "|3", "c", "d", "3", "f", "6", "#", "!", "7", "|<", "1", "m", "n", "0", "|>", "9", "|2", "$", "7", "u", "\\/", "w", "x", "'/", "2"),
      Array("@", "|3", "c", "|)", "&", "|=", "6", "#", "!", ",|", "|<", "1", "m", "n", "0", "|>", "9", "|2", "$", "7", "u", "\\/", "w", "x", "'/", "2"),
      Array("@", "|3", "[", "|)", "&", "|=", "6", "#", "!", ",|", "|<", "1", "^^", "^/", "0", "|*", "9", "|2", "5", "7", "(_)", "\\/", "\\/\\/", "><", "'/", "2"),
      Array("@", "8", "(", "|)", "&", "|=", "6", "|-|", "!", "_|", "|\(", "1", "|\\/|", "|\\|", "()", "|>", "(,)", "|2", "$", "|", "|_|", "\\/", "\\^/", ")(", "'/", "\"/_"),
      Array("@", "8", "(", "|)", "&", "|=", "6", "|-|", "!", "_|", "|\{", "|_", "/\\/\\", "|\\|", "()", "|>", "(,)", "|2", "$", "|", "|_|", "\\/", "\\^/", ")(", "'/", "\"/_"));
    }

    /**
     * Convert the string in _message_ to l33t-speak
     * using the l33t level specified by _leetLevel_.
     * l33t levels are 1-9 with 1 being the simplest
     * form of l33t-speak and 9 being the most complex.
     *
     * Note that _message_ is converted to lower-case if
     * the l33t conversion is performed.
     * Future versions can support mixed-case, if we need it.
     *
     * Using a _leetLevel_ <= 0 results in the original message
     * being returned.
     *
     */
    function convert($leetLevel, $message) {
  if ($leetLevel > -1 && $leetLevel < 9) {
      $ret = strtolower($message);
      for ($item = 0; $item < count($this->alphabet); $item++) {
    $ret = preg_replace($this->alphabet[$item], $this->levels[$leetLevel][$item], $ret);
      }
      return $ret;
  }
  return $message;
    }
}
$PasswordMaker_l33t = new PasswordMaker_l33t;

class PasswordMaker_HashUtils {
    var $chrsz = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
    /*
     * Encode a string as utf-8.
     * For efficiency, this assumes the input is valid utf-16.
     */
    /*str2rstr_utf8 : function(input) {
  var output = "";
  var i = -1;
  var x, y;
  
  while(++i < input.length)
  {
      // Decode utf-16 surrogate pairs
      x = input.charCodeAt(i);
      y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
      if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
      {
    x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
    i++;
      }
      
      // Encode output as utf-8
      if(x <= 0x7F)
    output += String.fromCharCode(x);
      else if(x <= 0x7FF)
    output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
          0x80 | ( x         & 0x3F));
      else if(x <= 0xFFFF)
    output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
          0x80 | ((x >>> 6 ) & 0x3F),
          0x80 | ( x         & 0x3F));
      else if(x <= 0x1FFFFF)
    output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
          0x80 | ((x >>> 12) & 0x3F),
          0x80 | ((x >>> 6 ) & 0x3F),
          0x80 | ( x         & 0x3F));
  }
  return output;
    }/**/
    
    /*
    * Convert a raw string to an array of little-endian words
    * Characters >255 have their high-byte silently ignored.
    */
    /*rstr2binl : function(input) {
  var output = Array(input.length >> 2);
  for(var i = 0; i < output.length; i++)
      output[i] = 0;
  for(var i = 0; i < input.length * 8; i += 8)
      output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
  return output;
    }/**/
    
    /*
    * Convert an array of little-endian words to a string
    */
    /*binl2rstr : function(input) {
  var output = "";
  for(var i = 0; i < input.length * 32; i += 8)
      output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
  return output;
    }/**/
    
    /*
    * Convert a raw string to an arbitrary string encoding
    */
    function rstr2any($input, $encoding) {
  $divisor = strlen($encoding);
  $remainders = Array();
  
  /* Convert to an array of 16-bit big-endian values, forming the dividend */
  // pad this
  $dividend = array_pad(array(), strlen($input) / 2, 0);
  $inp = $input; // Because Miquel is a lazy twit and didn't want to do a search and replace
  for($i = 0; $i < count($dividend); $i++) {
      $dividend[$i] = (ord($inp{$i * 2}) << 8) | ord($inp{$i * 2 + 1});
  }
  
  /*
  * Repeatedly perform a long division. The binary array forms the dividend,
  * the length of the encoding is the divisor. Once computed, the quotient
  * forms the dividend for the next step. We stop when the dividend is zero.
  * All remainders are stored for later use.
  */
  while(count($dividend) > 0) {
      $quotient = Array();
      $x = 0;
      for($i = 0; $i < count($dividend); $i++) {
    $x = ($x << 16) + $dividend[$i];
    $q = floor($x / $divisor);
    $x -= $q * $divisor;
    if(count($quotient) > 0 || $q > 0)
        $quotient[count($quotient)] = $q;
      }
      $remainders[count($remainders)] = $x;
      $dividend = $quotient;
  }
  
  /* Convert the remainders to the output string */
  $output = "";
  for($i = count($remainders) - 1; $i >= 0; $i--)
      $output .= $encoding{$remainders[$i]};
  
  return $output;
    }
    
    ///===== big endian =====\\\
    
    /*
    * Convert a raw string to an array of big-endian words
    * Characters >255 have their high-byte silently ignored.
    */
    /*rstr2binb : function(input) {
  var output = Array(input.length >> 2);
  for(var i = 0; i < output.length; i++)
      output[i] = 0;
  for(var i = 0; i < input.length * 8; i += 8)
      output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32);
  return output;
    }/**/
    
    /*
    * Convert an array of big-endian words to a string
    */
    /*binb2rstr : function(input) {
  var output = "";
  for(var i = 0; i < input.length * 32; i += 8)
      output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF);
  return output;
    }/**/
    
    /*
    * Bitwise rotate a 32-bit number to the left.
    */
    /*bit_rol : function(num, cnt) {
  return (num << cnt) | (num >>> (32 - cnt));
    }/**/
    
    /*
    * Add integers, wrapping at 2^32. This uses 16-bit operations internally
    * to work around bugs in some JS interpreters.
    */
    /*safe_add : function(x, y) {
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
    }/**/
}
$PasswordMaker_HashUtils = new PasswordMaker_HashUtils;

class PasswordMaker_MD5 {
    /*
    any_hmac_md5 : function(k, d, e) { return PasswordMaker_HashUtils.rstr2any(this.rstr_hmac_md5(PasswordMaker_HashUtils.str2rstr_utf8(k), PasswordMaker_HashUtils.str2rstr_utf8(d)), e); },
    */
    function any_md5($s, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_MD5, $s), $e);
    }
    
    function any_hmac_md5($k, $d, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_MD5, $d, $k), $e);
    }
}
$PasswordMaker_MD5 = new PasswordMaker_MD5;

class PasswordMaker_MD4 {
    function any_md4($s, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_MD4, $s), $e);
    }
    
    function any_hmac_md4($k, $d, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_MD4, $d, $k), $e);
    }
}
$PasswordMaker_MD4 = new PasswordMaker_MD4;

class PasswordMaker_RIPEMD160 {
    function any_rmd160($s, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_RIPEMD160 , $s), $e);
    }
    
    function any_hmac_rmd160($k, $d, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_RIPEMD160, $d, $k), $e);
    }
}
$PasswordMaker_RIPEMD160 = new PasswordMaker_RIPEMD160;

class PasswordMaker_SHA1 {
    function any_sha1($s, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_SHA1 , $s), $e);
    }
    
    function any_hmac_sha1($k, $d, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_SHA1, $d, $k), $e);
    }
}
$PasswordMaker_SHA1 = new PasswordMaker_SHA1;

class PasswordMaker_SHA256 {
    function any_sha256($s, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_SHA256 , $s), $e);
    }
    
    function any_hmac_sha256($k, $d, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_SHA256, $d, $k), $e);
    }
}
$PasswordMaker_SHA256 = new PasswordMaker_SHA256;

?>

And the test case file
Code: [Select]
<?php
include 'passwordmaker_class.php';

$base93="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".
"0123456789`~!@#$%^&*()_-+={}|[]\:\";'<>?,./";
$hex = '0123456789abcdef';
// Test the function
$master = 'power';
$url = 'miquelfire.mfd';
$username = '';
$modifier = '';
$leetwhere = LEET_BEFORE; // Either constants or the numbers 0-3 LEET_NONE=0 LEET_BEFORE=1 LEET_AFTER=2 LEET_BOTH=3
$leetlevel = 4; // Subtract 1 from the level you're testing with. See the HTML code on www.password.org to see that this is true (0-8)
$passlen = 12;
$charset = $base93;
$prefix = '';
$suffix = '';
echo "MD4 generated password: ";
echo generatepassword('md4', $master, $url.$username.$modifier, $leetwhere, $leetlevel, $passlen, $charset, $prefix, $suffix)."\n";
echo "---\nHMAC-MD4 generated password: ";
echo generatepassword('hmac-md4', $master, $url.$username.$modifier, $leetwhere, $leetlevel, $passlen, $charset, $prefix, $suffix)."\n";
echo "---\nMD5 generated password: ";
echo generatepassword('md5', $master, $url.$username.$modifier, $leetwhere, $leetlevel, $passlen, $charset, $prefix, $suffix)."\n";
echo "---\nHMAC-MD5 generated password: ";
echo generatepassword('hmac-md5', $master, $url.$username.$modifier, $leetwhere, $leetlevel, $passlen, $charset, $prefix, $suffix)."\n";
echo "---\nSHA-1 generated password: ";
echo generatepassword('sha1', $master, $url.$username.$modifier, $leetwhere, $leetlevel, $passlen, $charset, $prefix, $suffix)."\n";
echo "---\nHMAC-SHA-1 generated password: ";
echo generatepassword('hmac-sha1', $master, $url.$username.$modifier, $leetwhere, $leetlevel, $passlen, $charset, $prefix, $suffix)."\n";
echo "---\nSHA-256 generated password: ";
echo generatepassword('sha256', $master, $url.$username.$modifier, $leetwhere, $leetlevel, $passlen, $charset, $prefix, $suffix)."\n";
echo "---\nHMAC-SHA-256 generated password: ";
echo generatepassword('hmac-sha256', $master, $url.$username.$modifier, $leetwhere, $leetlevel, $passlen, $charset, $prefix, $suffix)."\n";
echo "---\nRIPEMD-160 generated password: ";
echo generatepassword('rmd160', $master, $url.$username.$modifier, $leetwhere, $leetlevel, $passlen, $charset, $prefix, $suffix)."\n";
echo "---\nHMAC-RIPEMD-160 generated password: ";
echo generatepassword('hmac-rmd160', $master, $url.$username.$modifier, $leetwhere, $leetlevel, $passlen, $charset, $prefix, $suffix)."\n";
?>
Now unless the trimming of zeros popped it's head with this test case, HMAC-SHA256 is the only algorithm that doesn't work from some reason.

Compare with the online version if you don't want to trash your current settings (or risk it at least)
"I'm not drunk, just sleep deprived."

Offline Eric H. Jung

  • grimholtz
  • Administrator
  • *****
  • Posts: 3353
PHP command-line and server-side version
« Reply #1 on: October 08, 2005, 04:30:49 AM »
WOW. Miquelfire, you're my best friend right now! :)  I've needed to do this for a long time in order to support non-java, non-.NET platforms -- such as some mobile phones and other constrained devices.

This will definitely make it to the PasswordMaker website shortly, probably in two versions. One version will produce WML, and another will produce HTML. And, of course, I'll make the PHP downloadable so people can host it on their own HTTPS servers.

By the way, if you want to see whether or not this trims leading zeros, here is the test case.

THANK YOU!
« Last Edit: October 08, 2005, 04:31:00 AM by Eric H. Jung »

Offline Miquel 'Fire' Burns

  • Administrator
  • *****
  • Posts: 1157
  • Programmer
PHP command-line and server-side version
« Reply #2 on: October 09, 2005, 01:43:22 AM »
That leading zeros problem has nothing to do with it, either the JS version of the HMAC-SHA256 (which, btw, I never heard of before PasswordMaker, I only knew of MD5 and SHA1 because PHP has builtin functions for them without mhash) or mhash's is broken it seems. Using the hex string, the two versions are not even close. PHP: 1166c1c91298, JS (Online version): f0117a128cb2

Thinking about it, I might get to work on the C++ version now, just need to learn MHASH. Only need to convert the rstr2any and the main function really.

P.S. Any future programming has to wait because Gentoo decided to change something with Apache that broke my development environment that I can't use suphp anymore (and their ebuild only works for Apache 2... And I'm using Apache 1 because that's what my web host uses)
« Last Edit: October 09, 2005, 01:45:10 AM by miquelfire »
"I'm not drunk, just sleep deprived."

Offline Eric H. Jung

  • grimholtz
  • Administrator
  • *****
  • Posts: 3353
PHP command-line and server-side version
« Reply #3 on: October 09, 2005, 06:23:06 PM »
I wouldn't be surprised if the JS versions are inaccurate, because I recently ported rstr2any to Java and, unless I made a mistake, I wasn't getting the same hash values for some of the algorithms (don't remember if HMAC-SHA-256 was one of them).

Here's the java version of rstr2any(). The hashing uses Bouncy Castle's library; they offer a C# version too, which I've started using. But I'm getting the same inconsistencies with the JS version. It's extremely frustrating so I would value your feedback.

A C++ version would be nicer than C#, of course, since (1) MS is ditching built-in support for .NET in Windows Vista, (2) it would work on non-Windows platforms if complied with a GUI library like QT, GTK, or whatever.

Anyway, I look forward to your feedback.

-Eric

Offline Miquel 'Fire' Burns

  • Administrator
  • *****
  • Posts: 1157
  • Programmer
PHP command-line and server-side version
« Reply #4 on: October 10, 2005, 12:41:26 AM »
I'm looking into making a C or C++ version, but I only understand QT, but that would require the program to be GPL, unless I shell out big money (and if I had that kind of money to throw around, I would have a Apple Laptop by now), so I'll see about creating the backend anyway so someone else who understands GTK, WxWdigets, or whatever has a licence that allows LGPL can make the frontend.

My theory is that, except maybe the leading zero prblem, the JS version goofed up with HMAC-SHA-256. rstr2any() trims leading zeros it seems though.
"I'm not drunk, just sleep deprived."

Offline Eric H. Jung

  • grimholtz
  • Administrator
  • *****
  • Posts: 3353
PHP command-line and server-side version
« Reply #5 on: November 14, 2005, 03:46:19 PM »
Here is a version of PasswordMaker for PHP written by Pablo Pedro Gimeno. If anyone knows what Pablo Pedro means by "A subtle implementation detail in the binb_sha256 JS function", please let me know. I'd be willing to change it so it is correct, and offer both versions of SHA256 in passwordmaker (as is currently done with MD-5).


Code: [Select]
<?php

  /* PasswordMaker-compatible password generator as a PHP
     include file.

     Written by Pedro Gimeno, 2005-11-14.
     <http://www.formauri.es/personal/pgimeno/>
     License: Public domain.

     Usage:

     makepasswd(alg, len, charset, mpw, domain, user
                [, data[, pfx[, sfx]]])

     Returns the password corresponding to the given data, or an
     empty string if something went wrong.

     alg is one of:
       PWMAKER_MD4
       PWMAKER_HMAC_MD4
       PWMAKER_MD5
       PWMAKER_HMAC_MD5
       PWMAKER_SHA1
       PWMAKER_HMAC_SHA1
       PWMAKER_SHA256
       PWMAKER_HMAC_SHA256
       PWMAKER_RIPEMD160
       PWMAKER_HMAC_RIPEMD160

     Note: Because of a bug in the HMAC-SHA256 implementation in
     PasswordMaker, the generated passwords are not compatible with
     that algorithm.

     len is the desired length of the generated password.
     charset is the set of characters that the password must include.
     mpw is the master password.
     domain is the domain name.
     user is the user name.
     data is the extra data/counter/etc., if any (optional)
     pfx is the prefix to add to the password (optional)
     sfx is the suffix the password must end in (optional)

  */

  if (! function_exists('mhash'))
    exit("Module <i>mhash</i> not installed. Can't continue, sorry.");

  define('PWMAKER_MD4', 0);
  define('PWMAKER_HMAC_MD4', 0x80);
  define('PWMAKER_MD5', 1);
  define('PWMAKER_HMAC_MD5', 0x81);
  define('PWMAKER_SHA1', 2);
  define('PWMAKER_HMAC_SHA1', 0x82);
  define('PWMAKER_SHA256', 3);
  define('PWMAKER_HMAC_SHA256', 0x83);
  define('PWMAKER_RIPEMD160', 4);
  define('PWMAKER_HMAC_RIPEMD160', 0x84);

/* THIS DOES NOT WORK- A subtle implementation detail in the
   binb_sha256 JS function forces us to write a complete SHA256
   implementation if we want to behave the same as the bug.
function _hmac_sha256($data, $key)
{
  $ipad = str_repeat('6', 64);
  $opad = str_repeat('\\', 64);
  for ($i = min(strlen($key), 32); $i > 0; $i--)
    {
      $ipad{$i - 1} = chr(ord($ipad{$i - 1}) ^ ord($key{$i - 1}));
      $opad{$i - 1} = chr(ord($opad{$i - 1}) ^ ord($key{$i - 1}));
    }
  $hash = mhash(MHASH_SHA256, $ipad . $data);
  return mhash(MHASH_SHA256, substr($opad . $hash, 0, (512+160)/8));
}
*/

function makepassword($alg, $len, $charset, $mpw, $domain, $user, $data = '', $pfx = '', $sfx = '')
{
  if ($charset == '')
    return '';
  $len = $len - 0;
  if (strlen($charset) == 1)
    $pwd = str_repeat($charset, $len);
  else
    {
      $map_alg = array(PWMAKER_MD4 => MHASH_MD4,
                       PWMAKER_HMAC_MD4 => MHASH_MD4,
                       PWMAKER_MD5 => MHASH_MD5,
                       PWMAKER_HMAC_MD5 => MHASH_MD5,
                       PWMAKER_SHA1 => MHASH_SHA1,
                       PWMAKER_HMAC_SHA1 => MHASH_SHA1,
                       PWMAKER_SHA256 => MHASH_SHA256,
                       PWMAKER_HMAC_SHA256 => MHASH_SHA256,
                       PWMAKER_RIPEMD160 => MHASH_RIPEMD160,
                       PWMAKER_HMAC_RIPEMD160 => MHASH_RIPEMD160);

      if (! isset($map_alg[$alg]))
        return '';
      $hmac = ($alg >= 0x80);
      /*if ($alg == PWMAKER_HMAC_SHA256)
        $hash = _hmac_sha256($domain . $user . $data, $mpw);
      else*/
      if ($hmac)
        $hash = mhash($map_alg[$alg], $domain . $user . $data, $mpw);
      else
        $hash = mhash($map_alg[$alg], $mpw . $domain . $user . $data);
      $num = '0';
      for ($i = 0; $i < strlen($hash); $i++)
        $num = bcadd(bcmul($num, '256'), strval(ord($hash{$i})));
      $pwd = '';
      while ($num)
        {
          $pwd = $charset{bcmod($num, strval(strlen($charset)))} . $pwd;
          $num = bcdiv($num, strval(strlen($charset)));
        }
    }
  if (strlen($sfx) < $len)
    return substr($pfx . $pwd, 0, $len - strlen($sfx)) . $sfx;
  return substr($sfx, 0, $len);
}

?>
« Last Edit: November 17, 2005, 09:04:52 PM by Eric H. Jung »

Offline pgimeno

  • Jr. Member
  • **
  • Posts: 11
PHP command-line and server-side version
« Reply #6 on: November 17, 2005, 05:07:31 PM »
Hello,

Eric,

The name's Pedro, not Pablo :)

As I noted by E-mail, the differences in the HMAC-SHA-256 are due to a bug in the JS implementation (patch).

The 'subtle implementation detail' is the mixing between 'm.length' and the passed parameter 'l' within the SHA-256 algorithm, which prevents working around the bug in the HMAC-SHA-256 calculation function without writing a complete SHA-256 implementation (there is an LGPLed one already but I don't think that's the way to go, as already discussed elsewhere).

I'm rejecting my poor implementation in favour of miquelfire's one. I wasn't aware of its existence but it is obviously complete while mine isn't.

-- Pedro Gimeno

Offline Miquel 'Fire' Burns

  • Administrator
  • *****
  • Posts: 1157
  • Programmer
PHP command-line and server-side version
« Reply #7 on: November 17, 2005, 06:20:57 PM »
My version support editting rstr2any to NOT trim leading zeroes as well (which, I believe my most recent copy of allows, it's just an extra parameter if you want the leading zeros, or whatever means zero in the character set). I know the current C++ version allows that.
"I'm not drunk, just sleep deprived."

Offline pgimeno

  • Jr. Member
  • **
  • Posts: 11
PHP command-line and server-side version
« Reply #8 on: November 28, 2005, 04:06:05 PM »
Here is my PHP front end to miquelfire's makepassword() function:

Code: [Select]
#!/usr/bin/php
<?php

  /* Command-line front end for the makepassword function defined in
     passwordmaker_class.php file.
     Version: 2.0.

     Written by Pedro Gimeno Fortea, 2005-11-26.
     <http://www.formauri.es/personal/pgimeno/>
     License: Public domain. NO WARRANTIES OF ANY KIND.

     Note: If only the CGI version of PHP is available, change the first
     line of this file to the path of the CGI program, adding -q in the
     line above to avoid the headers being printed.
  */

  require_once('passwordmaker_class.php');

  /* The 'leet' define contains the string used in the command
     line for the leet settings. The decision about using l33t=x
     or leet=x affects this. */
  define('leet', 'l33t');

function printerr_exit($errcode, $txt)
{
  /* This can later be conditioned to be in "verbose mode"
     or not to be in "quiet mode", if desired. By now it
     unconditionally prints the passed text. */
  print($txt);
  exit($errcode);
}

function help()
{
  $leet = leet;
  printerr_exit(0, "This is a preliminary help only.
Here's a little advance:

Parameters are in form of var=value pairs.

Available parameters:
  alg=[hmac-] md4/md5/sha1/sha256/rmd160 (default md5)
  mpw=master password (default blank)
  url=url to use (default blank)
  user=user id (default blank)
  mod=modifier (default blank)
  len=length of generated password (default 8)
  charset=characters to use in password (default 0123456789abcdef)
  pfx=prefix (default blank)
  sfx=suffix (default blank)
  $leet=n/bN/aN/2N
    $leet=n (default): none;
    $leet=bN (N=1..9): before;
    $leet=aN: after;
    $leet=2N: both
    e.g. $leet=23 means before and after generating password, with
    leet level 3.\n");
}

  if ($_SERVER['argc'] <= 1)
    help();

  $args=array();
  $argv = $_SERVER['argv'];

  /* The parsing is performed here. It can be done in several
     ways; the result must be the array $args[] filled with
     pairs of the form $args['var'] = 'value'.

     The following code just interprets the command line as
     multiple var=value arguments, like this:

           passwdmaker.php alg=md5 charset=ABC mpw=PW ...

     getopt()-like options can be used instead as long as the
     resulting array has the same structure.
  */

  for ($i = 1; $i < $_SERVER['argc']; $i++)
    {
      $argpos = strpos($argv[$i], '=');
      if ($argpos !== false) /* found '=' */
        $args[strtolower(substr($argv[$i], 0, $argpos))] =
          substr($argv[$i], $argpos + 1);
      else
        printerr_exit(1, "Invalid parameter: $argv[$i]\n");
    }

  /* Default algorithm is md5 */

  if (! isset($args['alg']))
    $args['alg'] = 'md5';

  /* Check for validity of algorithm */

  $valid_algs = array('md4', 'hmac-md4',
                      'md5', 'hmac-md5',
                      'sha1', 'hmac-sha1',
                      'sha256', 'hmac-sha256',
                      'rmd160', 'hmac-rmd160'
                      );

  if (! in_array(strtolower($args['alg']), $valid_algs))
    printerr_exit(1, "Unknown or misspelled algorithm: $args[alg]\n");

  /* Default for leet is LEET_NONE */

  $map_lmode = array('' => LEET_NONE,
                     'n' => LEET_NONE,
                     'b' => LEET_BEFORE,
                     'a' => LEET_AFTER,
                     '2' => LEET_BOTH);

  $leet = $map_lmode[$args[leet]{0}];
  $leetlevel = $args[leet]{1};
  if (! $leetlevel)
    $leetlevel = 0;
  else
    $leetlevel = (int)$leetlevel;

  if (! isset($leet) or
      ($leet != LEET_NONE and ($leetlevel < 1 or $leetlevel > 9)))
    printerr_exit(1, "Invalid " . leet . " setting: " . $args[leet]);

  if (! isset($args['len']))
    $args['len'] = 8;

  if (! isset($args['charset']))
    $args['charset'] = '0123456789abcdef';

  echo generatepassword($args['alg'],
                        $args['mpw'],
                        $args['url'] . $args['user'] . $args['mod'],
                        $leet,
                        $leetlevel - 1,
                        $args['len'],
                        $args['charset'],
                        $args['pfx'],
                        $args['sfx']),
        "\n";

?>

For a syntax-hilighted version see this link:

The link

Integrating support for leading zero removal/keeping should be straightforward when Miquelfire's backend supports it.

-- Pedro Gimeno
« Last Edit: November 28, 2005, 04:08:13 PM by pgimeno »

Offline Miquel 'Fire' Burns

  • Administrator
  • *****
  • Posts: 1157
  • Programmer
PHP command-line and server-side version
« Reply #9 on: November 29, 2005, 03:37:08 AM »
I been too lazy to post this, sorry.

Note to anyone not on the development team: there's an issue that needs to be taken care of before the keeping of the leading zeros option can be used. pgimeno is the guy who pointed out the issue as it stands.

Code: [Select]
<?php
define('LEET_NONE', 0);
define('LEET_BEFORE', 1);
define('LEET_AFTER', 2);
define('LEET_BOTH', 3);
// $data = $url.$username.$modifier
function generatepassword($hashAlgorithm, $key, $data, $whereToUseL33t, $l33tLevel, $passwordLength, $charset, $prefix, $suffix) {
    global $PasswordMaker_l33t, $PasswordMaker_SHA256, $PasswordMaker_SHA1, $PasswordMaker_MD4, $PasswordMaker_MD5, $PasswordMaker_RIPEMD160;
    // Never *ever, ever* allow the charset's length<2 else
    // the hash algorithms will run indefinitely
    if (strlen($charset) < 2)
  return "";
    
    // for non-hmac algorithms, the key is master pw and url concatenated
    $usingHMAC = strpos($hashAlgorithm, "hmac") !== false;
    if (!$usingHMAC)
  $key .= $data;
    
    // apply l33t before the algorithm?
    if ($whereToUseL33t == LEET_BOTH || $whereToUseL33t == LEET_BEFORE) {
  $key = $PasswordMaker_l33t->convert($l33tLevel, $key);
  if ($usingHMAC) {
      $data = $PasswordMaker_l33t->convert($l33tLevel, $data); // new for 0.3; 0.2 didn't apply l33t to _data_ for HMAC algorithms
  }
    }
    
    // apply the algorithm
    $password = '';
    /**/
    echo "$key $data\n";
    switch($hashAlgorithm) {
  case "sha256":
      $password = $PasswordMaker_SHA256->any_sha256($key, $charset);
      echo "$password\n";
      break;
  case "hmac-sha256":
      $password = $PasswordMaker_SHA256->any_hmac_sha256($key, $data, $charset);
      break;
  case "sha1":
      $password = $PasswordMaker_SHA1->any_sha1($key, $charset);
      break;
  case "hmac-sha1":
      $password = $PasswordMaker_SHA1->any_hmac_sha1($key, $data, $charset);
      break;
  case "md4":
      $password = $PasswordMaker_MD4->any_md4($key, $charset);
      break;
  case "hmac-md4":
      $password = $PasswordMaker_MD4->any_hmac_md4($key, $data, $charset);
      break;
  case "md5":
      $password = $PasswordMaker_MD5->any_md5($key, $charset);
      break;
  case "hmac-md5":
      $password = $PasswordMaker_MD5->any_hmac_md5($key, $data, $charset);
      break;
  case "rmd160":
      $password = $PasswordMaker_RIPEMD160->any_rmd160($key, $charset);
      break;
  case "hmac-rmd160":
      $password = $PasswordMaker_RIPEMD160->any_hmac_rmd160($key, $data, $charset);
      break;
    }
    /**/
    // apply l33t after the algorithm?
    if ($whereToUseL33t == LEET_BOTH || $whereToUseL33t == LEET_AFTER)
  $password = $PasswordMaker_l33t->convert($l33tLevel, $password);/**/
    if ($prefix)
  $password = $prefix . $password;
    if ($suffix)
  $password = substr($password, 0, $passwordLength-strlen($suffix)) . $suffix;
    return substr($password, 0, $passwordLength);
}

class PasswordMaker_l33t {
    var $alphabet, $levels;
    function PasswordMaker_l33t() {
  for($i = 0; $i < 26; $i++) {
      $t = chr(ord('a') + $i);
      $t = "/$t/";
      $this->alphabet[] = $t;
  }
  $this->levels = Array(
      Array("4", "b", "c", "d", "3", "f", "g", "h", "i", "j", "k", "1", "m", "n", "0", "p", "9", "r", "s", "7", "u", "v", "w", "x", "y", "z"),
      Array("4", "b", "c", "d", "3", "f", "g", "h", "1", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "y", "2"),
      Array("4", "8", "c", "d", "3", "f", "6", "h", "'", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "'/", "2"),
      Array("@", "8", "c", "d", "3", "f", "6", "h", "'", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "'/", "2"),
      Array("@", "|3", "c", "d", "3", "f", "6", "#", "!", "7", "|<", "1", "m", "n", "0", "|>", "9", "|2", "$", "7", "u", "\\/", "w", "x", "'/", "2"),
      Array("@", "|3", "c", "|)", "&", "|=", "6", "#", "!", ",|", "|<", "1", "m", "n", "0", "|>", "9", "|2", "$", "7", "u", "\\/", "w", "x", "'/", "2"),
      Array("@", "|3", "[", "|)", "&", "|=", "6", "#", "!", ",|", "|<", "1", "^^", "^/", "0", "|*", "9", "|2", "5", "7", "(_)", "\\/", "\\/\\/", "><", "'/", "2"),
      Array("@", "8", "(", "|)", "&", "|=", "6", "|-|", "!", "_|", "|\(", "1", "|\\/|", "|\\|", "()", "|>", "(,)", "|2", "$", "|", "|_|", "\\/", "\\^/", ")(", "'/", "\"/_"),
      Array("@", "8", "(", "|)", "&", "|=", "6", "|-|", "!", "_|", "|\{", "|_", "/\\/\\", "|\\|", "()", "|>", "(,)", "|2", "$", "|", "|_|", "\\/", "\\^/", ")(", "'/", "\"/_"));
    }

    /**
     * Convert the string in _message_ to l33t-speak
     * using the l33t level specified by _leetLevel_.
     * l33t levels are 1-9 with 1 being the simplest
     * form of l33t-speak and 9 being the most complex.
     *
     * Note that _message_ is converted to lower-case if
     * the l33t conversion is performed.
     * Future versions can support mixed-case, if we need it.
     *
     * Using a _leetLevel_ <= 0 results in the original message
     * being returned.
     *
     */
    function convert($leetLevel, $message) {
  if ($leetLevel > -1 && $leetLevel < 9) {
      $ret = strtolower($message);
      for ($item = 0; $item < count($this->alphabet); $item++) {
    $ret = preg_replace($this->alphabet[$item], $this->levels[$leetLevel][$item], $ret);
      }
      return $ret;
  }
  return $message;
    }
}
$PasswordMaker_l33t = new PasswordMaker_l33t;

class PasswordMaker_HashUtils {
    var $chrsz = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
    /*
    * Convert a raw string to an arbitrary string encoding
    * Set $trim to false for keeping leading zeros
    */
    function rstr2any($input, $encoding, $trim = true) {
  $divisor = strlen($encoding);
  $remainders = Array();
  
  /* Convert to an array of 16-bit big-endian values, forming the dividend */
  // pad this
  $dividend = array_pad(array(), ceil(strlen($input) / 2), 0);
  $inp = $input; // Because Miquel is a lazy twit and didn't want to do a search and replace
  for($i = 0; $i < count($dividend); $i++) {
      $dividend[$i] = (ord($inp{$i * 2}) << 8) | ord($inp{$i * 2 + 1});
  }
  
  $full_length = ceil((float)strlen($input) * 8    
      / (log(strlen($encoding)) / log(2)));
  /*
  * Repeatedly perform a long division. The binary array forms the dividend,
  * the length of the encoding is the divisor. Once computed, the quotient
  * forms the dividend for the next step. We stop when the dividend is zero.
  * All remainders are stored for later use.
  */
  if ($trim) {
      while(count($dividend) > 0) {
    $quotient = Array();
    $x = 0;
    for($i = 0; $i < count($dividend); $i++) {
        $x = ($x << 16) + $dividend[$i];
        $q = floor($x / $divisor);
        $x -= $q * $divisor;
        if(count($quotient) > 0 || $q > 0)
      $quotient[count($quotient)] = $q;
    }
    $remainders[count($remainders)] = $x;
    //$remainders[$j] = $x;
    $dividend = $quotient;
      }
  } else {
      for($j = 0; $j < $full_length; $j++) {
    $quotient = Array();
    $x = 0;
    for($i = 0; $i < count($dividend); $i++) {
        $x = ($x << 16) + $dividend[$i];
        $q = floor($x / $divisor);
        $x -= $q * $divisor;
        if(count($quotient) > 0 || $q > 0)
      $quotient[count($quotient)] = $q;
    }
    $remainders[$j] = $x;
    $dividend = $quotient;
      }
  }
  
  /* Convert the remainders to the output string */
  $output = "";
  for($i = count($remainders) - 1; $i >= 0; $i--)
      $output .= $encoding{$remainders[$i]};
  
  return $output;
    }
}
$PasswordMaker_HashUtils = new PasswordMaker_HashUtils;

class PasswordMaker_MD5 {
    /*
    any_hmac_md5 : function(k, d, e) { return PasswordMaker_HashUtils.rstr2any(this.rstr_hmac_md5(PasswordMaker_HashUtils.str2rstr_utf8(k), PasswordMaker_HashUtils.str2rstr_utf8(d)), e); },
    */
    function any_md5($s, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_MD5, $s), $e);
    }
    
    function any_hmac_md5($k, $d, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_MD5, $d, $k), $e);
    }
}
$PasswordMaker_MD5 = new PasswordMaker_MD5;

class PasswordMaker_MD4 {
    function any_md4($s, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_MD4, $s), $e);
    }
    
    function any_hmac_md4($k, $d, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_MD4, $d, $k), $e);
    }
}
$PasswordMaker_MD4 = new PasswordMaker_MD4;

class PasswordMaker_RIPEMD160 {
    function any_rmd160($s, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_RIPEMD160 , $s), $e);
    }
    
    function any_hmac_rmd160($k, $d, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_RIPEMD160, $d, $k), $e);
    }
}
$PasswordMaker_RIPEMD160 = new PasswordMaker_RIPEMD160;

class PasswordMaker_SHA1 {
    function any_sha1($s, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_SHA1 , $s), $e);
    }
    
    function any_hmac_sha1($k, $d, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_SHA1, $d, $k), $e);
    }
}
$PasswordMaker_SHA1 = new PasswordMaker_SHA1;

class PasswordMaker_SHA256 {
    function any_sha256($s, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_SHA256 , $s), $e);
    }
    
    function any_hmac_sha256($k, $d, $e) {
  global $PasswordMaker_HashUtils;
  return $PasswordMaker_HashUtils->rstr2any(mhash(MHASH_SHA256, $d, $k), $e);
    }
}
$PasswordMaker_SHA256 = new PasswordMaker_SHA256;

?>
« Last Edit: November 29, 2005, 03:48:55 AM by miquelfire »
"I'm not drunk, just sleep deprived."

Offline Eric H. Jung

  • grimholtz
  • Administrator
  • *****
  • Posts: 3353
PHP command-line and server-side version
« Reply #10 on: February 02, 2006, 06:50:15 PM »
To anyone who comes across this thread in the future, the PHP Edition of PasswordMaker has matured quite a bit. It is available for download here.

PasswordMaker Forums

PHP command-line and server-side version
« Reply #10 on: February 02, 2006, 06:50:15 PM »