這是why的第 103 篇原創php
你好呀,我是why。html
如圖,重放攻擊,這題我真的在面試的時候遇到過,兩次。程序員
印象比較深的是第一次遇到這個面試題的時候,也是第一次聽到「重放攻擊」這個詞的時候,一臉矇蔽,因而我就連蒙帶猜的,朝着接口冪等性的方向去答了。面試
結果就涼了。算法
要回答怎麼防止重放攻擊,那麼咱們得知道啥是重放攻擊。api
學術上的解釋是這樣的:安全
重放攻擊(英語:replay attack,或稱爲回放攻擊)是一種惡意或欺詐的重複或延遲有效數據的網絡攻擊形式。 這能夠由發起者或由攔截數據並從新傳輸數據的對手來執行,這多是經過IP數據包替換進行的欺騙攻擊的一部分。 這是「中間人攻擊」的一個較低級別版本。
這種攻擊的另外一種描述是: 「從不一樣上下文將消息重播到安全協議的預期(或原始和預期)上下文,從而欺騙其餘參與者,導致他們誤覺得已經成功完成了協議運行。」
舉個簡單的例子:服務器
咱們程序員日夜操勞的,在按摩店裏面辦個卡,偶爾去洗個腳放鬆一下不過度吧。微信
有一天,我去洗腳的時候對着店員說:給我安排一個 168 價位的,要小夥子啊,按着比較帶勁兒,個人卡號是 88888888。網絡
而後我在前臺簽上了本身的名字,店員就安排了一個精壯的小夥子給我按摩。
沒想到咱們的對話被其餘人聽到了,因而他也給店員說:給我安排一個 168 價位的,要小夥子啊,按着比較帶勁兒,個人卡號是 88888888。
還模仿了個人簽名,在前臺簽字。
把以前的、正常的請求再次發送,這就是重放攻擊。
有的朋友就會說了:個人接口是加簽的,應該沒問題吧?
你加簽咋了?
我沒有動你的報文,因此你也能夠正常驗籤呀。
我不只抄你報文裏面的正常字段,報文裏面的簽名我也抄全乎了。
因此,接收方接到報文以後能正常驗籤。
沒有任何毛病。
有的朋友還會說了:個人接口是有加密的,應該沒問題吧?
看來仍是不懂重放攻擊的基本原理。
你加密咋了?
反正我截取到了你的報文,雖然你報文加密了,我看起來是一段亂碼,可是我也不須要知道你報文的具體內容呀,直接重發就完事了。
仍是前面的例子。
假設我去洗腳的時候對着店員說:天王蓋地虎。
被旁邊的人聽到了,他根本就不知道「天王蓋地虎」是啥。
可是他看到了我說了這句話以後,就被安排了一個 168 元的技術服務。
因而他也對店員說:天王蓋地虎。
也能被安排。
因此,別人根本就不須要知道你報文的具體含義。
只要我再次發給你,你進行解密操做,發現能解密。
能解密說明暗號對上了。
因此,雖然報文是加密、加簽傳輸的,對於防止請求重放,並無什麼卵用。
來,說解決方案以前,咱們先明確兩個概念:加密和加簽。
字面意思不解釋了,你們都知道,說說目的。
加密的目的:爲了保證傳輸信息的隱私性,不被別人看到傳輸的具體內容,只能讓接收方看到正確的信息。
加簽的目的:消息接收方驗證信息是不是合法的發送方發送的,確認信息是否被其餘人篡改過。
不論是加密仍是加簽,都涉及到公私鑰。
記住了:公鑰加密、私鑰加簽。
簡單的說一下原理。
發送方有這樣三樣東西:本身的私鑰、本身的公鑰、接收方的公鑰。
接收方有這樣三樣東西:本身的私鑰、本身的公鑰、發送方的公鑰。
中間人有這樣兩樣東西:接收方的公鑰、發送方的公鑰。
爲何是公鑰加密呢?
來個反證法嘛。
假設消息發送方用本身的私鑰加密。而後消息被中間人攔截到了,由於他有發送方的公鑰,那麼中間人就能夠用公鑰對消息進行解密,獲取明文報文,這樣達不到加密的目的。
因此,正確的操做應該是用接收方的公鑰加密,這樣就算消息被中間人攔截到了,他也沒有接收方的私鑰呀,解不了密,看不到明文。
爲何是私鑰加簽呢?
一樣,反證法。
假設消息發送方,用接收方的公鑰加簽。若是消息被中間人攔截到了,巧了,我也有接收方的公鑰。咔一下,直接把消息一改,而後也拿着接收方的公鑰加簽,發過去了。
這樣的加簽是沒有意義的。
所以,要用本身的私鑰加簽,就算被攔截,中間人沒有私鑰,修改報文以後,搞不了簽名,也就沒啥卵用。
前面說了,對於重放攻擊,截取到的內容是否是加密都無所謂。由於我根本不須要大家在說什麼,我只須要把攔截下來的請求一遍遍的重發就好了。
因此,重要的是加簽和驗籤。
若是你能修改報文,而且從新加簽,那就不叫重放攻擊了,那就叫作中間人攻擊了。
其實重放攻擊也是「中間人攻擊」的一個較低級別版本。
啥是中間人攻擊呢?
我去洗腳的時候對着店員說:給我安排一個 168 價位的,要小夥子啊,按着比較帶勁兒,個人卡號是 88888888。
對話被偷聽到了,中間人對店員說:給我安排一個 1999 價位的,要小姑涼啊,按摩手法好一點的,個人卡號是 88888888。
篡改報文,這是中間人攻擊。
本文主要聚焦於重放攻擊的解決方案。
通過前面的分析,咱們知道要解決重放攻擊,就是想着怎麼在參與簽名的字段裏面搞事情。
能想到這裏,就比較好回答這個問題了。
若是是從數據加密角度回答這個問題的同窗,能夠回去等通知了。
另外,說到加密了,你們都會想到 HTTPS 數據加密。
因此,當面試官問你:HTTPS數據加密是否能夠防止重放攻擊?
答:否,加密能夠有效防止明文數據被監聽,可是卻防止不了重放攻擊。
接下來,咱們看看解決方案。
加時間戳
首先,常見的解決方案就是在請求報文裏面加上時間戳,並參與加簽。
當接收方收到報文,通過驗籤以後。
首先第一個事兒就是拿着請求中的時間戳字段和本地時間作個對比。
若是時間偏差在指定時間,好比 60 秒內,那麼認爲這個請求是合理的,程序能夠繼續處理。
爲何要有一個時間容錯範圍,能理解吧?
由於報文的傳輸、解密、驗籤是須要時間,不能假設我這一秒發出去,下一秒服務端就收到了。
因此,得有時間容錯範圍。
可是這個容錯範圍又帶來了另一個問題。
不能徹底避免重放攻擊。
至少時間容錯範圍內,好比 60 秒,重發過來的請求,服務端認爲是有效的。
那麼怎麼辦呢?
加隨機串
換個思路,咱們在請求報文裏面加個隨機串,而後讓它參與加簽。
接受方收到報文,驗籤以後,把隨機串拿出來,來判斷一下這個隨機串是否已經處理過了。好比判斷一下是否存在於 Redis 裏面。
當請求再次重放過來的時候,一看:嚯,好傢伙,這個隨機串已經被用過了呀,不處理了。
在這個狀況下,隨機串就得保證惟一性了,還得歷史全局惟一。
由於你指不定哪天就收到一個幾天前的被重放過來的請求。
確實是解決了請求重放的問題,可是弊端也很明顯:歷史全局惟一。
我還得存儲下來,並且存儲的數據量還會愈來愈大,是否是有點麻煩了?
確實麻煩了。
這個思想就和用全局惟一流水號去保證接口冪等性很像了。
因此,我第一次遇到這個面試題的時候,我朝着接口冪等的角度去回答了,也不能說回答的不對。
只能說回答的不是面試官想要的標準答案。
那麼什麼是面試官想要聽到的回答呢?
時間戳+隨機串
時間戳的問題是有必定的時間容錯窗口,這個時間窗口內的重放攻擊是防不住的。
隨機串的問題是要保證歷史全局惟一,保存隨機串成了一個麻煩的事情。
那麼當咱們把這兩個方案揉在一塊兒的時候,神奇的事情就發生了:
我只須要保證時間窗口內的生成的隨機串不重複就行。
並且假設時間窗口爲 60 秒,咱們用 Redis 來記錄出現過的隨機串,那麼這個串在後臺的超時時間設置爲 60 秒就行。
通常來講這個時間窗口都不會太長了,我對接過這麼多各類各樣的渠道,見過最長的也就 5 分鐘。
保證 5 分鐘內生成的兩個隨機串不重複,這個需求比保證明現一個歷史全局惟一的流水號容易實現多了吧?
另外,最關鍵的一句話必定要說:時間戳和隨機串得參與到加簽邏輯中去。
這個很好理解吧?
接受方看報文是否被篡改,看的就是簽名是否能匹配上。
而簽名的結果是和參與簽名的字段的值有直接關係的。
要是你時間戳和隨機串不參與加簽,那麼任意修改時間戳或者隨機串,都不會引發簽名的變化,那不白忙活一場嗎?
中間人咔一下攔截到請求,發現有時間戳和隨機串,正準備放棄的時候,想着死馬當作活馬醫,把隨機串一改,又扔給接收方了。
結果收到正確的響應了。
我要是這個中間人,我都會笑出來聲來:寫這個代碼的程序員也太可愛了吧?
其實說到時間戳加隨機串的時候,我就想起了微信支付。
剛剛入行的時候,但是被這個微信支付搞的服服帖帖的。
可是須要說明的是,雖然它的接口文檔裏面也有時間戳加隨機串,可是目的不是爲了防止重放攻擊的。
寫出來呢只是爲了讓對於加簽這個東西不太熟悉的朋友有一個具體的認知。
來,咱們看一下微信支付的接口文檔:
https://pay.weixin.qq.com/wik...
能夠看到請求參數裏面確實有時間戳(timeStamp)和隨機字符串(nonceStr),且人家還專門加粗了:
參與簽名的參數爲:appId、timeStamp、nonceStr、package、signType,參數區分大小寫。
那麼是怎麼簽名的呢?
官方也是給了詳盡的說明的:
https://pay.weixin.qq.com/wik...
首先就是按照字典序,對全部須要參與簽名的、非空的字段進行排序。並使用 URL 鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串 stringA。
而後在 stringA 最後拼接上 key(商戶密鑰) 獲得 stringSignTemp 字符串,並對 stringSignTemp 進行 MD5 運算,再將獲得的字符串全部字符轉換爲大寫,獲得 sign 值 signValue。
官方給了一個實際的案例,以下:
再說一次:微信支付的接口裏面雖然有時間戳加隨機串,可是目的不是爲了防止重放攻擊的。寫在這裏只是讓你們對於加簽這個過程有一個具體的認知。
別整茬了。
那麼它在接口裏面加入隨機串的目的是什麼呢?
官方本身都說了:
微信支付API接口協議中包含字段nonce_str,主要保證簽名不可預測。咱們推薦生成隨機數算法以下:調用隨機數函數生成,將獲得的值轉換爲字符串。
看完微信支付,再看看阿里的 API 網關是怎麼防止重放攻擊的。
https://help.aliyun.com/knowl...
阿里的 API 網關,就是在 HEADER 裏面加了兩個參數:X-Ca-Timestamp、X-Ca-Nonce。
這個解決方案就是咱們前面說的時間戳加隨機串。
接着看看它的簽名生成過程。
首先是客戶端生成簽名,三步:
- 1.從原始請求中提取關鍵數據,獲得一個用來簽名的字符串
- 2.使用加密算法加APP Secret對關鍵數據簽名串進行加密處理,獲得簽名
- 3.將簽名所相關的全部頭加入到原始HTTP請求中,獲得最終HTTP請求
一圖勝千言:
而後是服務端驗證簽名,四步:
- 1.從接收到的請求中提取關鍵數據,獲得一個用來簽名的字符串
- 2.從接收到的請求中讀取APP Key,經過APP Key查詢到對應的APP Secret
- 3.使用加密算法和APP Secret對關鍵數據簽名串進行加密處理,獲得簽名
- 4.從接收到的請求中讀取客戶端簽名,對比服務器端簽名和客戶端簽名的一致性。
而具體的簽名算法其實和微信支付,大同小異,主要也是對於參與簽名的字段按照字典序排序。
箇中差別就不進行對比說明了,有興趣的朋友能夠本身看一下。
好了,看到了這裏點個贊吧,周更很累的,須要一點正反饋。
才疏學淺,不免會有紕漏,若是你發現了錯誤的地方,能夠在留言區提出來,我對其加以修改。
感謝您的閱讀,我堅持原創,十分歡迎並感謝您的關注。