django 自行新增使用者的作法以及為何密碼檔裡要加 salt

寫測試程式時需要先建個測試帳號, 為了確保任何人取得原始碼後能直接跑成功測試, 我在測試碼執行時檢查是否已建立測試帳號, 若沒有則自己建一個。

單純地新增 User 放入 username 和 password 是行不通的, 順著命令 createsuperuser 看原始碼, 發覺正確的寫法如下:
try:
    user = models.User.objects.get(username='test')
except models.User.DoesNotExist, e:
    print 'Test account doest not exist. Now create it.'
    user = models.User(username='test', is_active=True)
    user.set_password('testtest')
    user.save()

好奇之下看了一下 set_password 怎麼寫的 (./contrib/auth/models.py):
def set_password(self, raw_password):
    import random
    algo = 'sha1'
    salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
    hsh = get_hexdigest(algo, salt, raw_password)
    self.password = '%s$%s$%s' % (algo, salt, hsh)

可以看到 Django Auth 用 sha1 和 salt 產生加密後的字串。於是查了一下 salt 的功效, 這回才真的明白它的用處。

Wikipedia 上舉了幾個情境:
  • 在沒用 salt, 且用單字當密碼的情況, 一但密文被取得後, 攻擊者只要查事先建好的表即可馬上找出原始密碼。表的 key 是 sha1 加密的結果, value 是原碼。
  • 同上, 改用 Rainbow table 建一般性的表, 而不是單純用字典檔。題外話, Rainbow table 的概念滿有趣的, 用到機率和時間空間取捨的技巧。
所以, 若加密時有用 salt, 攻擊者得另外取得 salt 才能開始攻擊。即使 salt 也存在密文裡, 攻擊者也得照「規矩」來試密碼, 不能用事先建好的表。
若整份密碼檔用同一份 salt, 攻擊者可以在取得 salt 後開始建表, 之後整份密文就可以用同一份表查出結果。但是當每個使用者的 salt 都不同時, 攻擊者只好每個密文都全試一次, 無法建表加速破解。換句話說, 破解單一特定使用者密碼的時間一樣, 但用多份 salt 讓破解所有人密碼的時間大增。
當然, 若使用者的密碼較長, 或是有摻雜攻擊者建表時沒用到的字元 (如攻擊者只用字母和數字, 使用者的密碼有含符號), 那加不加 salt 也沒差。不過任何當過系統管理員的人都知道, 絕大多數使用者的密碼都是很簡單的..., 簡單地加個 salt 可以降低不少風險, 讓攻擊者要花更多時間取得明文, 發覺密碼檔外洩時, 可以減少損失。

留言

這個網誌中的熱門文章

(C/C++ ) 如何在 Linux 上使用自行編譯的第三方函式庫

熟悉系統工具好處多多

virtualbox 使用 USB 裝置