SHA算法针对同一个密钥每次生成独一无二的哈希字符串。

3

我知道有很多关于散列和加密算法的文章。

我从这些文章中了解到,应该使用散列函数而不是加密函数 来存储密码在数据库中

因此,我决定使用SHA-256算法生成散列密钥,并将该散列密钥存储到我的服务器数据库中,而不是明文密码。

现在我真的不知道如何使用它,因为每次我传递相同的密码以生成SHA密钥时,它都会给出与先前不同的密钥,那么如何将其与存储在我的数据库中的散列密钥进行比较呢?

我正在使用Java,所以我的Java代码如下:

public class Test {
public static void main(String...arg) throws IOException{
    System.out.println("First time");
    String string64 = getEncryptedPassword("FenilShah");
    System.out.println(string64);
    System.out.println(StringUtils.newStringUtf8(Base64.decodeBase64(string64)));

    System.out.println("\nSecond time");
    string64 = getEncryptedPassword("FenilShah");
    System.out.println(string64);
    System.out.println(StringUtils.newStringUtf8(Base64.decodeBase64(string64)));

    System.out.println("\nThird time");
    string64 = getEncryptedPassword("FenilShah");
    System.out.println(string64);
    System.out.println(StringUtils.newStringUtf8(Base64.decodeBase64(string64)));

}

 public static String getEncryptedPassword(String clearTextPassword)   {  


        try {
          MessageDigest md = MessageDigest.getInstance("SHA-256");
          md.update(clearTextPassword.getBytes());
          byte pass[] = md.digest();
          System.out.println(pass.toString());
          return Base64.encodeBase64String(StringUtils.getBytesUtf8(pass.toString()));
        } catch (NoSuchAlgorithmException e) {
          //_log.error("Failed to encrypt password.", e);
        }
        return "";
      }
 }

因此,输出的结果类似于这样:
First time
[B@5bf825cc
W0JANWJmODI1Y2M=
[B@5bf825cc

Second time
[B@1abfb235
W0JAMWFiZmIyMzU=
[B@1abfb235

Third time
[B@1f4cc34b
W0JAMWY0Y2MzNGI=
[B@1f4cc34b
1个回答

5

这是您最紧急的问题:

byte pass[] = md.digest();
System.out.println(pass.toString());

你没有返回字符串的哈希值。你返回了在 byte[] 上调用 toString() 的结果。在 Java 中,数组不会覆盖 toString(),因此您会得到默认实现,它与对象的标识有关,而与字节数组中的数据无关:

Object 类的 toString 方法返回一个由对象实例的类名、符号 `@' 和对象的哈希码的无符号十六进制表示组成的字符串。

(数组也不会覆盖 hashCode(),因此这也是从 Object 中获取的默认实现...)

基本上,你需要一种不同的方法将byte[]转换为String...或者直接将字节数组存储到数据库中。如果你确实想要转换为字符串,我建议你使用十六进制或base64。对于base64,我建议使用iharder.net公共域库...或者如果你使用Java 8,可以使用java.util.Base64。(令人惊讶的是,在除了XML等上下文之外的标准库中获得一个base64类竟然如此漫长,但我们走到这里。)
return Base64.getEncoder().encodeToString(md.digest());

您的代码还有一个额外的问题:
md.update(clearTextPassword.getBytes());

这个方法使用平台默认编码将密码转换成一个字节数组。这不是一个好的做法 - 这意味着你可能会因为代码运行在不同的系统上而得到不同的哈希值。更好的做法是显式地指定编码:

md.update(clearTextPassword.getBytes(StandardCharsets.UTF_8));

此外,如果缺少SHA-256,则捕获异常、记录并继续使用空字符串几乎肯定是错误的方法。你真的想用空字符串填充数据库,让任何人都可以使用任何密码登录吗?如果你的系统处于这种状态,你几乎肯定希望失败任何与密码有关的请求。你可能希望将NoSuchAlgorithmException转换为某种RuntimeException并重新抛出。

最后,仅存储简单的SHA-256哈希值可能也不是一个好主意。

  • 你可能需要一个HMAC
  • 你应该至少使用随机盐来避免数据库中具有相同值的相同密码。(否则,攻击者可以利用这些信息来获得优势。)

我远非安全专家,所以我不会在正确的做事方式上给你太多建议 - 但我实际上建议尝试找到由安全专家编写的备受尊重的库,而不是尝试自己实现。安全非常难做到正确。


嗨Jon,你总是提供很大的帮助。这是我第一次接触安全问题。我按照你的建议使用了HMAC。那么在HMAC中我还需要使用盐吗?你建议使用iharder.net公共领域库。但我正在使用org.apache.commons.codec.binary.Base64,这样可以吗? - commit
@commit:那应该没问题 - 我只是建议iHarder,因为它只是base64,并且是公共领域,这对于许可证方面来说很简单。至于在HMAC中使用盐的用途-我认为这取决于HMAC,并且我不是那方面的专家。正如我所说,我建议首先不要构建自己的身份验证系统 :) - Jon Skeet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接