1. 程式人生 > >理解AES對稱加密

理解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