存儲用戶密碼應該使用什么加密算法?

概述

編程開發(fā)中拥诡,像用戶登錄注冊這種功能很常見触趴,那么對于用戶密碼處理氮发,我們該選擇什么樣的加密算法呢?在這種場景下冗懦,算法需要滿足下面兩個條件:

  • 算法需不可逆爽冕,這樣才能有效防止密碼泄露。
  • 算法需相對慢披蕉,可以動態(tài)調(diào)整計算成本颈畸,緩慢是應對暴力破解有效方式。

目前來看有這么幾個算法 PBKDF2没讲、 BCryptSCrypt 可以滿足眯娱。我們先看下舊的密碼加密方式。

舊的加密

過去密碼加密常用MD5或者SHA爬凑。MD5是早期設計的加密哈希徙缴,它生成哈希速度很快,隨著計算機能力的增強嘁信,出現(xiàn)了被破解的情況于样,所以又有了一些長度增大的哈希函數(shù),如:SHA-1潘靖,SHA-256等穿剖。下面是它們的一些比較:

  • MD5:速度快生成短哈希(16 字節(jié))。意外碰撞的概率約為:1.47 \times 10^{-29} 卦溢。

  • SHA1:比 md5 慢 20%糊余,生成的哈希比 MD5 長一點(20 字節(jié))。意外碰撞的概率約為:1 \times 10^{-45}单寂。

  • SHA256:最慢贬芥,通常比 md5 慢 60%,并且生成的哈希長(32 字節(jié))凄贩。意外碰撞的概率約為:4.3 \times 10^{-60} 誓军。

為了確保安全你可能會選擇目前長度最長的哈希SHA-512,但硬件能力在增強疲扎,或許有一天又會發(fā)現(xiàn)新的漏洞昵时,研究人員又推出較新的版本,新版本的長度也會越來越長椒丧,而且他們也可能會發(fā)布底層算法壹甥,所以我們應該另外尋找更合適的算法。

加鹽操作

密碼安全壶熏,除了要選擇足夠可靠的加密算法外句柠,輸入數(shù)據(jù)的強度也要提升,因為密碼是人設置的,其字符長度組合強度不可能一致溯职,如果直接進行哈希存儲往往會提升爆破的概率精盅,這時我們需要加鹽

加鹽是密碼學中經(jīng)常提到的概念谜酒,其實就是隨機數(shù)據(jù)叹俏。下面是一個 java 中生成鹽的例子:

    public static byte[] generateSalt() {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16];
        random.nextBytes(salt);
        return salt;
    }

SHA-512 加鹽哈希密碼

    public static String sha512(String rawPassword, byte[] salt) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-512");
            // 加點鹽
            md.update(salt);
            return Hex.encodeHexString(md.digest(rawPassword.getBytes(StandardCharsets.UTF_8)));
        } catch (GeneralSecurityException ex) {
            throw new IllegalStateException("Could not create hash", ex);
        }
    }

PBKDF2

PBKDF1PBKDF2是一個密鑰派生函數(shù),其作用就是根據(jù)指定的密碼短語生成加密密鑰僻族。之前在 常見加密算法 提到過粘驰。它雖然不是加密哈希函數(shù),但它仍然適用密碼存儲場景述么,因為它有足夠的安全性蝌数,PBKDF2 函數(shù)計算如下:
DK = PBKDF2(PRF, Password, Salt, Iterations, HashWidth)

  • PRF 是偽隨機函數(shù)兩個參數(shù),輸出固定的長度(例如度秘,HMAC)顶伞;
  • Password 是生成派生密鑰的主密碼;
  • Salt 是加密鹽剑梳;
  • Iterations 是迭代次數(shù)枝哄,次數(shù)越多;
  • HashWidth 是派生密鑰的長度阻荒;
  • DK 是生成的派生密鑰。

PRF(HMAC)大致迭代過程众羡,第一次時將 Password 作為密鑰和Salt傳入侨赡,然后再將輸出結(jié)果作為輸入重復完成后面迭代。

PBKDF2

HMAC:基于哈希的消息認證碼粱侣,可以使用共享密鑰提供身份驗證羊壹。比如HMAC-SHA256,輸入需要認證的消息密鑰進行計算齐婴,然后輸出sha256的哈希值油猫。

PBKDF2不同于MD和SHA哈希函數(shù),它通過增加迭代次數(shù)提升了破解難度柠偶,并且還可以根據(jù)情況進行配置情妖,這使得它具有滑動計算成本。

對于MD5和SHA诱担,攻擊者每秒可以猜測數(shù)十億個密碼毡证。而使用 PBKDF2,攻擊者每秒只能進行幾千次猜測(或更少蔫仙,取決于配置)料睛,所以它適用于抗擊暴力攻擊。

2021 年,OWASP 建議對 PBKDF2-HMAC-SHA256 使用 310000 次迭代恤煞,對 PBKDF2-HMAC-SHA512 使用 120000 次迭代

    public static String pbkdf2Encode(String rawPassword, byte[] salt) {
        try {
            int iterations = 310000;
            int hashWidth = 256;
            PBEKeySpec spec = new PBEKeySpec(rawPassword.toCharArray(), salt, iterations, hashWidth);
            SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            return Base64.getEncoder().encodeToString(skf.generateSecret(spec).getEncoded());
        } catch (GeneralSecurityException ex) {
            throw new IllegalStateException("Could not create hash", ex);
        }
    }

Bcrypt

簡介

bcrypt 是基于 eksblowfish 算法設計的加密哈希函數(shù)屎勘,它最大的特點是:可以動態(tài)調(diào)整工作因子(迭代次數(shù))來調(diào)整計算速度,因此就算以后計算機能力不斷增加居扒,它仍然可以抵抗暴力攻擊概漱。

關(guān)于eksblowfish算法,它是采用分組加密模式苔货,并且支持動態(tài)設定密鑰計算成本(迭代次數(shù))犀概。算法的詳細介紹可查看下面文章:

https://www.usenix.org/legacy/publications/library/proceedings/usenix99/full_papers/provos/provos_html/node4.html

結(jié)構(gòu)

bcrypt 函數(shù)輸入的密碼字符串不超過 72 個字節(jié)、包含算法標識符夜惭、一個計算成本和一個 16 字節(jié)(128 位)的鹽值姻灶。通過輸入計算得到 24字節(jié)(192位)哈希,最終輸出格式如下:

$2a$12$DQoa2eT/aXFPgIoGwfllHuj4wEA3F71WWT7E/Trez331HGDUSRvXi
\__/\/ \____________________/\_____________________________/
Alg Cost      Salt                        Hash
  • $2a$: bcrypt 算法標識符或叫版本诈茧;
  • 12: 工作因子 (2^12 表示 4096 次迭代)
  • DQoa2eT/aXFPgIoGwfllHu: base64 的鹽值产喉;
  • j4wEA3F71WWT7E/Trez331HGDUSRvXi: 計算后的 Base64 哈希值(24 字節(jié))。

bcrypt 版本

  • $2a$: 規(guī)定哈希字符串必須是 UTF-8 編碼敢会,必須包含空終止符曾沈。
  • $2y$: 該版本為修復 2011年6月 PHP 在 bcrypt 實現(xiàn)中的一個錯誤。
  • $2b$: 該版本為修復 2014年2月 OpenBSD 在 bcrypt 實現(xiàn)中的一個錯誤鸥昏。

2014年2月 在 OpenBSD 的 bcrypt 實現(xiàn)中發(fā)現(xiàn)塞俱,它使用一個無符號的 8 位值來保存密碼的長度。對于長度超過255字節(jié)的密碼吏垮,密碼將在72或長度模256 中的較小者處被截斷障涯,而不是被截斷為72字節(jié)。例如:260 字節(jié)的密碼將被截斷為4個字節(jié)膳汪,而不是截斷為 72 個字節(jié)唯蝶。

實踐

bcrypt 關(guān)鍵在于設定合適的工作因子,理想的工作因子沒有特定的法則遗嗽,主要還是取決于服務器的性能和應用程序上的用戶數(shù)量粘我,一般在安全性應用性能之間權(quán)衡設定。

假如你的因子設置比較高痹换,雖然可以保證攻擊者難以破解哈希征字,但是登錄驗證也會變慢,嚴重影響用戶體驗晴音,而且也可能被攻擊者通過大量登錄嘗試耗盡服務器的 CPU 來執(zhí)行拒絕服務攻擊柔纵。一般來說計算哈希的時間不應該超過一秒。

我們使用 spring security BCryptPasswordEncoder 看下不同因子下產(chǎn)生哈希的時間锤躁,我電腦配置如下:

處理器:2.2 GHz 四核Intel Core i7
內(nèi)存:16 GB 1600 MHz DDR3
顯卡:Intel Iris Pro 1536 MB

Map<Integer, BCryptPasswordEncoder> encoderMap = new LinkedHashMap<>();
        for (int i = 8; i <= 21; i++) {
            encoderMap.put(i, new BCryptPasswordEncoder(i));
        }
        String plainTextPassword = "huhdfJ*!4";
        for (int i : encoderMap.keySet()) {
            BCryptPasswordEncoder encoder = encoderMap.get(i);
            long start = System.currentTimeMillis();
            encoder.encode(plainTextPassword);
            long end = System.currentTimeMillis();
            System.out.println(String.format("bcrypt | cost: %d, time : %dms", i, end - start));
        }
bcrypt | cost: 8, time : 39ms
bcrypt | cost: 9, time : 45ms
bcrypt | cost: 10, time : 89ms
bcrypt | cost: 11, time : 195ms
bcrypt | cost: 12, time : 376ms
bcrypt | cost: 13, time : 720ms
bcrypt | cost: 14, time : 1430ms
bcrypt | cost: 15, time : 2809ms
bcrypt | cost: 16, time : 5351ms
bcrypt | cost: 17, time : 10737ms
bcrypt | cost: 18, time : 21417ms
bcrypt | cost: 19, time : 43789ms
bcrypt | cost: 20, time : 88723ms
bcrypt | cost: 21, time : 176704ms

擬合得到以下公式:
10.3064 \cdot e^{0.696464 x}

bcrypt

BCryptPasswordEncoder 因子范圍在 4-31 搁料,默認是 10或详,我們根據(jù)公式推導一下 31時需要多長時間。

    /**
     * @param strength the log rounds to use, between 4 and 31
     */
    public BCryptPasswordEncoder(int strength) {
        this(strength, null);
    }

10.3064 \cdot e^{0.696464(31)} = 24529665567.08815

工作因子 31 時大概需要 284 天郭计,所以我們知道使用 bcrypt 可以很容易的擴展哈希計算過程以適應更快的硬件霸琴,為我們留出很大的回旋余地,以防止攻擊者從未來的技術(shù)改進中受益昭伸。

SCrypt

SCrypt 比上面提到的算法出來較晚梧乘,是Colin Percival于 2009 年 3 月創(chuàng)建的基于密碼的密鑰派生函數(shù)。關(guān)于該算法我們需要明白下面兩點:

  • 該算法專門設計用于通過需要大量內(nèi)存來執(zhí)行大規(guī)模自定義硬件攻擊庐杨,成本高昂选调。
  • 它屬于密鑰派生函數(shù)和上面提到 PBKDF2 屬于同一類別。

Spring security 也實現(xiàn)該算法 SCryptPasswordEncoder 灵份,輸入?yún)?shù)如下:

  • CpuCost: 算法的 cpu 成本仁堪。 必須是大于 1 的 2 的冪。默認當前為 16,384 或 2^14)
  • MemoryCost: 算法的內(nèi)存成本填渠。默認當前為 8弦聂。
  • Parallelization: 算法的并行化當前默認為 1。請注意氛什,該實現(xiàn)當前不利用并行化莺葫。
  • KeyLength: 算法的密鑰長度。 當前默認值為 32枪眉。
  • SaltLength: 鹽長度捺檬。 當前默認值為 64。

不過也有人提到贸铜,并不建議在生產(chǎn)系統(tǒng)中使用它來存儲密碼欺冀,他的結(jié)論是首先 SCrypt 設計目的是密鑰派生函數(shù)而不是加密哈希,另外它實現(xiàn)上也并不那么完美萨脑。詳細可查看下面文章。

https://blog.ircmaxell.com/2014/03/why-i-dont-recommend-scrypt.html

結(jié)論

我會推薦使用 bcrypt饺饭。為什么是 bcrypt 呢渤早?

密碼存儲這種場景下,將密碼哈希處理是最好的方式瘫俊,第一它本身就是加密哈希函數(shù)鹊杖,其次按照摩爾定律的定義,集成系統(tǒng)上每平方英寸的晶體管數(shù)量大約每 18 個月翻一番扛芽。在 2 年內(nèi)骂蓖,我們可以增加它的工作因子以適應任何變化。

當然這并不是說其它算法不夠安全川尖,你仍然可以選擇其它算法登下。建議優(yōu)先使用 bcrypt,其次是密鑰派生類(PBKDF2 和 SCrypt),最后是哈希+鹽(SHA256(salt))被芳。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缰贝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子畔濒,更是在濱河造成了極大的恐慌剩晴,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侵状,死亡現(xiàn)場離奇詭異赞弥,居然都是意外死亡,警方通過查閱死者的電腦和手機趣兄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門绽左,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诽俯,你說我怎么就攤上這事妇菱。” “怎么了暴区?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵闯团,是天一觀的道長。 經(jīng)常有香客問我仙粱,道長房交,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任伐割,我火速辦了婚禮候味,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘隔心。我一直安慰自己白群,他們只是感情好,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布硬霍。 她就那樣靜靜地躺著帜慢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪唯卖。 梳的紋絲不亂的頭發(fā)上粱玲,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音拜轨,去河邊找鬼抽减。 笑死,一個胖子當著我的面吹牛橄碾,可吹牛的內(nèi)容都是我干的卵沉。 我是一名探鬼主播颠锉,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼偎箫!你這毒婦竟也來了木柬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤淹办,失蹤者是張志新(化名)和其女友劉穎眉枕,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怜森,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡速挑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了副硅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姥宝。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖恐疲,靈堂內(nèi)的尸體忽然破棺而出腊满,到底是詐尸還是另有隱情,我是刑警寧澤培己,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布碳蛋,位于F島的核電站,受9級特大地震影響省咨,放射性物質(zhì)發(fā)生泄漏肃弟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一零蓉、第九天 我趴在偏房一處隱蔽的房頂上張望笤受。 院中可真熱鬧,春花似錦敌蜂、人聲如沸箩兽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽比肄。三九已至,卻和暖如春囊陡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背掀亥。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工撞反, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人搪花。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓遏片,卻偏偏與公主長得像嘹害,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吮便,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容