今天非常尷尬,因?yàn)闆]有學(xué)過密碼學(xué),而且基于以前用nodejs寫后臺(tái)和與后臺(tái)對(duì)接的經(jīng)驗(yàn)上,單純的認(rèn)為后臺(tái)或者數(shù)據(jù)庫管理員是可以拿到我們的用戶名和密碼的,那么我們的賬戶其實(shí)是出于一個(gè)很不安全的狀態(tài)。
就算是對(duì)密碼做了加密也會(huì)用反解密的方式獲得密碼啊。
正好,今天我的wiki密碼忘記了,想叫運(yùn)維給我改個(gè)密碼,然后就聊到了這個(gè)話題,(改密碼是操作,是另一個(gè)問題,這是可以操作的)。
我拿到數(shù)據(jù)庫中的賬號(hào)密碼就能直接通過某種方式獲得明文密碼了嗎?
在使用nodejs的時(shí)候,我經(jīng)常會(huì)用MD5來對(duì)數(shù)據(jù)進(jìn)行加鹽。
今天的話題就從加鹽開始了,什么是加鹽呢?
下面引用了一篇文章來講解加鹽,也算一次科普。
鹽(Salt)
在密碼學(xué)中,是指通過在密碼任意固定位置插入特定的字符串,讓散列后的結(jié)果和使用原始密碼的散列結(jié)果不相符,這種過程稱之為“加鹽”。
第一代密碼
早期的軟件系統(tǒng)或者互聯(lián)網(wǎng)應(yīng)用,數(shù)據(jù)庫中設(shè)計(jì)用戶表的時(shí)候,大致是這樣的結(jié)構(gòu):
mysql> desc User;
+----------+--------------+------+-----+---------+-------+
| Field? ? | Type? ? ? ? | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| UserName | varchar(50)? | NO? |? ? |? ? ? ? |? ? ? |
| PassWord | varchar(150) | NO? |? ? |? ? ? ? |? ? ? |
+----------+--------------+------+-----+---------+-------+
數(shù)據(jù)存儲(chǔ)形式如下:
mysql> select * from User;
+----------+----------+
| UserName | PassWord |
+----------+----------+
| lichao? | 123? ? ? |
| akasuna? | 456? ? ? |
+----------+----------+
主要的關(guān)鍵字段就是這么兩個(gè),一個(gè)是登陸時(shí)的用戶名,對(duì)應(yīng)的一個(gè)密碼,而且那個(gè)時(shí)候的用戶名是明文存儲(chǔ)的,如果你登陸時(shí)用戶名是 123,那么數(shù)據(jù)庫里存的就是 123。這種設(shè)計(jì)思路非常簡單,但是缺陷也非常明顯,數(shù)據(jù)庫一旦泄露,那么所有用戶名和密碼都會(huì)泄露,后果非常嚴(yán)重。參見《CSDN 詳解 600 萬用戶密碼泄露始末》。
第二代密碼
為了規(guī)避第一代密碼設(shè)計(jì)的缺陷,聰明的人在數(shù)據(jù)庫中不在存儲(chǔ)明文密碼,轉(zhuǎn)而存儲(chǔ)加密后的密碼,典型的加密算法是 MD5 和 SHA1,其數(shù)據(jù)表大致是這樣設(shè)計(jì)的:
mysql> desc User;
+----------+--------------+------+-----+---------+-------+
| Field? ? | Type? ? ? ? | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| UserName | varchar(50)? | NO? |? ? |? ? ? ? |? ? ? |
| PwdHash? | char(32)? ? | NO? |? ? |? ? ? ? |? ? ? |
+----------+--------------+------+-----+---------+-------+
數(shù)據(jù)存儲(chǔ)形式如下:
mysql> select * from User;
+----------+----------------------------------+
| UserName | PwdHash? ? ? ? ? ? ? ? ? ? ? ? ? |
+----------+----------------------------------+
| lichao? | 202cb962ac59075b964b07152d234b70 |
| akasuna? | 250cf8b51c773f3f8dc8b4be867a9a02 |
+----------+----------------------------------+
假如你設(shè)置的密碼是 123,那么數(shù)據(jù)庫中存儲(chǔ)的就是 202cb962ac59075b964b07152d234b70 或 40bd001563085fc35165329ea1ff5c5ecbdbbeef。當(dāng)用戶登陸的時(shí)候,會(huì)把用戶輸入的密碼執(zhí)行 MD5(或者 SHA1)后再和數(shù)據(jù)庫就行對(duì)比,判斷用戶身份是否合法,這種加密算法稱為散列。
嚴(yán)格地說,這種算法不能算是加密,因?yàn)槔碚撋蟻碚f,它不能被解密。所以即使數(shù)據(jù)庫丟失了,但是由于數(shù)據(jù)庫里的密碼都是密文,根本無法判斷用戶的原始密碼,所以后果也不算太嚴(yán)重。
第三代密碼
本來第二代密碼設(shè)計(jì)方法已經(jīng)很不錯(cuò)了,只要你密碼設(shè)置得稍微復(fù)雜一點(diǎn),就幾乎沒有被破解的可能性。但是如果你的密碼設(shè)置得不夠復(fù)雜,被破解出來的可能性還是比較大的。
好事者收集常用的密碼,然后對(duì)他們執(zhí)行 MD5 或者 SHA1,然后做成一個(gè)數(shù)據(jù)量非常龐大的數(shù)據(jù)字典,然后對(duì)泄露的數(shù)據(jù)庫中的密碼就行對(duì)比,如果你的原始密碼很不幸的被包含在這個(gè)數(shù)據(jù)字典中,那么花不了多長時(shí)間就能把你的原始密碼匹配出來。這個(gè)數(shù)據(jù)字典很容易收集,CSDN 泄露的那 600w 個(gè)密碼,就是很好的原始素材。
于是,第三代密碼設(shè)計(jì)方法誕生,用戶表中多了一個(gè)字段:
mysql> desc User;
+----------+-------------+------+-----+---------+-------+
| Field? ? | Type? ? ? ? | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| UserName | varchar(50) | NO? |? ? |? ? ? ? |? ? ? |
| Salt? ? | char(50)? ? | NO? |? ? |? ? ? ? |? ? ? |
| PwdHash? | char(32)? ? | NO? |? ? |? ? ? ? |? ? ? |
+----------+-------------+------+-----+---------+-------+
數(shù)據(jù)存儲(chǔ)形式如下:
mysql> select * from User;
+----------+----------------------------+----------------------------------+
| UserName | Salt? ? ? ? ? ? ? ? ? ? ? | PwdHash? ? ? ? ? ? ? ? ? ? ? ? ? |
+----------+----------------------------+----------------------------------+
| lichao? | 1ck12b13k1jmjxrg1h0129h2lj | 6c22ef52be70e11b6f3bcf0f672c96ce |
| akasuna? | 1h029kh2lj11jmjxrg13k1c12b | 7128f587d88d6686974d6ef57c193628 |
+----------+----------------------------+----------------------------------+
Salt 可以是任意字母、數(shù)字、或是字母或數(shù)字的組合,但必須是隨機(jī)產(chǎn)生的,每個(gè)用戶的 Salt 都不一樣,用戶注冊(cè)的時(shí)候,數(shù)據(jù)庫中存入的不是明文密碼,也不是簡單的對(duì)明文密碼進(jìn)行散列,而是 MD5( 明文密碼 + Salt),也就是說:
MD5('123' + '1ck12b13k1jmjxrg1h0129h2lj') = '6c22ef52be70e11b6f3bcf0f672c96ce'
MD5('456' + '1h029kh2lj11jmjxrg13k1c12b') = '7128f587d88d6686974d6ef57c193628'
當(dāng)用戶登陸的時(shí)候,同樣用這種算法就行驗(yàn)證。
由于加了 Salt,即便數(shù)據(jù)庫泄露了,但是由于密碼都是加了 Salt 之后的散列,壞人們的數(shù)據(jù)字典已經(jīng)無法直接匹配,明文密碼被破解出來的概率也大大降低。
是不是加了 Salt 之后就絕對(duì)安全了呢?淡然沒有!壞人們還是可以他們數(shù)據(jù)字典中的密碼,加上我們泄露數(shù)據(jù)庫中的 Salt,然后散列,然后再匹配。但是由于我們的 Salt 是隨機(jī)產(chǎn)生的,假如我們的用戶數(shù)據(jù)表中有 30w 條數(shù)據(jù),數(shù)據(jù)字典中有 600w 條數(shù)據(jù),壞人們?nèi)绻胍耆采w的壞,他們加上 Salt 后再散列的數(shù)據(jù)字典數(shù)據(jù)量就應(yīng)該是 300000* 6000000 = 1800000000000,一萬八千億啊,干壞事的成本太高了吧。但是如果只是想破解某個(gè)用戶的密碼的話,只需為這 600w 條數(shù)據(jù)加上 Salt,然后散列匹配??梢?Salt 雖然大大提高了安全系數(shù),但也并非絕對(duì)安全。
實(shí)際項(xiàng)目中,Salt 不一定要加在最前面或最后面,也可以插在中間嘛,也可以分開插入,也可以倒序,程序設(shè)計(jì)時(shí)可以靈活調(diào)整,都可以使破解的難度指數(shù)級(jí)增長。