Java计算string的SHA-1摘要的hex表示

我将用户密码存储在数据库作为SHA1哈希。

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

我存储的string是这样的:

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

我想要这样的东西 – >

aff – >“0c05aa56405c447e6678b7f3127febde5c3a9238”

而不是

aff @ @ D </s> </s> </s> 8

发生这种情况的原因是cript.digest()返回一个字节数组,您试图将其作为string打印出来。 你想把它转换成可打印的hexstring。

简单的解决scheme:使用Apache的公共编解码器库 :

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

使用apache公共编解码器库:

 DigestUtils.sha1Hex("aff") 

结果是0c05aa56405c447e6678b7f3127febde5c3a9238

而已 :)

哈希algorithm的一次迭代是不安全的。 这太快了。 您需要通过多次迭代哈希来执行密钥加强。

此外,你不腌制密码。 这会对预先计算的字典造成一个漏洞,如“彩虹表”。

您可以使用Java运行时内置的代码,而不是试图自己编写代码(或者使用一些粗略的第三方英美软件)来正确执行此操作。 看到这个答案的细节。

一旦你正确地散列密码,你将有一个byte[] 。 将其转换为hexString的简单方法是使用BigInteger类:

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

如果你想确保你的string总是有40个字符,你可能需要在左边填零(你可以用String.format()来完成String.format()

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

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

crypt.digest()方法返回一个byte []。 这个字节数组是正确的SHA-1总和,但是encryption散列通常以hex格式显示给人类。 散列中的每个字节都将导致两个hex数字。

要安全地将字节转换为hex,请使用以

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

此代码片段可用于将char转换为hex :

 /* * 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中使用字节是非常容易出错的。 我会仔细检查一下,并testing一些奇怪的情况。

你也应该考虑使用比SHA-1更强的东西。 http://csrc.nist.gov/groups/ST/hash/statement.html

如果你使用Spring,很简单:

 MessageDigestPasswordEncoder encoder = new MessageDigestPasswordEncoder("SHA-1"); String hash = encoder.encodePassword(password, "salt goes here"); 

不仅仅是存储密码不可逆的简单标准哈希algorithm。

  1. 多轮循环使暴力攻击变慢
  2. 除了密码之外,使用每条logging的“盐”作为哈希algorithm的input,使字典攻击变得不可行,并避免输出冲突。
  3. 使用“胡椒”,一个应用程序configuration设置作为哈希algorithm的input,使一个未知的“胡椒”被盗的数据库转储无用。
  4. 填充input,以避免在一些哈希algorithm的弱点,例如通过修改哈希,你可以在不知道密码的情况下将密码附加到密码。

欲了解更多信息,请参阅例如

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

digest()返回一个字节数组,你使用默认的编码转换为一个string。 你想要做的是base64编码。

要使用UTF-8,请执行以下操作:

 userPass.getBytes("UTF-8"); 

为了从摘要中获得一个Base64string,可以这样做:

 this.password = new BASE64Encoder().encode(cript.digest()); 

由于MessageDigest.digest()返回一个字节数组,所以可以使用Apache的hex编码 (简单)将其转换为string。

例如

 this.password = Hex.encodeHexString(cript.digest()); 

如何将字节[]转换为base64string?

  byte[] chkSumBytArr = digest.digest(); BASE64Encoder encoder = new BASE64Encoder(); String base64CheckSum = encoder.encode(chkSumBytArr); 

你也可以使用这个代码(来自crackstation.net):

private static String toHex(byte[] array) { BigInteger bi = new BigInteger(1, array); String hex = bi.toString(16); int paddingLength = (array.length * 2) - hex.length(); if(paddingLength > 0) return String.format("%0" + paddingLength + "d", 0) + hex; else return hex; }

你可以使用谷歌番石榴 :

Maven的:

 <dependency> <artifactId>guava</artifactId> <groupId>com.google.guava</groupId> <version>14.0.1</version> </dependency> 

样品:

 HashFunction hashFunction = Hashing.sha1(); HashCode hashCode = hashFunction.newHasher() .putString(password,Charsets.UTF_8) .hash(); String hash = BaseEncoding.base16().lowerCase().encode(hashCode.asBytes()); 

你首先需要对结果进行hex编码。 MessageDigest返回一个“原始”哈希值,而不是人类可读的哈希值。

编辑:

@thejh提供了一个应该工作的代码的链接。 就个人而言,我build议使用Bouncycastle或Apache Commons Codec来完成这项工作。 如果你想做其他与密码相关的操作,Bouncycastle会很好。