在 Java 中使用 Rfc2898DeriveBytes.Pbkdf2

Working Rfc2898DeriveBytes.Pbkdf2 in Java

提问人:ieugen 提问时间:11/9/2023 最后编辑:ieugen 更新时间:11/10/2023 访问量:72

问:

此问题是从 Information Security Stack Exchange 迁移而来的,因为它可以在 Stack Overflow 上得到解答。13 天前迁移

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

但是,我似乎没有得到两个哈希值相同。 我不知道为什么以及如何。 我尝试了多种实现,它们在 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.htmlhttps://stackoverflow.com/a/1360237/638331 的实现。 我在 Java 中得到相同的哈希值。

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

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

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

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

UPDATE: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 次迭代和 192 位用于 dkLen 给我下面的哈希:

pR/jfNDZoVqCiXQ7YTGR3g/h7O3J/sMR
加密 哈希 java .net pbkdf2

评论

0赞 ieugen 11/9/2023
我也在Microsoft问答 learn.microsoft.com/en-us/answers/questions/1420745/ 中问过......

答:

3赞 Topaco 11/9/2023 #1

编码不同:在 C# 代码中,salt 是 Base64 解码的,在 Java 代码中是 UTF-8 编码的。在 C# 代码中,密码在 .NET 中指定 UTF16-LE 进行编码,在 Java 代码中,密码采用 UTF-8 编码。Unicode

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

但是,有一个小问题:PBKDF2 的 JCA/JCE 实现在内部执行 UTF-8 编码,即 getPasswordBytes()。既不能更改编码,也不能直接处理字节序列而不是 , s。 PBEKeySpec。解决办法:应用另一个库,例如 BouncyCastle 中的 PKCS5S2ParametersGenerator。下面是一个示例实现:char[]

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# 代码相同的结果。


关于 UTF-16LE 编码的备注:可以想出这样的想法:转换 C# 代码从中生成 UTF-16LE 字节序列的初始字符串,使 UTF-8 编码生成相同的字节序列:

String passwordStr = new String("<your password>".getBytes(StandardCharsets.UTF_16LE), StandardCharsets.UTF_8);     

但是,这在一般情况下不起作用,但仅在某些情况下有效,因为并非每个 UTF-16LE 字节序列都是 UTF-8 可解码的。
例如,Unicode 码位< 128(ASCII 字符)的字符是可能的,因此它适用于 ,但不适用于 (在转换过程中会损坏)。此外,转换后的密码包含空格(这在这里并不重要,因为密码没有打印出来)。
mamamama§

评论

0赞 ieugen 11/9/2023
谢谢!令人惊讶的是(看似微不足道的)事情是多么重要!这里有一些东西可以让你的一天变得更好 soundcloud.com/user-452914422/...... (没有 yt,因为他们对广告发疯了)