永強持續教你加解密:對稱篇(二)
- 2019 年 11 月 13 日
- 筆記
永強被嚇壞了!因為永強看到了某個微信群有人指出我上篇打醬油附送的那篇文章《震驚!北京一男子竟然用swoole做了這種事!》太low了,不過這都是誤會,一些都已經煙消雲散了。
但是給永強留下了面積巨大的心理陰影
所以永強本來昨天要發的文章拖到了今天,但是永強實在是怕了。雖然我百般鼓勵,但他還是心有餘悸。儘管我都已經直接告訴他「你那玩意根本就沒人看」了,他還是依然不敢發了。然後我不得不擺出PS大法給他做了一張圖,他看了看那張圖後又收了我6.66元的微信紅包,決定繼續鼓起勇氣發了。
是時候表現一下我的PS精湛技術了!
「我們歷經千辛萬苦,摸打滾爬過數不清的錯誤,發射了不知道多少枚長征系列,耗費了一代航天人的心血,終於看到了地球與月亮通訊的曙光,然後就在五分鐘,我們驚訝地發現,原來老王的smartmesh技術早就實現了,甚至連地球文明與外形文明的通訊都給出了完美的解決方案…」 ——— 尼古拉斯 * 趙永強
眾所周知,作為精通各種技術表演的我早就已經不屑於採用ppt的方式吹牛了,一般我都是直接上機操作表演,當然了,程式都是提前寫好了的,全是mock的假數據,腳本實現自動化,無論誰來操作都是流暢的,一切都是完美的!
作為一個追求完美的人,我還得繼續接著吹上次聊到結尾,好像是遺留了兩個問題:
- ecb、cfb、cbc等這些後綴是什麼意思
- iv向量又是什麼意思
鑒於DES和3DES已經屬於不建議使用的方法了,所以這次我們直接用AES加密進行裝逼表演,比如下面這坨程式碼,你們複製粘貼走運行一下:
<?php$ava_methods = openssl_get_cipher_methods();// 選用aes-128-ecb$my_method = 'aes-128-ecb';if ( !in_array( $my_method, $ava_methods ) ) { exit( '錯誤的加密方法'.PHP_EOL );}// 加密用的密碼$key = "1234567812345678";// 加密的內容$data = "12345678abcdxxoo12345678abcdxxoo";$enc_data = openssl_encrypt( $data, $my_method, $key, OPENSSL_RAW_DATA );$hex = bin2hex( $enc_data );echo $hex.' : '.strlen( $hex ).PHP_EOL;我這裡運行結果是:【c1391e34caf38f8c2a477cbda3772533c1391e34caf38f8c2a477cbda3772533d96aa42b59151a9e9b5925fc9d95adaf : 96】
分析一下上面程式碼:這次我們選用的加密方法是AES-128-ECB,這個128是什麼意思?128就是密鑰長度的意思:128bit;如果你留心的話,還會注意到有aes-192-ecb和aes-256-ecb,其實就是指加密密鑰長度為192bit、256bit,然後是值得注意的一個地方是:
$enc_data = openssl_encrypt( $data, $my_method, $key, OPENSSL_RAW_DATA );
最後一個參數是OPENSSL_RAW_DATA,如果選用這個option的話,經過加密後的數據會是奇怪的二進位數據,無法直接通過文本方式查看,所以要看的話必須先使用bin2hex函數處理一下。
注意了哈,我選的這個密鑰1234567812345678是有特殊用意的,這個密鑰的長度是16位元組也就是128bit,而我們選用的aes加密方法中要求的密鑰長度就是128bit,那麼我們嘗試將密鑰增加幾位變成:1234567812345678abc,然後其他程式碼不做任何改動,再次執行加密,結果如下:
c1391e34caf38f8c2a477cbda3772533c1391e34caf38f8c2a477cbda3772533d96aa42b59151a9e9b5925fc9d95adaf : 96
就是說用「1234567812345678」和「1234567812345678abc」加密後的數據都是一樣的。看起來如果我們選用128bit密鑰長度的話,一旦密鑰長度超過128bit後面多餘的部分會被直接無視掉~~~
然後我們再嘗試將密鑰「1234567812345678」縮短一個位元組,改成「123456781234567」,其他地方程式碼不做任何改動,運行一波兒,結果如下:
c202e5b1dc36c3147e50d02df7ab700cc202e5b1dc36c3147e50d02df7ab700cda89b056d926d3fea2e59ffc552b1d98 : 96
這次不行了,已經不一樣了~
然後,我們將注意力放到明文和密文上來:
明文:12345678abcdxxoo12345678abcdxxoo密文:c1391e34caf38f8c2a477cbda3772533c1391e34caf38f8c2a477cbda3772533d96aa42b59151a9e9b5925fc9d95adaf
仔細觀察有一個比較屌的地方,我們將密文每隔32個字元長度就分割一下,你們感受一下:
c1391e34caf38f8c2a477cbda3772533c1391e34caf38f8c2a477cbda3772533d96aa42b59151a9e9b5925fc9d95adaf
卧槽,竟然有前兩段是一樣的!???卧槽。。。 。。。
仔細看了一把明文12345678abcdxxoo12345678abcdxxoo,分析一下,卧槽!:
12345678abcdxxoo12345678abcdxxoo
難道說明文「12345678abcdxxoo」被密鑰「1234567812345678」加密後後的密文就是「c1391e34caf38f8c2a477cbda3772533」?
時機已然成熟了!是時候繼續深入裝逼了!為什麼會出現這個結果?現在我們開始說「ecb、cfb、cbc等這些後綴是什麼意思」。
你若有所思的猜測到:「難道說對稱加密的時候,都是將明文先分塊,然後再分別對分塊加密?」,我欣慰地看著你說:「嗯,是的,肯定是,不然老子往下沒法寫了,我特么都快編不下去了…」
- DES和3DES會將明文以64bit(8位元組)作為一個單元進行分組;
- AES則會將明文以128bit(16位元組)作為一個單元進行分組;
- 無論是AES還是DES,當最後一個分組的數據長度不滿足分組標準長度的時候,會用某種填充方式進行填充;
- AES對一個16位元組分組加密完畢後,分組大小依然為16位元組;
比如說這段明文「12345678abcdxxoo12345678abcdxxoo」,一共是32位元組,理論上說就會被先按照16位元組分組:「12345678abcdxxoo」是一組,剩下的「12345678abcdxxoo」是另外一組,我們用程式驗證一下:
<?php$ava_methods = openssl_get_cipher_methods();$my_method = 'aes-128-ecb';if ( !in_array( $my_method, $ava_methods ) ) { exit( '錯誤的加密方法'.PHP_EOL );}$key = "123456781234567";// 注意!這段明文長度剛好為32位元組!$data = "12345678abcdxxoo12345678abcdxxoo";echo '明文長度:'.strlen( $data ).PHP_EOL;$enc_data = openssl_encrypt( $data, $my_method, $key, OPENSSL_RAW_DATA );echo '密文長度:'.strlen( $enc_data ).PHP_EOL;注意明文長度我選擇剛好為32位元組。保存運行一下,至於你們那裡是什麼結果我不知道,反正我這裡是這樣的:

我日,感覺被打臉了,為毛加密後多出了16位元組?
我們將明文從32位元組的「12345678abcdxxoo12345678abcdxxoo」修改成33位元組的「12345678abcdxxoo12345678abcdxxooa」,這樣的話,明文會被分成三個16位元組的分組,由於最後一個分組只有一個位元組,所以剩餘15位元組會被填充:

似乎印證了我們一個猜測:當最後一個明文分組小於要求分組標準大小時,不會產生新的分組;當最後一個明文分組大於等於要求分組標準大小時,會產生一個新的分組。
我不想填充怎麼辦?修改一下加密和解密函數的最後那個OPENSSL_NO_PADDING選項即可,你們感受一下:
<?php$ava_methods = openssl_get_cipher_methods();// 選用aes-128-ecb$my_method = 'aes-128-ecb';if ( !in_array( $my_method, $ava_methods ) ) { exit( '錯誤的加密方法'.PHP_EOL );}// 加密用的密碼$key = "1234567812345678";// 加密的內容$data = "12345678abcdxxoo12345678abcdxxoo";echo '明文:'.$data.',長度為'.strlen( $data ).PHP_EOL;$enc_data = openssl_encrypt( $data, $my_method, $key, OPENSSL_NO_PADDING );echo '密文:'.$enc_data.',長度為'.strlen( $enc_data ).PHP_EOL;$hex = bin2hex( $enc_data );echo "密文十六進位:".$hex.',長度為'.strlen( $hex ).PHP_EOL;$dec_data = openssl_decrypt( $data, $my_method, $key, OPENSSL_NO_PADDING );$dec_data = openssl_decrypt( $enc_data, 'aes-128-ecb', $key, OPENSSL_NO_PADDING );echo "解密:".$dec_data.PHP_EOL;上面程式碼運行一下,結果如下圖:

有時候一些同學在做跨語言加解密的時候,基本上都是栽在了填充上。具體表現就是PHP加密後讓Java解密,然後發現解密失敗;或者Java加密PHP解密結果也是掛了。這個時候首先檢查一下PADDING這裡,基本上都是這樣的問題。
那麼說了這麼多,總結一下:
AES和DES以及3DES這種加密方式被稱為分組密碼,分組密碼每次只能加密固定長度的明文,所以如果明文很長的話,就需要輪流為每個分組明文進行加密,AES的分組長度是128bit,而DES的分組長度為64bit;如果一旦需要對多個分組進行輪流加密,加入明文被分成了三個明文分組,那麼就需要對三個明文進行迭代加密(粗暴理解就是輪流加密),然而會有很多種不同的迭代方式,這種不同的迭代方式專業名詞就叫「模式」,這些模式有:ECB、CBC、OFB、CFB、CTR… …
PS:⚠️對明文進行分組的方式是固定的,唯一不同的就是分組長度不一樣而已;模式是指對多個明文從第一個開始輪流加密到最後一個的這個過程,是怎麼輪流執行的。

