Read file, filesize 10878713B, 10.3747MB
Done padding
Encryption of 10878713B (10.3747MB) took 3759.78ms
Average speed of 2.7594MB/s
Decryption of 10878713B (10.3747MB) took 3305.84ms
Average speed of 3.13831MB/s
文件正在加密在CBC模式下。对于我的Galois乘法,我使用了查找表,因为计算它们花费了很长时间。我通过在数据后插入所需填充字节的数量来填充文件(PKCS7,如果我没记错的话)。
AES.hpp:
/**
* @author thomas
* @date 01/11/17.
*/
#ifndef AES_HPP
#define AES_HPP
#include <cstring>
//#include <array>
#include <iostream>
#include <vector>
#include <BlockCipher/BlockCipher.hpp>
#include <Util/Types.hpp>
#include <Util/Util.hpp>
namespace Crypto
{
class AES : public BlockCipher
{
private:
constexpr static uint8 S_BOX [256] =
{
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
};
constexpr static uint8 INV_S_BOX [256] =
{
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
};
constexpr static uint8 RCON [11] = {0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36};
void KeyExpansion(const uint8 key [], uint8 expandedKey[]) const;
static void KeyScheduleCore (uint8 roundNumber, const uint8 keyIn[4], uint8 keyOut[4]);
static void AddRoundKey (uint8 state[4][4], const uint8 roundKey[4][4]);
static void SubBytes (uint8 state[4][4]);
static void ShiftRows (uint8 state[4][4]);
static void MixColumns (uint8 state[4][4]);
static void InvSubBytes (uint8 state[4][4]);
static void InvShiftRows (uint8 state[4][4]);
static void InvMixColumns (uint8 state[4][4]);
public:
AES () = delete;
explicit AES (uint16 keyLen);
explicit AES (const BlockCipher& blockCipher);
explicit AES (const AES& aes);
void encrypt(const uint8 input[], const uint8 key[], uint8 output[]) const override;
void decrypt(const uint8 input[], const uint8 key[], uint8 output[]) const override;
};
}
#endif // AES_HPP
这是实现:
/**
* @author thomas
* @date 01/11/17.
*/
#include <BlockCipher/AES.hpp>
namespace Crypto
{
AES::AES(uint16 keyLen) : BlockCipher ("AES-" + std::to_string(keyLen), keyLen, 128/8) {}
AES::AES(const BlockCipher& blockCipher) : BlockCipher (blockCipher) {}
AES::AES(const AES& aes) : BlockCipher (aes) {}
void AES::encrypt(const uint8 input[], const uint8 key[], uint8 output[]) const
{
/*
* Initial Set-up phase.
* Setting some variables like maximum iterators, sizes, IV's, ...
*/
int8 numRounds = 0;
uint8 state [4][4]=
{ {input[0], input[4], input[8], input[12]},
{input[1], input[5], input[9], input[13]},
{input[2], input[6], input[10], input[14]},
{input[3], input[7], input[11], input[15]} };
uint8 expandedKeyLength = 0;
switch (this->keyLen)
{
case 128:
{
numRounds = 10;
expandedKeyLength = 176; // We need 176 bytes, we store blocks of 4 bytes, so 176 / 4
break;
}
case 192:
{
numRounds = 12;
expandedKeyLength = 208; // We need 208 bytes, we store blocks of 4 bytes, so 208 / 4
break;
}
case 256:
{
numRounds = 14;
expandedKeyLength = 240; // We need 240 bytes, we store blocks of 4 bytes, so 240 / 4
break;
}
default:
{
throw std::runtime_error("Invalid key length, must be 128 bits, 192 bits or 256 bits for AES.");
}
}
uint8 expandedKey [expandedKeyLength];
this->KeyExpansion(key, expandedKey);
/*
* Encryption
*/
uint8 roundKey [4][4]=
{ {expandedKey[0], expandedKey[4], expandedKey[8], expandedKey[12]},
{expandedKey[1], expandedKey[5], expandedKey[9], expandedKey[13]},
{expandedKey[2], expandedKey[6], expandedKey[10], expandedKey[14]},
{expandedKey[3], expandedKey[7], expandedKey[11], expandedKey[15]} };
AES::AddRoundKey (state, roundKey);
for (int8 roundCounter = 1; roundCounter <= numRounds; roundCounter++)
{
// Can't memcpy this because it involves a transposition
for (uint8 i = 0; i < 4; i++)
{
for (uint8 j = 0; j < 4; j++)
{
roundKey[i][j] = expandedKey[(roundCounter * this->blockSize) + (j * 4) + i];
}
}
AES::SubBytes(state);
AES::ShiftRows(state);
if (roundCounter != numRounds)
{
// Apply MixColumns in all rounds but the last
AES::MixColumns(state);
}
AES::AddRoundKey(state, roundKey);
}
// Copying final state to output
for (uint8 i = 0; i < 4; i++)
{
for (uint8 j = 0; j < 4; j++)
{
output[(j * 4) + i] = state[i][j];
}
}
};
void AES::decrypt(const uint8 input[], const uint8 key[], uint8 output[]) const
{
/*
* Initial Set-up phase.
* Setting some variables like maximum iterators, sizes, IV's, ...
*/
int8 numRounds = 0;
uint8 state [4][4]=
{ {input[0], input[4], input[8], input[12]},
{input[1], input[5], input[9], input[13]},
{input[2], input[6], input[10], input[14]},
{input[3], input[7], input[11], input[15]} };
uint8 expandedKeyLength = 0;
switch (this->keyLen)
{
case 128:
{
numRounds = 10;
expandedKeyLength = 176; // We need 176 bytes, we store blocks of 4 bytes, so 176 / 4
break;
}
case 192:
{
numRounds = 12;
expandedKeyLength = 208; // We need 208 bytes, we store blocks of 4 bytes, so 208 / 4
break;
}
case 256:
{
numRounds = 14;
expandedKeyLength = 240; // We need 240 bytes, we store blocks of 4 bytes, so 240 / 4
break;
}
default:
{
throw std::runtime_error("Invalid key length, must be 128 bits, 192 bits or 256 bits for AES.");
}
}
uint8 expandedKey [expandedKeyLength];
this->KeyExpansion(key, expandedKey);
uint8 roundKey [4][4]=
{ {expandedKey[expandedKeyLength - 16], expandedKey[expandedKeyLength - 12], expandedKey[expandedKeyLength - 8], expandedKey[expandedKeyLength - 4]},
{expandedKey[expandedKeyLength - 15], expandedKey[expandedKeyLength - 11], expandedKey[expandedKeyLength - 7], expandedKey[expandedKeyLength - 3]},
{expandedKey[expandedKeyLength - 14], expandedKey[expandedKeyLength - 10], expandedKey[expandedKeyLength - 6], expandedKey[expandedKeyLength - 2]},
{expandedKey[expandedKeyLength - 13], expandedKey[expandedKeyLength - 9], expandedKey[expandedKeyLength - 5], expandedKey[expandedKeyLength - 1]} };
AES::AddRoundKey(state, roundKey);
/*
* Decryption
*/
for (int8 roundCounter = (int8)(numRounds - 1); roundCounter >= 0; roundCounter--)
{
// Can't memcpy this because it involves a transposition
for (uint8 i = 0; i < 4; i++)
{
for (uint8 j = 0; j < 4; j++)
{
roundKey[i][j] = expandedKey[(roundCounter * this->blockSize) + (4 * j) + i];
}
}
AES::InvShiftRows(state);
AES::InvSubBytes(state);
AES::AddRoundKey(state, roundKey);
if (roundCounter != 0)
{
// Apply MixColumns in all rounds but the first
AES::InvMixColumns(state);
}
}
// Copying final state to output
for (uint8 i = 0; i < 4; i++)
{
for (uint8 j = 0; j < 4; j++)
{
output[(j * 4) + i] = state[i][j];
}
}
}
void AES::KeyExpansion(const uint8 key [], uint8 expandedKey[]) const
{
uint8 initialKeyLength = 0;
uint8 expandedKeyLength = 0;
uint8 n = 0;
uint8 m = 0;
switch (this->keyLen)
{
case 128:
{
n = 16;
m = 0;
initialKeyLength = 16;
expandedKeyLength = 176; // We need 176 bytes, we store blocks of 4 bytes, so 176 / 4
break;
}
case 192:
{
n = 24;
m = 2;
initialKeyLength = 24;
expandedKeyLength = 208; // We need 208 bytes, we store blocks of 4 bytes, so 208 / 4
break;
}
case 256:
{
n = 32;
m = 3;
initialKeyLength = 32;
expandedKeyLength = 240; // We need 240 bytes, we store blocks of 4 bytes, so 240 / 4
break;
}
default:
{
throw std::runtime_error("Invalid key length, must be 128 bits, 192 bits or 256 bits for AES.");
}
}
uint8 keySizeIterator = 0;
memset(expandedKey, 0, expandedKeyLength);
memcpy(expandedKey, key, initialKeyLength);
keySizeIterator += initialKeyLength;
// Start generating new words
for (uint8 rconIterator = 1; keySizeIterator < expandedKeyLength; rconIterator++)
{
uint8 t [4];
memcpy(t, expandedKey + (keySizeIterator - 4), 4); // Get previous 4 bytes
uint8 g [4];
KeyScheduleCore(rconIterator, t, g);
memcpy(t, expandedKey + (keySizeIterator - n), 4 * sizeof(uint8));
// todo: cast-interpret as 4-byte pointer to reduce to a single XOR operation
for (uint8 i = 0; i < 4; i++)
{
t[i] ^= g[i];
}
memcpy(expandedKey + keySizeIterator, t, 4 * sizeof(uint8));
keySizeIterator += 4;
for (uint8 i = 0; (i < 3) && (keySizeIterator < expandedKeyLength); i++)
{
memcpy(t, expandedKey + (keySizeIterator - 4), 4 * sizeof(uint8));
uint32* resultPtr = (uint32*)(expandedKey + keySizeIterator);
uint32* tPtr = (uint32*)t;
uint32* rhsPtr = (uint32*)(expandedKey + keySizeIterator - n);
*resultPtr = *tPtr ^ *rhsPtr;
/*
for (uint8 j = 0; j < 4; j++)
{
expandedKey[keySizeIterator + j] = t[j] ^ expandedKey[keySizeIterator - n + j];
}
*/
keySizeIterator += 4;
}
if ((this->keyLen == 256) && (keySizeIterator < expandedKeyLength))
{
memcpy(t, expandedKey + (keySizeIterator - 4), 4 * sizeof(uint8)); // Get previous 4 bytes
for (uint8 i = 0; i < 4; i++)
{
t[i] = S_BOX[t[i]];
}
uint32* resultPtr = (uint32*)(expandedKey + keySizeIterator);
uint32* tPtr = (uint32*)t;
uint32* rhsPtr = (uint32*)(expandedKey + keySizeIterator - n);
*resultPtr = *tPtr ^ *rhsPtr;
/*
for (uint8 i = 0; i < 4; i++)
{
expandedKey[keySizeIterator + i] = t[i] ^ expandedKey[keySizeIterator - n + i];
//t[i] ^= expandedKey[keySizeIterator - n - 4 + i];
}
*/
//memcpy(expandedKey + (keySizeIterator - 3 - 1), t, 4 * sizeof(uint8));
keySizeIterator += 4;
}
for (uint8 i = 0; (i < m) && (keySizeIterator < expandedKeyLength); i++)
{
memcpy(t, expandedKey + (keySizeIterator - 4), 4 * sizeof(uint8)); // Get previous 4 bytes
uint32* resultPtr = (uint32*)(expandedKey + keySizeIterator);
uint32* tPtr = (uint32*)t;
uint32* rhsPtr = (uint32*)(expandedKey + keySizeIterator - n);
*resultPtr = *tPtr ^ *rhsPtr;
/*
for (uint8 j = 0; j < 4; j++)
{
expandedKey[keySizeIterator + j] = t[j] ^ expandedKey[keySizeIterator - n + j];
}
*/
keySizeIterator += 4;
}
}
}
void AES::KeyScheduleCore(uint8 roundNumber, const uint8 keyIn[4], uint8 keyOut[4])
{
//todo memcpy
//for (uint8 i = 0; i < 4; i++)
//{
// keyOut[i] = keyIn[i];
//}
memcpy(keyOut, keyIn, 4);
//keyOut = keyIn;
// Rotate
uint8 tmp = keyOut[0];
for (uint8 i = 0; i < 3; i++)
{
keyOut[i] = keyOut[i+1];
}
keyOut[3] = tmp;
// Substitute
for (uint8 i = 0; i < sizeof(uint32); i++)
{
keyOut[i] = AES::S_BOX[keyOut[i]];
}
// Apply RCON to rightmost byte
keyOut[0] = keyOut[0] ^ RCON[roundNumber];
}
void AES::AddRoundKey(uint8 state[4][4], const uint8 roundKey[4][4])
{
for (uint8 i = 0; i < 4; i++)
{
uint32* keyPtr = (uint32*) roundKey[i];
uint32* statePtr = (uint32*) state[i];
*statePtr ^= *keyPtr;
/*
for (uint8 j = 0; j < 4; j++)
{
state[i][j] ^= roundKey[i][j];
}
*/
}
}
void AES::SubBytes(uint8 state[4][4])
{
for (uint8 i = 0; i < 4; i++)
{
for (uint8 j = 0; j < 4; j++)
{
state[i][j] = AES::S_BOX[state[i][j]];
}
}
}
void AES::ShiftRows(uint8 state[4][4])
{
for (uint8 i = 0; i < 4; i++)
{
if (i > 0)
{
uint8 row [4];
for (uint8 j = 0; j < 4; j++)
{
row[j] = state[i][j];
}
for (uint8 j = 0; j < 4; j++)
{
state[i][j] = row[(i + j) % 4]; // I got this formula by first writing down all shifts
// And then collapsing/generalizing them piece-by-piece
}
}
}
}
void AES::MixColumns(uint8 state[4][4])
{
for (uint8 i = 0; i < 4; i++)
{
uint8 col [4] = {state[0][i], state[1][i], state[2][i], state[3][i]};
state[0][i] = GALOIS_TABLE_2[col[0]] ^ GALOIS_TABLE_3[col[1]] ^ col[2] ^ col[3];
state[1][i] = col[0] ^ GALOIS_TABLE_2[col[1]] ^ GALOIS_TABLE_3[col[2]] ^ col[3];
state[2][i] = col[0] ^ col[1] ^ GALOIS_TABLE_2[col[2]] ^ GALOIS_TABLE_3[col[3]];
state[3][i] = GALOIS_TABLE_3[col[0]] ^ col[1] ^ col[2] ^ GALOIS_TABLE_2[col[3]];
}
}
void AES::InvSubBytes(uint8 state[4][4])
{
for (uint8 i = 0; i < 4; i++)
{
for (uint8 j = 0; j < 4; j++)
{
state[i][j] = INV_S_BOX[state[i][j]];
}
}
}
void AES::InvShiftRows(uint8 state[4][4])
{
for (uint8 i = 0; i < 4; i++)
{
if (i > 0)
{
uint8 row [4];
for (uint8 j = 0; j < 4; j++)
{
row[j] = state[i][j];
}
for (int8 j = 3; j >= 0; j--)
{
state[i][j] = row[(j + (4- i)) % 4];
}
}
}
}
void AES::InvMixColumns(uint8 state[4][4])
{
for (uint8 i = 0; i < 4; i++)
{
uint8 col [4] = {state[0][i], state[1][i], state[2][i], state[3][i]};
state[0][i] = GALOIS_TABLE_14[col[0]] ^ GALOIS_TABLE_11[col[1]] ^ GALOIS_TABLE_13[col[2]] ^ GALOIS_TABLE_9[col[3]];
state[1][i] = GALOIS_TABLE_9[col[0]] ^ GALOIS_TABLE_14[col[1]] ^ GALOIS_TABLE_11[col[2]] ^ GALOIS_TABLE_13[col[3]];
state[2][i] = GALOIS_TABLE_13[col[0]] ^ GALOIS_TABLE_9[col[1]] ^ GALOIS_TABLE_14[col[2]] ^ GALOIS_TABLE_11[col[3]];
state[3][i] = GALOIS_TABLE_11[col[0]] ^ GALOIS_TABLE_13[col[1]] ^ GALOIS_TABLE_9[col[2]] ^ GALOIS_TABLE_14[col[3]];
}
}
}
我已经使用NIST测试向量验证了实现的正确性,使用ECB向量验证了实际算法,并使用CBC向量验证我的CBC正常工作)并捕获单元测试。
我应该注意,我想避免使用AES-NI处理器扩展。我知道它可以大大提高我的实现速度,但是它首先会破坏实现它的目的。
注意:此代码不适用于专业用途。这是一个个人项目,但是由于人们总是说“正确执行加密”几乎是不可能的,所以我想我会去做的。
我正在链接我的Release-Build-Performance-将我的库作为发行版构建之后,将测试器转换为我的库的Debug-Built版本,从而对Performance Tester进行了调试版本。这是我得到的性能:
Read file, filesize 10878713B, 10.3747MB
Done padding
Encryption of 10878713B (10.3747MB) took 929.581ms
Average speed of 11.1607MB/s
Decryption of 10878713B (10.3747MB) took 764.709ms
Average speed of 13.5669MB/s
#1 楼
如果可能的话,我正在寻求改进代码,以使其不会“ DIY加密错误”。这不是我的专长,但是我对实现安全密码原语的知识不为零,从粗略的看,我发现了一些问题。
对于初学者:
keyOut[i] = AES::S_BOX[keyOut[i]];
和
state[i][j] = AES::S_BOX[state[i][j]];
和
state[i][j] = INV_S_BOX[state[i][j]];
您正在索引一个使用机密信息的数组,这意味着您的内存访问和缓存时间将根据此机密信息而有所不同。攻击者可以测量您的执行时间(或功耗)来窃取此类信息。这被称为“边通道攻击”。您仍然需要一种确保完整性的方法,即解密后的明文未被篡改。人们天真地认为,对密文所做的任何更改都会使解密后的明文完全乱码,但是对于CBC而言实际上并非如此。例如,如果在一个块中翻转一位,则该块确实会乱码,但除了在同一位置具有翻转位之外,下一个块都可以。这是一个练习,目标是在没有密匙的情况下更改密文中的文本。
出于不检查完整性的目的,还有Padding Oracle Attack,使攻击者能够完全解密仅通过提交密文并观察填充是否正确来检查密文。
可能存在或可能没有其他错误,但这是一个不错的起点。如果您想了解有关如何破解密码系统的更多示例,我建议您参加Cryptopals Challenge。为什么要动手做DIY加密非常困难,这是非常动手的。
编辑:我在聊天中看到一些答案,建议通过使用
sleep
固定时间的线程来保护定时攻击。我不认为这是正确的,而且我从未在经过验证的实现中看到过它。这是来自crypto.stackexchange的问题,关于如何适当地保护自己免受此攻击。评论
\ $ \ begingroup \ $
虽然我了解索引到数组如何导致我泄漏信息,但我真的不知道如何避免这种情况。这就是S-Boxes正确工作的方式,您给它们一个字节,然后它们返回另一个字节,两者之间没有(明确的)关系。自从我上传问题后,我相信我也将Galois乘法更改为使用查找表,因此,又如何在不基于秘密(或依赖秘密)数据建立索引的情况下使用这些表?
\ $ \ endgroup \ $
– shmoo6000
17-11-10在5:11
\ $ \ begingroup \ $
@ shmoo6000您不知道。您无法确保此类表查找的安全性。适当的仅SW实现将使用按位划分的实现(速度也更快)。
\ $ \ endgroup \ $
– SEJPM
17年10月10日在8:16
\ $ \ begingroup \ $
特别是@ shmoo6000,您在哪里进行各种侧通道攻击比较的解释性舞蹈? (请参阅AES简笔画指南中的“足部射击协议”)
\ $ \ endgroup \ $
–马丁·邦纳(Martin Bonner)支持莫妮卡(Monica)
17年11月10日在9:48
\ $ \ begingroup \ $
@ shmoo6000您可能有兴趣阅读有关cryptoSE的以下答案:AES S-Box实现:从一个字节到两个字节? //定时攻击和良好的编码实践//实施AES的不同方法/算法//实施AES 2000-2010:性能和安全性挑战(pdf)
\ $ \ endgroup \ $
– Biv
17年11月10日在9:53
\ $ \ begingroup \ $
@Biv不要忘记我们的Ursine霸主关于恒定时间加密的页面!
\ $ \ endgroup \ $
– Lery
17年10月10日13:55
#2 楼
由于AES只有三个有效的密钥大小,因此即使不使用任何uint16
值实例化AES类也很有意义。我将引入类似于以下内容的枚举:enum class AesKeyLen
{
Aes128,
Aes192,
Aes256
};
,然后将
uint16
构造函数更改为: />当然,它们仍然可以传递错误的值,但是必须通过显式创建无效的枚举值来加倍努力。另外,使其成为作用域枚举可提供额外的类型安全性,并防止命名冲突。无论哪种方式,我都认为如果键长错误,则立即退出构造函数而不是等到
encrypt
函数。如果要在编译时强制使用有效值,则可以使用
static_assert
,也可以使用智能枚举模式。这是一个真正快速被黑的例子:explicit AES (AesKeyLen keyLen);
,然后再次替换
uint16
构造函数:评论
\ $ \ begingroup \ $
通过单个enum-constructor转移到静态构造函数毫无意义:如果某人足够愚蠢地尝试绕过该枚举,那么他们愚蠢的就足以覆盖您的类实例中的内存。也称为您可以防御墨菲,但不能防御马基雅维利。
\ $ \ endgroup \ $
– Matthieu M.
17年9月9日在10:35
\ $ \ begingroup \ $
即使从没有范围的枚举中也不会将整数隐式转换(与从enum-to-int转换不同),这是自第一个C ++标准以来。您必须对C行为感到困惑,因为C行为在两个方向上的转换都是隐式的。
\ $ \ endgroup \ $
–俄罗斯
17年11月9日,19:19
#3 楼
界面设计: void encrypt(const uint8 input[], const uint8 key[], uint8 output[]) const override;
void decrypt(const uint8 input[], const uint8 key[], uint8 output[]) const override;
您的加密和解密是非常有限的。这意味着您需要先将所有输入加载到内存中,然后才能开始任何操作。拥有像流/迭代器这样的界面会更好,该界面使您可以根据需要简单地读取数据。它看起来像是沼泽标准的简单代码。我看不到任何明显的性能下降因素。
评论
\ $ \ begingroup \ $
是的,我需要将所有输入都加载到内存中,但是AES在128位(16字节)的块上工作。真的那么糟糕吗?无论如何,它将无法在流上工作,因为它需要这16个字节才能工作。如果您希望AES像流(密码)一样工作,则需要另一种操作模式(我相信CFB和OFB可以做到)
\ $ \ endgroup \ $
– shmoo6000
17年11月8日在20:07
\ $ \ begingroup \ $
为什么不能一次只从流中读取16个字节。加密/解密将结果值写入输出。重复直到流结束。
\ $ \ endgroup \ $
–马丁·约克
17年11月8日在22:55
\ $ \ begingroup \ $
我看不到通过流或静态数组提供数据如何改变内部加密模式的设置!这只是将数据输入算法的一种方法。
\ $ \ endgroup \ $
–马丁·约克
17年9月9日在2:49
\ $ \ begingroup \ $
@Loki,很正确,将密码与C ++流一起使用将非常有用。我不同意您的看法,在此级别包含该功能将是一件好事。在我看来,这将构成一个很好的下一层接口,利用分组密码原语并提供制作流密码所需的链接和填充。我绝对希望其实现远离实现块密码的细节。
\ $ \ endgroup \ $
– Toby Speight
17年9月9日在8:25
\ $ \ begingroup \ $
@LokiAstari:我认为您也误解了加密的用法;请参阅上方的我的评论。我想OP可以从此答案中学到的真正课程是“您应该记录功能”。
\ $ \ endgroup \ $
–user14393
17年11月10日13:18
#4 楼
我认为标题可以减少很多。常量表属于实现文件,因为类的定义不需要它们。由于AES类没有任何状态(从
BlockCipher
继承的状态除外),所以我不会声明标头中的私有函数,但只能将它们保留在匿名名称空间中的实现文件中。代码
for (i = 0; i < n; i++) if (i > 0)
可以写得更短,如for (i = 1; …
。评论
\ $ \ begingroup \ $
我还考虑过将私有静态方法变成常规的静态函数,但是我不想使用AES算法之外的任何人都不能使用的函数来污染“ Crypto”命名空间。我曾考虑过创建一个“ AES”命名空间,但那会产生命名冲突,而不是将其重命名为“ AES_Utils”之类,而是决定将这些函数设为私有方法。编辑:我刚刚注意到您说的是“静态”功能,因此在这种情况下不会造成名称空间污染,这实际上是一个好主意,谢谢!”
\ $ \ endgroup \ $
– shmoo6000
17年11月8日在21:09
\ $ \ begingroup \ $
如果您查看KeyExpansion,您会发现它对KeyScheduleCore使用值“ rconIterator”。 rconIterator在密钥扩展的每个“回合”中都会加一。在针对AES128的密钥扩展回合中,添加16个字节,对于AES192添加24个字节,对于AES256添加32个字节。这证实了Wikipedia的优势,即对于较长的密钥,您需要较少的RCON值:“仅实际使用其中的一些常量,对于AES-128,最多使用rcon [10](因为需要11个圆形密钥),而对于rcon [ 8]用于AES-192,最多rcon [7]用于AES-256。” (en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon)
\ $ \ endgroup \ $
– shmoo6000
17年11月8日在21:13
\ $ \ begingroup \ $
嗯,谢谢,在同一算法中将回合一词用于两种不同含义时会造成混淆。 :)
\ $ \ endgroup \ $
–罗兰·伊利格(Roland Illig)
17年11月8日在22:38
\ $ \ begingroup \ $
因为我们在谈论C ++,所以应该使用匿名名称空间而不是静态函数。
\ $ \ endgroup \ $
–n0rd
17年11月9日在1:26
#5 楼
您没有向我们展示BlockCipher
基类,但是看来它给我们强加了一个可怕的接口: uint8
的数量(尽管不确定为什么您不像许多缩写词一样全称它为std::uint8_t
)。这里的问题是,我们没有得到对论证的正确性。由于我们的输入和输出块大小固定为128位,所以我希望有更多类似的东西:
void encrypt(const uint8 input[], const uint8 key[], uint8 output[])
const override;
void decrypt(const uint8 input[], const uint8 key[], uint8 output[])
const override;
如果声明为
void encrypt(const uint8 (&input)[16], const uint8 key[], uint8 (&output)[16])
const override;
// and a similar decrypt
,则可以通过
u8
定义数组类型#6 楼
就像其他人所说的那样,还应确保AES实现不受定时/缓存攻击和其他有趣的旁通道的影响。如链接的加密法中所述。SEQA,通常应避免所有非持续对秘密数据进行操作,但正如BoppreH的答案所述,当前您依赖表查找,就像任何人从头开始实现AES一样。
我建议您阅读该链接,其中讨论了AES的恒定时间实现。如此处所述,获得恒定时间AES实现的通常方法是执行“位切片”。位切片意味着在位级别上进行操作,直接进行恒定时间的按位运算,并基本构建“ AES”布尔电路并将其转换为C(++)代码。
但是,这不是唯一的方法是,由于表最后只是一个记忆化的函数,如果您不太关心性能,那么也可以在每次迭代时显式地计算该函数。
最后,您可能会问自己为什么推出自己的加密货币并将其发布到网络上总是一个坏主意,这主要是因为您最终会遇到其他人,他们可以使您的实现来完成自己的工作而不必关心安全隐患...
例如,Matrix的OLM库依赖于非恒定时间AES实现,即使其创建者Brad Conte实际上对此表示:
“请注意,不是加密安全的实现。“
评论
评论不作进一步讨论;此对话已移至聊天。这是一个旧线程,所以我不知道您是否正在监视它。但是,您可以详细说说吗? “我应该注意,我想避免使用AES-NI处理器扩展。我知道它可以大大提高我的实现速度,但首先会破坏实现它的目的。”
有执行AES的x86汇编指令,Thesd被称为AES-NI指令,它们比我/我的软件实现(高达700MB / s)要快得多,但是,如果我使用它们,那么...,我不会在实施AES时,我只是在使用英特尔的实施。以下是有关AES-NI的一些信息:en.m.wikipedia.org/wiki/AES_instruction_set
也许我错过了一些东西,但是除非您只是作为练习来实现AES,否则为什么不支持硬件加速呢?它更容易受到某些攻击吗?
您完全正确,如果您的平台支持这些扩展,并且您正在编写生产代码,则应使用AES-NI扩展。我只是出于乐趣而实施了AES(“作为练习”),因此使用扩展首先会破坏实现AES的全部目的。