Tuesday, February 26, 2008

Passwords in phpBB 3

Here is a port of phpBB3's password handling to Java.

import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;

/**
* Port of phpBB3 password handling to Java.
* See phpBB3/includes/functions.php
*
* @author lars
*/
public class PHPBB3Password {
private static final int PHP_VERSION = 4;
private String itoa64 =
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

public String phpbb_hash(String password) {
String random_state = unique_id();
String random = "";
int count = 6;

if (random.length() < count) {
random = "";

for (int i = 0; i < count; i += 16) {
random_state = md5(unique_id() + random_state);
random += pack(md5(random_state));
}
random = random.substring(0, count);
}

String hash = _hash_crypt_private(
password, _hash_gensalt_private(random, itoa64));
if (hash.length() == 34)
return hash;

return md5(password);
}

private String unique_id() {
return unique_id("c");
}

// global $config;
// private boolean dss_seeded = false;

private String unique_id(String extra) {
// TODO Generate something random here.
return "1234567890abcdef";
}

private String _hash_gensalt_private(String input, String itoa64) {
return _hash_gensalt_private(input, itoa64, 6);
}

private String _hash_gensalt_private(
String input, String itoa64, int iteration_count_log2) {
if (iteration_count_log2 < 4 || iteration_count_log2 > 31) {
iteration_count_log2 = 8;
}

String output = "$H$";
output += itoa64.charAt(
Math.min(iteration_count_log2 +
((PHP_VERSION >= 5) ? 5 : 3), 30));
output += _hash_encode64(input, 6);

return output;
}

/**
* Encode hash
*/
private String _hash_encode64(String input, int count) {
String output = "";
int i = 0;

do {
int value = input.charAt(i++);
output += itoa64.charAt(value & 0x3f);

if (i < count)
value |= input.charAt(i) << 8;

output += itoa64.charAt((value >> 6) & 0x3f);

if (i++ >= count)
break;

if (i < count)
value |= input.charAt(i) << 16;

output += itoa64.charAt((value >> 12) & 0x3f);

if (i++ >= count)
break;

output += itoa64.charAt((value >> 18) & 0x3f);
} while (i < count);

return output;
}

String _hash_crypt_private(String password, String setting) {
String output = "*";

// Check for correct hash
if (!setting.substring(0, 3).equals("$H$"))
return output;

int count_log2 = itoa64.indexOf(setting.charAt(3));
if (count_log2 < 7 || count_log2 > 30)
return output;

int count = 1 << count_log2;
String salt = setting.substring(4, 12);
if (salt.length() != 8)
return output;

String m1 = md5(salt + password);
String hash = pack(m1);
do {
hash = pack(md5(hash + password));
} while (--count > 0);

output = setting.substring(0, 12);
output += _hash_encode64(hash, 16);

return output;
}

public boolean phpbb_check_hash(
String password, String hash) {
if (hash.length() == 34)
return _hash_crypt_private(password, hash).equals(hash);
else
return md5(password).equals(hash);
}

public static String md5(String data) {
try {
byte[] bytes = data.getBytes("ISO-8859-1");
MessageDigest md5er = MessageDigest.getInstance("MD5");
byte[] hash = md5er.digest(bytes);
return bytes2hex(hash);
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}

static int hexToInt(char ch) {
if(ch >= '0' && ch <= '9')
return ch - '0';

ch = Character.toUpperCase(ch);
if(ch >= 'A' && ch <= 'F')
return ch - 'A' + 0xA;

throw new IllegalArgumentException("Not a hex character: " + ch);
}

private static String bytes2hex(byte[] bytes) {
StringBuffer r = new StringBuffer(32);
for (int i = 0; i < bytes.length; i++) {
String x = Integer.toHexString(bytes[i] & 0xff);
if (x.length() < 2)
r.append("0");
r.append(x);
}
return r.toString();
}

static String pack(String hex) {
StringBuffer buf = new StringBuffer();
for(int i = 0; i < hex.length(); i += 2) {
char c1 = hex.charAt(i);
char c2 = hex.charAt(i+1);
char packed = (char) (hexToInt(c1) * 16 + hexToInt(c2));
buf.append(packed);
}
return buf.toString();
}
}

16 comments:

Dandy said...

Great effort. Thank you.

A couple of small changes are required for using PHP version 5 or above.

Change PHP_VERSION to 5.

In the _hash_crypt_private method, instead of

String m1 = md5(salt + password);
String hash = pack(m1);
do {
hash = pack(md5(hash + password));
} while (--count > 0);


use the code below -

String hash=md5(salt+password);
do{
hash=md5(hash+password);
}while(--count>0);

LkS said...

What this scripts does? Convert phpbb3 passwords to md5 standard?

Lars said...

phpbb_hash encrypts a password to the format PHPBB stores in its database.

phpbb_check_hash checks a password against a PHPBB password stored in a database.

deki.bg@gmail.com said...

This is all great, but is there a simple login script with inputs and submit button?
I do not know how to do it completely.
Thanks

Anonymous said...
This comment has been removed by a blog administrator.
Wade said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...

Thanks a lot. I was searching a few hours for something like that, since i was too lazy to implement it myself.

Anonymous said...

Doesn't work for me. I think the problem is in the "_hash_encode64" function. I'm using phpBB 3.0.6.

Anonymous said...

I've solved my problem. If you need "phpbb_check_hash" method, the critical code in the "_hash_crypt_private" method has to be like this:

if (PHP_VERSION < 5) {
hash = md5(salt + password);

do {
hash = md5(hash + password);
} while (--count > 0);
}
else {
String m1 = md5(salt + password);
hash = pack(m1);

do {
hash = pack(md5(hash + password));
} while (--count > 0);
}

the opposite of code mentioned by Dandy that is into functions.php from phpBB sources. I don't know why!

Jun Chen said...

I tried to implement your code then verify the hash code generated by phpBB3 and phpbb_hash()er
(http://www.cs278.org/tools/phpbb/hash.php).

Both failed, is there any difference between your hash and phpBB3 hash?

TIA

Lars said...

@Jun Not that I know of. It worked for me, but perhaps they have changed the code in the latest version. Also try the PHP_VERSION that others have commented about.

Jun Chen said...

Well, I did update the code for PHP version 5...

Jun Chen said...

My test code:

public static void main(String[] args) {
// TODO code application logic here
PHPBB3Password Java_Hash = new PHPBB3Password();
String pwd = "q1w2e3";
String hash = Java_Hash.phpbb_hash(pwd);
hash = "$H$9ISv6CU2EGzmlpQjdfsrv8rPHADEXr1";

boolean result = Java_Hash.phpbb_check_hash(pwd, hash);

if (result) {
System.out.println("Authentication successful.");
} else {
System.out.println("Authentication failed.");
}
}

Where hash = "$H$9ISv6CU2EGzmlpQjdfsrv8rPHADEXr1" I got from http://www.cs278.org/tools/phpbb/hash.php using password: q1w2e3.

And verification failed!

I did implement php version 5 code, can anyone else test it please?

MR_tsuJ said...

Thanks a lot for sharing this with us! I needed to implement authentication for PHPBB3 for my texas poker hold'em client. It works like a charm! Much appreciated!

@Jun Chen
I tested your password, it works:

public class phpbb {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String testPwd = "q1w2e3";
Password pwd = new Password();
String hash = pwd.phpbb_hash(testPwd);
// Hashed Pwd stored in database
String hashedPwd = "$H$9ISv6CU2EGzmlpQjdfsrv8rPHADEXr1";

System.out.println("Password used: " + testPwd);
System.out.println("Hashed Pwd from database " + hashedPwd);
System.out.println("phpbb_hash result " + hash);

boolean result = pwd.phpbb_check_hash(testPwd, hashedPwd);

if (result) {
System.out.println("Authentication successful.");
} else {
System.out.println("Authentication failed.");
}
}
}

Output:
Password used: q1w2e3
Hashed Pwd from database $H$9ISv6CU2EGzmlpQjdfsrv8rPHADEXr1
phpbb_hash result $H$99uwqEmAj8yY9.PEP6MiqxjiebFhQ81
Authentication successful.

The only thing I have changed is to set PHP version to 5. Running Phpbb3 Olympus out of the box from a fresh install.

Hope it helps!