如何生成一個足夠安全又容易記住的密碼?

2023-07-04 09:01:25

什麼是密碼強度?

密碼強度是指破解一個密碼需要嘗試的次數,其本質是密碼所包含資訊熵的大小。所以通常不是使用十進位制數位來表示,而是用這個數位以 2 為底的對數來表示,也可以理解為把這個數位轉換成二進位制的位數。

例如使用 6 位數位作為密碼,暴力破解最多需要嘗試 10^6 次。他的密碼強度就是 log2(10^6) = 23.25

AES-128/192/256 的金鑰長度分別是 128/192/256 位,因為破解 AES 演演算法除了把每種可能都嘗試一遍之外沒有更好的辦法了,需要嘗試的次數分別是 2^128/2^192/2^256 次,所以他們的密碼強度就是 128/192/256 位。

因為密碼強度是使用二進位制位數來表示的,所以密碼強度每增加一位,意味著二進位制數增加了一位,其破解的工作量就要翻倍了。

破解一個密碼並不需要嘗試到最後一種可能,平均來說,應該嘗試一半的密碼就可以了。所以實際破解密碼所需的平均工作量,是比密碼強度少一位的。

密碼強度就是破解難度嗎?

銀行卡密碼只有 6 位數位,而網站的密碼通常至少要有 8 位的字母、數位和符號。不用計算都知道前者密碼強度一定比後者低很多,但不會有人覺得銀行卡密碼不安全。

這是因為密碼強度只表示了要破解一個密碼需要嘗試的次數,還有一個重要的因素是每次嘗試所需的時間。兩者相乘才是預計破解一個密碼所需的時間。

銀行卡密碼雖然只有 6 位數位,但通常每天輸錯三次就鎖定了。假設銀行沒有別的風控手段,每天都可以試三次,那麼 6 位數位的密碼平均需要 10^6 / 2 / 3 / 365 = 456 年才試出來。等到這時候不知道銀行還在不在,反正我是不在了。

所以可以看出,像這種線上系統,更多是在嘗試時間上做文章,也就是系統本身的安全策略比密碼強度更重要。大多數網站也是這樣,可以進行控制的手段有很多,例如輸錯多少次就要鎖定一段時間,甚至每次鎖定的時間成指數變長;通過圖形驗證碼防止機器暴力破解,來降低嘗試的速度;結合簡訊驗證碼等其他驗證手段等。

除了線上系統外,還有一些離線的資料。例如一個加密的壓縮檔案,或者 OpenPGP、SSH 等各種金鑰。這些資料可以被人擷取之後離線攻擊,攻擊者可不會每天試三個密碼就停下來了。這時就需要通過一些演演算法來延緩攻擊者嘗試密碼的速度了,這就輪到我們的 KDF(金鑰派生函數) 登場了。

理論上我們也可以開發出一個演演算法,你輸入一個密碼,他要計算 8 個小時才能給出結果,同樣可以達到一天只能試三個密碼的效果,這樣按照上面的計算,同樣使用 6 為數位的密碼就可以保護四百多年了。但這裡存在幾個問題:

第一是演演算法可不分人,攻擊者試一個密碼要 8 小時,你也一樣,你能忍受輸入一個密碼睡一覺起來才能看到結果嗎?

第二個問題是晶片的效能是不斷增長的。銀行可以保證一百年後也一天只讓你試三個密碼(如果那時候還有密碼的話)。但根據摩爾定律,今天需要計算 8 小時的演演算法可能 18 個月後就只需要 4 個小時了。

第三個問題是,KDF 是有上限的。KDF 是基於你輸入的密碼派生出那個真正用來加密的金鑰。如果它真的慢到了一定程度,那攻擊者不如直接去嘗試派生出來的金鑰算了。

所以 KDF 只能把每次嘗試密碼延緩到一個合理的範圍,剩下的就要靠密碼強度了。

我們需要多強的密碼?

沒有永遠安全的密碼,隨著新技術的誕生、算力的提升,無論多強的密碼都會被破解。所以要想知道需要多強的密碼,首先需要想一下你希望密碼在能在多長時間內不被人破解?假設這個值是 y 年。

剛才提到破解一個密碼所需的時間還和嘗試一個密碼所需的時間相關,這通常可以估算或者實際測試出來。例如 KeePass 可以使用 Argon2d 演演算法,根據你的裝置設定引數,使嘗試一個密碼所需的時間大概為你所設定的時間。假設這個值是 t 秒。

根據這些資訊,我們能算出來在這些年內一共可以嘗試 (365 * 24 * 60 * 60) / t * y 個密碼。根據密碼強度的定義,所需的密碼強度為:

Math.log2((365 * 24 * 60 * 60) / t * y) + 1

在某些場景下,這個公式就夠了。但很多時候並不是這樣的,因為這個公式沒有考慮:算力是不斷提升的;攻擊者可以離線破解。

隨著算力的提升,每年能嘗試的密碼次數也將不斷提升。假設算力每年提升 n 倍,那麼每年能嘗試的密碼個數將是一個等比數列,在 y 年之內一共能嘗試的密碼次數就是一個等比數列求和問題了:(365 * 24 * 60 * 60) / t * (1 - n^y) / (1 - n)

算力通常都是用多少個月翻一番來表示,假設 m 個月翻一番,則 n^(m / 12) = 2,所以 n = 2^(12 / m)

在你的裝置上需要 1 秒鐘做到的事情,在攻擊者的裝置上可不是。攻擊者的裝置通常效能會更好,甚至可能是專用的硬體。我們可以用一個倍數來表示,假設攻擊者裝置的效能是我們的 x 倍,那麼這些年能嘗試的個數也將是 x 倍:(365 * 24 * 60 * 60) / t * (1 - n^y) / (1 - n) * x

這個倍數更多是一個成本的問題,也就是你認為攻擊者願意花多大代價來破解你的密碼。可以這樣假設,你認為攻擊者願意每年花一百萬來破解你的密碼,而你當前的裝置價值一萬元,那麼可以簡單的認為攻擊者的算力就是你的一百萬倍。這樣估算很粗暴,但對最終的結果不會產生太大的影響。我覺得攻擊者有一百萬,與其花在破解我的密碼上,不如去挖礦更好一點。或者可以試一下每年花一百萬來問我,我可能一秒就可以告訴他。

注意這個成本是每年,因為我們的公式還考慮到了算力是每年提升的,而攻擊者如果想利用算力每年提升的成果,就需要每年更新裝置才行。

如果攻擊者不能離線攻擊的話,只用第一個公式就夠了。因為不能離線攻擊,意味著每次密碼的驗證都要在你的裝置上發生,那麼無論攻擊者的裝置有多強都用不上了。而且不能離線攻擊,只要你不換裝置,摩爾定律對你就不生效。

最終的公式大概是這樣:

const n = 2 ** (12 / m)
Math.log2((365 * 24 * 60 * 60) / t * (1 - n ** y) / (1 - n) * x) + 1

例如使用如下引數計算可以得到大概需要 66.61 位的密碼強度。

t = 1, y = 30, m = 18, x = 1000000

如何生成一個這樣的密碼?

如果只使用 26 個字母作為密碼,4 位的密碼強度大概是 log2(26^4) = 18.80 位。那麼如果我使用 mima 作為密碼,他的強度是否有 18.80 位呢?

我們在最開始時就提到了,密碼強度的本質是資訊熵。4 位的密碼之所以有 18.80 位的強度,是因為他有 26^4 種可能,攻擊者平均需要做 26^4 / 2 次嘗試。如果你使用的是漢字拼音作為密碼,而攻擊者也知道了這一點,他就只會使用漢字拼音來嘗試,兩個拼音總共有 413^2 種可能,所以這個密碼的實際強度為 log2(412^2) = 17.38。如果更進一步他知道你只使用了包含了兩個字母的拼音,那麼密碼強度就只剩下 log2(79^2) = 12.61 了。

密碼學有個原則叫柯克霍夫原則,是說你必須假設攻擊者對你的系統完全瞭解,只是不知道你的金鑰。所以你看我們常用的加解密演演算法全都是公開的、廣為流傳的、久經考驗的,這根本就不是祕密,保密的只有金鑰或者非對稱加密的私鑰。你不能說靠腦洞,攻擊者肯定不知道我用的是拼音、雙拼、五筆編碼、古詩詞、名字、生日、地名等等。攻擊者可能就是會收集和你相關的資訊,把它們做成字典來破解你的密碼,這很容易做到。

所以計算一個密碼的強度,不能單純的看他所包含的字元數量。更合理的是應該看你用於組成密碼的最小單元(我們把他叫做元素吧)的數量。

例如你使用 5 個漢字的拼音來組成密碼,那麼它的密碼強度就是 log2(413^5) = 43.45。無論這五個拼音包含的字元數是 10 個還是 30 個,它的強度都是 43.45 位。每個元素所能提供的密碼強度為 log2(413) = 8.69

用於生成密碼的元素越多,單個元素所能提供的熵就越高,生成同等安全強度的密碼所需的元素數量就越少,也就越容易記憶。但元素多到一定程度,可能記住每個元素的編碼就成問題了。

平均每個元素所包含的字元越少,單位字元的熵就越高,生成的密碼就越短,越容易輸入。但這是有極限的,例如元素都是用小寫字母來編碼的,那麼單位字元所能提供的熵極限也就是 log2(26) = 4.70 了。這就意味著已經把鍵盤的所有排列組合都利用到了。

另外要保證這個密碼的強度真的是 43.45,還有個因素很重要,就是你選取的元素必須是足夠隨機的。如果你不願意記太長的密碼,只選或者傾向於選兩個字母的,那你的密碼長度還是隻有或略高於 log2(79^5) = 31.52

如果交給人主觀去想,那麼生成的密碼肯定是不夠隨機的。所以有一種專門用於生成密碼的方法叫 Diceware。大概步驟是提前準備好了一個包含了 6^5 個單詞的列表,給每個單詞編個號。然後扔五次骰子得到一個編號,去表裡找到這個編號對應的單詞。重複這個過程,找到多個單片語成一個密碼。每個單詞大概可以提供 log2(6^5) = 12.92 位的強度,六個單詞大概是 77.52。

我們可以對比一下容易想到的各種元素集合:

元素集合 包含元素個數 每個元素能提供的熵 每個元素平均包含的字元數 平均每個字元提供的熵
Diceware 單詞列表 7776 12.92 4.24 3.04
拼音 413 8.69 2.4 3.62
雙拼 同上 同上 2 4.35
五筆 6763 12.72 4 3.18
五筆二級簡碼 589 9.20 2 4.60
小寫字母 26 4.70 1 4.70
ASCII 可列印字元 95 6.57 1 6.57

可以看到五筆二級簡碼的熵基本已經接近 4.70 了,說明二級簡碼基本已經把兩個按鍵的組合覆蓋完了。差不多是 Diceware 單詞列表的 1.5 倍,也就是用 Diceware 生成的 30 個字元的密碼和五筆二級簡碼生成的 20 個字元的密碼強度是差不多的。ASCII 可列印字元的熵最高,但這個沒有參考意義,因為很多符號需要配合 Shift 鍵使用,輸入效率不一定更高。

結合這個表格,選一個適合自己的元素集合。然後根據前面計算出需要的密碼強度,就可以知道需要多少個元素組成密碼了,接下來就是扔骰子或者使用亂數等方式,選出這麼多個元素就可以了。