Nic Lin's Blog

喜歡在地上滾的工程師

Rails 5 簡單雙向加解密

在 Rails 中常用的 devise, 對於密碼的加密是單向加密,也就是不可直接回推原本密碼,屬於安全層級較高的算法。

舉例來說,有一種場景是,我希望加密用戶的 One time password 驗證密鑰,加密的原因很簡單,我希望如果哪天不小心 database 被挖走資料,也無法直接算出一次性密碼是多少,確保用戶的二步驗證是絕對安全。

想法:

「One time password secret」 -> 透過 application 層的算法加密 -> 確保存入 DB 存入的是密文 -> 顯示層可以透過算法回推真正的 secret 顯示

這樣一來,如果只拿走 database 中的密文,沒有 application 的算法以及鑰匙,是難以回推真正的明文,提高安全性。

在 Rails 5 中,可以使用 MessageEncryptor,這樣就不需要仰賴其他的 gem 或自己寫加密算法,算是符合需求了。

# 直接拿 Devise key 來用,如果不想也可以再建立一個
crypt = ActiveSupport::MessageEncryptor.new(Devise.secret_key[0..31])

這裡的 0..31 是因為指定要 32 bytes, 如果不加的話會出現

ArgumentError: key must be 32 bytes

# 欲加密的部分
secret = "123456"

# 可以拿到單向加密後的密文
crypt.encrypt_and_sign(secret)

Output: c1Rzazl3WEp2U0JTbk9ZVlVheHhWQT09LS02bXJLYytkRzlMWTNyT3RCZ3FwKzlBPT0=--0b25cf9972e55f3c1bea41710e5726f0b10f59a9

# 密文反解密

encrypt_secret = "c1Rzazl3WEp2U0JTbk9ZVlVheHhWQT09LS02bXJLYytkRzlMWTNyT3RCZ3FwKzlBPT0=--0b25cf9972e55f3c1bea41710e5726f0b10f59a9"

crypt.decrypt_and_verify(encrypt_secret)

Output: 123456 (Original value)

這個簡單的雙向加密方式,只要中間的加密 key 沒有被拿到,應當是算不出原本的明文的,算是比較簡單方便的作法,適合用在比較不這麼高危險的資料場景中。

MessageEncryptor 中預設的加密算法是 aes-256-gcm (可替換別的加密方式,詳見官方文件 API

值得注意的是,每次加密出來的密文都不盡相同,這是因為加密方式使用了 OpenSSL,而該工具有預設的 IV(初始向量)搭配 salt 來阻止攻擊者推斷加密消息之後的各段關係,雖然 Ruby 中的 OpenSSL 應該有不需要初始向量的算法的方法,不過一旦使用了就要承擔更高的安全風險,這邊就不推薦了。

參考資源

comments powered by Disqus