古時候的人誤以為資料庫很安全, 所以一開始密碼是放明碼.
後來的人意識到資料庫可能會把搬走, 所以密碼欄位內容改放 md5 hash 過的的值, 使用 md5 的好處是, 比對時可以是在資料庫的 sql 指令做比對.
md5 的好處是雖然比較難還原使用的一開始輸入的明碼, 資料被搬走後, 還是可以透過爆力的字串比對來取得使用者的明碼.
今天看到 tornado project 裡附的 blog 的 demo source code, 發現一個有趣的套件 bcrypt:
https://github.com/pyca/bcrypt/
實際使用bcrypt後, 缺點就是無法在資料庫裡做比對, 要先把 hash 過的字串, 拿出來在程式語言裡比對, 而且很酷的是, 使用同一個密碼, 每次產生出來的字串都長的不一樣, 當然這也是一個優點, 調整一下程式邏輯的順序就可以切換到 bcrypt 的比對了.
tornado 的 blog 的 demo source code:
https://github.com/tornadoweb/tornado/tree/stable/demos/blog
bcrypt 使用範例:
Usage
Password Hashing
Hashing and then later checking that a password matches the previous hashed password is very simple:
>>> import bcrypt >>> password = b"super secret password" >>> # Hash a password for the first time, with a randomly-generated salt >>> hashed = bcrypt.hashpw(password, bcrypt.gensalt()) >>> # Check that an unhashed password matches one that has previously been >>> # hashed >>> if bcrypt.checkpw(password, hashed): ... print("It Matches!") ... else: ... print("It Does not Match :(")
tornado blog 使用的 “產生” hash 的 code.
hashed_password = await tornado.ioloop.IOLoop.current().run_in_executor(
None,
bcrypt.hashpw,
tornado.escape.utf8(self.get_argument("password")),
bcrypt.gensalt(),
)
tornado blog 使用的 “比對” 的 code.
password_equal = await tornado.ioloop.IOLoop.current().run_in_executor(
None,
bcrypt.checkpw,
tornado.escape.utf8(self.get_argument("password")),
tornado.escape.utf8(author.hashed_password),
)
說明: 比起 checkpw, haskpw 比較不會有問題, checkpw 的問題在比對的時候的是在 webserver 上比對, 現在大多數的 tornodo code 都寫成 async, 遇到要同步處理時就要 await, 但在 sqlite 中, 使用到 await 會掛掉, 錯誤訊息是:
# TypeError: object sqlite3.Cursor can't be used in 'await' expression
這些都還很容易處理, 獨立寫一個 get_hashed_password_by_account(user_account) 的 function 就可以了, 這個 function 等同於 check_account_exist(), 只差在會不會把 hashed password 回傳.