OpenSSL之AES加密算法-ECB模式

本文属于《OpenSSL加密算法库使用系列教程》之一,欢迎查看其它文章。

一、AES简介

密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。

这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS
PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。

AES属于对称加密算法,加解密使用同一个秘钥。

对称加密算法,一般有至少4种模式,即ECB、CBC、CFB、OFB等。

具体的加密原理,就不进行介绍了,本文主要从使用角度,进行说明。

以下命令行和编程实现,均基于OpenSSL开源库。在命令行中,我们可以使用命令实现对文件加解密,以验证我们的编程实现,是否正确。

二、ECB模式

电子密码本模式 Electronic Code Book(ECB)。这种模式是最早采用和最简单的模式,它将加密的数据分成若干组,每组的大小跟加密 密钥长度相同,然后每组都用相同的密钥进行加密。

其缺点是:电子编码薄模式用一个密钥加密消息的所有块,如果原消息中重复明文
块,则加密消息中的相应密文块也会重复,因此,电子编码薄模式适于加密小消息。

1、命令行操作

使用aes-128-ecb对hello.txt加密,128位密钥为8cc72b05705d5c46f412af8cbed55aad,密文为hello.en。

openssl enc -e -aes-128-ecb -in hello.txt -out hello.en -K 8cc72b05705d5c46f412af8cbed55aad

  
  

    使用aes-128-ecb对hello.en解密,128位密钥为8cc72b05705d5c46f412af8cbed55aad,解密后的文件为hello.de。

    openssl enc -d -aes-128-ecb -in hello.en -out hello.de -K 8cc72b05705d5c46f412af8cbed55aad
    
      
      

      2、函数说明

      生成加密/解密的Key:

      int AES_set_encrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
      int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
      
        
        
        参数名称含义
        userKey用户指定的密码。注意:只能是16/24/32字节。如果密码字符串长度不够,可以在字符串末尾追加一些特定的字符,或者重复密码字符串,直到满足最少的长度。
        bits密码位数。即userKey的长度 * 8,只能是128/192/256位。
        key向外输出参数
        返回值0 - 成功; -1 - userkey,key为空;-2 - 密钥长度不是128/192/256

        AES ECB加密/解密:

        void AES_ecb_encrypt(const unsigned char *in, unsigned char *out, const AES_KEY *key, const int enc);
        
          
          
          参数名称含义
          in输入数据,长度固定为16字节
          out输出数据,长度与输入数据一致,固定为16字节
          key使用AES_set_encrypt_key/AES_set_decrypt_key生成的Key
          encAES_ENCRYPT 代表加密, AES_DECRYPT代表解密

          3、编程实现

          由于ECB模式,每次只能处理一个块的数据,即16字节,所以如果需要处理任意长度的数据,那么需要在原始数据末尾,先进行填充,使得数据长度为16的整数倍,随后再分块进行加密。解密时,也需要分块解密,最后将解密后的数据,进行取消填充。

          (1)PKCS7填充方式

          AES支持多种填充方式:如NoPadding、PKCS5Padding、ISO10126Padding、PKCS7Padding、ZeroPadding。密文长度与填充方式关系,可参考《AES加密模式和填充模式》

          PKCS7填充方式:

          假设数据长度需要填充n(n>0)个字节才对齐,那么填充n个字节,每个字节都是n;如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小。

          举个例子最直观,这里以块大小为16字节,进行PKCS7填充,如下:

          数据1{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}
          填充后:{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06}
          尾部填充了66,填充后数据长度为16。
          
          数据2{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}
          填充后:{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10}
          尾部填充了1616,填充后数据长度为32

          Padding类实现了上述的PKCS7填充功能,如下:

          Padding.h

          #ifndef PADDING_H
          #define PADDING_H
          
          #include <QByteArray>
          
          /**
           * @brief The Padding class
           * 算法数据填充模式,提供对数据进行PKCS7填充和去除填充的相关函数。
           */
          class Padding
          {
          public:
              static int getPKCS7PaddedLength(int dataLen, int alignSize);
              static QByteArray PKCS7Padding(const QByteArray &in, int alignSize);
              static QByteArray PKCS7UnPadding(const QByteArray &in);
          };
          
          #endif // PADDING_H
          
          

          Padding.cpp

          #include "Padding.h"
          
          /**
           * @brief Padding::getPKCS7PaddedLength
           * 根据原始数据长度,计算进行PKCS7填充后的数据长度
           * @param dataLen 原始数据长度
           * @param alignSize 对齐字节数
           * @return 返回填充后的数据长度
           */
          int Padding::getPKCS7PaddedLength(int dataLen, int alignSize)
          {
              // 计算填充的字节数(按alignSize字节对齐进行填充)
              int remainder = dataLen % alignSize;
              int paddingSize = (remainder == 0) ? alignSize : (alignSize - remainder);
              return (dataLen + paddingSize);
          }
          
          /**
           * @brief Padding::PKCS7Padding
           * 采用PKCS7Padding方式,将in数据进行alignSize字节对齐填充。
           * 此函数用于加密前,对明文进行填充。
           * @param in 数据
           * @param alignSize 对齐字节数
           * @return 返回填充后的数据
           */
          QByteArray Padding::PKCS7Padding(const QByteArray &in, int alignSize)
          {
              // 计算需要填充字节数(按alignSize字节对齐进行填充)
              int remainder = in.size() % alignSize;
              int paddingSize = (remainder == 0) ? alignSize : (alignSize - remainder);
          
              // 进行填充
              QByteArray temp(in);
              temp.append(paddingSize, paddingSize);
              return temp;
          }
          
          /**
           * @brief Padding::PKCS7UnPadding
           * 采用PKCS7Padding方式,将in数据去除填充。
           * 此函数用于解密后,对解密结果进一步去除填充,以得到原始数据。
           * @param in 数据
           * @return 返回去除填充后的数据
           */
          QByteArray Padding::PKCS7UnPadding(const QByteArray &in)
          {
              char paddingSize = in.at(in.size() - 1);
              return in.left(in.size() - paddingSize);
          }
          
          

          代码比较简单,注释也说的很明白啦。

          (2)实现ECB模式加解密

          下面,函数已经封装完毕,如下:

          #include "AES.h"
          #include <openssl/modes.h>
          #include <openssl/aes.h>
          #include "Padding.h"
          
          /**
           * @brief AES::ecb_encrypt
           * ECB模式加解密,填充模式采用PKCS7Padding,
           * 支持对任意长度明文进行加解密。
           * @param in 输入数据
           * @param out 输出结果
           * @param key 密钥,长度必须是16/24/32字节,否则加密失败
           * @param enc true-加密,false-解密
           * @return 执行结果
           */
          bool AES::ecb_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, bool enc)
          {
              // 检查密钥合法性(只能是16、24、32字节)
              Q_ASSERT(key.size() == 16 || key.size() == 24 || key.size() == 32);
          
              if (enc)
              {
                  // 生成加密key
                  AES_KEY aes_key;
                  if (AES_set_encrypt_key((const unsigned char*)key.data(), key.size() * 8, &aes_key) != 0)
                  {
                      return false;
                  }
          
                  // 进行PKCS7Padding填充
                  QByteArray inTemp = Padding::PKCS7Padding(in, AES_BLOCK_SIZE);
          
                  // 执行ECB模式加密
                  out.resize(inTemp.size()); // 调整输出buf大小
                  for (int i = 0; i < inTemp.size() / AES_BLOCK_SIZE; i++)
                  {
                      AES_ecb_encrypt((const unsigned char*)inTemp.data() + AES_BLOCK_SIZE * i,
                                      (unsigned char*)out.data() + AES_BLOCK_SIZE * i,
                                      &aes_key,
                                      AES_ENCRYPT);
                  }
                  return true;
              }
              else
              {
                  // 生成解密key
                  AES_KEY aes_key;
                  if (AES_set_decrypt_key((const unsigned char*)key.data(), key.size() * 8, &aes_key) != 0)
                  {
                      return false;
                  }
          
                  // 执行ECB模式解密
                  out.resize(in.size()); // 调整输出buf大小
                  for (int i = 0; i < in.size() / AES_BLOCK_SIZE; i++)
                  {
                      AES_ecb_encrypt((const unsigned char*)in.data() + AES_BLOCK_SIZE * i,
                                      (unsigned char*)out.data() + AES_BLOCK_SIZE * i,
                                      &aes_key,
                                      AES_DECRYPT);
                  }
          
                  // 解除PKCS7Padding填充
                  out = Padding::PKCS7UnPadding(out);
                  return true;
              }
          }
          
          

          加密过程:先通过AES_set_encrypt_key函数生成加密key,然后将输入数据进行PKCS7填充,保证得到16字节整数倍明文,ECB模式密文长度等于明文长度,故将输出缓冲out调整为明文大小,以保存密文数据,最后,将明文按16字节,分块进行加密。

          解密过程:先通过AES_set_decrypt_key函数生成解密key,由于输入数据,即密文本身就是16字节整数倍,故直接按16字节进行分块解密,并将解密后的数据,进行去除填充,得到真正的明文。

          经测试,本函数支持对任意长度输入数据进行加解密。

          (3)测试代码
          void createTestData(QByteArray& data, int size)
          {
              data.resize(size);
              for (int i = 0; i < size; i++)
              {
                  data[i] = i % 128;
              }
          }
          
          void testAES(const QByteArray& data)
          {
              QByteArray plainText = data;
              QByteArray encryptText;
              QByteArray decryptText;
          
              QByteArray key = QByteArray::fromHex("8cc72b05705d5c46f412af8cbed55aad");
          
              // AES ecb模式加密验证
              AES aes;
              aes.ecb_encrypt(plainText, encryptText, key, true);     // 加密
              aes.ecb_encrypt(encryptText, decryptText, key, false);  // 解密
              qDebug() << "AES ecb encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
          }
          
          int main(int argc, char *argv[])
          {
              QCoreApplication a(argc, argv);
          
              // 产生1MB+3B的测试数据,为了使该测试数据长度,不为8或16的整数倍
              QByteArray data;
              createTestData(data, 1*1024*1024+3);
          
              // 测试AES
              testAES(data);     // 测试,直接调用OpenSSL中AES算法函数
          
              return a.exec();
          }
          
          

          执行结果:

          在这里插入图片描述

          本文涉及工程代码地址:https://gitee.com/bailiyang/cdemo/tree/master/Qt/49OpenSSL/OpenSSL



          若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!

          同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。

          在这里插入图片描述

          • 1
            点赞
          • 2
            收藏
            觉得还不错? 一键收藏
          • 0
            评论
          AES-128-ECB是一种加密算法,其中AES代表高级加密标准(Advanced Encryption Standard),128代表密钥长度为128位,ECB代表电子密码本(Electronic Codebook)模式。 在PHP中,可以使用内置的openssl扩展来实现AES-128-ECB加密。首先,需要创建一个密钥,一个具有128位长度的密钥。然后,可以使用openssl_encrypt函数来加密明文数据。 下面是一个使用AES-128-ECB加密和解密数据的PHP示例代码: ```php <?php function encrypt($data, $key) { $encrypted_data = openssl_encrypt($data, 'AES-128-ECB', $key, OPENSSL_RAW_DATA); $base64_encrypted_data = base64_encode($encrypted_data); return $base64_encrypted_data; } function decrypt($encrypted_data, $key) { $encrypted_data = base64_decode($encrypted_data); $decrypted_data = openssl_decrypt($encrypted_data, 'AES-128-ECB', $key, OPENSSL_RAW_DATA); return $decrypted_data; } // 使用一个密钥进行加密和解密 $key = "0123456789012345"; $data = "这是要加密的数据"; $encrypted_data = encrypt($data, $key); $decrypted_data = decrypt($encrypted_data, $key); echo "加密后的数据:" . $encrypted_data . "\n"; echo "解密后的数据:" . $decrypted_data . "\n"; ?> ``` 在上述代码中,我们定义了encrypt和decrypt两个函数来对数据进行加密和解密操作。首先,使用openssl_encrypt函数来加密数据,然后使用base64_encode函数将加密后的数据进行Base64编码。在解密时,首先使用base64_decode函数将加密数据进行Base64解码,然后使用openssl_decrypt函数对解码后的数据进行解密操作。 注意:AES-128-ECB模式不具备数据完整性保护,因此在实际应用中,建议使用更安全的加密模式,如AES-128-CBC模式
          评论
          添加红包

          请填写红包祝福语或标题

          红包个数最小为10个

          红包金额最低5元

          当前余额3.43前往充值 >
          需支付:10.00
          成就一亿技术人!
          领取后你会自动成为博主和红包主的粉丝 规则
          hope_wisdom
          发出的红包
          实付
          使用余额支付
          点击重新获取
          扫码支付
          钱包余额 0

          抵扣说明:

          1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
          2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

          余额充值