Java 字符串到 SHA1

我尝试用 Java 编写一个简单的 String 到 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?还是我做错了?

299852 次浏览

更新
您可以使用 Apache Commons 编解码器(版本1.7 +)来完成这项工作。

DigestUtils.sha1Hex(stringToConvertToSHexRepresentation)

Thanks to @Jon Onstott for this suggestion.


Old Answer
Convert your Byte Array to Hex String. Real's How To tells you how.

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,有这个不错的实用程序,带走所有的痛苦。请参阅此网页

这种方法不起作用的原因是,当您调用 String(md.digest(convertme))时,您告诉 Java 将一系列加密的字节解释为 String。您需要的是将字节转换为十六进制字符。

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

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

将任意 byte[]表示为可打印字符的两种常见方法是 BASE64或简单的十六进制字符串(即用两个十六进制数字表示每个 byte)。看起来像是要生成一个十六进制字符串。

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

更好的解决方案是指定用于 String-to-byte[]转换的编码,如下所示: myString.getBytes("UTF-8")。选择 UTF-8(或者其他可以代表所有 Unicode字符的编码)是最安全的选择。

只需使用 apachecommons 编解码器库,它们有一个名为 DigestUtils的实用程序类

不用说细节了。

将字节数组转换为十六进制字符串。

public static String toSHA1(byte[] convertme) {
final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-1");
}
catch(NoSuchAlgorithmException e) {
e.printStackTrace();
}
byte[] buf = md.digest(convertme);
char[] chars = new char[2 * buf.length];
for (int i = 0; i < buf.length; ++i) {
chars[2 * i] = HEX_CHARS[(buf[i] & 0xF0) >>> 4];
chars[2 * i + 1] = HEX_CHARS[buf[i] & 0x0F];
}
return new String(chars);
}

这是我将 string 转换成 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;
}

使用 番石榴哈希类:

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

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

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);
}

正如在使用 apache commons codec 之前提到的,它也是 Spring 推荐的(参见 Spring 文档中的 DigestUtils)。例如:

DigestUtils.sha1Hex(b);

肯定不会用这里最好的答案。

因为需要使用 Base64编码,所以打印不正确。使用 Java8,您可以使用 基地64编码器类进行编码。

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

结果

这将给出 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8的预期输出

基数64代表 SHA1散列:

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

消息摘要(hash)是 byte []中的 byte [] out

消息摘要定义为接受原始字节数组并返回原始字节数组(即 byte[])的函数。例如,SHa-1(SHA家族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

注意,不需要使用十六进制编码。你也可以使用类似 基地64的东西。十六进制通常是首选的,因为它更容易被人类阅读,并且具有定义的输出长度而不需要填充。

您可以单独使用 JDK 功能将字节数组转换为十六进制:

new BigInteger(1, token).toString(16)

但是请注意,BigInteger将把给定的字节数组解释为 号码,而不是字节字符串。这意味着不会输出前导零,并且产生的字符串可能短于40个字符。

使用库对 HEX 进行编码

您现在可以使用 从 Stack Overflow 复制并粘贴未经测试的 byte-to-hex 方法或者大量的依赖项,比如 番石榴

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

要转换消息摘要字节数组,只需执行

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

或者你可以使用内置的散列特性

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

也许这会有所帮助(对 java 17有效) :

import org.apache.tomcat.util.codec.binary.Base64;
return new String(Base64.encodeBase64(md.digest(convertme)));