知識補充php
由於yii2 csrf的驗證的加解密 涉及到異或運算html
因此須要先補充php裏字符串異或運算的相關知識,不須要的能夠跳過web
^異或運算
不同返回1 否者返回 0
在PHP語言中,常常用來作加密的運算,解密也直接用^就行
字符串運算時 利用字符的ascii碼轉換爲2進制來運算
單個字符運算
舉例的ascii見下表安全
字符yii2 |
二進制cookie |
ASCIIsession |
aapp |
1100001dom |
97yii |
b |
1100010 |
98 |
c |
1100011 |
99 |
d |
1100100 |
100 |
計算結果
運算 |
二進制 |
ASCII |
a^b |
0000 0011 |
3 |
a^c |
0000 0010 |
2 |
b^d |
0000 0110 |
6 |
ab^cd |
0000 0010 |
2 |
a^cd |
0000 0010 |
2 |
ab^c |
0000 0010 |
2 |
1.對於單個字符和單個字符的
直接計算其結果便可 好比表裏的a^b
2.對於長度同樣的多個字符串 如表裏的ab^cd
計算a^c對應的結果和和b^d對應的結果 對應的字符鏈接起來
<?php $str1='ab'; $str2="cd"; $r= $str1^$str2; var_dump($r); echo "<hr>"; for($i=0;$i<strlen($r) ;$i++){ echo ord($r[$i])."<br>"; } ?>
對於不等的
以短的字符串長度位進行計算
Yii2的csrf token驗證
在yii2的接收post請求時
在若是開啓
enableCsrfValidation爲true
在/vendor/yiisoft/yii2/web/Controller.php
<?php public function beforeAction($action) { if (parent::beforeAction($action)) { if ($this->enableCsrfValidation && Yii::$app->getErrorHandler()->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) { throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.')); } return true; } return false; } ?>
會進行validateCsrfToken驗證
在/vendor/yiisoft/yii2/web/Request.php
<?php public function validateCsrfToken($token = null) { $method = $this->getMethod(); // only validate CSRF token on non-"safe" methods http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) { return true; } $trueToken = $this->loadCsrfToken(); if ($token !== null) { return $this->validateCsrfTokenInternal($token, $trueToken); } else { return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken) || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken); } } ?>
說明在 GET, HEAD, OPTIONS 均不驗證,除了這幾種主要用的也就post了
說明在咱們發送post請求時必須發送相關驗證的字段和值
下面看CsrfToken產生過程
在/vendor/yiisoft/yii2/web/Request.php裏
<?php public function getCsrfToken($regenerate = false) { if ($this->_csrfToken === null || $regenerate) { if ($regenerate || ($token = $this->loadCsrfToken()) === null) { $token = $this->generateCsrfToken(); } // the mask doesn't need to be very random
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.'; $mask = substr(str_shuffle(str_repeat($chars, 5)), 0, static::CSRF_MASK_LENGTH); // The + sign may be decoded as blank space later, which will fail the validation
$this->_csrfToken = str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask))); } return $this->_csrfToken; } ?>
會發現
_csrfToken的產生大體以下
若是開啓了enableCsrfCookie,
CsrfToken就從cookie裏取,否者從session裏取(更安全)
可在
/vendor/yiisoft/yii2/web/Request.php的下面部位看到
<?php protected function loadCsrfToken() { if ($this->enableCsrfCookie) { return $this->getCookies()->getValue($this->csrfParam); } else { return Yii::$app->getSession()->get($this->csrfParam); } } ?>
從loadCsrfToken()裏取出的值這裏稱token
在post裏發送的也就是Yii::$app->getRequest()->csrfParam 這裏稱csrfToken 如今根據代碼大體說下生成和驗證的主要思路,固然本身看代碼更能細緻的瞭解 1.從cookie或者session裏取出token ,固然cookie或者session裏若是沒有就是初始化操做的過程了,這裏初始化不是重點 2.隨機產生CSRF_MASK_LENGTH(Yii2裏默認是8位)長度的字符串 mask 3.對mask和token進行以下運算 str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask))); $this->xorTokens($arg1,$arg2) 是一個先補位異或運算
傳入$arg1,$arg2
長度短的要用自身補到長度長的字符串的位置
見代碼部分
在 /vendor/yiisoft/yii2/web/Request.php 的以下部分
<?php private function xorTokens($token1, $token2) { $n1 = StringHelper::byteLength($token1); $n2 = StringHelper::byteLength($token2); if ($n1 > $n2) { $token2 = str_pad($token2, $n1, $token2); } elseif ($n1 < $n2) { $token1 = str_pad($token1, $n2, $n1 === 0 ? ' ' : $token1); } return $token1 ^ $token2; } ?>
就是說若是 $arg1比$arg2短,$arg1要用自身補齊 補到和和$arg2同樣的長度
這裏爲何要這樣作?
由於在php裏
'a'^'bc' 會只算 a^b 而不考慮c了,這裏採用了向長度更長的來補
若是用
xorTokens來處理 'a'和'bc'
會先把a用本身填充到和bc同樣的長度後再進行異或運算
異或運算詳見上文補充
str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask)));
計算後即會得出在post請求時要發送的值 csrfToken
下面是驗證過程
1.根據 表單字段名
Yii::$app->getRequest()->csrfParam;
從post裏拿到
csrfToken的值
從方法 validateCsrfToken裏能夠看到
代碼
在/vendor/yiisoft/yii2/web/Request.php 的以下部分
<?php public function validateCsrfToken($token = null) { $method = $this->getMethod(); // only validate CSRF token on non-"safe" methods http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) { return true; } $trueToken = $this->loadCsrfToken(); if ($token !== null) { return $this->validateCsrfTokenInternal($token, $trueToken); } else { return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken) || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken); } } ?>
$this->getBodyParam($this->csrfParam)
能夠看出
解密的目的就是要從
csrfToken裏取出token 而後和會話裏的token比較
見/vendor/yiisoft/yii2/web/Request.php 的以下部分
<?php private function validateCsrfTokenInternal($token, $trueToken) { $token = base64_decode(str_replace('.', '+', $token)); $n = StringHelper::byteLength($token); if ($n <= static::CSRF_MASK_LENGTH) { return false; } $mask = StringHelper::byteSubstr($token, 0, static::CSRF_MASK_LENGTH); $token = StringHelper::byteSubstr($token, static::CSRF_MASK_LENGTH, $n - static::CSRF_MASK_LENGTH); /* 注意此時的$token在加密過程當中是xorTokens($trueToken,$mask)的結果 */
$token = $this->xorTokens($mask, $token); return $token === $trueToken; } ?>
加密時用的是
str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask)));
解密
1.首先要把.替換成+
2.而後base64_decode
再 根據長度分別取出$mask和$this->xorTokens($token, $mask) ;
爲了說明方便 $this->xorTokens($token, $mask) 這裏稱做 token1
而後
進行mask和token1的異或運算,即得token
注意在加密時
token1=token^mask
因此
解密時
token=mask^token1=mask^(token^mask)
yii2
中的核心思路
token是從會話中取得的
用隨機串和token進行運算處理 獲得一個加密串
驗證的時候經過這個加密串解密出來這個token和會話裏的值進行比較