Java字符串转换为SHA1

181

我试图在Java中制作一个简单的将字符串转换为SHA1的程序,这就是我所拥有的...

public static String toSHA1(byte[] convertme) {
    MessageDigest md = null;
    try {
        md = MessageDigest.getInstance("SHA-1");
    }
    catch(NoSuchAlgorithmException e) {
        e.printStackTrace();
    } 
    return new String(md.digest(convertme));
}
当我传递 toSHA1("password".getBytes()) 时,我得到了 [�a�ɹ??�%l�3~��.。我知道这可能只是一个简单的编码修复,比如UTF-8,但有人能告诉我应该怎么做才能得到我想要的结果吗?即 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8。还是说我完全错了?

在调用 getBytes() 时指定字符编码是一个好的实践,例如使用 toSHA1("password".getBytes("UTF-8")) - Qwerky
可能是Java计算字符串的SHA1的重复问题。 - Tulains Córdova
1
@TheScrumMeister 这个算法的标准名称是带连字符的_SHA-1_。请参阅https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest。 - John McClane
13个回答

204

更新
你可以使用 Apache Commons Codec(版本1.7及以上)来完成此任务。

DigestUtils.sha1Hex(stringToConvertToSHexRepresentation)

感谢 @Jon Onstott 提供的建议。


旧答案
将您的字节数组转换为十六进制字符串。 Real's How To 能够指导您完成这项任务。

return byteArrayToHexString(md.digest(convertme))

并且(从 Real's How To 复制而来)

public static String byteArrayToHexString(byte[] b) {
  String result = "";
  for (int i=0; i < b.length; i++) {
    result +=
          Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 );
  }
  return result;
}

顺便说一下,你可以使用Base64来获得更紧凑的表示方式。 Apache Commons Codec API 1.4有一个很好的实用程序可以帮你消除所有不必要的烦恼。点击这里


5
base64和sha1非常不同,不要将它们作为替代方案。 - Ryan A.
14
他的建议是用 base64 替代十六进制编码的 SHA1 哈希值(而不是完全替代 SHA1)。 - helmbert
我还没有尝试过,但你能解释一下这是如何工作的吗? - Jivay
12
为什么不使用像 DigestUtils.sha1Hex("my string") 这样的库,而要重复造轮子呢?(虽然了解如何手动转换为十六进制很有趣) - Jon Onstott
4
因为当这个答案被撰写时,DigestUtils(1.7版本于2012年9月发布)并没有这个功能。感谢您指出这一点。+1 - Nishant
不要使用Apache Commons Codec。这是一个非常混乱的库,非常遗憾。 - Patrick

73

这是我将字符串转换为SHA1的解决方案。它在我的Android应用程序中表现良好:

private static String encryptPassword(String password)
{
    String sha1 = "";
    try
    {
        MessageDigest crypt = MessageDigest.getInstance("SHA-1");
        crypt.reset();
        crypt.update(password.getBytes("UTF-8"));
        sha1 = byteToHex(crypt.digest());
    }
    catch(NoSuchAlgorithmException e)
    {
        e.printStackTrace();
    }
    catch(UnsupportedEncodingException e)
    {
        e.printStackTrace();
    }
    return sha1;
}

private static String byteToHex(final byte[] hash)
{
    Formatter formatter = new Formatter();
    for (byte b : hash)
    {
        formatter.format("%02x", b);
    }
    String result = formatter.toString();
    formatter.close();
    return result;
}

8
可能需要明确指出这是java.util.Formatter,并在结尾处添加formatter.close()以避免警告。 - Eric Chen
encryptPassword("test")和在Linux终端中执行的echo test|sha1sum应该输出相同的结果,但事实并非如此。 - Tulains Córdova
2
关于控制台调用:如果您使用echo test,则包括换行符的输出将被管道传输到sha1sum。如果您想要对没有尾随换行符的纯字符串进行哈希处理,则可以使用echo -n test | sha1sum-n参数使echo省略换行符。 - MrSnrub
1
虽然问题不是很明确,但是总的来说:你的 encryptPassword() 函数看起来像是用于存储认证数据。请注意,你的编码容易受到字典攻击的影响,因为没有应用种子。检查你的安全环境,看看这是否会对你的应用程序造成问题! - EagleRainbow
byteToHex 方法帮我度过难关。 - undefined

61

使用Guava的Hashing类

Hashing.sha1().hashString( "password", Charsets.UTF_8 ).toString()

1
这个答案可能需要更新,因为现在会产生一个有关哈希不稳定的警告。 - Developer Thing

36

SHA-1(以及所有其他哈希算法)返回二进制数据。这意味着(在Java中)它们生成一个byte[]数组。该byte数组不代表任何特定字符,这意味着你不能像你一样简单地将其转换为String

如果您需要一个String,那么您必须以可以表示为String的方式格式化该byte[](否则,只需保留byte[])。

表示任意byte[]为可打印字符的两种常用方法是BASE64或简单的十六进制字符串(即通过两个十六进制数字表示每个byte)。看起来你正在尝试生成一个十六进制字符串。

还有另一个陷阱:如果您想获取Java String的SHA-1值,则首先需要将该String转换为byte[](因为SHA-1的输入也是byte[])。如果您只是使用myString.getBytes(),如您所示,那么它将使用平台默认编码,因此将依赖于您运行它的环境(例如,它可能基于OS的语言/区域设置返回不同的数据)。

更好的解决方案是指定用于String-to-byte[]转换的编码,就像这样:myString.getBytes("UTF-8")。选择UTF-8(或另一种可以表示每个Unicode字符的编码)是最安全的选择。


27

这是一种简单的解决方案,可用于将字符串转换为十六进制格式:

private static String encryptPassword(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {

    MessageDigest crypt = MessageDigest.getInstance("SHA-1");
    crypt.reset();
    crypt.update(password.getBytes("UTF-8"));

    return new BigInteger(1, crypt.digest()).toString(16);
}

4
警告:以“0”开头的散列值生成不正确。您将收到一个长度为39个字符的字符串。 - philn
@philn,你能提供一个解决方案吗? - Nikita Koksharov
3
我猜想,如果你从一个有足够多前导零的byte[]创建一个大整数,这些0将被丢失。因此,16进制字符串表示中的“0”将不存在,导致哈希值只有39个或更少的字符。我使用了上面petrnohejls的解决方案,它可以正常工作... - philn

25

只需使用apache commons codec库。他们有一个实用类叫做DigestUtils

不需要深入细节。


57
我不同意,深入细节是重点所在。 - ninesided
12
问题是你是否有时间深入细节,通常重点是按时完成任务。并非所有人都是学生或有奢侈品去学习所有细节。 - Timmo
DigestUtils返回一个字节数组,因此要获得字符串表示形式,您需要通过Hex.encodeHexString来运行它。Java: 现在是2014年,我们仍然没有一步sha方法。 - ryber
5
一步 SHA-1 方法:String result = DigestUtils.sha1Hex("一个输入字符串") ;o) - Jon Onstott

19

如前所述,使用Apache Commons Codec。Spring的开发人员也推荐使用它(请参阅Spring文档中的DigestUtils)。例如:

DigestUtils.sha1Hex(b);

我肯定不会使用这里排名最高的答案。


13

由于需要使用Base64编码,因此打印结果不正确。 在Java 8中,您可以使用Base64编码器类进行编码。

public static String toSHA1(byte[] convertme) throws NoSuchAlgorithmException {
    MessageDigest md = MessageDigest.getInstance("SHA-1");
    return Base64.getEncoder().encodeToString(md.digest(convertme));
}

结果

这将给您期望的输出:5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8


1
@Devenv 这是SHA-1,三个点表示它将保持原始代码,转换为sha1。OP最初的问题是正确打印字符串。 - Eduardo Dennis
2
使用Base64 encodeToString(..)方法,我得到的结果是W6ph5Mm5Pz8GgiULbPgzG37mj9g=而不是5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8 - Jmini

5

消息摘要(哈希)是字节数组输入和字节数组输出

消息摘要被定义为一个函数,它接受一个原始的字节数组并返回一个原始的字节数组(也称为byte[])。例如SHA-1(安全哈希算法1)的摘要大小为160位或20个字节。原始的字节数组通常不能被解释为字符编码,如UTF-8,因为不是每个字节按照每个顺序都是合法的编码。所以将它们转换为String的方法是:

new String(md.digest(subject), StandardCharsets.UTF_8)

可能会创建一些非法序列或具有指向未定义Unicode映射的代码指针:

[�a�ɹ??�%l�3~��.

二进制到文本编码

为此,使用二进制到文本编码。在哈希中,最常用的是十六进制编码或Base16。基本上,一个字节可以具有从0255(或-128127有符号)的值,这相当于0x00-0xFF的十六进制表示。因此,十六进制将使输出所需的长度加倍,这意味着20个字节的输出将创建一个40个字符长的十六进制字符串,例如:

2fd4e1c67a2d28fced849ee1bb76e7391b93eb12

请注意,不一定需要使用十六进制编码。您也可以使用类似于base64的东西。十六进制通常更受欢迎,因为它更容易被人类阅读,并且具有定义的输出长度,无需填充。
您可以仅使用JDK功能将字节数组转换为十六进制:
new BigInteger(1, token).toString(16)

请注意,BigInteger 将把给定的字节数组解释为一个数字而不是一个字节字符串。这意味着前导零将不会被输出,结果字符串可能比40个字符短。

使用库将其编码为十六进制

您现在可以从Stack Overflow复制并粘贴未经测试的字节到十六进制方法,或者使用大量依赖项,如Guava

为了解决大多数字节相关问题,我实现了一个实用程序来处理这些情况:bytes-java (GitHub)

要转换您的消息摘要字节数组,您只需执行以下操作:

String hex = Bytes.wrap(md.digest(subject)).encodeHex();

或者你可以直接使用内置的哈希功能

String hex =  Bytes.from(subject).hashSha1().encodeHex();

3

SHA1哈希的Base 64表示:

String hashedVal = Base64.getEncoder().encodeToString(DigestUtils.sha1(stringValue.getBytes(Charset.forName("UTF-8"))));

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