在 Java 中工作 Rfc2898DeriveBytes.Pbkdf2

问题描述 投票:0回答:1

我有一些使用

Rfc2898DeriveBytes.Pbkdf2(bytes,src,5000,HashAlgorithmName.SHA1,24)
(较旧的实现)通过 C# 进行哈希处理的密码,并且希望将此代码移植到 java。

但是我似乎没有让两个哈希值相同。 我不知道为什么以及如何。 我尝试了多种实现,它们在 Java 中产生相同的结果,与 C# 不同。

我还发布在 https://github.com/skradel/Zetetic.Security/issues/1 上,因为原始代码使用

Zetetic.Security
包。

C#代码:

using System.Security.Cryptography;
using System;
using System.Text;

namespace MyApp
{
    class Program
    {
        private const string _ALGORITHM = "pbkdf2";

        public static string PrintBytes(byte[] byteArray)
        {
            var sb = new StringBuilder("new byte[] { ");
            for (var i = 0; i < byteArray.Length; i++)
            {
                var b = byteArray[i];
                sb.Append(b);
                if (i < byteArray.Length - 1)
                {
                    sb.Append(", ");
                }
            }
            sb.Append(" }");
            return sb.ToString();
        }

        static Program()
        {
            CryptoConfig.AddAlgorithm(typeof(Zetetic.Security.Pbkdf2Hash), _ALGORITHM);
        }



        public static string EncodePassword(string pass, int passwordFormat, string salt)
        {
            var bytes = Encoding.Unicode.GetBytes(pass);
            var src = Convert.FromBase64String(salt);
            byte[] inArray;
               var hashAlgorithm = HashAlgorithm.Create(_ALGORITHM);
                    Console.WriteLine("hashAlgorithm is KeyedHashAlgorithm");
                    var algorithm2 = (KeyedHashAlgorithm)hashAlgorithm;
                    Console.WriteLine("algorithm2.Key.Length == src.Length " + src.Length);
                    algorithm2.Key = src;
                    Console.WriteLine("Compute hash" + algorithm2.HashSize);
                    inArray = algorithm2.ComputeHash(bytes);
            Console.WriteLine("Bytes " + inArray.Length + PrintBytes(inArray));
            Console.WriteLine("Salt " + PrintBytes(src));


            var pass2 = Rfc2898DeriveBytes.Pbkdf2(bytes,src,5000,HashAlgorithmName.SHA1,24);
            Console.WriteLine(Convert.ToBase64String(pass2)); 
            return Convert.ToBase64String(inArray);
        }

        static void Main(string[] args)
        {
            var password = args[0];
            var salt = args[1];
            var format = Int32.Parse(args[2]);

            Console.WriteLine("==Convert password '" + password + "' using salt '" + salt + "' and format '" + format + "' ==");
            Console.WriteLine(EncodePassword(password, format, salt));

        }
    }
}

Java 代码:

byte[] saltBytes = saltStr.getBytes(UTF_8);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec pbeKeySpec = new PBEKeySpec("my-secret".toCharArray(), saltBytes, 5000, 192);
Key secretKey = factory.generateSecret(pbeKeySpec);
java.util.Base64.getEncoder().encodeToString(key.getEncoded())

除了官方的 Java 实现之外,我还尝试了另外 2 个:https://rtner.de/software/PBKDF2.html 和来自 https://stackoverflow.com/a/1360237/638331 的一个。 我在 Java 中得到相同的哈希值。

我尝试过 Zetetic 实现和 C# 标准库中的实现,我在那里得到了相同的结果,但与 Java 中不同。

更新: Java 版本与 https://8gwifi.org/pbkdf.jsp 兼容。 我可以使用相同的参数在 Java 中生成与上述 JS 应用程序相同的哈希值:5000 次迭代、PBKDF2WithHmacSHA1、192 位和我的盐。

(顺便说一句,网站将数据发送到服务器,所以不要使用任何真实的密码/数据)

哈希值与 C# 中的哈希值不同。

更新:C# 运行示例

$ ./bin/Debug/net7.0/pw-encoder mama dNuhxK7K5SaJk2M5HqwyNA== 1 
==Convert password 'mama' using salt 'dNuhxK7K5SaJk2M5HqwyNA==' and format '1' ==
hashAlgorithm is KeyedHashAlgorithm
algorithm2.Key.Length == src.Length 16
Compute hash192
Bytes 24new byte[] { 25, 111, 18, 46, 51, 211, 183, 94, 103, 93, 54, 65, 64, 46, 0, 105, 76, 70, 33, 212, 150, 24, 185, 168 }
Salt new byte[] { 116, 219, 161, 196, 174, 202, 229, 38, 137, 147, 99, 57, 30, 172, 50, 52 }
GW8SLjPTt15nXTZBQC4AaUxGIdSWGLmo
GW8SLjPTt15nXTZBQC4AaUxGIdSWGLmo

使用与 Java 本地代码相同的密码和值,以及 https://8gwifi.org/pbkdf.jsp 使用参数:PBKDF2WithHmacSHA1,16 位初始向量 [ dNuhxK7K5SaJk2M5HqwyNA==] ,5000 次迭代和 dkLen 的 192 位 给我下面的哈希:

pR/jfNDZoVqCiXQ7YTGR3g/h7O3J/sMR
cryptography hash java .net pbkdf2
1个回答
1
投票

编码不同:在 C# 代码中,盐是 Base64 解码的,在 Java 代码中,盐是 UTF-8 编码的。在 C# 代码中,密码采用

Unicode
编码,在 .NET 中指定 UTF16-LE,在 Java 代码中,密码采用 UTF-8 编码。

修复:在 Java 代码中使用与 C# 代码中相同的编码。

但是,有一个小问题:PBKDF2 的 JCA/JCE 实现在内部执行 UTF-8 编码。既不可能更改编码,也无法直接处理字节序列而不是字符串 s。

PBEKeySpec
。解决方法:应用另一个库,例如来自 BouncyCastle 的
PKCS5S2ParametersGenerator
。这是一个示例实现:

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.bouncycastle.crypto.PBEParametersGenerator;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
...
String saltStr = "dNuhxK7K5SaJk2M5HqwyNA==";
String passwordStr = "mama";
byte[] salt = Base64.getDecoder().decode(saltStr);                                      // Fix: Base64 decode salt
byte[] password = passwordStr.getBytes(StandardCharsets.UTF_16LE);                      // Fix: UTF-16LE encode password
PBEParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA1Digest());    // Fix: Apply a PBKDF2 implementation that can handle byte sequences
generator.init(password, salt, 5000);
byte[] key = ((KeyParameter)generator.generateDerivedParameters(192)).getKey(); 
System.out.println(Base64.getEncoder().encodeToString(key)); // GW8SLjPTt15nXTZBQC4AaUxGIdSWGLmo

此代码提供与 C# 代码相同的结果。

© www.soinside.com 2019 - 2024. All rights reserved.