简体   繁体   English

在PHP和Java之间散列

[英]Hashing between PHP and Java

I'm trying to create a hash in PHP store it in a database then verify the hash in Java. 我试图在PHP中创建一个哈希,将其存储在数据库中,然后在Java中验证哈希。 So far they both work fine independently from each other ... Java can hash and verify java, Php can hash and verify php, but they aren't playing nicely together despite my best efforts. 到目前为止,它们彼此独立运行良好... Java可以哈希并验证Java,Php可以哈希并验证php,但是尽管我尽了最大努力,但它们并不能很好地协作。

  1. I've changed the algorithm to sha1 in php to match javas PBKDF2WithHmacSHA1 - Do they match? 我已将算法更改为php中的sha1以匹配javas PBKDF2WithHmacSHA1它们匹配吗?
  2. I've used Base64.decodeBase64() from the apache commons codec library to decode the php base64_encode() function - is this ok? 我已经使用了Apache Commons编解码器库中的Base64.decodeBase64()来解码php base64_encode()函数-可以吗?
  3. I'm manually removing the algorithm "sha1:" part for java for now. 我现在正在手动删除Java的算法“ sha1:”部分。

Here's the code, can you spot any reasons why the java implementation wouldn't be able to verify the hash produced by the php version? 这是代码,您能找到导致Java实现无法验证php版本产生的哈希值的任何原因吗? - I'm not getting error, it's just the verification is failing when the correct password is "password" for that hash. -我没有收到错误,只是当正确的密码是该哈希的“密码”时,验证失败。

Part 1 ... I define all the variables for PHP, the only different variable is the algorithm in use 第1部分...我定义了PHP的所有变量,唯一不同的变量是使用的算法

define("PBKDF2_HASH_ALGORITHM", "sha1");
define("PBKDF2_ITERATIONS", 1000);
define("PBKDF2_SALT_BYTE_SIZE", 24);
define("PBKDF2_HASH_BYTE_SIZE", 24);

define("HASH_SECTIONS", 4);
define("HASH_ALGORITHM_INDEX", 0);
define("HASH_ITERATION_INDEX", 1);
define("HASH_SALT_INDEX", 2);
define("HASH_PBKDF2_INDEX", 3);

Part 2 ... The code to create / validate hashes in php 第2部分...在php中创建/验证哈希的代码

function create_hash($password)
{
    // format: algorithm:iterations:salt:hash
    $salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTE_SIZE, MCRYPT_DEV_URANDOM));
    return PBKDF2_HASH_ALGORITHM . ":" . PBKDF2_ITERATIONS . ":" .  $salt . ":" .
    base64_encode(pbkdf2(
        PBKDF2_HASH_ALGORITHM,
        $password,
        $salt,
        PBKDF2_ITERATIONS,
        PBKDF2_HASH_BYTE_SIZE,
        true
    ));
}

function validate_password($password, $correct_hash)
{
$params = explode(":", $correct_hash);
if(count($params) < HASH_SECTIONS)
   return false;
$pbkdf2 = base64_decode($params[HASH_PBKDF2_INDEX]);
return slow_equals(
    $pbkdf2,
    pbkdf2(
        $params[HASH_ALGORITHM_INDEX],
        $password,
        $params[HASH_SALT_INDEX],
        (int)$params[HASH_ITERATION_INDEX],
        strlen($pbkdf2),
        true
    )
);
}

// Compares two strings $a and $b in length-constant time.
function slow_equals($a, $b)
{
$diff = strlen($a) ^ strlen($b);
for($i = 0; $i < strlen($a) && $i < strlen($b); $i++)
{
    $diff |= ord($a[$i]) ^ ord($b[$i]);
}
return $diff === 0;
}

function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
$algorithm = strtolower($algorithm);
if(!in_array($algorithm, hash_algos(), true))
    trigger_error('PBKDF2 ERROR: Invalid hash algorithm.', E_USER_ERROR);
if($count <= 0 || $key_length <= 0)
    trigger_error('PBKDF2 ERROR: Invalid parameters.', E_USER_ERROR);

if (function_exists("hash_pbkdf2")) {
    // The output length is in NIBBLES (4-bits) if $raw_output is false!
    if (!$raw_output) {
        $key_length = $key_length * 2;
    }
    return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
}

$hash_length = strlen(hash($algorithm, "", true));
$block_count = ceil($key_length / $hash_length);

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

if($raw_output)
    return substr($output, 0, $key_length);
else
    return bin2hex(substr($output, 0, $key_length));
}

This is the java verification code: 这是java验证代码:

Part 3 ... Setup the variables in java 第3部分...在Java中设置变量

public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
public static final int SALT_BYTE_SIZE = 24;
public static final int HASH_BYTE_SIZE = 24;
public static final int PBKDF2_ITERATIONS = 1000;

public static final int ITERATION_INDEX = 0;
public static final int SALT_INDEX = 1;
public static final int PBKDF2_INDEX = 2;

Part 4 ... Setup the verification part for java 第4部分...设置Java的验证部分

public static boolean validatePassword(String password, String correctHash) throws NoSuchAlgorithmException, InvalidKeySpecException {
return validatePassword(password.toCharArray(), correctHash);
}

public static boolean validatePassword(char[] password, String correctHash)
    throws NoSuchAlgorithmException, InvalidKeySpecException {
// Decode the hash into its parameters
String[] params = correctHash.split(":");
int iterations = Integer.parseInt(params[ITERATION_INDEX]);
byte[] salt = Base64.decodeBase64(params[SALT_INDEX]);
byte[] hash = Base64.decodeBase64(params[PBKDF2_INDEX]);
// Compute the hash of the provided password, using the same salt,
// iteration count, and hash length
byte[] testHash = pbkdf2(password, salt, iterations, hash.length);
// Compare the hashes in constant time. The password is correct if
// both hashes match.
return slowEquals(hash, testHash);
}

private static boolean slowEquals(byte[] a, byte[] b) {
int diff = a.length ^ b.length;
for (int i = 0; i < a.length && i < b.length; i++)
    diff |= a[i] ^ b[i];
return diff == 0;
}

private static byte[] pbkdf2(char[] password, byte[] salt, int iterations,
    int bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, bytes * 8);
SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
return skf.generateSecret(spec).getEncoded();
}

Step 5 ... Call it 步骤5 ...调用它

public static void main(String[] args) throws NoSuchAlgorithmException,
    InvalidKeySpecException {
System.out.println(validatePassword("password", "1000:PoTTC/xEqAgH9A4vCnagBPioC71cPm+C:bLBiDjW8+VukY9PnRTOrMy/JDSfPEW8Y"));
}

I know this is a really old question, but I found it in the process of figuring it out myself, and found out that you had pretty much everything right except for the bytes conversion for the salt and hash . 我知道这是一个确实很老的问题,但是我是在自己弄清楚这个问题的过程中发现的,发现除了salthashbytes转换之外,您几乎没有其他问题。

Anyway, for your problem, instead of using Base64.decodeBase64(params[SALT_INDEX]) , just use params[SALT_INDEX].getBytes() . 无论如何,对于您的问题,不要使用Base64.decodeBase64(params[SALT_INDEX]) ,而要使用params[SALT_INDEX].getBytes() This should return the proper bytecode for hash usage. 这应该返回正确的字节码以供哈希使用。

As a side note, I don't know what library you were using, I ended up with 附带说明,我不知道您使用的是什么库,最后我得到了
com.sun.org.apache.xerces.internal.impl.dv.util.Base64
and thus my method was actually Base64.decode() 因此我的方法实际上是Base64.decode()

So, part 4 ends up being like this: 因此,第4部分最终如下所示:

public static boolean validatePassword(String password, String correctHash) throws NoSuchAlgorithmException, InvalidKeySpecException {
return validatePassword(password.toCharArray(), correctHash);
}

public static boolean validatePassword(char[] password, String correctHash)
    throws NoSuchAlgorithmException, InvalidKeySpecException {
// Decode the hash into its parameters
String[] params = correctHash.split(":");
int iterations = Integer.parseInt(params[ITERATION_INDEX]);

// these two lines were changed
byte[] salt = params[SALT_INDEX].getBytes();
byte[] hash = params[PBKDF2_INDEX].getBytes();

// Compute the hash of the provided password, using the same salt,
// iteration count, and hash length
byte[] testHash = pbkdf2(password, salt, iterations, hash.length);
// Compare the hashes in constant time. The password is correct if
// both hashes match.
return slowEquals(hash, testHash);
}

private static boolean slowEquals(byte[] a, byte[] b) {
int diff = a.length ^ b.length;
for (int i = 0; i < a.length && i < b.length; i++)
    diff |= a[i] ^ b[i];
return diff == 0;
}

private static byte[] pbkdf2(char[] password, byte[] salt, int iterations,
    int bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, bytes * 8);
SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
return skf.generateSecret(spec).getEncoded();
}

I tested this working with both your slowEquals function and with String comparison: 我使用您的slowEquals函数和String比较测试了此功能:

if(Base64.encode(testHash).equals(params[PBKDF2_INDEX]))

I used my own salt and hash generated with PHP 's crypto and transferred as strings into my own code, but I used all of your PHP code as the base of my script, and even used your pbkdf2 function as-is. 我使用了自己的用PHP的crypto生成的盐和哈希值,并作为字符串转移到了自己的代码中,但是我使用了所有PHP代码作为脚本的基础,甚至直接使用了pbkdf2函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM