使用 SecretKeyFactory 生成 secret 时,Java 字节数组到字符串必须等于 python 字节数组字符串

标签 java python character-encoding cryptography pbkdf2

我有一个任务将一些 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, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    SecretKey tmp = factory.generateSecret(new PBEKeySpec(new String(PASSWORD).toCharArray(), salt, iterations, 256));
    byte[] key = tmp.getEncoded();

正如你所看到的,PASSWORD是一个字节数组,我从十六进制字符串中获得,即010203....0F10,我无法更改它(即无法在Python实现中将其指定为字符串,因为我了解服务器也将 PASSWORD 转换为字节数组)。使用这个虚拟密码一切正常,即由 python 和 java 代码生成的 key 是相同的。但是当密码更改为任意时,我遇到了一个问题,即例如 AFFFFFFFFDBGEHTH... 据我了解,java字节数组表示为有符号整数的问题。 IE。例如,当我将十六进制“FFFAAABBBCCCDDDDFFAAAAAAAAAAAABB”转换为字节数组时,它将是字节数组 [-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]。然后,当我将 PBEKeySpec 构造函数的 java 字节数组转换为 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 中相同的 key ?据我了解,我必须将 java 字节数组字符串编码为与 python .derive(...) 方法中相同的值。 提前致谢。

更新:

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

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

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

更新2:我将密码更改为 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, "salt".getBytes(), 512, 256))
                    .getEncoded();
    

最佳答案

除了不同的摘要(s. 1st answer )之外,问题在于使用 PBKDF2WithHmacSHA256 派生的 key 是 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)221, (byte)255, (byte)170, (byte)170, (byte)170, (byte)170, (byte)170, (byte)170, (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, 221, 255, 170, 170, 170, 170, 170, 170, 187))
key = PBKDF2HMAC(hashes.SHA256(), 32, salt, iterations).derive(PASSWORD)

print(key.hex()) # d8aa4772e9648572611fe6dca7f653353de934cdb3b29fab94eb13ba2b198b9f

关于使用 SecretKeyFactory 生成 secret 时,Java 字节数组到字符串必须等于 python 字节数组字符串,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63900725/

相关文章:

java - 使用 XPath 选择 XML 节点时如何忽略 namespace

java - 如何在Java中获取各种操作系统的当前磁盘容量?

python - 如何让python在多个字符串之间随机选择?

c# - MySQL(和c#?)转换字符格式

java - 检查每个列表项的语句

python - Python 中分离表单和处理

python - 打包带有依赖项的 python 项目的标准方法是什么?

mysql - 使用现有数据更改数据库编码

java - Google App Engine 上的瑞典语字符编码错误

java - OpenCV VideoWriter for Java 在 MacOS 上无法按预期工作