如何在每一轮中多次哈希并连接一个字符串
How to hash multiple times and concatenate a string in each round
我正在编写一个程序,它在密码末尾连接一个单词 R
,然后计算 SHA-256 哈希值。稍后,在十六进制结果的末尾再次添加 R
单词并使用 SHA256 计算新的哈希值。
我要重复 100 次。每次我想打印散列。
所以像这样,在伪代码中:
hash = SHA256(...(SHA256(SHA256(“password”||R)||R)||R)..)
我目前正在通过哈希 2 次来测试我的代码:
String R = "f@ghj!$g";
hash = password.concat(R);
MessageDigest md = MessageDigest.getInstance("SHA-256");
digest = hash.getBytes(StandardCharsets.UTF_8);
for (int i=0;i<2;i++) {
md.update(digest);
digest = md.digest();
hash = String.format("%064x", new BigInteger(1,digest)).concat(R);
System.out.println(hash);
digest = hash.getBytes(StandardCharsets.UTF_8);
}
让我们暂时忘记这个串联。
例如无法理解为什么以下两个代码会产生不同的结果:
代码 1:
for (int i=0;i<2;i++) {
md.update(digest);
digest = md.digest();
}
hash = String.format("%064x", new BigInteger(1,digest));
System.out.println(hash);
代码 2:
for (int i=0;i<2;i++) {
md.update(digest);
digest = md.digest();
//convert hash to string
hash = String.format("%064x", new BigInteger(1,digest));
//convert string again to bytes
digest = hash.getBytes(StandardCharsets.UTF_8);
}
System.out.println(hash);
我的问题是:每次将哈希 (Byte[]
) 解码为十六进制字符串以连接 R
字并以正确的方式再次编码为字节的正确方法是什么?
在您的第一个代码块中,R 在每次迭代时都连接在一起,在第二个代码(现在是代码片段 1)中,它仅在最后连接,这解释了不同的结果。这是指initial post.
中的代码
代码片段 1 是正确的,但您需要向其中添加 print 语句以获得预期的输出。但是,为此您需要使用真正的十六进制编码器/解码器,但它在 java.util
.
中默认不提供,但毫无帮助。
这是一个重做的例子,没有连接,我故意遗漏了它让你有事可做。
该代码使用了相对较慢但易于记忆和阅读的 toHex
函数。 BigInteger
首先需要构造一个 BigInteger
,这很浪费,而且可能更慢。虽然代码似乎可以正确处理 32 字节哈希值,但我仍然认为代码难以维护。
public static byte[] printHexadecimalHashIterations(byte[] input, int iterations)
{
var digest = input.clone();
MessageDigest md;
try
{
md = MessageDigest.getInstance("SHA-256");
}
catch (NoSuchAlgorithmException e)
{
throw new IllegalStateException("SHA-256 hash should be available", e);
}
for (int i = 0; i < iterations; i++)
{
md.update(digest);
digest = md.digest();
printDigest("Intermediate hash", digest);
}
printDigest("Final hash", digest);
return digest;
}
public static void printDigest(String hashType, byte[] digest)
{
var digestInHex = toHex(digest);
System.out.printf("%s: %s%n", hashType, digestInHex);
}
public static String toHex(byte[] data)
{
var sb = new StringBuilder(data.length * 2);
for (int i = 0; i < data.length; i++)
{
sb.append(String.format("%02X", data[i]));
}
return sb.toString();
}
public static void main(String[] args)
{
printHexadecimalHashIterations("password".getBytes(StandardCharsets.UTF_8), 2);
}
要摆脱这一点的主要事情是(安全)散列函数的数据由字节组成(或者八位字节,如果你喜欢那个名字)。 十六进制字符串只是这些字节的文本表示。它与数据本身不同。
你应该能够区分二进制数据和十六进制,十六进制只是二进制数据的一种表示。永远不要像您在问题中那样调用二进制数据 "hex":这是一个危险信号,您无法区分。
但是,在您的情况下,您只需要十六进制就可以将它们打印到屏幕上;您根本不需要将 digest
字节数组转换为十六进制;它仍然可用。所以你可以继续它。
如果您需要将此文本表示转换回字节,则需要执行十六进制 解码。显然,您将再次需要一个不涉及 BigInteger
的好方法。有很多库(Guava、Apache Commons、Bouncy Castle)提供了很好的十六进制编码器/解码器和很好的问题/答案on SO。代码片段2中的语句hash.getBytes(StandardCharsets.UTF_8)
不执行十六进制解码,它执行字符编码。
作为最后的提示:update
方法允许将数据流式传输到摘要函数中。这意味着您实际上不必连接任何东西来计算连接上的摘要:您可以改为对 update
执行多次调用。
编程愉快。
编辑:
为了完成你的任务,我会做这样的事情:
final byte[] passwordBytes = "password".getBytes(StandardCharsets.UTF_8);
final byte[] rBytes = "f@ghj!$g".getBytes(StandardCharsets.UTF_8);
digest.update(passwordBytes);
digest.update(rBytes);
byte[] currentHash = digest.digest();
for (int i = 1; i < iterations; i++)
{
digest.update(currentHash);
digest.update(rBytes);
currentHash = digest.digest();
}
我正在编写一个程序,它在密码末尾连接一个单词 R
,然后计算 SHA-256 哈希值。稍后,在十六进制结果的末尾再次添加 R
单词并使用 SHA256 计算新的哈希值。
我要重复 100 次。每次我想打印散列。
所以像这样,在伪代码中:
hash = SHA256(...(SHA256(SHA256(“password”||R)||R)||R)..)
我目前正在通过哈希 2 次来测试我的代码:
String R = "f@ghj!$g";
hash = password.concat(R);
MessageDigest md = MessageDigest.getInstance("SHA-256");
digest = hash.getBytes(StandardCharsets.UTF_8);
for (int i=0;i<2;i++) {
md.update(digest);
digest = md.digest();
hash = String.format("%064x", new BigInteger(1,digest)).concat(R);
System.out.println(hash);
digest = hash.getBytes(StandardCharsets.UTF_8);
}
让我们暂时忘记这个串联。
例如无法理解为什么以下两个代码会产生不同的结果:
代码 1:
for (int i=0;i<2;i++) {
md.update(digest);
digest = md.digest();
}
hash = String.format("%064x", new BigInteger(1,digest));
System.out.println(hash);
代码 2:
for (int i=0;i<2;i++) {
md.update(digest);
digest = md.digest();
//convert hash to string
hash = String.format("%064x", new BigInteger(1,digest));
//convert string again to bytes
digest = hash.getBytes(StandardCharsets.UTF_8);
}
System.out.println(hash);
我的问题是:每次将哈希 (Byte[]
) 解码为十六进制字符串以连接 R
字并以正确的方式再次编码为字节的正确方法是什么?
在您的第一个代码块中,R 在每次迭代时都连接在一起,在第二个代码(现在是代码片段 1)中,它仅在最后连接,这解释了不同的结果。这是指initial post.
中的代码代码片段 1 是正确的,但您需要向其中添加 print 语句以获得预期的输出。但是,为此您需要使用真正的十六进制编码器/解码器,但它在 java.util
.
这是一个重做的例子,没有连接,我故意遗漏了它让你有事可做。
该代码使用了相对较慢但易于记忆和阅读的 toHex
函数。 BigInteger
首先需要构造一个 BigInteger
,这很浪费,而且可能更慢。虽然代码似乎可以正确处理 32 字节哈希值,但我仍然认为代码难以维护。
public static byte[] printHexadecimalHashIterations(byte[] input, int iterations)
{
var digest = input.clone();
MessageDigest md;
try
{
md = MessageDigest.getInstance("SHA-256");
}
catch (NoSuchAlgorithmException e)
{
throw new IllegalStateException("SHA-256 hash should be available", e);
}
for (int i = 0; i < iterations; i++)
{
md.update(digest);
digest = md.digest();
printDigest("Intermediate hash", digest);
}
printDigest("Final hash", digest);
return digest;
}
public static void printDigest(String hashType, byte[] digest)
{
var digestInHex = toHex(digest);
System.out.printf("%s: %s%n", hashType, digestInHex);
}
public static String toHex(byte[] data)
{
var sb = new StringBuilder(data.length * 2);
for (int i = 0; i < data.length; i++)
{
sb.append(String.format("%02X", data[i]));
}
return sb.toString();
}
public static void main(String[] args)
{
printHexadecimalHashIterations("password".getBytes(StandardCharsets.UTF_8), 2);
}
要摆脱这一点的主要事情是(安全)散列函数的数据由字节组成(或者八位字节,如果你喜欢那个名字)。 十六进制字符串只是这些字节的文本表示。它与数据本身不同。
你应该能够区分二进制数据和十六进制,十六进制只是二进制数据的一种表示。永远不要像您在问题中那样调用二进制数据 "hex":这是一个危险信号,您无法区分。
但是,在您的情况下,您只需要十六进制就可以将它们打印到屏幕上;您根本不需要将 digest
字节数组转换为十六进制;它仍然可用。所以你可以继续它。
如果您需要将此文本表示转换回字节,则需要执行十六进制 解码。显然,您将再次需要一个不涉及 BigInteger
的好方法。有很多库(Guava、Apache Commons、Bouncy Castle)提供了很好的十六进制编码器/解码器和很好的问题/答案on SO。代码片段2中的语句hash.getBytes(StandardCharsets.UTF_8)
不执行十六进制解码,它执行字符编码。
作为最后的提示:update
方法允许将数据流式传输到摘要函数中。这意味着您实际上不必连接任何东西来计算连接上的摘要:您可以改为对 update
执行多次调用。
编程愉快。
编辑:
为了完成你的任务,我会做这样的事情:
final byte[] passwordBytes = "password".getBytes(StandardCharsets.UTF_8);
final byte[] rBytes = "f@ghj!$g".getBytes(StandardCharsets.UTF_8);
digest.update(passwordBytes);
digest.update(rBytes);
byte[] currentHash = digest.digest();
for (int i = 1; i < iterations; i++)
{
digest.update(currentHash);
digest.update(rBytes);
currentHash = digest.digest();
}