Java计算字符串SHA-1摘要的十六进制表示

65

我正在将用户密码作为sha1哈希值存储在数据库中。

不幸的是,我得到了一些奇怪的答案。

我将字符串存储为:

MessageDigest cript = MessageDigest.getInstance("SHA-1");
              cript.reset();
              cript.update(userPass.getBytes("utf8"));
              this.password = new String(cript.digest());

我需要这样的东西 -->

aff --> "0c05aa56405c447e6678b7f3127febde5c3a9238"

而不是

aff --> �V@\D~fx����:�8


3
жіЁж„ҸпјҡдҪ дёҚеә”иҜҘдҪҝз”Ёзұ»дјјsha1зҡ„е“ҲеёҢз®—жі•жқҘе“ҲеёҢеҜҶз ҒгҖӮдҪ еә”иҜҘдҪҝз”ЁеғҸbcryptжҲ–иҖ…scryptиҝҷж ·зҡ„еҜҶз Ғе“ҲеёҢз®—жі•гҖӮ - chacham15
4
注意:您不应该在没有盐值的情况下对密码进行哈希处理。 - divanov
注意:在密码相关的操作中应当使用 char[] 替代 String,并且在使用后尽快清空。 - Surasin Tancharoen
16个回答

111

使用apache common codec库:

DigestUtils.sha1Hex("aff")
结果是0c05aa56405c447e6678b7f3127febde5c3a9238。

为什么它不能产生与在终端中运行以下命令相同的输出:echo aff|sha1sum --> c4d21f9a4f09a6645eb25089d39857bc45739ef7 - Tulains Córdova
5
echo -n "aff" | sha1sum 会产生正确的输出(默认情况下,echo会插入一个换行符)。 - klang

42

出现这种情况,是因为cript.digest()返回一个字节数组,你试图将其作为字符字符串输出。你需要将其转换为可打印的十六进制字符串。

简单的解决方案:使用Apache的commons-codec库

String password = new String(Hex.encodeHex(cript.digest()),
                             CharSet.forName("UTF-8"));

无法编辑您的帖子,但您正在尝试调用CharSet构造函数而没有为静态方法使用new。我想删除(),但不允许我这样做。 - stan229
2
出现错误,提示构造函数String(char[], Charset)未定义。使用this.password = Hex.encodeHexString(cript.digest()); - thisisananth
我遇到了与@thisisananth相同的错误。此外,String构造函数使用java.nio.charset.Charset(而不是CharSet)。 - typoerrpr
这怎么成为被接受的答案了?String 不接受 char[]。 - Tony B

26

一个哈希算法的迭代不够安全。它运行得太快了。您需要通过多次迭代哈希来执行密钥强化。

此外,您没有对密码进行盐值处理。这会导致预先计算字典(如“彩虹表”)的漏洞。

与其尝试编写自己的代码(或使用一些可疑的第三方软件)来正确执行此操作,不如使用 Java 运行时中内置的代码。有关详细信息,请参见此答案

一旦正确地对密码进行了哈希,您将拥有一个 byte[]。将其转换为十六进制 String 的简单方法是使用 BigInteger 类:

String passwordHash = new BigInteger(1, cript.digest()).toString(16);

如果您想确保字符串始终具有40个字符,则可能需要在左侧使用零进行一些填充(可以使用String.format()来完成此操作)。


1
对于填充,请使用:String hex = String.format("%040x", new BigInteger(1, cript.digest())); - Philippe

15

如果您不想向您的项目添加任何额外的依赖项,您也可以使用

MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(message.getBytes("utf8"));
byte[] digestBytes = digest.digest();
String digestStr = javax.xml.bind.DatatypeConverter.printHexBinary(digestBytes);

5

crypt.digest()方法返回一个byte[]。这个byte数组是正确的SHA-1摘要,但是加密哈希通常以十六进制形式显示给人类。你哈希中的每个字节将导致两个十六进制数字。

为了安全地将字节转换为十六进制,请使用以下方法:

// %1$ == arg 1
// 02  == pad with 0's
// x   == convert to hex
String hex = String.format("%1$02x", byteValue);

这段代码片段可用于将字符转换为十六进制

/*
 * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle or the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */ 
import java.io.*;

public class UnicodeFormatter  {

   static public String byteToHex(byte b) {
      // Returns hex String representation of byte b
      char hexDigit[] = {
         '0', '1', '2', '3', '4', '5', '6', '7',
         '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
      };
      char[] array = { hexDigit[(b >> 4) & 0x0f], hexDigit[b & 0x0f] };
      return new String(array);
   }

   static public String charToHex(char c) {
      // Returns hex String representation of char c
      byte hi = (byte) (c >>> 8);
      byte lo = (byte) (c & 0xff);
      return byteToHex(hi) + byteToHex(lo);
   }
}

请注意,在Java中使用字节非常容易出错。建议您仔细检查每个步骤并测试一些奇怪的情况。
此外,您应该考虑使用比SHA-1更强大的加密算法。 http://csrc.nist.gov/groups/ST/hash/statement.html

+1 更好的是一个可以理解(并且必要时可以更改)的小代码片段,而不是一个多MB库,你只使用其中的1%。 - Thomas S.

3
使用 Google Guava
Maven:
<dependency>
   <artifactId>guava</artifactId>
   <groupId>com.google.guava</groupId>
   <version>14.0.1</version>
</dependency>

样例:

HashCode hashCode = Hashing.sha1().newHasher()
   .putString(password, Charsets.UTF_8)
   .hash();            

String hash = BaseEncoding.base16().lowerCase().encode(hashCode.asBytes());

2
如果您使用Spring,那么这就非常简单:
MessageDigestPasswordEncoder encoder = new MessageDigestPasswordEncoder("SHA-1");
String hash = encoder.encodePassword(password, "salt goes here");

2
“在存储密码时,不仅涉及简单的标准哈希算法,还需要采取以下措施以使其不可逆:
  1. 进行多轮加密,以使暴力破解攻击变得更慢
  2. 使用每个记录的“盐”作为哈希算法的输入,除了密码之外,使字典攻击变得不太可行,并避免输出冲突。
  3. 使用“pepper”,即应用程序配置设置作为哈希算法的输入,使具有未知“pepper”的窃取数据库转储无用。
  4. 填充输入以避免某些哈希算法中的弱点,例如,在不知道密码的情况下附加字符到密码中,通过修改哈希来实现。

有关更多信息,请参见:


您还可以使用http://en.wikipedia.org/wiki/Password-authenticated_key_agreement方法,完全避免以明文形式将密码传递给服务器。

2
在Java 17中,引入了HexFormat。它允许您编写以下内容:
var cript = MessageDigest.getInstance("sha1");
var hexformat = HexFormat.of();
this.password = hexformat.formatHex(cript.digest(userPass.getBytes(StandardCharsets.UTF_8)))

1
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
        messageDigest.reset();
        messageDigest.update(password.getBytes("UTF-8"));
        String sha1String = new BigInteger(1, messageDigest.digest()).toString(16);

add some description too - Anandhu Nadesh

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