【问题标题】:Hashing between PHP and JavaPHP和Java之间的散列
【发布时间】:2015-01-16 07:52:53
【问题描述】:

我正在尝试在 PHP 中创建一个散列,将其存储在数据库中,然后在 Java 中验证该散列。到目前为止,它们都彼此独立运行良好...... Java 可以散列和验证 java,Php 可以散列和验证 php,但尽管我尽了最大努力,但它们并没有很好地结合在一起。

  1. 我已将 php 中的算法更改为 sha1 以匹配 javas PBKDF2WithHmacSHA1 - 它们匹配吗?
  2. 我已使用 apache commons 编解码器库中的 Base64.decodeBase64() 来解码 php base64_encode() 函数 - 可以吗?
  3. 我现在正在手动删除 java 的算法“sha1:”部分。

这是代码,你能找出java实现无法验证php版本产生的哈希的任何原因吗? - 我没有收到错误,只是当正确密码是该哈希的“密码”时验证失败。

第 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);

第 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));
}

这是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;

第 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();
}

第 5 步 ... 调用它

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

【问题讨论】:

    标签: java php hash


    【解决方案1】:

    我知道这是一个非常古老的问题,但我在自己解决这个问题的过程中发现了它,并发现除了 bytes 转换为 salt 和 @987654323 之外,您几乎所有事情都是正确的@。

    无论如何,对于您的问题,不要使用Base64.decodeBase64(params[SALT_INDEX]),而是使用params[SALT_INDEX].getBytes()。这应该为哈希使用返回正确的字节码。

    顺便说一句,我不知道你使用的是什么库,我最终得到了
    com.sun.org.apache.xerces.internal.impl.dv.util.Base64
    因此我的方法实际上是Base64.decode()

    所以,第 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();
    }
    

    我用你的slowEquals 函数和字符串比较测试了这个:

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

    我使用了我自己的 salt 和由 PHP 的加密生成的哈希,并作为字符串传输到我自己的代码中,但我使用了你所有的 PHP 代码作为我的脚本的基础,甚至使用了你的 @987654333 @按原样运行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-03-24
      • 2017-10-29
      • 1970-01-01
      • 2020-05-27
      • 2023-03-13
      • 2013-08-02
      • 1970-01-01
      • 2015-10-15
      相关资源
      最近更新 更多