如何在每一轮中多次散列并连接一个字符串

Posted

技术标签:

【中文标题】如何在每一轮中多次散列并连接一个字符串【英文标题】:How to hash multiple times and concatenate a string in each round 【发布时间】:2019-08-21 03:12:18 【问题描述】:

我正在编写一个程序,它在密码末尾连接一个单词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,没有散列。但是您的第一个代码将 R 附加到 String 并对其进行哈希处理以准备下一次迭代。 对不起!我之前解释错了。我编辑了我的问题。 【参考方案1】:

代码片段 1 是正确的,但您需要在其中添加打印语句才能获得预期的输出。但是,为此,您需要使用真正的十六进制编码器/解码器,但在 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);


要消除这一点的主要内容是(安全)散列函数的数据由字节(或八位字节,如果您更喜欢该名称)组成。 十六进制字符串只是这些字节的文本表示。它与数据本身并不完全相同。

您应该能够区分二进制数据和十六进制,这只是二进制数据的表示。永远不要像在问题中那样将二进制数据称为“十六进制”:这是一个危险信号,您没有得到区别。

但是,在您的情况下,您只需要十六进制即可将它们打印到屏幕上;您根本不需要将 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 字符串连接起来,然后需要对其进行字符编码的可能性很小但可能。但是,您的伪代码并未反映这一点。如果是这种情况,那么创建需求/协议的人也不了解编码。你应该回去解释协议不好。 我知道每次十六进制字符串都必须与 R 连接,但在你回答之后我不确定了。 好吧,如果是这种情况,那么代码片段 2 可能是正确的。如果对编码的十六进制进行太多哈希处理没有意义。散列大小写十六进制也会有所不同,仅举一个问题。请注意,当表示二进制或字节时,许多 程序员甚至教师都会说“十六进制”。如果您怀疑是这种情况,最好要求澄清。 所以您建议使用 byte[] R ( md.update(R) ) 在每次迭代中更新?在您的代码中,“var”字是什么? var 只是一种不必键入类名的方法,它声明了一个 local 变量,然后需要立即将其分配给编译器可以赋予该类.它已在 Java 10 中引入,以使该语言更易于编写。所以例如digestbyte[],因为这是 input.clone() 返回的内容。【参考方案2】:

在您的第一个代码块中,R 在每次迭代时都被连接起来,在第二个代码(现在的代码片段 1)中,它只在最后连接起来,这解释了不同的结果。这是指initial post中的代码。

【讨论】:

代码片段 1 或 2 中没有串联,所以我没有得到这个答案。 您不明白,因为问题已被编辑太多,以至于我的回答不再有意义!检查初始版本。 好的,很公平。第一篇文章没有太大意义,但如果我们不考虑编辑,你的答案是正确的。 BlackMam 请在重新阅读您的问题后尝试发布,否则可能会出现这样的混乱。

以上是关于如何在每一轮中多次散列并连接一个字符串的主要内容,如果未能解决你的问题,请参考以下文章

「2017 山东一轮集训 Day6」子序列

使用 MD5 或 sha-256 C# 散列密码

ForeignGame [博弈论][DP]

Rails 将 YAML 加载到散列并按符号引用

如何在 Map 类型的数据框中获取一列并创建一个字符串,该字符串只是 Map 列的键/值

我可以通过加盐现有的 MD5 散列并使用 Scrypt 或 PBKDF2 HMACSHA256 散列结果来提高 MD5 散列密码的安全性吗?