AES 加密方案的深入學(xué)習(xí)

之前通過重新學(xué)習(xí)各種常見 web 密碼的加密方案,對各種方案的加密原理和應(yīng)用場景有了更深入的理解,接下來就是再學(xué)習(xí)下數(shù)據(jù)加密方案,數(shù)據(jù)加密方案常見的其實(shí)就是對稱加密(AES)和非對稱加密(RSA)。

AES

AES(Advanced Encryption Standard)高級加密標(biāo)準(zhǔn),基于 Rijndael 算法,該算法為比利時密碼學(xué)家 Joan Daemen 和 Vincent Rijmen 所設(shè)計(jì),結(jié)合兩位作者的名字,以 Rijndael 為名投稿高級加密標(biāo)準(zhǔn)的甄選流程,經(jīng)過五年的甄選流程,在 2001 年由美國國家標(biāo)準(zhǔn)與技術(shù)研究院(NIST)發(fā)布,并在 2002 年成為有效的標(biāo)準(zhǔn),目前已經(jīng)成為最流行的對稱加密算法之一。

對稱加密算法是在加密和解密時使用相同的密鑰。舉個極簡的例子,假如 A 端和 B 端使用對稱加密的方案進(jìn)行數(shù)據(jù)通訊,他們共同的密鑰為數(shù)字 3,A 向 B 傳輸?shù)臄?shù)字在發(fā)送前都會乘以密鑰數(shù)字 3,當(dāng) B 端接收到 A 端的數(shù)字后,除以密鑰數(shù)字 3 就能得到真實(shí)的數(shù)字。

AES 的實(shí)現(xiàn)原理

AES 是一種區(qū)塊加密標(biāo)準(zhǔn),概括來說是將明文數(shù)據(jù)按每 128 位的大小切塊,再用密鑰將每個塊的數(shù)據(jù)進(jìn)行加密,密鑰長度可以選擇 128 位,192位,256位。因?yàn)?AES 算法基于 Rijndael 算法,并不完全等同,所以這兩者在使用上是有區(qū)別的, Rijndael 算法允許明文數(shù)據(jù)可以按照 128位、192位、256位 來切塊,支持的切分范圍更廣,但是當(dāng) Rijndael 算法 被選為 AES 時,NIST 限制了 AES 的參數(shù)范圍,只允許明文按照 128 位切塊。

AES 的加密過程是在一個 4×4 的字節(jié)矩陣上運(yùn)作的,其初值就是一個明文區(qū)塊,128 位對應(yīng)的就是 16 字節(jié),剛好構(gòu)成一個 4×4 的明文區(qū)塊。


明文矩陣區(qū)塊.png

AES 根據(jù)密鑰長度來確定加密輪數(shù),加密輪數(shù)是指,將當(dāng)前計(jì)算出來的密文按照相同的計(jì)算方式帶入下一輪進(jìn)行計(jì)算,輪數(shù)就是控制循環(huán)的次數(shù),密鑰長度和加密輪數(shù)的關(guān)系為:

密鑰長度 加密輪數(shù)
128 10
192 12
256 14
AES 加密步驟

1、輪密鑰加(AddRoundKey)
將回合密鑰和數(shù)據(jù)矩陣中值做異或運(yùn)算(XOR)得到新的數(shù)據(jù)矩陣,回合密鑰是每輪循環(huán)都會由密碼生成方案通過主密鑰生成的一個子密鑰,子密鑰也是一個 4×4 的字節(jié)矩陣。


異或運(yùn)算.png

2、字節(jié)代換(SubBytes)
將數(shù)據(jù)矩陣中的每個字節(jié)與 S 盒(S-BOX)中的對應(yīng)元素進(jìn)行置換,S 盒是密碼學(xué)中用于對輸入數(shù)據(jù)進(jìn)行非線性替代的基本組件,其主要目的是引入混淆(confusion),從而使得輸出與輸入之間的關(guān)系更加復(fù)雜,增強(qiáng)密碼系統(tǒng)的抗分析能力。AES 的 S 盒作為標(biāo)準(zhǔn)的一部分是固定不變的,所有人用到的 S 盒是一樣的。


s 盒.png

3、行移位(ShiftRows)
數(shù)據(jù)矩陣的第一行保持列位置不變,其他每一行都向左循環(huán)移動特定的偏移量,第二行移動1個偏移量,第三行移動2個偏移量,第四行移動3個偏移量。假如源數(shù)據(jù)為以下所示:


行移位.png

4、列混合(MixColumns)
使用固定矩陣對每一列進(jìn)行轉(zhuǎn)換,替換得到新的列。


固定矩陣.png

在每次加密輪次中重復(fù)上述 1-4 的步驟,只有在最后一次加密輪中省略第四步列混合,將每個塊加密后的密文進(jìn)行拼接,最后就得到了最終的密文數(shù)據(jù)。

分組密碼工作模式

分組密碼工作模式是指在使用分組密碼(如 AES、DES)進(jìn)行加密時,處理明文數(shù)據(jù)的方法。分組密碼通常將數(shù)據(jù)分成固定長度的塊來加密,而分組密碼工作模式?jīng)Q定了如何處理這些塊,以及如何將它們組合起來生成密文,工作模式有 ECB、CBC、OFB、CFB、GCM、CTR,其中最常用的是 CBC 模式。

  • CBC 密碼分組鏈接模式(Cipher Block Chaining Mode)
    每個明文塊在加密前會與前一個密文塊進(jìn)行異或(XOR)運(yùn)算,首個塊與初始化向量(IV)異或,初始化向量是隨機(jī)化的。因?yàn)槊總€塊的加密需要依賴上一個塊,所有并行處理能力不強(qiáng)。

塊數(shù)據(jù)補(bǔ)全

因?yàn)榉纸M密碼自身只能加密長度等于密碼分組長度的單塊數(shù)據(jù),所以當(dāng)明文塊數(shù)據(jù)長度不夠時,需要使用填充方式將明文塊大小填充到指定長度,填充模式現(xiàn)在普遍使用的是 pkcs7 標(biāo)準(zhǔn),pkcs7 填充方式為,先計(jì)算最后塊需要補(bǔ)齊的字節(jié)數(shù),然后每個字節(jié)填充的數(shù)據(jù)就是這個需要補(bǔ)齊的字節(jié)數(shù)。這里需要注意的是,pkcs7 要求即使塊的字節(jié)數(shù)和塊大小取余不為0,也要填充一個16字節(jié)的塊,方便之后的塊數(shù)據(jù)去除 pkcs7 填充字節(jié)。

代碼實(shí)踐

  • php
    php 想要實(shí)現(xiàn) AES 加解密需要安裝 OpenSSL 擴(kuò)展
<?php
// 加密數(shù)據(jù)
function aesEncrypt($plaintext, $key, $iv) {
    // Ensure the key is 32 bytes (256 bits) for AES-256
    $key = substr(hash('sha256', $key, true), 0, 32);

    // Encrypt the plaintext
    $ciphertext = openssl_encrypt($plaintext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);

    // Return the base64 encoded ciphertext
    return base64_encode($ciphertext);
}

// 解密數(shù)據(jù)
function aesDecrypt($ciphertext, $key, $iv) {
    // Ensure the key is 32 bytes (256 bits) for AES-256
    $key = substr(hash('sha256', $key, true), 0, 32);

    // Decode the base64 encoded ciphertext
    $ciphertext = base64_decode($ciphertext);

    // Decrypt the ciphertext
    $plaintext = openssl_decrypt($ciphertext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);

    return $plaintext;
}

// 明文
$plaintext = "This is a secret message.";
$key = "your-secret-key";
$iv = openssl_random_pseudo_bytes(16); // IV should be 16 bytes for AES-256-CBC

// 加密數(shù)據(jù)
$ciphertext = aesEncrypt($plaintext, $key, $iv);
echo "Ciphertext: " . $ciphertext . PHP_EOL;

// 解密信息
$decryptedText = aesDecrypt($ciphertext, $key, $iv);
echo "Decrypted text: " . $decryptedText . PHP_EOL;
?>
  • go 代碼實(shí)現(xiàn)
    go 沒有 php 那樣方便的函數(shù),需要自己實(shí)現(xiàn)塊填充和塊去除。
package encrypt

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "encoding/hex"
    "errors"
    "io"
)

// AesCbcEncrypt AES CBC模式加密
func AesCbcEncrypt(content, secret string) string {
    //記載密鑰,并且轉(zhuǎn)16進(jìn)制,方便對比密鑰字節(jié)長度
    key, _ := hex.DecodeString(secret)
    //明文字符串轉(zhuǎn)字節(jié)切片
    plaintext := []byte(content)

    //采用 pkcs7 標(biāo)準(zhǔn)進(jìn)行塊數(shù)據(jù)填充
    plaintext = pkcs7Pad(plaintext, aes.BlockSize)

    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err)
    }

    //CBC 模式的需要加向量值 IV,IV 的字節(jié)數(shù)和塊的字節(jié)長度相等,將IV拼接在明文的前邊
    ciphertext := make([]byte, aes.BlockSize+len(plaintext))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        panic(err)
    }

    mode := cipher.NewCBCEncrypter(block, iv)
    mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)

    //轉(zhuǎn)base64方便傳輸
    result := base64.StdEncoding.EncodeToString(ciphertext)
    return result
}

// pkcs7 填充標(biāo)準(zhǔn)
func pkcs7Pad(data []byte, blockSize int) []byte {
    padding := blockSize - len(data)%blockSize
    padText := make([]byte, len(data)+padding)
    copy(padText, data)
    for i := len(data); i < len(padText); i++ {
        padText[i] = byte(padding)
    }
    return padText
}

// AesDecrypt AES CBC模式解密
func AesCbcDecrypt(content, secret string) []byte {
    ciphertext, err := base64.StdEncoding.DecodeString(content)
    if err != nil {
        panic(err)
    }

    key, _ := hex.DecodeString(secret)

    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err)
    }

    if len(ciphertext) < aes.BlockSize {
        panic("密文長度過短")
    }

    //取前16個字節(jié)作為 IV 值
    iv := ciphertext[:aes.BlockSize]
    cipherContent := ciphertext[aes.BlockSize:]

    //驗(yàn)證塊的完整性
    if len(cipherContent)%aes.BlockSize != 0 {
        panic("密文格式錯誤")
    }

    mode := cipher.NewCBCDecrypter(block, iv)

    paddingText := make([]byte, len(cipherContent))
    mode.CryptBlocks(paddingText, cipherContent)
    result, err := pkcs7UnPad(paddingText, aes.BlockSize)
    if err != nil {
        panic(err)
    }

    return result
}

// pkcs7 塊填充去除
func pkcs7UnPad(plainText []byte, blockSize int) ([]byte, error) {
    length := len(plainText)
    number := int(plainText[length-1])

    if number >= length || number > blockSize {
        return nil, errors.New("byte length is error")
    }
    return plainText[:length-number], nil
}


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容