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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時(shí)間:2025/3/20 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何生成安全的密码 Hash:MD5, SHA, PBKDF2, BCrypt 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

. 一、前言

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

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

. 二、簡(jiǎn)單的密碼安全實(shí)現(xiàn)使用 MD5 算法

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

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

public class SimpleMD5Example {public static void main(String[] args) {String passwordToHash = "password";String generatedPassword = null;try {// Create MessageDigest instance for MD5MessageDigest md = MessageDigest.getInstance("MD5");//Add password bytes to digestmd.update(passwordToHash.getBytes());//Get the hash's bytes byte[] bytes = md.digest();//This bytes[] has bytes in decimal format;//Convert it to hexadecimal formatStringBuilder 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 formatgeneratedPassword = sb.toString();} catch (NoSuchAlgorithmException e) {e.printStackTrace();}System.out.println(generatedPassword);} }

雖然 MD5 是一個(gè)廣為流傳的 Hash 算法, 但它并不安全且所生成的 Hash 值也是相當(dāng)?shù)谋∪酢K饕膬?yōu)點(diǎn)在于生成速度快且易于實(shí)現(xiàn)。但是,這也意味著它是容易被暴力攻擊和字典攻擊。例如使用明文和 Hash 生成的彩虹表可以快速地搜索已知 Hash 對(duì)應(yīng)的原數(shù)據(jù)。

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

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

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

在 Wikipedia 上對(duì) salt 的定義是通過一個(gè)單向函數(shù)獲取隨機(jī)數(shù)據(jù)來為密碼或口令添加一些額外的數(shù)據(jù)。更簡(jiǎn)單的說法則是通過生成一些隨機(jī)的文本將其附加到密碼上來生成 Hash。

為 Hash 加 salt 的主要目的是用來防止預(yù)先被計(jì)算好的彩虹表攻擊。現(xiàn)在加 slat 好處是將原本一次比較變?yōu)槎啻伪容^從而減慢對(duì)密碼 Hash 值的猜測(cè),否則對(duì) Hash 密碼庫的破解效率將是非常之高。

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

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

SHA1PRNG 算法是基于 SHA-1 算法實(shí)現(xiàn)且保密性較強(qiáng)的偽隨機(jī)數(shù)生成器。要注意的是,如果不為它提供隨機(jī)數(shù)種子,它將會(huì)通過真隨機(jī)數(shù)來生成一個(gè)種子(TRNG)。 譯注:關(guān)于 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 83ee5baeea20b6c21635e4ea67847f66String 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 MD5MessageDigest md = MessageDigest.getInstance("MD5");//Add password bytes to digestmd.update(salt.getBytes());//Get the hash's bytesbyte[] bytes = md.digest(passwordToHash.getBytes());//This bytes[] has bytes in decimal format;//Convert it to hexadecimal formatStringBuilder 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 formatgeneratedPassword = sb.toString();}catch (NoSuchAlgorithmException e) {e.printStackTrace();}return generatedPassword;}//Add saltprivate static String getSalt() throws NoSuchAlgorithmException, NoSuchProviderException{//Always use a SecureRandom generatorSecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "SUN");//Create array for saltbyte[] salt = new byte[16];//Get a random saltsr.nextBytes(salt);//return saltreturn salt.toString();} }

重要提示:請(qǐng)注意,現(xiàn)在你必須每一個(gè)密碼 Hash 存儲(chǔ)它 slat 值。因?yàn)楫?dāng)用戶登錄系統(tǒng),你必須使用最初生成的 slat 來再次生成 Hash 與存儲(chǔ)的 Hash 進(jìn)行匹配。如果使用不同的 slat(生成隨機(jī) slat)那么生成的 Hash 將不同。

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

salt+password+salt => hash

實(shí)際上你沒必要這樣去做,因?yàn)檫@并不能幫助你進(jìn)一步鞏固 Hash 的安全性。如果你需要更高的安全性,正確的做法應(yīng)該去選擇一個(gè)更好的算法。
中等的密碼安全實(shí)現(xiàn)使用SHA算法

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

在 Java 中有提供有4種 SHA 算法的實(shí)現(xiàn),相對(duì) MD5(128 bit hash) 它提供了以下長(zhǎng)度的 Hash:

  • SHA-1 (簡(jiǎn)單實(shí)現(xiàn) – 160 bits Hash)

  • SHA-256 (強(qiáng)于 SHA-1 – 256 bits Hash)

  • SHA-384 (強(qiáng)于 SHA-256 – 384 bits Hash)

  • SHA-512 (強(qiáng)于 SHA-384 – 512 bits Hash)

通常越長(zhǎng)的 Hash 越難破解,這是核心思想。

要獲取相應(yīng)算法實(shí)現(xiàn),可通過參數(shù)的方式傳給 MessageDigest 來獲得實(shí)例。如下所示:

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

下面通過測(cè)試程序來看 SHA 的應(yīng)用:

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 saltprivate static String getSalt() throws NoSuchAlgorithmException{SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");byte[] salt = new byte[16];sr.nextBytes(salt);return salt.toString();} }

如上代碼所示,在使用 SHA 的同時(shí)同樣可以為其加 salt 來加強(qiáng)它的安全性。
較高密碼安全實(shí)現(xiàn)使用 PBKDF2WithHmacSHA1 算法

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

為了解決這個(gè)問題,主要想法是盡可能降低暴力攻擊速度來保證最小化的損失。我們下一個(gè)算法同樣是基于這個(gè)概念。目標(biāo)是使 Hash 函數(shù)足夠慢以妨礙攻擊,并對(duì)用戶來說仍然非常快且不會(huì)感到有明顯的延時(shí)。

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

Java 中可通過 "PBKDF2WithHmacSHA1" 來實(shí)現(xiàn)"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;} }

接下來的是當(dāng)重新回來時(shí),需要提供登錄密碼驗(yàn)證的方法實(shí)現(xiàn):

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

參考以上代碼時(shí)需注意,如果發(fā)現(xiàn)任何問題請(qǐng)下載本文章結(jié)尾處附件中的代碼。

. 三、更加安全的密碼實(shí)現(xiàn)使用 bcrypt 和 scrypt 算法

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

下以是 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 算法類似,我已經(jīng)從 github 下載了 scrypt 算法的源代碼并添加到在最后一節(jié)中的源碼下載中。看看它是如何使用:

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

. 四、最后說明

在應(yīng)用程序中存儲(chǔ)密碼明文是極其危險(xiǎn)的事情。
? MD5 提供了最基本的安全 Hash 生成,使用時(shí)應(yīng)為其添加 slat 來進(jìn)一步加強(qiáng)它的安全性。
? MD5 生成128位的 Hash。為了使它更安全,應(yīng)該使用 SHA 算法生成 160-bit 或 512-bit 的長(zhǎng) Hash,其中 512-bit 是最強(qiáng)的。
? 雖然使用 SHA Hash 密碼也能被當(dāng)今快速的硬件破解,如要避免這一點(diǎn),你需要的算法是能讓暴力攻擊盡可能的變慢且使影響減至最低。這時(shí)候你可以使用 PBKDF2, BCrypt 或 SCrypt 算法。
? 請(qǐng)?jiān)谏钏际鞈]后來選擇適當(dāng)?shù)陌踩惴ā?/p>

轉(zhuǎn)自:https://www.cnblogs.com/interdrp/p/4935819.html
文章有不當(dāng)之處,歡迎指正,你也可以關(guān)注我的微信公眾號(hào):好好學(xué)java,獲取優(yōu)質(zhì)資源。

總結(jié)

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

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。