用SecretKeyFactory

问题描述

我的任务是将一些python加密代码重写为java。我是python的新手。 Python代码

from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.backends import default_backend
backend = default_backend()  



PASSWORD = bytes((1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16))

key = PBKDF2HMAC(hashes.SHA256(),32,salt,iterations,backend).derive(PASSWORD)

我的Java实现:

import javax.crypto.SecretKeyFactory;
    import javax.crypto.spec.PBEKeySpec;
     byte[] PASSWORD = new byte[]{1,16};
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    SecretKey tmp = factory.generateSecret(new PBEKeySpec(new String(PASSWORD).tochararray(),256));
    byte[] key = tmp.getEncoded();

您可以看到PASSWORD是我从一个十六进制字符串(即010203 .... 0F10)中得到的字节数组,我无法更改它(即无法在python实现中将其指定为字符串,因为我了解服务器还将PASSWORD转换为字节数组)。这个虚拟的PASSWORD都可以正常工作,即python和java代码生成的密钥是相等的。但是当密码更改为任意密码时,例如AFFFFFFFfdbGEHTH ...,我遇到了一个问题。 据我了解Java字节数组表示形式为带符号整数的问题。即例如,当我将十六进制“ FFFAAABBBCCCddddFFFFAAAAAAAAAAAABB”转换为字节数组时,它将是字节数组[-1,-6,-86,-69,-68,-52,-35,-35,-1,-86,-86 ,-86,-86,-86,-86,-69],但在python中它将为[255,250,170,187,188,204,221,221,255,170,170,170,170,170 ,170,187]。然后,当我将Java字节数组转换为PBEKeySpec构造函数的chararray时-new PBEKeySpec(new String(new byte [] {-1,-6,-86,-69,-68,-52,-35,-35,-1 ,-86,-86,-86,-86,-86,-86,-69})。tochararray()...它以意外方式工作。

我如何更改我的Java代码以接收与python中相同的密钥?据我了解,我必须将java字节数组字符串编码为与python .derive(...)方法中相同的值。 预先感谢。

更新:

salt       = b'salt'
PASSWORD = = bytes((255,250,170,187,188,204,221,255,187))
key = PBKDF2HMAC(hashes.SHA256(),512,backend).derive(PASSWORD)

SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
password = new String(new byte[]{-1,-6,-86,-69,-68,-52,-35,-1,-69});
var key = secretKeyFactory
                    .generateSecret(new PBEKeySpec(password.tochararray(),"salt".getBytes(),256))
                    .getEncoded();

应该给出相同的结果。它适用于新的字节[] {1,4,.... 16}密码

UPDATE2:我将密码更改为unsigned int [],但仍然无法正常工作:

    char[] password = new char[PASSWORD.length];
            for (int i = 0; i<PASSWORD.length; password[i] = (char)(PASSWORD[i++] & 0xFF));
    var key = secretKeyFactory
                    .generateSecret(new PBEKeySpec(password,256))
                    .getEncoded();
    

解决方法

除了不同的摘要(第一个answer)以外,问题在于,用PBKDF2WithHmacSHA256派生的密钥是PBKDF2KeyImpl的实例,它需要一个字符串作为密码。该字符串是用PBKDF2KeyImpl编码的UTF8(请参见类PBKDF2KeyImpl的文档)。但是,这里的密码是一个(任意)字节序列,通常与UTF8不兼容,因此在UTF8解码期间数据会被破坏。可能的解决方案是将PBEKeySpec替换为BouncyCastle的PKCS5S2ParametersGenerator,后者期望密码为字节数组(在init中):

import java.nio.charset.StandardCharsets;
import org.bouncycastle.crypto.PBEParametersGenerator;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
...
byte[] salt = "salt".getBytes(StandardCharsets.UTF_8);
int iterations = 512;
byte[] PASSWORD = new byte[] { (byte)255,(byte)250,(byte)170,(byte)187,(byte)188,(byte)204,(byte)221,(byte)255,(byte)187 };
PBEParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA256Digest());
generator.init(PASSWORD,salt,iterations);
byte[] keyBytes = ((KeyParameter)generator.generateDerivedParameters(256)).getKey(); 
// with bytesToHex from https://stackoverflow.com/a/9855338
System.out.println(bytesToHex(keyBytes).toLowerCase());  // d8aa4772e9648572611fe6dca7f653353de934cdb3b29fab94eb13ba2b198b9f

现在,结果与Python代码的结果匹配:

from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes

salt = b'salt'
iterations = 512
PASSWORD = bytes((255,250,170,187,188,204,221,255,187))
key = PBKDF2HMAC(hashes.SHA256(),32,iterations).derive(PASSWORD)

print(key.hex()) # d8aa4772e9648572611fe6dca7f653353de934cdb3b29fab94eb13ba2b198b9f
,

您在Java代码中使用SHA1,在Python代码中使用SHA256,这就是为什么您得到不同结果的原因。使用PBKDF2WithHmacSHA256