Java byterray to string must be equal python bytearray string when generate secret with SecretKeyFactory

huangapple go评论86阅读模式
英文:

Java byterray to string must be equal python bytearray string when generate secret with SecretKeyFactory

问题

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class CryptoExample {

    public static void main(String[] args) throws Exception {
        byte[] PASSWORD = new byte[]{(byte) 0xFF, (byte) 0xFA, (byte) 0xAA, (byte) 0xBB, (byte) 0xBC,
                (byte) 0xCC, (byte) 0xDD, (byte) 0xDD, (byte) 0xFF, (byte) 0xAA, (byte) 0xAA,
                (byte) 0xAA, (byte) 0xAA, (byte) 0xAA, (byte) 0xAA, (byte) 0xBB};
        String salt = "salt";

        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");

        PBEKeySpec spec = new PBEKeySpec(new String(PASSWORD, StandardCharsets.UTF_8).toCharArray(),
                salt.getBytes(), 512, 256);

        byte[] keyBytes = factory.generateSecret(spec).getEncoded();

        System.out.println("Generated Key: " + Arrays.toString(keyBytes));
    }
}

Please note that I've replaced the use of the explicit (char) cast with StandardCharsets.UTF_8 encoding when converting the password to a char array. This should handle the conversion correctly without issues related to signed integer representation.

Ensure that you have the appropriate cryptographic libraries in your Java environment.

英文:

I have a task to rewrite some python crypto code to java. I'm new in python. Python code:

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)

My java implementation:

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

As you can see PASSWORD is a byte array which I get from a hex string, i.e 010203....0F10, I can't change it (i.e. can't specify it as a string in python realization, as I understand server transforms PASSWORD to byte array also). All worked fine with this dummy PASSWORD i.e. keys generated by python and java code were equal. But I'm faced with a problem when the password changed to arbitrary, i.e. for example AFFFFFFFFDBGEHTH....
As I understand the problem with java byte array representation as signed integers. I.e. when I convert hex "FFFAAABBBCCCDDDDFFAAAAAAAAAAAABB" for example to byte array it will be byte array [-1, -6, -86, -69, -68, -52, -35, -35, -1, -86, -86, -86, -86, -86, -86, -69], but in python it will [255, 250, 170, 187, 188, 204, 221, 221, 255, 170, 170, 170, 170, 170, 170, 187]. Then when I convert java byte array to charArray for PBEKeySpec constructor - new PBEKeySpec(new String(new byte[]{-1, -6, -86, -69, -68, -52, -35, -35, -1, -86, -86, -86, -86, -86, -86, -69}).toCharArray()... it works as unexpected.

How I have to change my java code to receive the same key as in python? As I understand I have to encode java byte array string to the same value as in python .derive(...) method.
Thanks in advance.

UPDATE:

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)

and

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

should give the same result. It works for new byte[]{1,2,3,4,....16} password.

UPDATE2: I changed password to unsigned int[] but it not works anyway:

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

答案1

得分: 2

除了不同的摘要方法(参见第一个答案),问题在于使用 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)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(); 
// 使用 https://stackoverflow.com/a/9855338 中的 bytesToHex 方法
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
英文:

Apart from the different digests (s. 1st answer), the problem is that the key derived with PBKDF2WithHmacSHA256 is an instance of PBKDF2KeyImpl, which requires a string as password. This string is UTF8 encoded in PBKDF2KeyImpl (see documentation of the class PBKDF2KeyImpl). Here, however, the password is an (arbitrary) byte sequence, which is generally not compatible with UTF8, so that the data is corrupted during UTF8 decoding. A possible solution is to replace PBEKeySpec with BouncyCastle's PKCS5S2ParametersGenerator, which expects the password as byte array (in 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

The result now matches that of the Python code:

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

答案2

得分: 1

你的Java代码中使用了SHA1,而Python代码中使用了SHA256,这就是导致结果不同的原因。请使用PBKDF2WithHmacSHA256

英文:

You're using SHA1 in your Java code and SHA256 in your Python code, which is why you're getting different results. Use PBKDF2WithHmacSHA256.

huangapple
  • 本文由 发表于 2020年9月15日 19:18:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/63900725.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定