前言 Laravel 的加密機制使用 OpenSSL 提供 AES-256 和 AES-128 的加密,本文將詳細介紹關于Laravel中encrypt和decrypt的實現(xiàn),,分享出來供大家參考學習,,下面話不多說了,,來一起看看詳細的介紹吧。 1. 使用方法 首先是生成秘鑰,。要需要在.env目錄里提供APP_KEY,,這個如果沒有的話,可以通過命令 APP_KEY=base64:5BM1BXGOBrGeeqJMAWJZSzyzh5yPcCGOcOGPtUij65g= 在文件配置加密key和加密算法,在config/app.php的目錄里有配置 $ 'key' => env('APP_KEY'), 'cipher' => 'AES-256-CBC', 使用方法,,在laravel里已經(jīng)有使用方法了,,這里就不在過多的說了。主要使用的兩個方法,,一個是encrypt的加密,,一個是decrypt的解密 2. 查找加密解密的文件 實現(xiàn)方法的位置是在vendor/illuminate/encryption/的目錄下發(fā)現(xiàn)兩個文件,一個是EncryptionServiceProvider另外一個是Encrypter 3. 分析EncryptionServiceProvider文件 public function register() { $this->app->singleton('encrypter', function ($app) { $config = $app->make('config')->get('app'); //從config/app.php里拿到配置文件 if (Str::startsWith($key = $config['key'], 'base64:')) { //分析配置文件里的key里面有沒有帶'base64' $key = base64_decode(substr($key, 7)); //如果有的話,,把key前面的base64:給取消,,并且解析出原來的字符串 } return new Encrypter($key, $config['cipher']); //實例化Encrypte類,注入到框架里 }); } 這個文件沒太多東西,,但是通過這個我們可以看出,,其實在配置文件的,我們能直接寫key,,并且前面不帶base64也是可以解析,。相當于省幾步操作 另外,在實例化類的時候,,需要傳入key以及加密方式 4. 分析Encrypter文件 1. 分析__construct,,在實例化之前執(zhí)行 public function __construct($key, $cipher = 'AES-128-CBC') { $key = (string) $key; //把key轉(zhuǎn)換為字符串 if (static::supported($key, $cipher)) { //調(diào)用一個自定義的方法,,用來判斷加密方式和要求的key長度是否一樣 $this->key = $key; $this->cipher = $cipher; } else { throw new RuntimeException('The only supported ciphers are AES-128-CBC and AES-256-CBC with the correct key lengths.'); } } 上面的方法,主要是用來判斷加密方式和傳的key的長度是否相同,,因為不同的加密方式,,要求的相應的key的長度也是有要求的,具體每種加密方式要求key的長度可以查找對應的文檔 public static function supported($key, $cipher) { $length = mb_strlen($key, '8bit'); //判斷key的字符的長度,,按照8bit位的方式計算字符長度 return ($cipher === 'AES-128-CBC' && $length === 16) || ($cipher === 'AES-256-CBC' && $length === 32); //編碼格式為AES128的要求字符長度為16,。編碼格式為AES256的要求字符長度為32位 } 上面這個方法展現(xiàn)了一個嚴謹?shù)牡胤剑昧薽b_strlen方法,,并且要求計算長度是按照8bit位來計算的,。這樣的好處是,不管是在哪種操作系統(tǒng),,計算的長度都是一樣的,。 通過這個考慮到不同操作系統(tǒng)的情況,不會出現(xiàn)加密出現(xiàn)問題的情況,。 2. 分析encrypt方法 public function encrypt($value, $serialize = true) { $iv = random_bytes(16); //生成一個16位的隨機字符串 // 使用openssl_encrypt把數(shù)據(jù)生成一個加密的數(shù)據(jù) // 1,、判斷需要不需要生成一個可存儲表示的值,這樣做是為了不管你的數(shù)據(jù)是數(shù)組還是字符串都能給你轉(zhuǎn)成一個字符串,,不至于在判斷你傳過來的數(shù)據(jù)是數(shù)組還是字符串了,。 // 2、使用openssl_encrypt,。第一個參數(shù)是傳入數(shù)據(jù),,第二個參數(shù)是傳入加密方式,目前使用AES-256-CBC的加密方式,,第三個參數(shù)是,,返回加密后的原始數(shù)據(jù),還是把加密的數(shù)據(jù)在經(jīng)過一次base64的編碼,,0的話表示base64位數(shù)據(jù),。第四個參數(shù)是項量,這個參數(shù)傳入隨機數(shù),,是為了在加密數(shù)據(jù)的時候每次的加密數(shù)據(jù)都不一樣,。 $value = \openssl_encrypt( $serialize ? serialize($value) : $value, $this->cipher, $this->key, 0, $iv ); //使用AES256加密內(nèi)容 if ($value === false) { throw new EncryptException('Could not encrypt the data.'); } $mac = $this->hash($iv = base64_encode($iv), $value); //生成一個簽名,用來保證內(nèi)容參數(shù)沒有被更改 $json = json_encode(compact('iv', 'value', 'mac')); //把隨機碼,,加密內(nèi)容,,已經(jīng)簽名,組成數(shù)組,,并轉(zhuǎn)成json格式 if (! is_string($json)) { throw new EncryptException('Could not encrypt the data.'); } return base64_encode($json); //把json格式轉(zhuǎn)換為base64位,,用于傳輸 } 上面用到了一個自定義的方法hash(),我們可以看下方法的實現(xiàn),。 protected function hash($iv, $value) { // 生成簽名 // 1,、把隨機值轉(zhuǎn)為base64 // 2,、使用hash_hmac生成sha256的加密值,用來驗證參數(shù)是否更改,。第一個參數(shù)表示加密方式,,目前是使用sha256,第二個是用隨機值連上加密過后的內(nèi)容進行,,第三個參數(shù)是上步使用的key,。生成簽名。 return hash_hmac('sha256', $iv.$value, $this->key); /根據(jù)隨機值和內(nèi)容,,生成一個sha256的簽名 } 以上加密共分了三大步 1,、生成隨機碼 2、生成加密內(nèi)容 3,、生成簽名 框架用到一個優(yōu)雅的方法,,使用serialize生成一個值,,這個方法高雅在哪里,,就是不管你得內(nèi)容是數(shù)組還是字符串,都能轉(zhuǎn)換成字符串,。 而使用serialize和使用json_encode的區(qū)別在哪,,我想最大的好處是,你所要加密的內(nèi)容比較大的時候,,serialize相對于要快,。 另外一個地方是,框架在加密的時候使用了一個隨機字符串,。為什么要使用隨機字符串呢,,因為使用了隨機字符串,使每次加密的內(nèi)容都是不一樣的,,防止別人猜出來,。 3. 分析decrypt方法 解密數(shù)據(jù),可以說是最復雜的一塊,,不僅要進行數(shù)據(jù)的解密,,而且還要保證數(shù)據(jù)的完整性,以及數(shù)據(jù)防篡改 public function decrypt($payload, $unserialize = true) { $payload = $this->getJsonPayload($payload); //把加密后的字符串轉(zhuǎn)換出成數(shù)組,。 $iv = base64_decode($payload['iv']); //把隨機字符串進行base64解密出來 $decrypted = \openssl_decrypt( //解密數(shù)據(jù) $payload['value'], $this->cipher, $this->key, 0, $iv ); if ($decrypted === false) { throw new DecryptException('Could not decrypt the data.'); } return $unserialize ? unserialize($decrypted) : $decrypted; //把數(shù)據(jù)轉(zhuǎn)換為原始數(shù)據(jù) } getJsonPayload方法 protected function getJsonPayload($payload) { $payload = json_decode(base64_decode($payload), true); //把數(shù)據(jù)轉(zhuǎn)換為原來的數(shù)組形式 if (! $this->validPayload($payload)) { //驗證是不是數(shù)組以及數(shù)組里有沒有隨機字符串,,加密后的內(nèi)容,簽名 throw new DecryptException('The payload is invalid.'); } if (! $this->validMac($payload)) { //驗證數(shù)據(jù)是否被篡改 throw new DecryptException('The MAC is invalid.'); } return $payload; } validPayload方法就不說了,,比較簡單和基本,,重點就說說validMac驗證這塊,保證數(shù)據(jù)不被篡改,,這是最重要的 protected function validMac(array $payload) { $calculated = $this->calculateMac($payload, $bytes = random_bytes(16)); //拿數(shù)據(jù)和隨機值生成一個簽名 return hash_equals( //比對上一步生成的簽名和下面生成的簽名的hash是否一樣,。 hash_hmac('sha256', $payload['mac'], $bytes, true), $calculated //根據(jù)原始數(shù)據(jù)里的簽名在新生成一個簽名 ); } calculateMac方法是為了根據(jù)原始數(shù)據(jù)和隨機值生成一個簽名,,然后用這簽名再次生成一個簽名 protected function calculateMac($payload, $bytes) { return hash_hmac( 'sha256', $this->hash($payload['iv'], $payload['value']), $bytes, true ); } 以上解密共分了三大步 1、判斷數(shù)據(jù)的完整性 2,、判斷數(shù)據(jù)的一致性 3,、解密數(shù)據(jù)內(nèi)容。 |
|