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

时间:2019-03-30 09:22:08

标签: java cryptography sha256 hash-function

我正在编写一个程序,该程序在密码末尾连接单词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字并再次以正确的方式再次编码为字节的正确方法?

2 个答案:

答案 0 :(得分:1)

在您的第一个代码块中,R在每次迭代时级联,在第二个代码(现为代码片段1)中,它仅在末尾级联,这说明了不同的结果。这是指initial post中的代码。

答案 1 :(得分:1)

代码片段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);
}

要摆脱的主要问题是(安全)哈希函数的数据由字节(如果您喜欢该名称,则为八位字节)组成。 十六进制字符串只是这些字节的文本表示形式。它与数据本身并不相同。

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

但是,在您的情况下,您只需要十六进制就可以将它们打印到屏幕上了;您根本不需要将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();
}