如何在 Java 中使用 SHA-512 对密码进行哈希处理?

Posted

技术标签:

【中文标题】如何在 Java 中使用 SHA-512 对密码进行哈希处理?【英文标题】:How to hash a password with SHA-512 in Java? 【发布时间】:2016-01-10 04:33:42 【问题描述】:

我一直在研究 Java 字符串加密技术,不幸的是我没有找到任何好的教程,如何在 Java 中使用 SHA-512 对字符串进行哈希处理;我阅读了一些关于 MD5 和 Base64 的博客,但它们并没有我想的那么安全(实际上,Base64 不是一种加密技术),所以我更喜欢 SHA-512。

【问题讨论】:

SHA2 也不是一种加密技术。它是一系列加密安全散列函数,它可以用于消息摘要,而不是加密(至少在正常模式下没有)。您需要多描述一下您想要做什么。除此之外,您的 MD5 代码也应该与其他哈希算法一起使用,只需交换算法名称即可。它们的输出都是二进制 byte[] 数组,因此您可能希望使用 base64 或 binhex 来仅生成 ascii 的结果。 SHA-512 也不是加密算法。实际上,它是专门设计用来“解密”这种散列算法产生的摘要的。你想达到什么目标? Java 字符串包含有效的 Unicode。获取某些字符集中的字节(如 UTF-8)然后对其进行加密会产生无法正确转换为字符串(用于解密)的字节,当然不是需要正确序列的 UTF-8。因此,通常会添加 Base64 以将加密字节转换为 ASCII。 没有加密算法可以做你想做的事。要么你想要加密(但如果目标是存储密码,你不应该这样做),并且你需要一个密钥来加密/解密。或者你想要一个盐和一个摘要函数(你想要一个散列函数,尽管 SHA-512 被认为对于这种用法来说太弱了)。加盐+摘要的原理是你可以通过重新加盐和重新消化来检查提交的密码是否正确,然后检查结果是否与存储在数据库中的相同。但是无法将结果反转并得到原始密码。 使用 PBKDF2WithHmacSHA1(JDK 自带)或 BCrypt。但首先,学习加密的原理。 【参考方案1】:

您可以将其用于 SHA-512

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public String get_SHA_512_SecurePassword(String passwordToHash, String salt)
    String generatedPassword = null;
    try 
        MessageDigest md = MessageDigest.getInstance("SHA-512");
        md.update(salt.getBytes(StandardCharsets.UTF_8));
        byte[] bytes = md.digest(passwordToHash.getBytes(StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder();
        for(int i=0; i< bytes.length ;i++)
            sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
        
        generatedPassword = sb.toString();
     catch (NoSuchAlgorithmException e) 
        e.printStackTrace();
    
    return generatedPassword;

【讨论】:

@stackpepe - 这不应该用于散列密码,SHA* 系列太快并且很容易被暴力破解。使用好的 GPU,您可以计算出 4 Giga SHA512 per second,这就是为什么要切换到 BCrypt、PBKDF2 或 SCrypt。 @Abhi - 盐防止攻击者可以构建一个彩虹表来一次获取所有密码,相反,他必须为每个盐构建一个彩虹表,是什么使得攻击不可行。但是,盐对暴力破解(通常是字典攻击)一个密码完全没有帮助。如果暴力破解整个英语词典只需要几分之一毫秒,那么我们需要一个具有成本因子的算法,它定义了计算单个哈希(BCrypt、PBKDF2、SCrypt)需要多少时间。 除了Salt作为UTF-8字符串输入(虽然它通常以字节定义),长度没有检查,异常处理没有很好的定义(以至于代码无法编译),十六进制例程难以阅读并包含在方法中,不遵守 Java 命名约定,在不需要时首先分配 null 是不好的编码习惯......只是一个关于如何使用 SHA 的糟糕演示-512。我想投票的主要原因是这个问题在谷歌中first出现在“SHA-512 Java”搜索词中。 @MaartenBodewes 人们也需要付出一些努力才能获得更好的输出。我提供的代码不是用于复制粘贴,而只是一个提示,可以根据需要进行改进。 @Abhi:但这正是人们会做的事情:复制和粘贴。【参考方案2】:

请停止使用哈希函数对密码进行编码!它们不能提供您需要的保护。相反,您应该使用 PBKDF2、bcrypt 或 scrypt 等算法。

参考资料:

http://blog.tjll.net/please-stop-hashing-passwords/ http://security.blogoverflow.com/2011/11/why-passwords-should-be-hashed/ https://crackstation.net/hashing-security.htm http://www.sitepoint.com/risks-challenges-password-hashing/ http://security.blogoverflow.com/2013/09/about-secure-password-hashing/

【讨论】:

我支持你的回答,但通常这些算法也被称为散列函数(例如,PBKDF 甚至使用 SHA)。很难解释加密和散列之间的区别,因此不使用散列函数的建议可能有点令人困惑。缓慢会有所不同。 我完全同意术语令人困惑!我在第 1.4 节中有我唯一的抱怨:eprint.iacr.org/2015/387.pdf。术语很重要! @martinstoeckli 回过头来看,这不是一个正确的评论。 PBKDF2 是一个密钥派生函数密码散列函数。它使用安全散列并不能使其本身成为安全散列。有许多 PBKDF 的/密码哈希在内部不使用安全哈希。 @MaartenBodewes - 也许合适算法的最佳表达是密码散列函数。我的意思是,当我们说:不要使用散列函数,使用密码散列函数时,这听起来很混乱。如果我们这样做,我们应该解释是什么造成了差异,在这种情况下,重要的一点是“成本因素”,或者更一般地说,我们可以控制计算哈希的必要时间。 它没有回答问题。【参考方案3】:

使用Guava:

Hashing.sha512().hashString(s, StandardCharsets.UTF_8).toString()

【讨论】:

顺便说一句,这仍处于测试阶段【参考方案4】:

使用 Apache Commons Crypt,它具有基于 SHA-512 的 crypt() 函数,可以生成甚至与 libc 的 crypt 兼容的加盐哈希,因此也可以在 php/Perl/Python/C 和大多数数据库中使用。

https://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/digest/Crypt.html#Crypt%28%29

【讨论】:

【参考方案5】:

如果你愿意,你可以使用它在 java 中散列密码。

public static boolean isHashMatch(String password, // the password you want to check.
                                  String saltedHash, // the salted hash you want to check your password against.
                                  String hashAlgorithm, // the algorithm you want to use.
                                  String delimiter) throws NoSuchAlgorithmException // the delimiter that has been used to delimit the salt and the hash.

    // get the salt from the salted hash and decode it into a byte[].
    byte[] salt = Base64.getDecoder()
                        .decode(saltedHash.split(delimiter)[0]);
    // compute a new salted hash based on the provided password and salt.
    String pw_saltedHash = computeSaltedBase64Hash(password, 
                                                   salt,
                                                   hashAlgorithm,
                                                   delimiter);
    // check if the provided salted hash matches the salted hash we computed from the password and salt.
    return saltedHash.equals(pw_saltedHash);


public static String computeSaltedBase64Hash(String password, // the password you want to hash
                                             String hashAlgorithm, // the algorithm you want to use.
                                             String delimiter) throws NoSuchAlgorithmException // the delimiter that will be used to delimit the salt and the hash.

    // compute the salted hash with a random salt.
    return computeSaltedBase64Hash(password, null, hashAlgorithm, delimiter);


public static String computeSaltedBase64Hash(String password, // the password you want to hash
                                             byte[] salt, // the salt you want to use (uses random salt if null).
                                             String hashAlgorithm, // the algorithm you want to use.
                                             String delimiter) throws NoSuchAlgorithmException // the delimiter that will be used to delimit the salt and the hash.

    // transform the password string into a byte[]. we have to do this to work with it later.
    byte[] passwordBytes = password.getBytes();
    byte[] saltBytes;

    if(salt != null)
    
        saltBytes = salt;
    
        else
        
            // if null has been provided as salt parameter create a new random salt.
            saltBytes = new byte[64];
            SecureRandom secureRandom = new SecureRandom();
            secureRandom.nextBytes(saltBytes);              
        

    // MessageDigest converts our password and salt into a hash. 
    MessageDigest messageDigest = MessageDigest.getInstance(hashAlgorithm);
    // concatenate the salt byte[] and the password byte[].
    byte[] saltAndPassword = concatArrays(saltBytes, passwordBytes);
    // create the hash from our concatenated byte[].
    byte[] saltedHash = messageDigest.digest(saltAndPassword);
    // get java's base64 encoder for encoding.
    Encoder base64Encoder = Base64.getEncoder();
    // create a StringBuilder to build the result.
    StringBuilder result = new StringBuilder();

    result.append(base64Encoder.encodeToString(saltBytes)) // base64-encode the salt and append it.
          .append(delimiter) // append the delimiter (watch out! don't use regex expressions as delimiter if you plan to use String.split() to isolate the salt!)
          .append(base64Encoder.encodeToString(saltedHash)); // base64-encode the salted hash and append it.

    // return a salt and salted hash combo.
    return result.toString();


public static byte[] concatArrays(byte[]... arrays)
   
    int concatLength = 0;
    // get the actual length of all arrays and add it so we know how long our concatenated array has to be.
    for(int i = 0; i< arrays.length; i++)
    
        concatLength = concatLength + arrays[i].length;
    
    // prepare our concatenated array which we're going to return later.
    byte[] concatArray = new byte[concatLength];
    // this index tells us where we write into our array.
    int index = 0;
    // concatenate the arrays.
    for(int i = 0; i < arrays.length; i++)
    
        for(int j = 0; j < arrays[i].length; j++)
        
            concatArray[index] = arrays[i][j];
            index++;
        
    
    // return the concatenated arrays.
    return concatArray;     

【讨论】:

【参考方案6】:
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.binary.Hex;

public String getHashSHA512(String StringToHash, String salt)
        String generatedPassword = null;
        try 
            MessageDigest md = MessageDigest.getInstance("SHA-512");
            md.update(salt.getBytes(StandardCharsets.UTF_8));
            byte[] bytes = md.digest(StringToHash.getBytes(StandardCharsets.UTF_8));
            generatedPassword = Hex.encodeHexString(bytes);
        
        catch (NoSuchAlgorithmException e)
            e.printStackTrace();
        
        return generatedPassword;
    

虽然不建议对密码使用哈希函数,但存在 bcrypt 或 scrypt 等较新的算法

【讨论】:

【参考方案7】:

使用安全散列将 3 个 salt 组件(每个 150 个随机字符)组合到单个用户 salt(用户数据库表中的用户 salt,数据库表中的一般 salt(使用 cron 作业每月更改)并在应用程序库)。根据您的需要调整安全哈希的 for 循环量。有关哈希方法,请参见上面的答案。

private static String generateSalt(int lenght)
    String abcCapitals = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    String abcLowerCase = "abcdefghijklmnopqrstuvwxyz";
    String numbers = "01234567890123456789";
    String characters = "!@#$%^&*!@#$%%^^&*";

    String total = abcCapitals + abcLowerCase + numbers + characters;

    String response = "";

    char letters[] = new char[lenght];
    for (int i=0; i<lenght-1; i++)
        Random r = new Random();
        char letter = total.charAt(r.nextInt(total.length()));
        letters[i] = letter;
    

    response = Arrays.toString(letters).replaceAll("\\s+","");
    response = response.replaceAll(",","");

    return response;


private static String getHash(String passwordToHash, String salt)
            String generatedPassword = null;
            try 
                 MessageDigest md = MessageDigest.getInstance("SHA-512");
                 md.update(salt.getBytes(StandardCharsets.UTF_8));
                 byte[] bytes = md.digest(passwordToHash.getBytes(StandardCharsets.UTF_8));
                 StringBuilder sb = new StringBuilder();
                 for(int i=0; i< bytes.length ;i++)
                    sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
                 
                 generatedPassword = sb.toString();
                 
               catch (NoSuchAlgorithmException e)
                   System.out.println(e);
               
            return generatedPassword;
        

    public static String getSecureHash(String password, String salt)
        String hash = getHash(password, salt);
        for (int i=0; i<20000; i++)
            hash = getHash(password, hash);      
        
        return hash;
    

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) 
        String salt = generateSalt(150);
        String salt2 = generateSalt(150);
        String salt3 = generateSalt(150);
        String someString = "This is some string!";

        String hash = getSecureHash(someString, salt + salt2 + salt3);
        System.out.println(hash);
    

【讨论】:

以上是关于如何在 Java 中使用 SHA-512 对密码进行哈希处理?的主要内容,如果未能解决你的问题,请参考以下文章

使用 SHA-512 和 salt 来散列 MD5 散列密码?

散列密码的最佳实践 - SHA256 还是 SHA512?

如何为一个密码实现sha 512,md5和salt加密[重复]

登录表单不读取 sha512

Openldap 支持 sha256 和 sha512 密码格式

使用sha512算法加密linux密码