C++實踐(三):C++實現加密演算法AES
本篇主要講2015年寫的加密演算法。包括:AES,AES-CMAC,HMAC,基於RSA與HMAC的數字簽名演算法。當時大概寫了2天。哈哈!
AES演算法
AES是一個對稱加密標準,用以取代DES的商業應用。其分組長度為128位,192位或者256位。
AES不同於典型的Feistel結構,在每一輪都使用代替和混淆來將整個資料分組作為一個單一的矩陣處理。對AES-128來說,輸入的金鑰被擴充套件成44個32位字組成的陣列w[i]。每輪有4個不同的字作為改輪的輪金鑰,總共進行11輪運算。AES的結構如圖:
每輪的的基本結構由4個運算構成,分別是:位元組代替、行移位、列混淆、輪金鑰加。如圖:
AES的變換函式
位元組代替(SubBytes)
如上圖,位元組代替變換是一個簡單的查表操作。AES定義了一個S盒,它由16*16個位元組組成的矩陣,包含了8位所能表示的256個數的一個置換。State中的每個位元組按照如下的方式對映為一個新的位元組,把該位元組的高4位作為行值,低四位作為列值,以這些行列值作為索引從S盒的對應位置取出元素作為輸出。而其逆向操作則是有對應的逆S盒可進行查表。
行移位變換(ShiftRows)
正向行移位變換中,state的第一行保持不變,把state的第二行迴圈左移一個位元組,state的第三行迴圈左移兩個位元組,state的第四行迴圈左移三個位元組。
列混淆變換(MixColumns)
如上圖,列混淆的正向變換是對每列單獨進行操作。每列中的每個位元組被對映為一個新值,此值由該列中的4個字通過函式變換得到。這個變換可有下面基於state的矩陣乘法表示:
輪金鑰加變換(AddRoundKey)
如上圖,在輪金鑰加變換中,128位的state按位與128位的輪祕鑰XOR。
AES金鑰擴充套件
AES金鑰擴充套件演算法的輸入值是一個4個字,輸出值是一個由44個字組成的移位線性陣列。
輸入金鑰位元組被複制到擴充套件金鑰陣列的前4個字。然後每次用四個字填充擴充套件金鑰陣列餘下的部分。在擴充套件陣列中,每一個新增的字w[i]的值依賴於w[i-1]和w[i-4]。在4個情形中,三個使用了異或。對w陣列中下標為4的倍數的的元素採用了更復雜的函式來計算:
(1)字迴圈的功能時使一個字中的四個位元組的迴圈左移一個位元組,即將輸入字[B0,B1,B2,B3]變成為[B1,B2,B3,B0].
(2)字代替利用S盒對輸入字中的每個位元組進行位元組代替。
(3)步驟1和步驟2的結果再與輪常量Rcon[j]相異或。
程式碼實現
AES演算法實現與測試
編寫類AES,分別實現AES輪函式中的四個基本操作、AES的金鑰擴充套件、有限域上的乘法。AES類可以用於AES-128/AES-192/AES-256,在構造AES類時我們可以指定金鑰的長度。
對於有限域GF(2,8)上的乘法,因為實際上只需要計算s*{01},s*{02},s*{03}的結果,所以這裡我們只計算低位元組。顯然此時函式byte GF28Multi(byte s, byte a)的兩個引數不是對稱的。
AES演算法測試
為了便於測試,在aes.h標頭檔案中我們加入了DEBUG巨集定義,對所有的測試程式碼均採用條件編譯的方式嵌入,測試時如果定義該巨集,則條件編譯後會包含測試用的程式碼,這時會打印出整個AES加密過程的詳細資訊。
在main()函式中,我們同樣採用條件編譯的方式將AES與CMAC的測試程式碼融合在一起,如果巨集AES_CMAC_TEST被定義,則進行CMAC的測試,否則進行AES的測試。
測試AES,適當地設定斷點,可以看到中間結果如圖:
檢視第1輪的詳細資訊:
對照密碼編碼學與網路安全——原理與實踐(中文版)第121頁(或者英文版第171頁)的AES例子,分別比對各輪的結果,符合預期。
加密到第10輪,產生密文:
接著進行解密,如圖:
解密時逆用金鑰,最後得到明文。
註釋掉DEBUG巨集定義,再次執行,結果如下:
解密出來的結果與輸入的訊息是一致的。
/************************************************************************/
/*AES: AES-128,AES-192,AES-256
/*Author: chenweiliang
/*Version: 1.0
/*Note: The input data must be 128-bit.
/* To see all information output,please uncommet the macro
/* definition "#define DEBUG"
/************************************************************************/
#pragma once
#include <string.h>
#include <stdio.h>
#ifndef DEBUG
//#define DEBUG
#endif // !DEBUG
typedef unsigned char byte;
//length of secret key
enum KEYLENGTH
{
KEY_LENGTH_16BYTES, KEY_LENGTH_24BYTES, KEY_LENGTH_32BYTES
};
class AES
{
public:
AES();
AES(const byte key[],enum KEYLENGTH keyBytes);
virtual ~AES();
void encrypt(const byte data[16], byte out[16]);
void decrypt(const byte data[16], byte out[16]);
private:
//
int mNb;
//word length of the secret key used in one turn
int mNk;
//number of turns
int mNr;
//the secret key,which can be 16bytes,24bytes or 32bytes
byte mKey[32];
//the extended key,which can be 176bytes,208bytes,240bytes
byte mW[60][4];
static byte sBox[];
static byte invSBox[];
//constant
static byte rcon[];
void setKey(const byte key[], const int keyBits);
void subBytes(byte state[][4]);
void shiftRows(byte state[][4]);
void mixColumns(byte state[][4]);
void addRoundKey(byte state[][4], byte w[][4]);
void invSubBytes(byte state[][4]);
void invShiftRows(byte state[][4]);
void invMixColumns(byte state[][4]);
void keyExpansion();
//
byte GF28Multi(byte s, byte a);
void rotWord(byte w[]);
void subWord(byte w[]);
//get the secret key
void getKeyAt(byte key[][4],int i);
#ifdef DEBUG
void print128(byte state[16]);
void print128(byte state[4][4]);
#endif // DEBUG
};
/************************************************************************/
/*AES: AES-128,AES-192,AES-256
/*Author: chenweiliang
/*Version: 1.0
/*Note: The input data must be 128-bit.
/************************************************************************/
#include "AES.h"
byte AES::sBox[] =
{ /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, /*0*/
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, /*1*/
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, /*2*/
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, /*3*/
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, /*4*/
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, /*5*/
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, /*6*/
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, /*7*/
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, /*8*/
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, /*9*/
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, /*a*/
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, /*b*/
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, /*c*/
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, /*d*/
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, /*e*/
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 /*f*/
};
byte AES::invSBox[] =
{ /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, /*0*/
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, /*1*/
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, /*2*/
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, /*3*/
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, /*4*/
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, /*5*/
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, /*6*/
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, /*7*/
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, /*8*/
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, /*9*/
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, /*a*/
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, /*b*/
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, /*c*/
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, /*d*/
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, /*e*/
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d /*f*/
};
byte AES::rcon[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 };
AES::AES()
{
//If no key is input,use the default key
int keyBits = 128;
byte key[16] = {
0x0f, 0x15, 0x71, 0xc9,
0x47, 0xd9, 0xe8, 0x59,
0x0c, 0xb7, 0xad, 0xd6,
0xaf, 0x7f, 0x67, 0x98
};
setKey(key, keyBits);
keyExpansion();
}
AES::AES(const byte key[],enum KEYLENGTH keyBytes){
int keyBits;
if (keyBytes == KEYLENGTH::KEY_LENGTH_16BYTES){
keyBits = 128;
}
else if (keyBytes==KEYLENGTH::KEY_LENGTH_24BYTES){
keyBits == 192;
}
else if (keyBytes==KEYLENGTH::KEY_LENGTH_32BYTES){
keyBits == 256;
}
setKey(key,keyBits);
keyExpansion();
}
void AES::setKey(const byte key[], const int keyBits){
mNb = 4;
if (keyBits == 128){
mNk = 4;
mNr = 10;
}
else if (keyBits == 192){
mNk = 6;
mNr = 12;
}
else if (keyBits == 256){
mNk = 8;
mNr = 14;
}
memcpy(mKey,key,mNk*4);
}
void AES::keyExpansion(){
//the first mNk words will be filled in mW derictly
for (int i = 0; i < mNk; i++){
for (int j = 0; j < 4; j++){
//arranged vertically
mW[i][j] = mKey[j+i*4];
}
}
//generate the secret key words
for (int i = mNk; i < mNb*(mNr + 1); i++){
//last secret key word
byte pre_w[4];
for (int k = 0; k < 4; k++){
pre_w[k] = mW[i-1][k];
}
if(i%mNk == 0){
rotWord(pre_w);
subWord(pre_w);
pre_w[0] = pre_w[0] ^= rcon[i / mNk - 1];
}
else if ((mNk>6)&&(i%mNk==4)){
subWord(pre_w);
}
for (int k = 0; k < 4; k++){
mW[i][k] = pre_w[k] ^ mW[i-mNk][k];
}
}
}
void AES::subBytes(byte state[][4])
{
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
state[i][j] = sBox[state[i][j]];
}
}
}
void AES::shiftRows(byte state[][4])
{
byte t[4];
for (int i = 1; i < 4; i++){
for (int j = 0; j < 4; j++){
t[j] = state[i][(i + j) % 4];
}
for (int j = 0; j < 4; j++){
state[i][j] = t[j];
}
}
}
void AES::mixColumns(byte state[][4])
{
byte t[4];
for (int j = 0; j < 4; j++){
for (int i = 0; i < 4; i++){
t[i] = state[i][j];
}
for (int i = 0; i < 4; i++){
state[i][j] = GF28Multi(t[i], 0x02)
^ GF28Multi(t[(i + 1) % 4], 0x03)
^ GF28Multi(t[(i + 2) % 4], 0x01)
^ GF28Multi(t[(i + 3) % 4], 0x01);
}
}
}
void AES::addRoundKey(byte state[][4], byte w[][4])
{
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
state[i][j] ^= w[i][j];
}
}
}
void AES::invSubBytes(byte state[][4])
{
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
state[i][j] = invSBox[state[i][j]];
}
}
}
void AES::invShiftRows(byte state[][4])
{
byte t[4];
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
t[j] = state[i][(j-i + 4) % 4];
}
for (int j = 0; j < 4; j++){
state[i][j] = t[j];
}
}
}
void AES::invMixColumns(byte state[][4])
{
byte t[4];
//calculate columns by columns
for (int j = 0; j < 4; j++){
for (int i = 0; i < 4; i++){
t[i] = state[i][j];
}
for (int i = 0; i < 4; i++){
state[i][j] = GF28Multi(t[i], 0x0e)
^ GF28Multi(t[(i+1)%4],0x0b)
^ GF28Multi(t[(i+2)%4],0x0d)
^ GF28Multi(t[(i+3)%4],0x09);
}
}
}
void AES::rotWord(byte w[])
{
byte t;
t = w[0];
w[0] = w[1];
w[1] = w[2];
w[2] = w[3];
w[3] = t;
}
void AES::subWord(byte w[])
{
for (int i = 0; i < 4; i++){
w[i] = sBox[w[i]];
}
}
//calculate the least significant byte only for we only need
//the least significant byte
byte AES::GF28Multi(byte s,byte a){
byte t[4];
byte result = 0;
t[0] = s;
//calculate s*{02},s*{03},s*{04}
for (int i = 1; i < 4; i++){
t[i] = t[i - 1] << 1;
if (t[i - 1] & 0x80){
t[i] ^= 0x1b;
}
}
//multiply a and s bit by bit and sum together
for (int i = 0; i < 4; i++){
if ((a >> i) & 0x01){
result ^= t[i];
}
}
return result;
}
void AES::encrypt(const byte data[16], byte out[16])
{
byte state[4][4];
byte key[4][4];
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
state[i][j] = data[i+j*4];
}
}
#ifdef DEBUG
printf("plaintext:\n");
print128(state);
printf("--------------------------Round 0 begin:--------------------------\n");
#endif // DEBUG
getKeyAt(key, 0);
addRoundKey(state, key);
#ifdef DEBUG
printf("addRoundKey:\n");
print128(state);
#endif // DEBUG
for (int i = 1; i <= mNr; i++){
#ifdef DEBUG
printf("--------------------------Round %d begin:--------------------------\n",i);
#endif // DEBUG
subBytes(state);
#ifdef DEBUG
printf("subBytes:\n");
print128(state);
#endif // DEBUG
shiftRows(state);
#ifdef DEBUG
printf("shiftRows:\n");
print128(state);
#endif // DEBUG
if (i != mNr){
mixColumns(state);
#ifdef DEBUG
printf("mixColumns:\n");
print128(state);
#endif // DEBUG
}
getKeyAt(key, i);
addRoundKey(state, key);
#ifdef DEBUG
printf("addRoundKey:\n");
print128(state);
#endif // DEBUG
}
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
out[i+j*4] = state[i][j];
}
}
}
void AES::decrypt(const byte data[16], byte out[16])
{
byte state[4][4];
byte key[4][4];
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
state[i][j] = data[i+j*4];
}
}
getKeyAt(key, mNr);
addRoundKey(state,key);
for (int i = (mNr - 1); i >= 0; i--){
invShiftRows(state);
invSubBytes(state);
getKeyAt(key, i);
addRoundKey(state,key);
if (i){
invMixColumns(state);
}
}
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
out[i + j * 4] = state[i][j];
}
}
}
#ifdef DEBUG
void AES::print128(unsigned char state[16]){
for (int i = 0; i < 16; i++){
printf("%02hhx ", state[i]);
}
}
void AES::print128(unsigned char state[4][4]){
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
printf("%02hhx ", state[i][j]);
}
printf("\n");
}
}
#endif // DEBUG
//get the secret key for round "index",which will
//be arranged vetically
void AES::getKeyAt(byte key[][4], int index){
for (int i = index*4; i < index*4+4; i++){
for (int j = 0; j < 4; j++){
key[j][i-index*4] = mW[i][j];
}
}
#ifdef DEBUG
printf("secret key for round %d\n", index);
print128(key);
#endif // DEBUG
}
AES::~AES()
{
}