AES 加密方案的深入学习

之前通过重新学习各种常见 web 密码的加密方案,对各种方案的加密原理和应用场景有了更深入的理解,接下来就是再学习下数据加密方案,数据加密方案常见的其实就是对称加密(AES)和非对称加密(RSA)。

AES

AES(Advanced Encryption Standard)高级加密标准,基于 Rijndael 算法,该算法为比利时密码学家 Joan Daemen 和 Vincent Rijmen 所设计,结合两位作者的名字,以 Rijndael 为名投稿高级加密标准的甄选流程,经过五年的甄选流程,在 2001 年由美国国家标准与技术研究院(NIST)发布,并在 2002 年成为有效的标准,目前已经成为最流行的对称加密算法之一。

对称加密算法是在加密和解密时使用相同的密钥。举个极简的例子,假如 A 端和 B 端使用对称加密的方案进行数据通讯,他们共同的密钥为数字 3,A 向 B 传输的数字在发送前都会乘以密钥数字 3,当 B 端接收到 A 端的数字后,除以密钥数字 3 就能得到真实的数字。

AES 的实现原理

AES 是一种区块加密标准,概括来说是将明文数据按每 128 位的大小切块,再用密钥将每个块的数据进行加密,密钥长度可以选择 128 位,192位,256位。因为 AES 算法基于 Rijndael 算法,并不完全等同,所以这两者在使用上是有区别的, Rijndael 算法允许明文数据可以按照 128位、192位、256位 来切块,支持的切分范围更广,但是当 Rijndael 算法 被选为 AES 时,NIST 限制了 AES 的参数范围,只允许明文按照 128 位切块。

AES 的加密过程是在一个 4×4 的字节矩阵上运作的,其初值就是一个明文区块,128 位对应的就是 16 字节,刚好构成一个 4×4 的明文区块。


明文矩阵区块.png

AES 根据密钥长度来确定加密轮数,加密轮数是指,将当前计算出来的密文按照相同的计算方式带入下一轮进行计算,轮数就是控制循环的次数,密钥长度和加密轮数的关系为:

密钥长度 加密轮数
128 10
192 12
256 14
AES 加密步骤

1、轮密钥加(AddRoundKey)
将回合密钥和数据矩阵中值做异或运算(XOR)得到新的数据矩阵,回合密钥是每轮循环都会由密码生成方案通过主密钥生成的一个子密钥,子密钥也是一个 4×4 的字节矩阵。


异或运算.png

2、字节代换(SubBytes)
将数据矩阵中的每个字节与 S 盒(S-BOX)中的对应元素进行置换,S 盒是密码学中用于对输入数据进行非线性替代的基本组件,其主要目的是引入混淆(confusion),从而使得输出与输入之间的关系更加复杂,增强密码系统的抗分析能力。AES 的 S 盒作为标准的一部分是固定不变的,所有人用到的 S 盒是一样的。


s 盒.png

3、行移位(ShiftRows)
数据矩阵的第一行保持列位置不变,其他每一行都向左循环移动特定的偏移量,第二行移动1个偏移量,第三行移动2个偏移量,第四行移动3个偏移量。假如源数据为以下所示:


行移位.png

4、列混合(MixColumns)
使用固定矩阵对每一列进行转换,替换得到新的列。


固定矩阵.png

在每次加密轮次中重复上述 1-4 的步骤,只有在最后一次加密轮中省略第四步列混合,将每个块加密后的密文进行拼接,最后就得到了最终的密文数据。

分组密码工作模式

分组密码工作模式是指在使用分组密码(如 AES、DES)进行加密时,处理明文数据的方法。分组密码通常将数据分成固定长度的块来加密,而分组密码工作模式决定了如何处理这些块,以及如何将它们组合起来生成密文,工作模式有 ECB、CBC、OFB、CFB、GCM、CTR,其中最常用的是 CBC 模式。

  • CBC 密码分组链接模式(Cipher Block Chaining Mode)
    每个明文块在加密前会与前一个密文块进行异或(XOR)运算,首个块与初始化向量(IV)异或,初始化向量是随机化的。因为每个块的加密需要依赖上一个块,所有并行处理能力不强。

块数据补全

因为分组密码自身只能加密长度等于密码分组长度的单块数据,所以当明文块数据长度不够时,需要使用填充方式将明文块大小填充到指定长度,填充模式现在普遍使用的是 pkcs7 标准,pkcs7 填充方式为,先计算最后块需要补齐的字节数,然后每个字节填充的数据就是这个需要补齐的字节数。这里需要注意的是,pkcs7 要求即使块的字节数和块大小取余不为0,也要填充一个16字节的块,方便之后的块数据去除 pkcs7 填充字节。

代码实践

  • php
    php 想要实现 AES 加解密需要安装 OpenSSL 扩展
<?php
// 加密数据
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);
}

// 解密数据
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

// 加密数据
$ciphertext = aesEncrypt($plaintext, $key, $iv);
echo "Ciphertext: " . $ciphertext . PHP_EOL;

// 解密信息
$decryptedText = aesDecrypt($ciphertext, $key, $iv);
echo "Decrypted text: " . $decryptedText . PHP_EOL;
?>
  • go 代码实现
    go 没有 php 那样方便的函数,需要自己实现块填充和块去除。
package encrypt

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

// AesCbcEncrypt AES CBC模式加密
func AesCbcEncrypt(content, secret string) string {
    //记载密钥,并且转16进制,方便对比密钥字节长度
    key, _ := hex.DecodeString(secret)
    //明文字符串转字节切片
    plaintext := []byte(content)

    //采用 pkcs7 标准进行块数据填充
    plaintext = pkcs7Pad(plaintext, aes.BlockSize)

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

    //CBC 模式的需要加向量值 IV,IV 的字节数和块的字节长度相等,将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)

    //转base64方便传输
    result := base64.StdEncoding.EncodeToString(ciphertext)
    return result
}

// pkcs7 填充标准
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个字节作为 IV 值
    iv := ciphertext[:aes.BlockSize]
    cipherContent := ciphertext[aes.BlockSize:]

    //验证块的完整性
    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
}


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容