日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

如何生成安全的密码 Hash:MD5, SHA, PBKDF2, BCrypt 示例

發布時間:2025/3/20 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何生成安全的密码 Hash:MD5, SHA, PBKDF2, BCrypt 示例 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章推薦

  • 精選java等全套學習資源
  • 精選java電子圖書資源
  • 精選大數據學習資源
  • java項目練習精選

. 一、前言

密碼 Hash 值的產生是將用戶所提供的密碼通過使用一定的算法計算后得到的加密字符序列。在 Java 中提供很多被證明能有效保證密碼安全的 Hash 算法實現,我將在這篇文章中討論其中的部分算法。

需要注意的是,一旦生成密碼的 Hash 值并存儲在數據庫中后,你將不可能再把它轉換回密碼明文。只能每次用戶在登錄到應用程序時,須重新生成 Hash 值與數據庫中的 Hash 值匹配來完成密碼的校驗。

. 二、簡單的密碼安全實現使用 MD5 算法

MD5消息摘要算法(MD5 Message-Digest Algorithm)是一種廣泛使用的加密 Hash 函數,主要用于生成一個128bit(16byte) Hash 值。它的實現思路非常簡單且易懂,其最基本的思路是將可變長度的數據集映射為固定長度的數據集。為了做到這一點,它將輸入消息分割成 512-bit 的數據塊。通過填補消息到末尾以確保其長度能除以512?,F在這些塊將通過 MD5 算法處理,其結果將是一個128位的散列值。使用MD5后,生成的散列值通常是32位16進制的數字。

在該文中,對于被加密的密碼明文被稱為“消息(message)”,其加密后所生成的 Hash 值稱為 “消息摘要(message digest)” 或簡稱 "摘要(digest)"。以下是 MD5 產生 Hash 值的代碼示例:

public class SimpleMD5Example
{
public static void main(String[] args)
{
String passwordToHash = "password";
String generatedPassword = null;
try {
// Create MessageDigest instance for MD5
MessageDigest md = MessageDigest.getInstance("MD5");
//Add password bytes to digest
md.update(passwordToHash.getBytes());
//Get the hash's bytes
byte[] bytes = md.digest();
//This bytes[] has bytes in decimal format;
//Convert it to hexadecimal format
StringBuilder sb = new StringBuilder();
for(int i=0; i< bytes.length ;i++)
{
sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
//Get complete hashed password in hex format
generatedPassword = sb.toString();
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
System.out.println(generatedPassword);
}
}

雖然 MD5 是一個廣為流傳的 Hash 算法, 但它并不安全且所生成的 Hash 值也是相當的薄弱。它主要的優點在于生成速度快且易于實現。但是,這也意味著它是容易被暴力攻擊和字典攻擊。例如使用明文和 Hash 生成的彩虹表可以快速地搜索已知 Hash 對應的原數據。

此外,MD5 并沒有避免 Hash 碰撞:這意味不同的密碼會導致生成相同的 Hash 值。

不過,如果你仍然需要使用 MD5,可以考慮為其加 salt 來進一步保證它的安全性。
使用 salt 讓生成的 MD5 更加安全

這里需要注意的是,加 salt 並不是 MD5 所特有的, 你同樣可以把它應用在其它算法中。所以,在這里你只需關注它是如何應用而不是它與 MD5 的聯系。

在 Wikipedia 上對 salt 的定義是通過一個單向函數獲取隨機數據來為密碼或口令添加一些額外的數據。更簡單的說法則是通過生成一些隨機的文本將其附加到密碼上來生成 Hash。

為 Hash 加 salt 的主要目的是用來防止預先被計算好的彩虹表攻擊?,F在加 slat 好處是將原本一次比較變為多次比較從而減慢對密碼 Hash 值的猜測,否則對 Hash 密碼庫的破解效率將是非常之高。

重要的是:在 Java 中,我們總是需要使用 SecureRandom 來生成一個好的 salt 值,因此可以利用 SecureRandom 類所提供 “SHA1PRNG” 算法來生成偽隨機數。代碼如下所示:

private static String getSalt() throws NoSuchAlgorithmException
{
//Always use a SecureRandom generator
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
//Create array for salt
byte[] salt = new byte[16];
//Get a random salt
sr.nextBytes(salt);
//return salt
return salt.toString();
}

SHA1PRNG 算法是基于 SHA-1 算法實現且保密性較強的偽隨機數生成器。要注意的是,如果不為它提供隨機數種子,它將會通過真隨機數來生成一個種子(TRNG)。 譯注:關于 TRGN 和 PRGN。

接下來,看看為 MD5 Hash 加 slat 的代碼示例:

public class SaltedMD5Example
{
public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException
{
String passwordToHash = "password";
String salt = getSalt();
String securePassword = getSecurePassword(passwordToHash, salt);
System.out.println(securePassword); //Prints 83ee5baeea20b6c21635e4ea67847f66
String regeneratedPassowrdToVerify = getSecurePassword(passwordToHash, salt);
System.out.println(regeneratedPassowrdToVerify); //Prints 83ee5baeea20b6c21635e4ea67847f66
}
private static String getSecurePassword(String passwordToHash, String salt)
{
String generatedPassword = null;
try {
// Create MessageDigest instance for MD5
MessageDigest md = MessageDigest.getInstance("MD5");
//Add password bytes to digest
md.update(salt.getBytes());
//Get the hash's bytes
byte[] bytes = md.digest(passwordToHash.getBytes());
//This bytes[] has bytes in decimal format;
//Convert it to hexadecimal format
StringBuilder sb = new StringBuilder();
for(int i=0; i< bytes.length ;i++)
{
sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
//Get complete hashed password in hex format
generatedPassword = sb.toString();
}
catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return generatedPassword;
}
//Add salt
private static String getSalt() throws NoSuchAlgorithmException, NoSuchProviderException
{
//Always use a SecureRandom generator
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "SUN");
//Create array for salt
byte[] salt = new byte[16];
//Get a random salt
sr.nextBytes(salt);
//return salt
return salt.toString();
}
}

重要提示:請注意,現在你必須每一個密碼 Hash 存儲它 slat 值。因為當用戶登錄系統,你必須使用最初生成的 slat 來再次生成 Hash 與存儲的 Hash 進行匹配。如果使用不同的 slat(生成隨機 slat)那么生成的 Hash 將不同。

此外,你可能聽說過多次 Hash 和加 salt。通常就是指創建自定義的組合,如:

salt+password+salt => hash

實際上你沒必要這樣去做,因為這并不能幫助你進一步鞏固 Hash 的安全性。如果你需要更高的安全性,正確的做法應該去選擇一個更好的算法。
中等的密碼安全實現使用SHA算法

SHA(安全散列算法)同樣是作為加密 Hash 函數家族中的一員。除了它生成的 Hash 安全性比 MD5 更強之外其它都與 MD5 非常類似。然而,它們生成的 Hash 并不總是唯一的,這意味著輸入兩個不同的值所獲的 Hash 卻是相同的。通常這種情況的發生我們稱之為“碰撞”。 不過,SHA 碰撞的幾率小于 MD5。你甚至無需擔心碰撞的發生,因為這種情況非常罕見。

在 Java 中有提供有4種 SHA 算法的實現,相對 MD5(128 bit hash) 它提供了以下長度的 Hash:

  • SHA-1 (簡單實現 – 160 bits Hash)
  • SHA-256 (強于 SHA-1 – 256 bits Hash)
  • SHA-384 (強于 SHA-256 – 384 bits Hash)
  • SHA-512 (強于 SHA-384 – 512 bits Hash)

通常越長的 Hash 越難破解,這是核心思想。

要獲取相應算法實現,可通過參數的方式傳給 MessageDigest 來獲得實例。如下所示:

MessageDigest md = MessageDigest.getInstance("SHA-1");
//OR
MessageDigest md = MessageDigest.getInstance("SHA-256");

下面通過測試程序來看 SHA 的應用:

package com.howtodoinjava.hashing.password.demo.sha;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class SHAExample {
public static void main(String[] args) throws NoSuchAlgorithmException {
String passwordToHash = "password";
String salt = getSalt();
String securePassword = get_SHA_1_SecurePassword(passwordToHash, salt);
System.out.println(securePassword);
securePassword = get_SHA_256_SecurePassword(passwordToHash, salt);
System.out.println(securePassword);
securePassword = get_SHA_384_SecurePassword(passwordToHash, salt);
System.out.println(securePassword);
securePassword = get_SHA_512_SecurePassword(passwordToHash, salt);
System.out.println(securePassword);
}
private static String get_SHA_1_SecurePassword(String passwordToHash, String salt)
{
String generatedPassword = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(salt.getBytes());
byte[] bytes = md.digest(passwordToHash.getBytes());
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;
}
private static String get_SHA_256_SecurePassword(String passwordToHash, String salt)
{
//Use MessageDigest md = MessageDigest.getInstance("SHA-256");
}
private static String get_SHA_384_SecurePassword(String passwordToHash, String salt)
{
//Use MessageDigest md = MessageDigest.getInstance("SHA-384");
}
private static String get_SHA_512_SecurePassword(String passwordToHash, String salt)
{
//Use MessageDigest md = MessageDigest.getInstance("SHA-512");
}
//Add salt
private static String getSalt() throws NoSuchAlgorithmException
{
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
byte[] salt = new byte[16];
sr.nextBytes(salt);
return salt.toString();
}
}

如上代碼所示,在使用 SHA 的同時同樣可以為其加 salt 來加強它的安全性。
較高密碼安全實現使用 PBKDF2WithHmacSHA1 算法

到目前為止,我們已經了解如何為密碼生成安全的 Hash 值以及通過利用 salt 來加強它的安全性。但今天的問題是,硬件的速度已經遠遠超過任何使用字典或彩虹表進行的暴力攻擊,并且任何密碼都能被破解,只是使用時間多少的問題。

為了解決這個問題,主要想法是盡可能降低暴力攻擊速度來保證最小化的損失。我們下一個算法同樣是基于這個概念。目標是使 Hash 函數足夠慢以妨礙攻擊,并對用戶來說仍然非??烨也粫械接忻黠@的延時。

要達到這個目的通常是使用某些 CPU 密集型算法來實現,比如 PBKDF2, Bcrypt 或 Scrypt 。這些算法采用 work factor(也稱之為 security factor)或迭代次數作為參數來確定 Hash 函數將變的有多慢,并且隨著日后計算能力的提高,可以逐步增大 work factor 來使之與計算能力達到平衡。

Java 中可通過 "PBKDF2WithHmacSHA1" 來實現"PBKDF2"算法,下面代碼是使用示例:

public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException
{
String originalPassword = "password";
String generatedSecuredPasswordHash = generateStorngPasswordHash(originalPassword);
System.out.println(generatedSecuredPasswordHash);
}
private static String generateStorngPasswordHash(String password) throws NoSuchAlgorithmException, InvalidKeySpecException
{
int iterations = 1000;
char[] chars = password.toCharArray();
byte[] salt = getSalt().getBytes();
PBEKeySpec spec = new PBEKeySpec(chars, salt, iterations, 64 * 8);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] hash = skf.generateSecret(spec).getEncoded();
return iterations + ":" + toHex(salt) + ":" + toHex(hash);
}
private static String getSalt() throws NoSuchAlgorithmException
{
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
byte[] salt = new byte[16];
sr.nextBytes(salt);
return salt.toString();
}
private static String toHex(byte[] array) throws NoSuchAlgorithmException
{
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;
}
}

接下來的是當重新回來時,需要提供登錄密碼驗證的方法實現:

public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException
{
String originalPassword = "password";
String generatedSecuredPasswordHash = generateStorngPasswordHash(originalPassword);
System.out.println(generatedSecuredPasswordHash);
boolean matched = validatePassword("password", generatedSecuredPasswordHash);
System.out.println(matched);
matched = validatePassword("password1", generatedSecuredPasswordHash);
System.out.println(matched);
}
private static boolean validatePassword(String originalPassword, String storedPassword) throws NoSuchAlgorithmException, InvalidKeySpecException
{
String[] parts = storedPassword.split(":");
int iterations = Integer.parseInt(parts[0]);
byte[] salt = fromHex(parts[1]);
byte[] hash = fromHex(parts[2]);
PBEKeySpec spec = new PBEKeySpec(originalPassword.toCharArray(), salt, iterations, hash.length * 8);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] testHash = skf.generateSecret(spec).getEncoded();
int diff = hash.length ^ testHash.length;
for(int i = 0; i < hash.length && i < testHash.length; i++)
{
diff |= hash[i] ^ testHash[i];
}
return diff == 0;
}
private static byte[] fromHex(String hex) throws NoSuchAlgorithmException
{
byte[] bytes = new byte[hex.length() / 2];
for(int i = 0; i<bytes.length ;i++)
{
bytes[i] = (byte)Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
}
return bytes;
}

參考以上代碼時需注意,如果發現任何問題請下載本文章結尾處附件中的代碼。

. 三、更加安全的密碼實現使用 bcrypt 和 scrypt 算法

bcrypt 的背后的思想與 PBKDF2 類似。只是 Java 中并沒有內置支持使攻擊者變慢的 bcrypt 算法實現,但你仍然可以找到并下載它的源碼。

下以是 bcrypt 的使用代碼示例(其中 Bcrypt.java 已提供在源碼中):

public class BcryptHashingExample
{
public static void main(String[] args) throws NoSuchAlgorithmException
{
String originalPassword = "password";
String generatedSecuredPasswordHash = BCrypt.hashpw(originalPassword, BCrypt.gensalt(12));
System.out.println(generatedSecuredPasswordHash);
boolean matched = BCrypt.checkpw(originalPassword, generatedSecuredPasswordHash);
System.out.println(matched);
}
}

提供bcrypt.java文件的下載地址:下載

與 bcrypt 算法類似,我已經從 github 下載了 scrypt 算法的源代碼并添加到在最后一節中的源碼下載中??纯此侨绾问褂?#xff1a;

public class ScryptPasswordHashingDemo
{
public static void main(String[] args) {
String originalPassword = "password";
String generatedSecuredPasswordHash = SCryptUtil.scrypt(originalPassword, 16, 16, 16);
System.out.println(generatedSecuredPasswordHash);
boolean matched = SCryptUtil.check("password", generatedSecuredPasswordHash);
System.out.println(matched);
matched = SCryptUtil.check("passwordno", generatedSecuredPasswordHash);
System.out.println(matched);
}
}

. 四、最后說明

在應用程序中存儲密碼明文是極其危險的事情。
MD5 提供了最基本的安全 Hash 生成,使用時應為其添加 slat 來進一步加強它的安全性。
MD5 生成128位的 Hash。為了使它更安全,應該使用 SHA 算法生成 160-bit 或 512-bit 的長 Hash,其中 512-bit 是最強的。
雖然使用 SHA Hash 密碼也能被當今快速的硬件破解,如要避免這一點,你需要的算法是能讓暴力攻擊盡可能的變慢且使影響減至最低。這時候你可以使用 PBKDF2, BCrypt 或 SCrypt 算法。
請在深思熟慮后來選擇適當的安全算法。

轉自:https://www.cnblogs.com/interdrp/p/4935819.html

總結

以上是生活随笔為你收集整理的如何生成安全的密码 Hash:MD5, SHA, PBKDF2, BCrypt 示例的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。