如果你的系統有加密敏感資料的需求,Laravel Encryptor 提供了一個非常友善的介面,同時也應用了足夠強度的演算法。這篇文章會一邊說明 Laravel Encrypter 加解密資料的過程,並佐上一些密碼學的筆記,若有解說不正確的部分,指出的話我會非常感謝!
實際應用 Encrypter
假設你需要在註冊時加密使用者的身份證字號,於是在 Application 層級做加密,儲存進 database 時是密文,但在後台編輯的時候需要看到明文,我們可以用 Laravel 提供的 Model Mutator 和 Accessor 來方便地實作。
// 若 DB 裡面的 field name 是 ssnpublic function setSsnAttribute($value)
{
$this->attributes[‘ssn’] = entrypt($value);
}public function getSsnAttribute($value)
{
return decrypt($value);
}
如此一來,使用者資料在存進資料庫前會先進行加密,而取得資料時,ssn field 會自己進行解密,然而,在這兩個 function 裡, Laravel 實際上做了什麼事情呢?
Encrypt 的實作步驟
1. 使用 AES-256-CBC 加密資料
Laravel 預設使用 AES-256-CBC
來加密資料(你也可以選擇用 AES-128)。
AES (Advanced Encryption Standard):進階加密標準
AES 是一種對稱金鑰加密演算法,用來取代早期的DES
,目前在運用在許多產品中,如 WiFi 加密的WPA2-AES
就是採用此演算法,AES-256
與AES-128
、AES-192
之間的差距在於金鑰的長度(單位 bits),而 Laravel 可以透過key:generate
來產出金鑰。對稱金鑰?不對稱金鑰?
在加密的演算法中,可以將這些演算法分為對稱金鑰(Symmetric Key)和非對稱金鑰(Asymmetric Key)兩種。
對稱金鑰的演算法中,參與傳輸的成員們會共同持有一個秘密的金鑰,用它來進行加密和解密。
非對稱金鑰的演算法中,金鑰分為 公鑰(Public Key) 與 私鑰(Private Key),這兩把金鑰不能擁有推導關係,你就算擁有其一也不能透過計算得到另一把,而加密與解密分別使用不同的金鑰。在這種演算法中,我們能夠將其中一種金鑰公開(通常是公鑰),因此大家都可以使用公鑰來解密,在做數位簽章(Digital Signature)時通常使用這種演算法。
2. 產生一個隨機的 iv
(Initialization vector)
Laravel 採用的 Block cipher operation 是 CBC (Cipher Block Chaining) 模式,在 Block cipher 中,我們需要一個隨機的初始化向量 iv 來將加密隨機化,因此同樣的明文經過了同樣的加密後,會有不同的密文,是增加安全性的方式。
Stream cipher 與 Block cipher 的比較
在加密演算法中,根據處理 input 的方式可以分為 Stream 和 Block cipher 兩種,Stream cipher 顧名思義,就像串流一樣一次加密一個 bit 或 byte,每次加密使用的金鑰也不同,所以也需要一個 key stream。
而 Block cipher 則是將 input 切割為數個 blocks ,像AES
的 block 是固定 128 bits,切割完後會對這些 Blocks 進行不同的工作模式(Block cipher operation),Laravel 採用的 CBC 就是其中一種。
至於何者比較好呢?目前網路上的對稱加密大多採用 Block cipher,Stream cipher 並非不好,而是實際上很難達到他的理想狀況,理想的 Stream cipher 除非拿到 Key stream 不然是無法破解的,但要達到理想必須符合兩個條件,首先產生的 keys 必須是完全隨機、不可預測的,第二是參與的成員們必須都拿到同樣的 keys⋯⋯在安全的狀況下,這使得 Stream cipher 的成本過高,而 Block cipher 在經過一些操作後,可以得到接近 Stream cipher 的加密效果,因此被大多數採用。
3. 將 iv 與 密文 進行 hash_hmac,產生 MAC
在這裡, Laravel 將 iv
和 密文
串接起來,然後丟進 hash_hmac
裡面產生 MAC。
MAC (Message authentication code):訊息鑑別碼
MAC 主要的用途是來檢驗資料的完整性,也就是資料的傳輸的過程中,是否有被修改過,因為 MAC 必須從資料產生,又必須不可逆,所以通常會透過 HMAC,也就是 hash-based MAC 來將資料進行 hash,Receiver 收到後只要將資料再做一次 HMAC 並比較結果就可以知道資料是否有經過竄改。
4. 將 iv、MAC 和 密文 包進一個 json ,base64 encode 後回傳
Decrypt 的實作步驟
1. 將拿到的 json string 進行 base64 decode
2. 檢查 json structure
json 裡需有 iv
、密文
與MAC
,且 iv
需符合長度(16 bytes)
3. 驗證 MAC 是否正確
(1) 產生一個 Random key
(2) 將 json 裡的iv
和密文
串接後進行hash_hmac
,再用 Random key 再額外進行一次hash_hmac
3. 用hmac_equals
來比較兩者是否相同
4. 解密,這邊要傳入和加密時一樣的 iv
搜尋加密資料
Laravel Encrypter 固然好用,但倘若我們有資料唯一性的需求,例如說身份證字號必須是唯一值,但每次加密後的值都不一樣耶!於是 DB 的 unique key 就失效了,而且也沒辦法進行資料搜尋,總不能一個一個拉出來用迴圈檢查吧?
CipherSweet 提供了一個方法,是在儲存密文之外,再額外儲存一個 hash 過的值,稱為 blind index,用此值來進行搜尋,希望未來有機會來分享一下它的實作方法。
參考資料
- Cryptography and Network Security : Principles and Practice 6th Edition
- [Block Cipher Mode of Operation](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation)
- [Initialization Vector](https://en.wikipedia.org/wiki/Initialization_vector)
- [Laravel encryption](https://laravel.com/docs/5.8/encryption)