跳到主要內容

不要使用字串實體處理 Hash 結果

去年幫一台 server 從 JDK6 升上 JDK 8 後,發現那台 server 的多數 hash 比對失效了
查了原因,發現問題出在那台的服務使用的一段處理 hash 的 legacy code
以 Java 為例,產生 hash 方式是這樣

MessageDigest digest = MessageDigest.getInstance("SHA-512");
byte[] digest = digest.digest(text.getBytes(StandardCharsets.UTF_8));


* 實務上用在密碼比對時需要加鹽(Salt)

那台Server將摘要(digest)直接用new String(byte[] bytes, Charset charset)轉字串再存入DB
但是不同的 Java 版本,即使傳入相同的 hash 跟相同的語系碼很可能產生不同的結果
摘要並不是以產出有意義的文字為目的,所以千萬不能這麼做

文字接收 byte[] 產出文字的過程中,會有錯誤字元的忽略機制
以及他會有多種變體的判斷過程,允許用不同的 byte[] 表達同一個字
Unicode 本身是一個持續演進的規格,像是近期加入的 Emoji 便是一例
在內容增加後,原先被判定成錯誤而忽略、或是變體一部份的內容也可能是不同的判定結果
也因為這些機制,要從最後結果找回原本傳入的 byte[] 是不可能的

特別是密碼比對的流程,我們需要的是一個能夠穩定的比對摘要的機制
如果資料庫需要以字串類型儲存結果
合適的做法是將 digest 轉成不會忽略任何內容或變更判定結果的 hex 字串 / base 64 字串

至於已經存到資料庫的hash結果要怎麼辦呢?
你有沒有聽過一個鄉野奇談呢?  (純傳言不代表實際處理或任何推薦XD)

留言