理解AES對稱加密
很多人對於AES加密並不是很瞭解,導致互相之間進行加密解密困難。
本文用簡單的方式來介紹AES在使用上需要的知識,而不涉及內部演算法。最後給出例子來幫助理解AES加密解密的使用方法。
AES的麻煩
相比於其他加密,AES加密似乎模式很多,包括ECB、CBC等等等等,每個模式又包括IV引數和Padding引數,並且,不同語言對AES加密的庫設計有區別。這些導致AES加密在不同人之間聯調會很麻煩。
AES屬於塊加密
不難理解,對越長的字串進行加密,代價越大,所以通常對明文進行分段,然後對每段明文進行加密,最後再拼成一個字串。塊加密的一個要面臨的問題就是如何填滿最後一塊?所以這就是PADDING的作用,使用各種方式填滿最後一塊字串,所以對於解密端,也需要用同樣的PADDING來找到最後一塊中的真實資料的長度。
加密模式
AES分為幾種模式,比如ECB,CBC,CFB等等,這些模式除了ECB由於沒有使用IV而不太安全,其他模式差別並沒有太明顯,大部分的區別在IV和KEY來計算密文的方法略有區別。具體可參考WIKI的說明。
另外,AES分為AES128,AES256等,表示期待祕鑰的長度,比如AES256祕鑰的長度應該是256/8的32位元組,一些語言的庫會進行自動擷取,讓人以為任何長度的祕鑰都可以。而這其實是有區別的。
IV的作用
IV稱為初始向量,不同的IV加密後的字串是不同的,加密和解密需要相同的IV,既然IV看起來和key一樣,卻還要多一個IV的目的,對於每個塊來說,key是不變的,但是隻有第一個塊的IV是使用者提供的,其他塊IV都是自動生成。
IV的長度為16位元組。超過或者不足,可能實現的庫都會進行補齊或截斷。但是由於塊的長度是16位元組,所以一般可以認為需要的IV是16位元組。
PADDING
AES塊加密說過,PADDING是用來填充最後一塊使得變成一整塊,所以對於加密解密兩端需要使用同一的PADDING模式,大部分PADDING模式為PKCS5, PKCS7, NOPADDING。
加密解密端
所以,在設計AES加密的時候
- 對於加密端,應該包括:加密祕鑰長度,祕鑰,IV值,加密模式,PADDING方式。
- 對於解密端,應該包括:解密祕鑰長度,祕鑰,IV值,解密模式,PADDING方式。
Nodejs實現
這裡使用Nodejs的cryptojs庫模擬AES加密解密
var crypto = require("crypto");
var algorithm='aes-256-cbc' ;
var key = new Buffer("aaaabbbbccccddddeeeeffffgggghhhh");
var iv = new Buffer("1234567812345678");
function encrypt(text){
var cipher=crypto.createCipheriv(algorithm,key,iv);
cipher.update(text,"utf8");
return cipher.final("base64");
}
function decrypt(text){
var cipher=crypto.createDecipheriv(algorithm,key,iv);
cipher.update(text,"base64");
return cipher.final("utf8");
}
var text="ni你好hao";
var encoded=encrypt(text)
console.log(encoded);
console.log(decrypt(encoded))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
結果如下
WfH4hzIc3dc0pjxa9V/RgQ==
ni你好hao
nodejs自帶的並不能自動配置padding等引數,演示起來並不方便。
於是使用另一個框架crypto-js的nodejs庫實現和之前完全相同的版本
var CryptoJS = require("crypto-js");
var key ="aaaabbbbccccddddeeeeffffgggghhhh";
var iv = "1234567812345678";
function encrypt(text){
return CryptoJS.AES.encrypt(text,CryptoJS.enc.Utf8.parse(key),{
iv:CryptoJS.enc.Utf8.parse(iv),
mode:CryptoJS.mode.CBC,
padding:CryptoJS.pad.Pkcs7
})
}
function decrypt(text){
var result = CryptoJS.AES.decrypt(text,CryptoJS.enc.Utf8.parse(key),{
iv:CryptoJS.enc.Utf8.parse(iv),
mode:CryptoJS.mode.CBC,
padding:CryptoJS.pad.Pkcs7
})
return result.toString(CryptoJS.enc.Utf8)
}
var text="ni你好hao";
var encoded=encrypt(text)
console.log(encoded.toString());
console.log(decrypt(encoded))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
現在aes的引數都變成可配置的,接下來驗證一下之前對AES的理解。
- 改變IV的長度,發現當IV大於16位元組的時候,不管16位元組之後的是什麼,都不影響加密結果,應該是種自動擷取機制(nodejs原生庫IV不是16位元組,就會報錯)
- 改變IV的長度,當IV小於16位元組,還可以成功加密,可能是自動補齊機制
- 加密IV和解密IV不同的時候,並不影響解密是否成功,但是解密的結果有差別,比如將解密的IV變成1234567813345678,則解密結果變為ni你好h`o
- 修改padding,加密解密的padding換成NoPadding,發現解密之後生成utf8字串出錯
- 經過多次嘗試,加密為Pkcs7和ZeroPadding時,加密後的字串變化顯著,這時解密用任何padding模式,都可以成功解密。
ni你好hao,經過Pkcs7後,輸出為
WfH4hzIc3dc0pjxa9V/RgQ==
nopadding後,輸出為
OtSNypfx1SF6C2E=
zeropadding後,輸出為
OtSNypfx1SF6C2GfyXMidA==
Pkcs7的結果和其他結果相差很大,很難相信其padding是補充最後一塊
有趣的是Pkcs7的結果和zeropadding的結果通過同樣的解密設定,能解出同樣的字串ni你好hao
總結
AES加密解密的祕鑰有一對,一個是IV一個是KEY,並且他們的長度都有嚴格要求。
Padding的作用似乎不只是補齊最後,如果自己什麼都對,但是加密失敗,可以嘗試不同Padding