AES加解密-CBC ECB


要想學習AES,首先要清楚三個基本的概念:密鑰、填充、模式。

1、密鑰

密鑰是AES算法實現加密和解密的根本。對稱加密算法之所以對稱,是因為這類算法對明文的加密和解密需要使用同一個密鑰。

AES支持三種長度的密鑰: 128位,192位,256位

平時大家所說的AES128AES192AES256,實際上就是指AES算法對不同長度密鑰的使用。

三種密鑰的區別:

從安全性來看,AES256安全性最高。從性能看,AES128性能最高。本質原因是它們的加密處理輪數不同。

2、填充

要想了解填充的概念,我們先要了解AES的分組加密特性。
什么是分組加密?

AES算法在對明文加密的時候,並不是把整個明文一股腦的加密成一整段密文,而是把明文拆分成一個個獨立的明文塊,每一個明文塊長度128bit

這些明文塊經過AES加密器復雜處理,生成一個個獨立的密文塊,這些密文塊拼接在一起,就是最終的AES加密的結果。

但這里涉及到一個問題,假如一段明文長度是196bit,如果按每128bit一個明文塊來拆分的話,第二個明文塊只有64bit,不足128bit。這時候怎么辦呢?就需要對明文塊進行填充(Padding) 。

幾種典型的填充方式:

NoPadding: 不做任何填充,但是要求明文必須是16字節的整數倍。
PKCS5Padding(默認): 如果明文塊少於16個字節(128bit),在明文塊末尾補足相應數量的字符,且每個字節的值等於缺少的字符數。 比如明文:{1,2,3,4,5,a,b,c,d,e},缺少6個字節,則補全為{1,2,3,4,5,a,b,c,d,e,6,6,6,6,6,6 }
ISO10126Padding:如果明文塊少於16個字節(128bit),在明文塊末尾補足相應數量的字節,最后一個字符值等於缺少的字符數,其他字符填充隨機數。比如明文:{1,2,3,4,5,a,b,c,d,e},缺少6個字節,則可能補全為{1,2,3,4,5,a,b,c,d,e,5,c,3,G,$,6}
PKCS7Padding原理與PKCS5Padding相似,區別是PKCS5Paddingblocksize為8字節,而PKCS7Paddingblocksize可以為1到255字節

需要注意的是,如果在AES加密的時候使用了某一種填充方式,解密的時候也必須采用同樣的填充方式。

3、模式

AES的工作模式,體現在把明文塊加密成密文塊的處理過程中。AES加密算法提供了五種不同的工作模式:CBC,ECB,CTR,CFB,OFB
模式之間的主題思想是近似的,在處理細節上有一些差別

AES加密算法 - 加密模式

ECB模式
  優點:
  1.簡單;
  2.有利於並行計算;
  3.誤差不會被傳送;
  缺點:
  1.不能隱藏明文的模式;
  2.可能對明文進行主動攻擊;
CBC模式:
  優點:
  1.不容易主動攻擊,安全性好於ECB,適合傳輸長度長的報文,是SSL、IPSec的標准。
  缺點:
  1.不利於並行計算
  2.誤差傳遞;
  3.需要初始化向量IV
CFB模式:
  優點:
  1.隱藏了明文模式;
  2.分組密碼轉化為流模式;
  3.可以及時加密傳送小於分組的數據;
  缺點:
  1.不利於並行計算;
  2.誤差傳送:一個明文單元損壞影響多個單元;
  3.唯一的IV;
ofb模式:
  優點:
  1.隱藏了明文模式;
  2.分組密碼轉化為流模式;
  3.可以及時加密傳送小於分組的數據;
  缺點:
  1.不利於並行計算;
  2.對明文的主動攻擊是可能的;
  3.誤差傳送:一個明文單元損壞影響多個單元;

上面四個不同模式原理圖鏈接:https://www.cnblogs.com/adylee/archive/2007/09/14/893438.html

3.1、ECB模式簡介

img

2)、CBC模式簡介

下圖中,IV一般為16字節全0,數據塊長度為16字節的整數倍,則在此數據塊后附加一個8字節長的數據塊,

附加的數據塊為:16進制的“80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00”

img

cbc本質上和ecb差別不大,唯一區別是將前一次加密結果,與要加密的內容異或。因此,cbc的並行性較差,因為每次都要等待前一次的結果,而ecb則不用,速度較快。其主要區別仍然看文章開頭,原理圖看參考鏈接。

3.2、AES算法ECB模式

生成加密/解密的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,否則是負數。

使用函數AES_ecb_encrypt對數據進行加解密

函數原型:

void AES_ecb_encrypt(const unsigned char *in, unsigned char *out, const AES_KEY *key, const int enc);

函數說明:

AES加密/解密單個數據塊(16個字節),ECB模式

參數說明:

參數名稱 描述
in 需要加密/解密的數據
out 計算后輸出的數據
key 密鑰
enc AES_ENCRYPT 代表加密, AES_DECRYPT代表解密

3.3、AES算法CBC模式

生成加密/解密的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,否則是負數。

使用AES_cbc_encrypt對數據進行加解密

void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
                     size_t length, const AES_KEY *key,
                     unsigned char *ivec, const int enc);

函數說明:

AES加密/解密單個數據塊(16個字節),CBC模式

參數說明:

參數名稱 描述
in 輸入數據。長度任意。
out 輸出數據。能夠容納下輸入數據,且長度必須是16字節的倍數。
length 輸出數據的實際長度。
key 使用AES_set_encrypt/decrypt_key生成的Key。
ivec 可讀寫的一塊內存。長度必須是16字節。
enc 是否是加密操作。AES_ENCRYPT表示加密,AES_DECRYPT表示解密。

​ 這個函數比AES_encrypt多了一個ivec參數,ivec的內容可以任意指定,但是加密和解密操作必須使用同樣的數據。在AES_cbc_encrypt底層,實際上是每16個字節做一次處理,先和ivec做異或運算,然后調用AES_encrypt函數進行加密
AES_cbc_encrypt在加密的過程中會修改ivec的內容,因此ivec參數不能是一個常量,而且不能在傳遞給加密函數后再立馬傳遞給解密函數,必須重新賦值之后再傳遞給解密函數。

關於輸出數據的長度
輸出數據緩沖區的長度必須是16字節的倍數,加密完成后,比輸入長度多出來的輸出數據是不可以丟棄的。因此,存檔的時候,需要記錄原始數據的長度

關於輸入數據的長度不必是16字節的倍數(做個備忘):
下面是AES_cbc_encrypt函數的底層實現代碼

    ... 
    //處理16字節倍數的數據
    while (len >= 16) {
        for (n = 0; n < 16; ++n)
            out[n] = in[n] ^ iv[n];
        (*block) (out, out, key); //調用AES_encrypt處理數據
        iv = out;
        len -= 16;
        in += 16;
        out += 16;
    }
    //當數據小於16字節的時候,進入下面的循環
    while (len) { 
        for (n = 0; n < 16 && n < len; ++n)
            out[n] = in[n] ^ iv[n];
        for (; n < 16; ++n)
            out[n] = iv[n]; //使用ivec補齊不足16字節的部分
        (*block) (out, out, key); //調用AES_encrypt處理數據
        iv = out;
        if (len <= 16)
            break;
        len -= 16;
        in += 16;
        out += 16;
    }

ECBCBC都是封裝下面的加密解密函數實現的。

使用AES加密/解密

void AES_encrypt(const unsigned char *in, unsigned char *out, const AES_KEY *key);
void AES_decrypt(const unsigned char *in, unsigned char *out, const AES_KEY *key);

參數說明:

參數名稱 描述
in 輸入數據。必須是16字節
out 輸出數據。必須是16字節
key 使用AES_set_encrypt/decrypt_key生成的Key。

AES_encrypt/AES_decrypt一次只處理16個字節。如果輸入數據較長,你需要使用循環語句,每16個字節處理一次,直到所有數據處理完畢。如果數據不足16字節,可以用0填充至16字節。

4、加密解密后的長度

AES有幾種擴展算法,其中ecb和cbc需要填充,即加密后長度可能會不一樣,cfb和ofb不需要填充,密文長度和明文長度一樣

5、實戰代碼

/usr/include/openssl/aes.h

#ifndef HEADER_AES_H
# define HEADER_AES_H

# include <openssl/opensslconf.h>

# ifdef OPENSSL_NO_AES
#  error AES is disabled.
# endif

# include <stddef.h>

# define AES_ENCRYPT     1
# define AES_DECRYPT     0

/*
 * Because array size can't be a const in C, the following two are macros.
 * Both sizes are in bytes.
 */
# define AES_MAXNR 14
# define AES_BLOCK_SIZE 16

#ifdef  __cplusplus
extern "C" {
#endif

/* This should be a hidden type, but EVP requires that the size be known */
struct aes_key_st {
# ifdef AES_LONG
    unsigned long rd_key[4 * (AES_MAXNR + 1)];
# else
    unsigned int rd_key[4 * (AES_MAXNR + 1)];
# endif
    int rounds;
};
typedef struct aes_key_st AES_KEY;

const char *AES_options(void);

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);

int private_AES_set_encrypt_key(const unsigned char *userKey, const int bits,
                                AES_KEY *key);
int private_AES_set_decrypt_key(const unsigned char *userKey, const int bits,
                                AES_KEY *key);

void AES_encrypt(const unsigned char *in, unsigned char *out,
                 const AES_KEY *key);
void AES_decrypt(const unsigned char *in, unsigned char *out,
                 const AES_KEY *key);

void AES_ecb_encrypt(const unsigned char *in, unsigned char *out,
                     const AES_KEY *key, const int enc);
void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
                     size_t length, const AES_KEY *key,
                     unsigned char *ivec, const int enc);
void AES_cfb128_encrypt(const unsigned char *in, unsigned char *out,
                        size_t length, const AES_KEY *key,
                        unsigned char *ivec, int *num, const int enc);
void AES_cfb1_encrypt(const unsigned char *in, unsigned char *out,
                      size_t length, const AES_KEY *key,
                      unsigned char *ivec, int *num, const int enc);
void AES_cfb8_encrypt(const unsigned char *in, unsigned char *out,
                      size_t length, const AES_KEY *key,
                      unsigned char *ivec, int *num, const int enc);
void AES_ofb128_encrypt(const unsigned char *in, unsigned char *out,
                        size_t length, const AES_KEY *key,
                        unsigned char *ivec, int *num);
void AES_ctr128_encrypt(const unsigned char *in, unsigned char *out,
                        size_t length, const AES_KEY *key,
                        unsigned char ivec[AES_BLOCK_SIZE],
                        unsigned char ecount_buf[AES_BLOCK_SIZE],
                        unsigned int *num);
/* NB: the IV is _two_ blocks long */
void AES_ige_encrypt(const unsigned char *in, unsigned char *out,
                     size_t length, const AES_KEY *key,
                     unsigned char *ivec, const int enc);
/* NB: the IV is _four_ blocks long */
void AES_bi_ige_encrypt(const unsigned char *in, unsigned char *out,
                        size_t length, const AES_KEY *key,
                        const AES_KEY *key2, const unsigned char *ivec,
                        const int enc);

int AES_wrap_key(AES_KEY *key, const unsigned char *iv,
                 unsigned char *out,
                 const unsigned char *in, unsigned int inlen);
int AES_unwrap_key(AES_KEY *key, const unsigned char *iv,
                   unsigned char *out,
                   const unsigned char *in, unsigned int inlen);


#ifdef  __cplusplus
}
#endif

#endif                          /* !HEADER_AES_H */

5.1、CBC模式的實戰代碼

頭文件:AES_CBC256.h

#ifndef _AES_CBC256_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/aes.h>
#include <stddef.h>

#define _AES_CBC256_H_
#define USER_KEY_LENGTH 32
#define IVEC_LENGTH     16
#define AES_BLOCK_SIZE  16
#define BITS_LENGTH   (USER_KEY_LENGTH * 8)
class AES_CBC256 {

public:
     AES_CBC256();
     virtual ~AES_CBC256();
     // CBC Mode Encrypt
     bool AES_CBC256_Encrypt(const unsigned char *in, unsigned char *out, size_t length);
     // CBC Mode Decrypt
     bool AES_CBC256_Decrypt(const unsigned char *in, unsigned char *out, size_t length);

     unsigned char m_userKey [USER_KEY_LENGTH];
     unsigned char m_ivec [IVEC_LENGTH];  // Default value is all 0 of 16

};
#endif   // _AES_CBC256_H_

源文件:AES_CBC256.cpp

#ifndef _AES_CBC256_H_
#   include "AES_CBC256.h"
#endif

AES_CBC256::AES_CBC256() {
    memcpy(m_userKey, "XZJE151628AED2A6ABF7158809CF4F3C2B7E151628AED2A6ABF7158809CF4FTP", USER_KEY_LENGTH);
    memcpy(m_ivec, "XZJ2030405060708090A0B0C0D0E0FTP", IVEC_LENGTH);  // Vector initialization

}
AES_CBC256::~AES_CBC256() {

}

bool AES_CBC256::AES_CBC256_Encrypt(const unsigned char *in, unsigned char *out, size_t length) {

    if (0 != (length % AES_BLOCK_SIZE)) {
        printf("%s\n", "the length is not multiple of AES_BLOCK_SIZE(16bytes)");
        return false;
    }
    unsigned char ivec [IVEC_LENGTH];
    memcpy(ivec, m_ivec, IVEC_LENGTH);
    AES_KEY key;
    // get the key with userkey
    if (AES_set_encrypt_key(m_userKey, BITS_LENGTH, &key) < 0) {
    	printf("%s\n", "get the key error");
    	return false;
    } else {
    	printf("%s\n", "get the key successful");
    }

    AES_cbc_encrypt(in, out, length, &key, ivec, AES_ENCRYPT);
    return true;
}

bool AES_CBC256::AES_CBC256_Decrypt(const unsigned char *in, unsigned char *out, size_t length) {

    if (0 != (length % AES_BLOCK_SIZE)) {
        printf("%s\n", "the length is not multiple of AES_BLOCK_SIZE(16bytes)");
        return false;
    }
    unsigned char ivec [IVEC_LENGTH];
    memcpy(ivec, m_ivec, IVEC_LENGTH);
    AES_KEY key;
    // get the key with userkey
    if (AES_set_decrypt_key(m_userKey, BITS_LENGTH, &key) < 0) {
    	printf("%s\n", "get the key error");
    	return false;
    } else {
    	printf("%s\n", "get the key successful");
    }

    AES_cbc_encrypt(in, out, length, &key, ivec, AES_DECRYPT);
    return true;
}

入口測試文件:AES_main.cpp

#ifndef _AES_CBC256_H_
#   include "AES_CBC256.h"
#endif
#define NAME_SIZE 34

struct persion
{
    int age;
    unsigned char name [NAME_SIZE];
};


int main(int argc, char const *argv[])
{
    AES_CBC256 m_pcAES_CBC256;
    persion m_persion = {28,"xzj8023tp"};
    size_t length = 0;
    if (0 == (sizeof(persion) % AES_BLOCK_SIZE)) {
    	length = sizeof(persion);
    } else {
	length = sizeof(persion) + (AES_BLOCK_SIZE - sizeof(persion) % AES_BLOCK_SIZE);
    }
    unsigned char *encrypt_in_data  = new unsigned char[sizeof(persion)];
    unsigned char *encrypt_out_data  = new unsigned char[length];
    memcpy(encrypt_in_data, &m_persion, sizeof(persion));
    // encrypt
    bool encrypt_ret = m_pcAES_CBC256.AES_CBC256_Encrypt(encrypt_in_data, encrypt_out_data, length);
    if (false == encrypt_ret) {
	printf("encrypt error!\n");
    } else {
	printf("encrypt successful!\n");
    }

    //decrypt
    unsigned char *decrypt_out_data  = new unsigned char[length];
    bool decrypt_ret = m_pcAES_CBC256.AES_CBC256_Decrypt(encrypt_out_data, decrypt_out_data, length);
    if (false == decrypt_ret) {
	printf("decrypt error!\n");
    } else {
	printf("decrypt successful!\n");
	persion showData;
	memcpy(&showData, decrypt_out_data, sizeof(persion));
        printf("my name is [%s] and I am [%d] years old\n", showData.name, showData.age);
    }
    return 0;
}

當出現下面錯誤的時候

xzj@xzj-virtual-machine:~/Code/C++Code/AES$ g++ AES_main.cpp  AES_CBC256.cpp  -o main.run
/tmp/ccsNmQln.o:在函數‘AES_CBC256::AES_CBC256_Encrypt(unsigned char const*, unsigned char*, unsigned long)’中:
AES_CBC256.cpp:(.text+0x131):對‘AES_set_encrypt_key’未定義的引用
AES_CBC256.cpp:(.text+0x184):對‘AES_cbc_encrypt’未定義的引用
/tmp/ccsNmQln.o:在函數‘AES_CBC256::AES_CBC256_Decrypt(unsigned char const*, unsigned char*, unsigned long)’中:
AES_CBC256.cpp:(.text+0x235):對‘AES_set_decrypt_key’未定義的引用
AES_CBC256.cpp:(.text+0x288):對‘AES_cbc_encrypt’未定義的引用
collect2: error: ld returned 1 exit status

鏈接到openssl庫 – 將其添加到您的命令行:-lssl -lcrypto

在Linux里的編譯命令:

g++ AES_main.cpp  AES_CBC256.cpp  -o main.run -lssl -lcrypto

當出現下面錯誤openssl/aes.h: 沒有那個文件或目錄

xzj@xzj-virtual-machine:~/Code/C++Code/AES$ g++ AES_main.cpp  AES_CBC256.cpp  -o main.run
In file included from AES_main.cpp:2:0:
AES_CBC256.h:5:25: fatal error: openssl/aes.h: 沒有那個文件或目錄
 #include <openssl/aes.h>
                         ^
compilation terminated.
In file included from AES_CBC256.cpp:2:0:
AES_CBC256.h:5:25: fatal error: openssl/aes.h: 沒有那個文件或目錄
 #include <openssl/aes.h>
                         ^
compilation terminated.

解決辦法:

sudo apt-get install openssl
sudo apt-get install libssl-dev

最后執行結果:

5.2、ECB模式的實戰代碼

頭文件:AES_ECB256.h

#ifndef _AES_ECB256_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/aes.h>
#include <stddef.h>

#define _AES_ECB256_H_
#define USER_KEY_LENGTH 32
#define AES_BLOCK_SIZE  16
#define BITS_LENGTH   (USER_KEY_LENGTH * 8)
class AES_ECB256 {

public:
     AES_ECB256();
     virtual ~AES_ECB256();
     // CBC Mode Encrypt
     bool AES_ECB256_Encrypt(const unsigned char *in, unsigned char *out);
     // CBC Mode Decrypt
     bool AES_ECB256_Decrypt(const unsigned char *in, unsigned char *out);

     unsigned char m_userKey [USER_KEY_LENGTH];

};
#endif   // _AES_ECB256_H_

源文件:AES_ECB256.cpp

#ifndef _AES_ECB256_H_
#   include "AES_ECB256.h"
#endif

AES_ECB256::AES_ECB256() {
    memcpy(m_userKey, "XZJE151628AED2A6ABF7158809CF4F3C2B7E151628AED2A6ABF7158809CF4FTP", USER_KEY_LENGTH);

}
AES_ECB256::~AES_ECB256() {

}

bool AES_ECB256::AES_ECB256_Encrypt(const unsigned char *in, unsigned char *out) {

    AES_KEY key;
    // get the key with userkey
    if (AES_set_encrypt_key(m_userKey, BITS_LENGTH, &key) < 0) {
    	printf("%s\n", "get the key error");
    	return false;
    } else {
    	printf("%s\n", "get the key successful");
    }

    AES_ecb_encrypt(in, out, &key, AES_ENCRYPT);
    return true;
}

bool AES_ECB256::AES_ECB256_Decrypt(const unsigned char *in, unsigned char *out) {

    AES_KEY key;
    // get the key with userkey
    if (AES_set_decrypt_key(m_userKey, BITS_LENGTH, &key) < 0) {
    	printf("%s\n", "get the key error");
    	return false;
    } else {
    	printf("%s\n", "get the key successful");
    }

    AES_ecb_encrypt(in, out, &key, AES_DECRYPT);
    return true;
}

入口測試文件:ECB_main.cpp

#ifndef _AES_ECB256_H_
#   include "AES_ECB256.h"
#endif
#define NAME_SIZE 34

struct persion
{
    int age;
    unsigned char name [NAME_SIZE];
};


int main(int argc, char const *argv[])
{
    AES_ECB256 m_pcAES_ECB256;
    persion m_persion = {28,"xzj8023tp"};
    unsigned char *encrypt_in_data  = new unsigned char[sizeof(persion)];
    unsigned char *encrypt_out_data  = new unsigned char[sizeof(persion)];
    memcpy(encrypt_in_data, &m_persion, sizeof(persion));
    // encrypt
    bool encrypt_ret = m_pcAES_ECB256.AES_ECB256_Encrypt(encrypt_in_data, encrypt_out_data);
    if (false == encrypt_ret) {
	printf("encrypt error!\n");
    } else {
	printf("encrypt successful!\n");
    }

    //decrypt
    unsigned char *decrypt_out_data  = new unsigned char[sizeof(persion)];
    bool decrypt_ret = m_pcAES_ECB256.AES_ECB256_Decrypt(encrypt_out_data, decrypt_out_data);
    if (false == decrypt_ret) {
	printf("decrypt error!\n");
    } else {
	printf("decrypt successful!\n");
	persion showData;
	memcpy(&showData, decrypt_out_data, sizeof(persion));
        printf("my name is [%s] and I am [%d] years old\n", showData.name, showData.age);
    }
    return 0;
}

最后執行結果:

xzj@xzj-virtual-machine:~/Code/C++Code/AES/AES_ECB$ g++ AES_ECB256.cpp  ECB_main.cpp  -o main_run -lssl -lcrypto
xzj@xzj-virtual-machine:~/Code/C++Code/AES/AES_ECB$ ls
AES_ECB256.cpp  AES_ECB256.h  ECB_main.cpp  main_run
xzj@xzj-virtual-machine:~/Code/C++Code/AES/AES_ECB$ ./main_run 
get the key successful
encrypt successful!
get the key successful
decrypt successful!
my name is [xzj8023tp] and I am [28] years old
xzj@xzj-virtual-machine:~/Code/C++Code/AES/AES_ECB$ 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM