laravel的cookie并不简单
背景:
在之前的文章中,我已经阐述了laravel的cookie产生的过程,那laravel的cookie在响应到客户端之前,又经历了什么呢?
laravel的cookie生成以后,在响应到客户端之前,会经过encrypt的过程,encrypt的加密逻辑定义在src/Illuminate/Cookie/Middleware/EncryptCookies.php
protected function encrypt(Response $response) { foreach ($response->headers->getCookies() as $cookie) { if ($this->isDisabled($cookie->getName())) { continue; } $response->headers->setCookie($this->duplicate( $cookie, $this->encrypter->encrypt( CookieValuePrefix::create($cookie->getName(), $this->encrypter->getKey()).$cookie->getValue(), static::serialized($cookie->getName()) ) )); } return $response; }
而上面的encrypt,会调用src/Illuminate/Encryption/Encrypter.php的encrypt方法
public function encrypt($value, $serialize = true) { $iv = random_bytes(openssl_cipher_iv_length(strtolower($this->cipher))); $value = self::$supportedCiphers[strtolower($this->cipher)]['aead'] ? \openssl_encrypt( $serialize ? serialize($value) : $value, strtolower($this->cipher), $this->key, 0, $iv, $tag ) : \openssl_encrypt( $serialize ? serialize($value) : $value, strtolower($this->cipher), $this->key, 0, $iv ); if ($value === false) { throw new EncryptException('Could not encrypt the data.'); } $iv = base64_encode($iv); $tag = base64_encode($tag); $mac = self::$supportedCiphers[strtolower($this->cipher)]['aead'] ? '' // For AEAD-algoritms, the tag / MAC is returned by openssl_encrypt... : $this->hash($iv, $value); $json = json_encode(compact('iv', 'value', 'mac', 'tag'), JSON_UNESCAPED_SLASHES); if (json_last_error() !== JSON_ERROR_NONE) { throw new EncryptException('Could not encrypt the data.'); } return base64_encode($json); }
看不懂?没关系,下面我将会逐个分析:
一、计算初始向量$iv
$iv = random_bytes(openssl_cipher_iv_length(strtolower($this->cipher)));
$this->ciphier,默认是:aes-128-cbc,在对cookie进行加密的时候使用的是:aes-256-cbc,据此可知:初始向量$iv,是一个由随机二进制的字节组成的长度为16的字符串
二、使用openssl_encrypt进行初步加密
private static $supportedCiphers = [ 'aes-128-cbc' => ['size' => 16, 'aead' => false], 'aes-256-cbc' => ['size' => 32, 'aead' => false], 'aes-128-gcm' => ['size' => 16, 'aead' => true], 'aes-256-gcm' => ['size' => 32, 'aead' => true], ];
因为对cookie进行加密的时候,使用的是:aes-256-cbc,对应的aead为false,因此初步加密,执行的是下面的代码
\openssl_encrypt( $serialize ? serialize($value) : $value, strtolower($this->cipher), $this->key, 0, $iv )
如果初步加密,返回false,抛出异常
if ($value === false) { throw new EncryptException('Could not encrypt the data.'); }
二、计算$mac
如果初步加密顺利,没有返回false,则计算$mac,以便于再次加密
$iv = base64_encode($iv); $tag = base64_encode($tag); $mac = self::$supportedCiphers[strtolower($this->cipher)]['aead'] ? '' // For AEAD-algoritms, the tag / MAC is returned by openssl_encrypt... : $this->hash($iv, $value);
计算$mac的过程,其实是对初始向量$iv和初步加密的$value,通过hash_hmac方法,使用sha256加密算法和私钥$this->key,进行hash的过程
protected function hash($iv, $value) { return hash_hmac('sha256', $iv.$value, $this->key); }
三、将$iv、$value、$mac、$tag进行compact,然后再以JSON_UNESCAPED_SLASHES保留/的形式进行json_encode
$json = json_encode(compact('iv', 'value', 'mac', 'tag'), JSON_UNESCAPED_SLASHES);
如果json_encode出现异常,则抛出异常
if (json_last_error() !== JSON_ERROR_NONE) { throw new EncryptException('Could not encrypt the data.'); }
四、如果json_encode一切顺利,就以base64_encode方式对$json进行编码,响应最终的cookie
return base64_encode($json);
这就是laravel的cookie生成以后,在响应到客户端之前的完整加密过程。