Compare commits

...

68 Commits

Author SHA1 Message Date
86811dd24f added exports symbols 2021-04-29 21:42:14 +03:00
ffeda3b4a4 addded export symbols 2021-04-29 21:32:08 +03:00
6e94df14d5
Merge pull request #1 from QuasarApp/update
Update
2021-04-16 09:11:24 +03:00
d14c463d28 Merge remote-tracking branch 'base/master' into HEAD 2020-12-12 11:55:27 +03:00
Oleg Derevenetz
0ddf76d59a
Minor improvements (#33)
* Pass expKey parameter of addRoundKey() by const reference to avoid unnecessary copy.

* Use C++11 nullptr instead of NULL, make it clear that default value of iv parameter in encode() and decode() is empty QByteArray instead of implicit conversion from null pointer via QByteArray(const char *, int = -1) constructor.

* Change parameter names in declarations of cipher(), invCipher() and byteXor() to match definitions.

* Convert AES-NI-related files to headers, place functions with internal linkage to anonymous namespace to avoid exporting them, don't use inline specifier (inline keyword have different meaning in C++ rather than in C).

* Use char literals instead of implementation-defined int-to-signed-char conversions where possible.

* Set default value for padding argument in static RemovePadding() to match sample in README.
2020-09-09 12:39:11 -07:00
Ilya Chesalin
a22e7bd5f9
Fixed invShiftRows in Shift 3 (#30)
This must be broken. The indices of iterable inside shift 3 seem random and do not correspond to the AES algorighm. More than that, it should be reversed from shiftRows methods, and shifts 1 and 2 seem normal.
2020-05-15 17:28:30 -07:00
Matteo Brichese
74643b2570 updated readme after aesni merge 2020-05-15 16:58:38 -07:00
Matteo B
78efdb4f3f
Adding ECB and CBC modes via AES-NI (#32)
Added ECB and CBC modes via AES-NI, only is enabled and supported
2020-05-15 16:45:35 -07:00
60e07bbb94 update Copyright 2020-01-06 14:12:22 +03:00
2a35a79597 fix build on qt 5.9 2019-10-07 13:01:29 +03:00
61876c7e06 fix win 2019-08-22 18:06:26 +03:00
18ae6b5306 added support of ccache 2019-08-22 17:49:39 +03:00
Matteo B
f5ef736a7f
Merge pull request #29 from mcmule/retrocomp-qbytearray-qt-lts
adding qt version check to keep retrocomp with qt pre 5.10 qbytearray
2019-07-09 10:44:11 -07:00
8c590ddd34 added export marker for class 2019-07-09 14:09:42 +03:00
Marc Muller
57b0cdf743 adding qt version check to keep retrocomp with qt pre 5.10 qbytearray 2019-06-28 11:40:31 +02:00
5ecbf59fca fix git ignore 2019-06-07 09:31:13 +03:00
1b3ddbd2d2 fix tests pro file 2019-06-04 18:13:13 +03:00
b296606aab fix git ignore 2019-05-30 15:27:05 +03:00
Matteo Brichese
4074157f0d cleanup 2019-03-27 21:34:19 -07:00
Matteo B
62d159430b
Merge pull request #25 from bricke/fix_PKCS7_blocklen
Fix PKCS7 when text size is a multiple of block size
2019-03-27 21:12:59 -07:00
Matteo Brichese
9537c6e5ad fixed size for blocklen 2019-03-27 21:09:59 -07:00
Matteo Brichese
4a06e8728a fix PKCS7 when text size is a multiple of block size 2019-03-27 21:04:58 -07:00
Matteo Brichese
3d4c882683 fix types and new added new test 2018-12-06 19:43:42 -08:00
Matteo B
39bc5e170e
Merge pull request #19 from tomgey/master
Fix ISO removePadding
2018-12-06 11:38:58 -08:00
Thomas Geymayer
3f2b2bcdce
Fix ISO removePadding
Fix removing of ISO padding when no padding was added.
2018-11-14 12:10:55 +01:00
Matteo B
a2ef03872b
Update README.md 2018-10-18 08:21:05 -07:00
Matteo Brichese
935b3d9c14
Update README.md 2018-04-09 16:07:33 -07:00
Matteo Brichese
d040dc73dc Merge branch 'master' of https://github.com/bricke/Qt-AES 2018-04-05 16:23:11 -07:00
Matteo Brichese
6096400737 adding OFB test 2018-04-05 16:23:01 -07:00
Matteo Brichese
c0cf26380d
Update README.md 2018-04-05 16:16:11 -07:00
Matteo Brichese
d96ee60dee
Update README.md 2018-04-05 16:15:32 -07:00
Matteo Brichese
e0d35f2fde
updated readme 2018-04-05 16:13:03 -07:00
Matteo Brichese
e5f942d924
Merge pull request #9 from bricke/OFB_dev
Ofb dev
2018-04-05 16:09:41 -07:00
Matteo Brichese
278565d661 adding OFB with basic test 2018-04-05 16:07:22 -07:00
Matteo Brichese
95104a6ef9 removing insert to fix compatibility with Qt5.6 2018-04-04 11:41:41 -07:00
Matteo Brichese
4ce12493f6 first try on OFB - not tested 2018-04-03 17:42:03 -07:00
Matteo Brichese
6fc1d88485 Merge branch 'master' of https://github.com/bricke/Qt-AES 2018-04-03 16:52:56 -07:00
Matteo Brichese
b376ee9723 added long text test 2018-04-03 16:52:39 -07:00
Matteo Brichese
a74a46e08e
Update README.md 2018-03-28 18:02:21 -07:00
Matteo Brichese
839c544555
updated readme 2018-03-28 18:01:14 -07:00
Matteo Brichese
80574a11a9 cleanup plus static/instance methods for padding removal 2018-03-28 17:59:11 -07:00
Matteo Brichese
b616caf7c1
Merge pull request #7 from bricke/test_padding
no conflicts
2018-03-28 17:51:38 -07:00
Matteo Brichese
56c2554df5 added padding stuff 2018-03-28 17:50:41 -07:00
Matteo Brichese
f94a6339de padding tests 2018-03-28 17:47:20 -07:00
Matteo Brichese
1809de722f working on padding feature 2018-03-28 17:42:09 -07:00
Matteo Brichese
dcb49d1fd1 adding padding standards 2018-03-28 16:43:13 -07:00
Matteo Brichese
c44296051f
fix title 2018-03-28 16:03:51 -07:00
Matteo Brichese
b7f75cbd2e
adding padding disclaimer 2018-03-28 16:03:30 -07:00
Matteo Brichese
8be7a0851d
Merge pull request #5 from BeardedBeaver/feature/Feature-ByteArrayByReference
QByteArrays now passed by reference to improve performance
2018-01-29 11:27:51 -08:00
Matteo Brichese
d69745c43e fixing IV in examples 2018-01-29 11:16:58 -08:00
linev
8b9bcdd933 QByteArrays now passed by reference to improve performance 2018-01-13 15:47:49 +03:00
Matteo Brichese
8b58ac9a3b
Merge pull request #3 from fetzerch/qt5.5
Fix compatibility with Qt 5.5
2017-11-06 14:28:27 -08:00
Christian Fetzer
dc64718fd5 Fix compatibility with Qt 5.5
QByteArray &QByteArray::append(int count, char ch) was introduced in Qt
5.7. This patch allows the project to be used with for example with the
current Ubuntu LTS version (16.04 Xenial).
2017-11-05 20:08:14 +01:00
Matteo Brichese
355230f397 security disclaimer 2017-07-13 09:07:19 -07:00
Matteo Brichese
3350cf1760 Merge pull request #1 from bricke/CFB-mode
CFB mode
2017-07-10 14:12:55 -07:00
Matteo Brichese
666e9dd004 updated readme 2017-07-10 14:12:09 -07:00
Matteo Brichese
e05e387b5c adding CFB-mode 2017-07-10 14:03:57 -07:00
Matteo Brichese
f651250939 first attempt at CFB mode 2017-07-10 13:10:38 -07:00
Matteo Brichese
8470ae108a Update README.md 2017-07-10 10:40:59 -07:00
Matteo Brichese
e40e4ae8d6 Update README.md 2017-07-10 10:40:43 -07:00
Matteo Brichese
ae940ecbaf Update README.md 2017-07-07 15:42:28 -07:00
Matteo Brichese
4e150f10fb optimizing Rcon table 2017-07-07 15:26:03 -07:00
Matteo Brichese
3b8b68cddc relocating var declaration 2017-07-07 14:20:52 -07:00
Matteo Brichese
ff02304e23 updating readme with method calls 2017-07-07 14:20:06 -07:00
Matteo Brichese
15d10586cc formatted following guidelines 2017-07-07 14:10:45 -07:00
Matteo Brichese
8f569d3fcb Update CONTRIBUTING.md 2017-07-07 13:57:21 -07:00
Matteo Brichese
0ed02f4e24 Create CONTRIBUTING.md 2017-07-07 13:56:39 -07:00
Matteo Brichese
e5c51ed205 Update README.md 2017-07-07 13:24:57 -07:00
16 changed files with 1727 additions and 245 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
*.user
*moc_*
*qrc_res*
*.swp
*.o
build/*
build/release/QAESEncryption
*.qaesencryption

5
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,5 @@
# Qt Conding Style
See [Qt Coding Style Doc](http://wiki.qt.io/Qt_Coding_Style)
# Qt Conding Conventions
See [Qt Coding Conventions Doc](http://wiki.qt.io/Coding_Conventions)

119
README.md
View File

@ -1,18 +1,64 @@
# Qt-AES # Qt-AES
Small and portable AES encryption class for Qt. Small and portable AES encryption class for Qt.
Supports all key sizes - 128/192/256 bits - ECB and CBC modes Native support for all key sizes - 128/192/256 bits - ECB, CBC, CFB and OFB modes
AES-NI support for all key sizes - ECB, CBC modes
## Usage ## Usage
### Usage via instance ### Available Methods
Example for 128bit ECB ```
// Encode of rawText with key
// iv is used in CBC mode
// return the encrypted byte array
QByteArray encode(const QByteArray rawText, const QByteArray key, const QByteArray iv = QByteArray());
// Decode of rawText with key
// iv is used in CBC mode
// return the decrypted byte array
QByteArray decode(const QByteArray rawText, const QByteArray key, const QByteArray iv = QByteArray());
// Key expansion in Rijndael schedule
// return the new expanded key as byte array
QByteArray expandKey(const QByteArray key);
```
The same methods are available as static calls
```
QAESEncryption::Crypt => encode(...)
QAESEncryption::Decrypt => decode(...)
QAESEncryption::ExpandKey => expandKey(...)
```
#### AES Levels
The class supports all AES key lenghts
* AES_128
* AES_192
* AES_256
#### Modes
The class supports the following operating modes
* ECB
* CBC
* CFB
* OFB
#### Padding
By default the padding method is `ISO`, however, the class supports:
* ZERO
* PKCS7
* ISO
### Example
Sample code using a 128bit key in ECB mode
``` ```
#include "qaesencryption.h" #include "qaesencryption.h"
QAESEncryption encryption(QAESEncryption::AES_128, QAESEncryption::ECB); QAESEncryption encryption(QAESEncryption::AES_128, QAESEncryption::ECB);
QByteArray encodedText = encryption.encode(plainText, key); QByteArray encodedText = encryption.encode(plainText, key);
QByteArray decodedText = encryption.decode(encodedText, key); QByteArray decodedText = encryption.decode(encodedText, key);
``` ```
Example for 256bit CBC using QString Example for 256bit CBC using QString
@ -20,47 +66,70 @@ Example for 256bit CBC using QString
#include <QCryptographicHash> #include <QCryptographicHash>
#include "qaesencryption.h" #include "qaesencryption.h"
QAESEncryption encryption(QAESEncryption::AES_256, QAESEncryption::CBC); QAESEncryption encryption(QAESEncryption::AES_256, QAESEncryption::CBC);
QString inputStr("The Advanced Encryption Standard (AES), also known by its original name Rijndael " QString inputStr("The Advanced Encryption Standard (AES), also known by its original name Rijndael "
"is a specification for the encryption of electronic data established by the U.S. " "is a specification for the encryption of electronic data established by the U.S. "
"National Institute of Standards and Technology (NIST) in 2001"); "National Institute of Standards and Technology (NIST) in 2001");
QString key("your-string-key"); QString key("your-string-key");
QString iv("your-IV-vector"); QString iv("your-IV-vector");
QByteArray hashKey = QCryptographicHash::hash(key.toLocal8Bit(), QCryptographicHash::Sha256); QByteArray hashKey = QCryptographicHash::hash(key.toLocal8Bit(), QCryptographicHash::Sha256);
QByteArray hashIV = QCryptographicHash::hash(iv.toLocal8Bit(), QCryptographicHash::Sha256); QByteArray hashIV = QCryptographicHash::hash(iv.toLocal8Bit(), QCryptographicHash::Md5);
QByteArray encodeText = encryption.encode(inputStr.toLocal8Bit(), hashKey, hashIV); QByteArray encodeText = encryption.encode(inputStr.toLocal8Bit(), hashKey, hashIV);
QByteArray decodeText = encryption.decode(encodeText, hashKey, hashIV); QByteArray decodeText = encryption.decode(encodeText, hashKey, hashIV);
QString decodedString = QString(encryption.removePadding(decodeText));
//decodedString == inputStr !!
``` ```
### Usage via static invocation ### Example via static invocation
Example of static invocation without creating instances Static invocation without creating instances, 256 bit key, ECB mode, starting from *QString* text/key
``` ```
#include <QCryptographicHash> #include <QCryptographicHash>
#include "qaesencryption.h" #include "qaesencryption.h"
QString inputStr("The Advanced Encryption Standard (AES), also known by its original name Rijndael " QString inputStr("The Advanced Encryption Standard (AES), also known by its original name Rijndael "
"is a specification for the encryption of electronic data established by the U.S. " "is a specification for the encryption of electronic data established by the U.S. "
"National Institute of Standards and Technology (NIST) in 2001"); "National Institute of Standards and Technology (NIST) in 2001");
QString key("your-string-key"); QString key("your-string-key");
QString iv("your-IV-vector"); QString iv("your-IV-vector");
QByteArray hashKey = QCryptographicHash::hash(key.toLocal8Bit(), QCryptographicHash::Sha256); QByteArray hashKey = QCryptographicHash::hash(key.toLocal8Bit(), QCryptographicHash::Sha256);
QByteArray hashIV = QCryptographicHash::hash(iv.toLocal8Bit(), QCryptographicHash::Sha256); QByteArray hashIV = QCryptographicHash::hash(iv.toLocal8Bit(), QCryptographicHash::Md5);
//Static invocation //Static invocation
QAESEncryption::Crypt(QAESEncryption::AES_256, QAESEncryption::CBC, inputStr.toLocal8Bit(), hashKey, hashIV); QByteArray encrypted = QAESEncryption::Crypt(QAESEncryption::AES_256, QAESEncryption::CBC,
inputStr.toLocal8Bit(), hashKey, hashIV);
//...
// Removal of Padding via Static function
QString decodedString = QString(QAESEncryption::RemovePadding(decodeText));
``` ```
## AES New Instructions Set
To use the hardware acceleration provided by the AES New Instructions Set, define USE_INTEL_AES_IF_AVAILABLE
If the CPU supports it, the code will switch to use AESNI automatically.
The feature is enabled by default
## Unit Testing ## Unit Testing
The unit testing vectors are available at [NIST-Recommendation for Block Cipher Modes of Operation](http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf) The unit testing vectors used are included in [NIST-Recommendation for Block Cipher Modes of Operation](http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf)
Please note that this code is not audited or AES-certified by any competent authority, use it at your own risk.
## Dependencies
* qtcore
No OpenSSL required.
## Contact ## Contact
Question or suggestions are welcome! Question or suggestions are welcome!
Please use the GitHub issue tracking to report suggestions or issues. Please use the GitHub issue tracking to report suggestions or issues.
## Licence ## License
This software is provided under the [UNLICENSE](http://unlicense.org/) This software is provided under the [UNLICENSE](http://unlicense.org/)
## Known Issues
Please take a look at the list of currently open issues

63
aesni/aesni-enc-cbc.h Normal file
View File

@ -0,0 +1,63 @@
#ifndef AESNIENCCBC_H
#define AESNIENCCBC_H
#include <wmmintrin.h>
namespace {
void AES_CBC_encrypt(const unsigned char *in,
unsigned char *out,
unsigned char ivec[16],
unsigned long length,
const char *key,
int number_of_rounds)
{
__m128i feedback,data;
unsigned long i;
int j;
if (length%16)
length = length/16+1;
else length /=16;
feedback=_mm_loadu_si128 ((__m128i*)ivec);
for(i=0; i < length; i++) {
data = _mm_loadu_si128 (&((__m128i*)in)[i]);
feedback = _mm_xor_si128 (data,feedback);
feedback = _mm_xor_si128 (feedback,((__m128i*)key)[0]);
for(j=1; j <number_of_rounds; j++)
feedback = _mm_aesenc_si128 (feedback,((__m128i*)key)[j]);
feedback = _mm_aesenclast_si128 (feedback,((__m128i*)key)[j]); _mm_storeu_si128 (&((__m128i*)out)[i],feedback);
}
}
#if 0
void AES_CBC_decrypt(const unsigned char *in,
unsigned char *out,
unsigned char ivec[16],
unsigned long length,
const char *key,
int number_of_rounds)
{
__m128i data,feedback,last_in;
unsigned long i;
int j;
if (length%16)
length = length/16+1;
else length /=16;
feedback=_mm_loadu_si128 ((__m128i*)ivec);
for(i=0; i < length; i++) {
last_in=_mm_loadu_si128 (&((__m128i*)in)[i]);
data = _mm_xor_si128 (last_in,((__m128i*)key)[0]);
for(j=1; j <number_of_rounds; j++) {
data = _mm_aesdec_si128 (data,((__m128i*)key)[j]);
}
data = _mm_aesdeclast_si128 (data,((__m128i*)key)[j]);
data = _mm_xor_si128 (data,feedback);
_mm_storeu_si128 (&((__m128i*)out)[i],data);
feedback=last_in;
}
}
#endif
}
#endif // AESNIENCCBC_H

61
aesni/aesni-enc-ecb.h Normal file
View File

@ -0,0 +1,61 @@
#ifndef AESNIENCECB_H
#define AESNIENCECB_H
#include <wmmintrin.h>
namespace {
/* Note the length of the output buffer is assumed to be a multiple of 16 bytes */
void AES_ECB_encrypt(const unsigned char *in, //pointer to the PLAINTEXT
unsigned char *out, //pointer to the CIPHERTEXT buffer
unsigned long length, //text length in bytes
const char *key, //pointer to the expanded key schedule
int number_of_rounds) //number of AES rounds 10,12 or 14
{
__m128i tmp;
unsigned long i;
int j;
if(length%16)
length = length/16+1;
else
length = length/16;
for(i = 0; i < length; i++) {
tmp = _mm_loadu_si128 (&((__m128i*)in)[i]);
tmp = _mm_xor_si128 (tmp,((__m128i*)key)[0]);
for(j=1; j <number_of_rounds; j++) {
tmp = _mm_aesenc_si128 (tmp, ((__m128i*)key)[j]);
}
tmp = _mm_aesenclast_si128 (tmp,((__m128i*)key)[j]);
_mm_storeu_si128 (&((__m128i*)out)[i],tmp);
}
}
#if 0
void AES_ECB_decrypt(const unsigned char *in, //pointer to the CIPHERTEXT
unsigned char *out, //pointer to the DECRYPTED TEXT buffer
unsigned long length, //text length in bytes
const char *key, //pointer to the expanded key schedule
int number_of_rounds) //number of AES rounds 10,12 or 14
{
__m128i tmp;
unsigned long i;
int j;
if(length%16)
length = length/16+1;
else
length = length/16;
for(i = 0; i < length; i++) {
tmp = _mm_loadu_si128 (&((__m128i*)in)[i]);
tmp = _mm_xor_si128 (tmp,((__m128i*)key)[0]);
for(j = 1; j < number_of_rounds; j++) {
tmp = _mm_aesdec_si128 (tmp,((__m128i*)key)[j]);
}
tmp = _mm_aesdeclast_si128 (tmp,((__m128i*)key)[j]);
_mm_storeu_si128 (&((__m128i*)out)[i],tmp);
}
}
#endif
}
#endif // AESNIENCECB_H

199
aesni/aesni-key-exp.h Normal file
View File

@ -0,0 +1,199 @@
#ifndef AESNIKEYEXP_H
#define AESNIKEYEXP_H
#include <wmmintrin.h>
#define cpuid(func, ax, bx, cx, dx)\
__asm__ __volatile__("cpuid": "=a" (ax), "=b" (bx), "=c" (cx), "=d" (dx) : "a" (func));
namespace {
bool check_aesni_support()
{
unsigned int a,b,c,d;
cpuid(1, a,b,c,d);
return (c & 0x2000000);
}
__m128i AES_128_ASSIST (__m128i temp1, __m128i temp2)
{
__m128i temp3;
temp2 = _mm_shuffle_epi32 (temp2 ,0xff);
temp3 = _mm_slli_si128 (temp1, 0x4);
temp1 = _mm_xor_si128 (temp1, temp3);
temp3 = _mm_slli_si128 (temp3, 0x4);
temp1 = _mm_xor_si128 (temp1, temp3);
temp3 = _mm_slli_si128 (temp3, 0x4);
temp1 = _mm_xor_si128 (temp1, temp3);
temp1 = _mm_xor_si128 (temp1, temp2);
return temp1;
}
void AES_128_Key_Expansion (const unsigned char *userkey,
unsigned char *key)
{
__m128i temp1, temp2;
__m128i *Key_Schedule = (__m128i*)key;
temp1 = _mm_loadu_si128((__m128i*)userkey);
Key_Schedule[0] = temp1;
temp2 = _mm_aeskeygenassist_si128 (temp1 ,0x1);
temp1 = AES_128_ASSIST(temp1, temp2);
Key_Schedule[1] = temp1;
temp2 = _mm_aeskeygenassist_si128 (temp1,0x2);
temp1 = AES_128_ASSIST(temp1, temp2);
Key_Schedule[2] = temp1;
temp2 = _mm_aeskeygenassist_si128 (temp1,0x4);
temp1 = AES_128_ASSIST(temp1, temp2);
Key_Schedule[3] = temp1;
temp2 = _mm_aeskeygenassist_si128 (temp1,0x8);
temp1 = AES_128_ASSIST(temp1, temp2);
Key_Schedule[4] = temp1;
temp2 = _mm_aeskeygenassist_si128 (temp1,0x10);
temp1 = AES_128_ASSIST(temp1, temp2);
Key_Schedule[5] = temp1;
temp2 = _mm_aeskeygenassist_si128 (temp1,0x20);
temp1 = AES_128_ASSIST(temp1, temp2);
Key_Schedule[6] = temp1;
temp2 = _mm_aeskeygenassist_si128 (temp1,0x40);
temp1 = AES_128_ASSIST(temp1, temp2);
Key_Schedule[7] = temp1;
temp2 = _mm_aeskeygenassist_si128 (temp1,0x80);
temp1 = AES_128_ASSIST(temp1, temp2);
Key_Schedule[8] = temp1;
temp2 = _mm_aeskeygenassist_si128 (temp1,0x1b);
temp1 = AES_128_ASSIST(temp1, temp2);
Key_Schedule[9] = temp1;
temp2 = _mm_aeskeygenassist_si128 (temp1,0x36);
temp1 = AES_128_ASSIST(temp1, temp2);
Key_Schedule[10] = temp1;
}
void KEY_192_ASSIST(__m128i* temp1, __m128i * temp2, __m128i * temp3)
{
__m128i temp4;
*temp2 = _mm_shuffle_epi32 (*temp2, 0x55);
temp4 = _mm_slli_si128 (*temp1, 0x4);
*temp1 = _mm_xor_si128 (*temp1, temp4);
temp4 = _mm_slli_si128 (temp4, 0x4);
*temp1 = _mm_xor_si128 (*temp1, temp4);
temp4 = _mm_slli_si128 (temp4, 0x4);
*temp1 = _mm_xor_si128 (*temp1, temp4);
*temp1 = _mm_xor_si128 (*temp1, *temp2);
*temp2 = _mm_shuffle_epi32(*temp1, 0xff);
temp4 = _mm_slli_si128 (*temp3, 0x4);
*temp3 = _mm_xor_si128 (*temp3, temp4);
*temp3 = _mm_xor_si128 (*temp3, *temp2);
}
void AES_192_Key_Expansion (const unsigned char *userkey, unsigned char *key)
{
__m128i temp1, temp2, temp3;
__m128i *Key_Schedule = (__m128i*)key;
temp1 = _mm_loadu_si128((__m128i*)userkey);
temp3 = _mm_loadu_si128((__m128i*)(userkey+16));
Key_Schedule[0]=temp1; Key_Schedule[1]=temp3;
temp2=_mm_aeskeygenassist_si128 (temp3,0x1);
KEY_192_ASSIST(&temp1, &temp2, &temp3);
Key_Schedule[1] = (__m128i)_mm_shuffle_pd((__m128d)Key_Schedule[1], (__m128d)temp1,0);
Key_Schedule[2] = (__m128i)_mm_shuffle_pd((__m128d)temp1,(__m128d)temp3,1);
temp2=_mm_aeskeygenassist_si128 (temp3,0x2);
KEY_192_ASSIST(&temp1, &temp2, &temp3);
Key_Schedule[3]=temp1;
Key_Schedule[4]=temp3;
temp2=_mm_aeskeygenassist_si128 (temp3,0x4);
KEY_192_ASSIST(&temp1, &temp2, &temp3);
Key_Schedule[4] = (__m128i)_mm_shuffle_pd((__m128d)Key_Schedule[4], (__m128d)temp1,0);
Key_Schedule[5] = (__m128i)_mm_shuffle_pd((__m128d)temp1,(__m128d)temp3,1);
temp2=_mm_aeskeygenassist_si128 (temp3,0x8);
KEY_192_ASSIST(&temp1, &temp2, &temp3);
Key_Schedule[6]=temp1;
Key_Schedule[7]=temp3;
temp2=_mm_aeskeygenassist_si128 (temp3,0x10);
KEY_192_ASSIST(&temp1, &temp2, &temp3);
Key_Schedule[7] = (__m128i)_mm_shuffle_pd((__m128d)Key_Schedule[7], (__m128d)temp1,0);
Key_Schedule[8] = (__m128i)_mm_shuffle_pd((__m128d)temp1,(__m128d)temp3,1);
temp2=_mm_aeskeygenassist_si128 (temp3,0x20);
KEY_192_ASSIST(&temp1, &temp2, &temp3);
Key_Schedule[9]=temp1;
Key_Schedule[10]=temp3;
temp2=_mm_aeskeygenassist_si128 (temp3,0x40);
KEY_192_ASSIST(&temp1, &temp2, &temp3);
Key_Schedule[10] = (__m128i)_mm_shuffle_pd((__m128d)Key_Schedule[10], (__m128d)temp1,0);
Key_Schedule[11] = (__m128i)_mm_shuffle_pd((__m128d)temp1,(__m128d)temp3,1);
temp2=_mm_aeskeygenassist_si128 (temp3,0x80);
KEY_192_ASSIST(&temp1, &temp2, &temp3);
Key_Schedule[12]=temp1;
}
void KEY_256_ASSIST_1(__m128i* temp1, __m128i * temp2)
{
__m128i temp4;
*temp2 = _mm_shuffle_epi32(*temp2, 0xff);
temp4 = _mm_slli_si128 (*temp1, 0x4);
*temp1 = _mm_xor_si128 (*temp1, temp4);
temp4 = _mm_slli_si128 (temp4, 0x4);
*temp1 = _mm_xor_si128 (*temp1, temp4);
temp4 = _mm_slli_si128 (temp4, 0x4);
*temp1 = _mm_xor_si128 (*temp1, temp4);
*temp1 = _mm_xor_si128 (*temp1, *temp2);
}
void KEY_256_ASSIST_2(__m128i* temp1, __m128i * temp3)
{
__m128i temp2,temp4;
temp4 = _mm_aeskeygenassist_si128 (*temp1, 0x0);
temp2 = _mm_shuffle_epi32(temp4, 0xaa);
temp4 = _mm_slli_si128 (*temp3, 0x4);
*temp3 = _mm_xor_si128 (*temp3, temp4);
temp4 = _mm_slli_si128 (temp4, 0x4);
*temp3 = _mm_xor_si128 (*temp3, temp4);
temp4 = _mm_slli_si128 (temp4, 0x4);
*temp3 = _mm_xor_si128 (*temp3, temp4);
*temp3 = _mm_xor_si128 (*temp3, temp2);
}
void AES_256_Key_Expansion (const unsigned char *userkey, unsigned char *key)
{
__m128i temp1, temp2, temp3;
__m128i *Key_Schedule = (__m128i*)key;
temp1 = _mm_loadu_si128((__m128i*)userkey);
temp3 = _mm_loadu_si128((__m128i*)(userkey+16));
Key_Schedule[0] = temp1; Key_Schedule[1] = temp3;
temp2 = _mm_aeskeygenassist_si128 (temp3,0x01);
KEY_256_ASSIST_1(&temp1, &temp2);
Key_Schedule[2]=temp1;
KEY_256_ASSIST_2(&temp1, &temp3);
Key_Schedule[3]=temp3;
temp2 = _mm_aeskeygenassist_si128 (temp3,0x02);
KEY_256_ASSIST_1(&temp1, &temp2);
Key_Schedule[4]=temp1;
KEY_256_ASSIST_2(&temp1, &temp3);
Key_Schedule[5]=temp3;
temp2 = _mm_aeskeygenassist_si128 (temp3,0x04);
KEY_256_ASSIST_1(&temp1, &temp2);
Key_Schedule[6]=temp1;
KEY_256_ASSIST_2(&temp1, &temp3);
Key_Schedule[7]=temp3;
temp2 = _mm_aeskeygenassist_si128 (temp3,0x08);
KEY_256_ASSIST_1(&temp1, &temp2);
Key_Schedule[8]=temp1;
KEY_256_ASSIST_2(&temp1, &temp3);
Key_Schedule[9]=temp3;
temp2 = _mm_aeskeygenassist_si128 (temp3,0x10);
KEY_256_ASSIST_1(&temp1, &temp2);
Key_Schedule[10]=temp1;
KEY_256_ASSIST_2(&temp1, &temp3);
Key_Schedule[11]=temp3;
temp2 = _mm_aeskeygenassist_si128 (temp3,0x20);
KEY_256_ASSIST_1(&temp1, &temp2);
Key_Schedule[12]=temp1;
KEY_256_ASSIST_2(&temp1, &temp3);
Key_Schedule[13]=temp3;
temp2 = _mm_aeskeygenassist_si128 (temp3,0x40);
KEY_256_ASSIST_1(&temp1, &temp2);
Key_Schedule[14]=temp1;
}
}
#endif // AESNIKEYEXP_H

23
ccache.pri Executable file
View File

@ -0,0 +1,23 @@
#
# Copyright (C) 2018-2020 QuasarApp.
# Distributed under the lgplv3 software license, see the accompanying
# Everyone is permitted to copy and distribute verbatim copies
# of this license document, but changing it is not allowed.
#
!isEmpty(CCACHE_INCLUDE):error("ccache.pri already included")
CCACHE_INCLUDE = 1
contains(QMAKE_HOST.os, Linux):{
BIN = $$system(which ccache)
!isEmpty(BIN) {
message(ccache detected in $$BIN)
QMAKE_CXX='$$BIN $$QMAKE_CXX'
}
}

View File

@ -1,14 +1,15 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QTest> #include <QTest>
#ifdef __cplusplus
#include "unit_test/aestest.h" #include "unit_test/aestest.h"
#endif
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
QCoreApplication a(argc, argv); QCoreApplication a(argc, argv);
AesTest test1; AesTest test1;
QTest::qExec(&test1); QTest::qExec(&test1);
return 0; return 0;
} }

View File

@ -1,35 +1,113 @@
#include "qaesencryption.h" #include "qaesencryption.h"
#include <QDebug> #include <QDebug>
#include <QVector>
#ifdef USE_INTEL_AES_IF_AVAILABLE
#include "aesni/aesni-key-exp.h"
#include "aesni/aesni-enc-ecb.h"
#include "aesni/aesni-enc-cbc.h"
#endif
/* /*
* Static Functions * Static Functions
* */ * */
QByteArray QAESEncryption::Crypt(QAESEncryption::AES level, QAESEncryption::MODE mode, const QByteArray rawText, const QByteArray key, const QByteArray iv) QByteArray QAESEncryption::Crypt(QAESEncryption::Aes level, QAESEncryption::Mode mode, const QByteArray &rawText,
const QByteArray &key, const QByteArray &iv, QAESEncryption::Padding padding)
{ {
return QAESEncryption(level, mode).encode(rawText, key, iv); return QAESEncryption(level, mode, padding).encode(rawText, key, iv);
} }
QByteArray QAESEncryption::Decrypt(QAESEncryption::AES level, QAESEncryption::MODE mode, const QByteArray rawText, const QByteArray key, const QByteArray iv) QByteArray QAESEncryption::Decrypt(QAESEncryption::Aes level, QAESEncryption::Mode mode, const QByteArray &rawText,
const QByteArray &key, const QByteArray &iv, QAESEncryption::Padding padding)
{ {
return QAESEncryption(level, mode).decode(rawText, key, iv); return QAESEncryption(level, mode, padding).decode(rawText, key, iv);
} }
QByteArray QAESEncryption::ExpandKey(QAESEncryption::AES level, QAESEncryption::MODE mode, const QByteArray key) QByteArray QAESEncryption::ExpandKey(QAESEncryption::Aes level, QAESEncryption::Mode mode, const QByteArray &key)
{ {
return QAESEncryption(level, mode).expandKey(key); return QAESEncryption(level, mode).expandKey(key);
} }
QByteArray QAESEncryption::RemovePadding(const QByteArray &rawText, QAESEncryption::Padding padding)
{
if (rawText.isEmpty())
return rawText;
QByteArray ret(rawText);
switch (padding)
{
case Padding::ZERO:
//Works only if the last byte of the decoded array is not zero
while (ret.at(ret.length()-1) == 0x00)
ret.remove(ret.length()-1, 1);
break;
case Padding::PKCS7:
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
ret.remove(ret.length() - ret.back(), ret.back());
#else
ret.remove(ret.length() - ret.at(ret.length() - 1), ret.at(ret.length() - 1));
#endif
break;
case Padding::ISO:
{
// Find the last byte which is not zero
int marker_index = ret.length() - 1;
for (; marker_index >= 0; --marker_index)
{
if (ret.at(marker_index) != 0x00)
{
break;
}
}
// And check if it's the byte for marking padding
if (ret.at(marker_index) == '\x80')
{
ret.truncate(marker_index);
}
break;
}
default:
//do nothing
break;
}
return ret;
}
/* /*
* End Static function declarations * End Static function declarations
* */ * */
/*
* Local Functions
* */
namespace {
quint8 xTime(quint8 x)
{
return ((x<<1) ^ (((x>>7) & 1) * 0x1b));
}
quint8 multiply(quint8 x, quint8 y)
{
return (((y & 1) * x) ^ ((y>>1 & 1) * xTime(x)) ^ ((y>>2 & 1) * xTime(xTime(x))) ^ ((y>>3 & 1)
* xTime(xTime(xTime(x)))) ^ ((y>>4 & 1) * xTime(xTime(xTime(xTime(x))))));
}
}
/* /*
* Constructor * End Local functions
* */ * */
QAESEncryption::QAESEncryption(QAESEncryption::AES level, QAESEncryption::MODE mode) : m_nb(4), m_blocklen(16), m_level(level), m_mode(mode)
QAESEncryption::QAESEncryption(Aes level, Mode mode,
Padding padding)
: m_nb(4), m_blocklen(16), m_level(level), m_mode(mode), m_padding(padding)
, m_aesNIAvailable(false), m_state(nullptr)
{ {
m_state = NULL; #ifdef USE_INTEL_AES_IF_AVAILABLE
m_aesNIAvailable = check_aesni_support();
#endif
switch (level) switch (level)
{ {
@ -63,32 +141,93 @@ QAESEncryption::QAESEncryption(QAESEncryption::AES level, QAESEncryption::MODE m
m_keyLen = aes.keylen; m_keyLen = aes.keylen;
m_nr = aes.nr; m_nr = aes.nr;
m_expandedKey = aes.expandedKey; m_expandedKey = aes.expandedKey;
qDebug() << "Defaulting to AES128";
} }
break; break;
} }
} }
QByteArray QAESEncryption::getPadding(int currSize, int alignment)
QByteArray QAESEncryption::expandKey(const QByteArray key)
{ {
int size = (alignment - currSize % alignment) % alignment;
switch(m_padding)
{
case Padding::ZERO:
return QByteArray(size, 0x00);
break;
case Padding::PKCS7:
if (size == 0)
size = alignment;
return QByteArray(size, size);
break;
case Padding::ISO:
if (size > 0)
return QByteArray (size - 1, 0x00).prepend('\x80');
break;
default:
return QByteArray(size, 0x00);
break;
}
return QByteArray();
}
QByteArray QAESEncryption::expandKey(const QByteArray &key)
{
#ifdef USE_INTEL_AES_IF_AVAILABLE
if (true){
switch(m_level) {
case AES_128: {
AES128 aes128;
quint8 ret[aes128.expandedKey];
memset(ret, 0x00, sizeof(ret));
quint8 uchar_key[key.size()];
memcpy(uchar_key, key.data(), key.size());
AES_128_Key_Expansion(uchar_key, ret);
return QByteArray((char*) ret, aes128.expandedKey);
}
break;
case AES_192: {
AES192 aes192;
quint8 ret[aes192.expandedKey];
memset(ret, 0x00, sizeof(ret));
quint8 uchar_key[key.size()];
memcpy(uchar_key, key.data(), key.size());
AES_192_Key_Expansion(uchar_key, ret);
return QByteArray((char*) ret, aes192.expandedKey);
}
break;
case AES_256: {
AES256 aes256;
quint8 ret[aes256.expandedKey];
memset(ret, 0x00, sizeof(ret));
quint8 uchar_key[key.size()];
memcpy(uchar_key, key.data(), key.size());
AES_256_Key_Expansion(uchar_key, ret);
return QByteArray((char*) ret, aes256.expandedKey);
}
break;
default:
return QByteArray();
break;
}
} else
#endif
{
int i, k; int i, k;
quint8 tempa[4]; // Used for the column/row operations quint8 tempa[4]; // Used for the column/row operations
QByteArray roundKey(key); QByteArray roundKey(key); // The first round key is the key itself.
// The first round key is the key itself.
// ...
// All other round keys are found from the previous round keys. // All other round keys are found from the previous round keys.
//i == Nk //i == Nk
for(i = m_nk; i < m_nb * (m_nr + 1); i++) for(i = m_nk; i < m_nb * (m_nr + 1); i++)
{
{ {
tempa[0] = (quint8) roundKey.at((i-1) * 4 + 0); tempa[0] = (quint8) roundKey.at((i-1) * 4 + 0);
tempa[1] = (quint8) roundKey.at((i-1) * 4 + 1); tempa[1] = (quint8) roundKey.at((i-1) * 4 + 1);
tempa[2] = (quint8) roundKey.at((i-1) * 4 + 2); tempa[2] = (quint8) roundKey.at((i-1) * 4 + 2);
tempa[3] = (quint8) roundKey.at((i-1) * 4 + 3); tempa[3] = (quint8) roundKey.at((i-1) * 4 + 3);
}
if (i % m_nk == 0) if (i % m_nk == 0)
{ {
@ -96,53 +235,45 @@ QByteArray QAESEncryption::expandKey(const QByteArray key)
// [a0,a1,a2,a3] becomes [a1,a2,a3,a0] // [a0,a1,a2,a3] becomes [a1,a2,a3,a0]
// Function RotWord() // Function RotWord()
{
k = tempa[0]; k = tempa[0];
tempa[0] = tempa[1]; tempa[0] = tempa[1];
tempa[1] = tempa[2]; tempa[1] = tempa[2];
tempa[2] = tempa[3]; tempa[2] = tempa[3];
tempa[3] = k; tempa[3] = k;
}
// SubWord() is a function that takes a four-byte input word and
// applies the S-box to each of the four bytes to produce an output word.
// Function Subword() // Function Subword()
{
tempa[0] = getSBoxValue(tempa[0]); tempa[0] = getSBoxValue(tempa[0]);
tempa[1] = getSBoxValue(tempa[1]); tempa[1] = getSBoxValue(tempa[1]);
tempa[2] = getSBoxValue(tempa[2]); tempa[2] = getSBoxValue(tempa[2]);
tempa[3] = getSBoxValue(tempa[3]); tempa[3] = getSBoxValue(tempa[3]);
}
tempa[0] = tempa[0] ^ Rcon[i/m_nk]; tempa[0] = tempa[0] ^ Rcon[i/m_nk];
} }
if (m_level == AES_256 && i % m_nk == 4) if (m_level == AES_256 && i % m_nk == 4)
{ {
// Function Subword() // Function Subword()
{
tempa[0] = getSBoxValue(tempa[0]); tempa[0] = getSBoxValue(tempa[0]);
tempa[1] = getSBoxValue(tempa[1]); tempa[1] = getSBoxValue(tempa[1]);
tempa[2] = getSBoxValue(tempa[2]); tempa[2] = getSBoxValue(tempa[2]);
tempa[3] = getSBoxValue(tempa[3]); tempa[3] = getSBoxValue(tempa[3]);
} }
}
roundKey.insert(i * 4 + 0, (quint8) roundKey.at((i - m_nk) * 4 + 0) ^ tempa[0]); roundKey.insert(i * 4 + 0, (quint8) roundKey.at((i - m_nk) * 4 + 0) ^ tempa[0]);
roundKey.insert(i * 4 + 1, (quint8) roundKey.at((i - m_nk) * 4 + 1) ^ tempa[1]); roundKey.insert(i * 4 + 1, (quint8) roundKey.at((i - m_nk) * 4 + 1) ^ tempa[1]);
roundKey.insert(i * 4 + 2, (quint8) roundKey.at((i - m_nk) * 4 + 2) ^ tempa[2]); roundKey.insert(i * 4 + 2, (quint8) roundKey.at((i - m_nk) * 4 + 2) ^ tempa[2]);
roundKey.insert(i * 4 + 3, (quint8) roundKey.at((i - m_nk) * 4 + 3) ^ tempa[3]); roundKey.insert(i * 4 + 3, (quint8) roundKey.at((i - m_nk) * 4 + 3) ^ tempa[3]);
} }
return roundKey; return roundKey;
}
} }
// This function adds the round key to state. // This function adds the round key to state.
// The round key is added to the state by an XOR function. // The round key is added to the state by an XOR function.
void QAESEncryption::addRoundKey(const quint8 round, const QByteArray expKey) void QAESEncryption::addRoundKey(const quint8 round, const QByteArray &expKey)
{ {
QByteArray::iterator it = m_state->begin(); QByteArray::iterator it = m_state->begin();
for(int i=0; i < 16; ++i) { for(int i=0; i < 16; ++i)
it[i] = (quint8) it[i] ^ (quint8) expKey.at(round * m_nb * 4 + (i/4) * m_nb + (i%4)); it[i] = (quint8) it[i] ^ (quint8) expKey.at(round * m_nb * 4 + (i/4) * m_nb + (i%4));
}
} }
// The SubBytes Function Substitutes the values in the // The SubBytes Function Substitutes the values in the
@ -193,8 +324,7 @@ void QAESEncryption::mixColumns()
QByteArray::iterator it = m_state->begin(); QByteArray::iterator it = m_state->begin();
quint8 tmp, tm, t; quint8 tmp, tm, t;
for(int i = 0; i < 16; i += 4) for(int i = 0; i < 16; i += 4){
{
t = (quint8)it[i]; t = (quint8)it[i];
tmp = (quint8)it[i] ^ (quint8)it[i+1] ^ (quint8)it[i+2] ^ (quint8)it[i+3] ; tmp = (quint8)it[i] ^ (quint8)it[i+1] ^ (quint8)it[i+2] ^ (quint8)it[i+3] ;
@ -219,8 +349,7 @@ void QAESEncryption::invMixColumns()
{ {
QByteArray::iterator it = m_state->begin(); QByteArray::iterator it = m_state->begin();
quint8 a,b,c,d; quint8 a,b,c,d;
for(int i = 0; i < 16; i+=4) for(int i = 0; i < 16; i+=4){
{
a = (quint8) it[i]; a = (quint8) it[i];
b = (quint8) it[i+1]; b = (quint8) it[i+1];
c = (quint8) it[i+2]; c = (quint8) it[i+2];
@ -265,27 +394,28 @@ void QAESEncryption::invShiftRows()
it[6] = (quint8)temp; it[6] = (quint8)temp;
//Shift 3 //Shift 3
temp = (quint8)it[15]; temp = (quint8)it[7];
it[15] = (quint8)it[3];
it[3] = (quint8)it[7];
it[7] = (quint8)it[11]; it[7] = (quint8)it[11];
it[11] = (quint8)temp; it[11] = (quint8)it[15];
it[15] = (quint8)it[3];
it[3] = (quint8)temp;
} }
QByteArray QAESEncryption::ivXor(const QByteArray in, const QByteArray iv) QByteArray QAESEncryption::byteXor(const QByteArray &a, const QByteArray &b)
{ {
QByteArray::const_iterator it = in.begin(); QByteArray::const_iterator it_a = a.begin();
QByteArray::const_iterator it_iv = iv.begin(); QByteArray::const_iterator it_b = b.begin();
QByteArray ret; QByteArray ret;
for(int i = 0; i < m_blocklen; i++) //for(int i = 0; i < m_blocklen; i++)
ret.insert(i,it[i] ^ it_iv[i]); for(int i = 0; i < std::min(a.size(), b.size()); i++)
ret.insert(i,it_a[i] ^ it_b[i]);
return ret; return ret;
} }
// Cipher is the main function that encrypts the PlainText. // Cipher is the main function that encrypts the PlainText.
QByteArray QAESEncryption::cipher(const QByteArray expKey, const QByteArray in) QByteArray QAESEncryption::cipher(const QByteArray &expKey, const QByteArray &in)
{ {
//m_state is the input buffer... //m_state is the input buffer...
@ -298,8 +428,7 @@ QByteArray QAESEncryption::cipher(const QByteArray expKey, const QByteArray in)
// There will be Nr rounds. // There will be Nr rounds.
// The first Nr-1 rounds are identical. // The first Nr-1 rounds are identical.
// These Nr-1 rounds are executed in the loop below. // These Nr-1 rounds are executed in the loop below.
for(quint8 round = 1; round < m_nr; ++round) for(quint8 round = 1; round < m_nr; ++round){
{
subBytes(); subBytes();
shiftRows(); shiftRows();
mixColumns(); mixColumns();
@ -315,7 +444,7 @@ QByteArray QAESEncryption::cipher(const QByteArray expKey, const QByteArray in)
return output; return output;
} }
QByteArray QAESEncryption::invCipher(const QByteArray expKey, const QByteArray in) QByteArray QAESEncryption::invCipher(const QByteArray &expKey, const QByteArray &in)
{ {
//m_state is the input buffer.... handle it! //m_state is the input buffer.... handle it!
QByteArray output(in); QByteArray output(in);
@ -327,8 +456,7 @@ QByteArray QAESEncryption::invCipher(const QByteArray expKey, const QByteArray i
// There will be Nr rounds. // There will be Nr rounds.
// The first Nr-1 rounds are identical. // The first Nr-1 rounds are identical.
// These Nr-1 rounds are executed in the loop below. // These Nr-1 rounds are executed in the loop below.
for(quint8 round=m_nr-1; round>0 ; round--) for(quint8 round=m_nr-1; round>0 ; round--){
{
invShiftRows(); invShiftRows();
invSubBytes(); invSubBytes();
addRoundKey(round, expKey); addRoundKey(round, expKey);
@ -344,52 +472,155 @@ QByteArray QAESEncryption::invCipher(const QByteArray expKey, const QByteArray i
return output; return output;
} }
QByteArray QAESEncryption::encode(const QByteArray rawText, const QByteArray key, const QByteArray iv) QByteArray QAESEncryption::printArray(uchar* arr, int size)
{ {
if (m_mode == CBC && (iv.isNull() || iv.size() != m_blocklen)) QByteArray print("");
for(int i=0; i<size; i++)
print.append(arr[i]);
return print.toHex();
}
QByteArray QAESEncryption::encode(const QByteArray &rawText, const QByteArray &key, const QByteArray &iv)
{
if (m_mode >= CBC && (iv.isEmpty() || iv.size() != m_blocklen))
return QByteArray(); return QByteArray();
QByteArray ret;
QByteArray expandedKey = expandKey(key); QByteArray expandedKey = expandKey(key);
QByteArray alignedText(rawText); QByteArray alignedText(rawText);
QByteArray ivTemp(iv);
//Fill array with padding
alignedText.append(getPadding(rawText.size(), m_blocklen));
alignedText.append(getPadding(rawText.size(), m_blocklen), 0); //filling the array with zeros switch(m_mode)
for(int i=0; i < alignedText.size(); i+= m_blocklen)
{ {
if (m_mode == CBC) case ECB: {
alignedText.replace(i, m_blocklen, ivXor(alignedText.mid(i, m_blocklen),ivTemp)); #ifdef USE_INTEL_AES_IF_AVAILABLE
if (m_aesNIAvailable){
unsigned char in[alignedText.size()];
memcpy(in, alignedText.data(), alignedText.size());
unsigned char out[alignedText.size()];
memcpy(out, alignedText.data(), alignedText.size());
char expKey[expandedKey.size()];
memcpy(expKey, expandedKey.data(), expandedKey.size());
AES_ECB_encrypt(in, out, alignedText.size(),
expKey, m_nr);
return QByteArray((char*)out, alignedText.size());
}
#endif
QByteArray ret;
for(int i=0; i < alignedText.size(); i+= m_blocklen)
ret.append(cipher(expandedKey, alignedText.mid(i, m_blocklen)));
return ret;
}
break;
case CBC: {
#ifdef USE_INTEL_AES_IF_AVAILABLE
if (m_aesNIAvailable){
quint8 in[alignedText.size()];
memcpy(in, alignedText.constData(), alignedText.size());
quint8 ivec[iv.size()];
memcpy(ivec, iv.data(), iv.size());
char out[alignedText.size()];
memset(out, 0x00, alignedText.size());
char expKey[expandedKey.size()];
memcpy(expKey, expandedKey.data(), expandedKey.size());
AES_CBC_encrypt(in,
(unsigned char*) out,
ivec,
alignedText.size(),
expKey,
m_nr);
return QByteArray(out, alignedText.size());
}
#endif
QByteArray ret;
QByteArray ivTemp(iv);
for(int i=0; i < alignedText.size(); i+= m_blocklen) {
alignedText.replace(i, m_blocklen, byteXor(alignedText.mid(i, m_blocklen),ivTemp));
ret.append(cipher(expandedKey, alignedText.mid(i, m_blocklen))); ret.append(cipher(expandedKey, alignedText.mid(i, m_blocklen)));
if (m_mode == CBC)
ivTemp = ret.mid(i, m_blocklen); ivTemp = ret.mid(i, m_blocklen);
} }
return ret; return ret;
}
break;
case CFB: {
QByteArray ret;
ret.append(byteXor(alignedText.left(m_blocklen), cipher(expandedKey, iv)));
for(int i=0; i < alignedText.size(); i+= m_blocklen) {
if (i+m_blocklen < alignedText.size())
ret.append(byteXor(alignedText.mid(i+m_blocklen, m_blocklen),
cipher(expandedKey, ret.mid(i, m_blocklen))));
}
return ret;
}
break;
case OFB: {
QByteArray ret;
QByteArray ofbTemp;
ofbTemp.append(cipher(expandedKey, iv));
for (int i=m_blocklen; i < alignedText.size(); i += m_blocklen){
ofbTemp.append(cipher(expandedKey, ofbTemp.right(m_blocklen)));
}
ret.append(byteXor(alignedText, ofbTemp));
return ret;
}
break;
default: break;
}
return QByteArray();
} }
QByteArray QAESEncryption::decode(const QByteArray rawText, const QByteArray key, const QByteArray iv) QByteArray QAESEncryption::decode(const QByteArray &rawText, const QByteArray &key, const QByteArray &iv)
{ {
if (m_mode == CBC && (iv.isNull() || iv.size() != m_blocklen)) if (m_mode >= CBC && (iv.isEmpty() || iv.size() != m_blocklen))
return QByteArray(); return QByteArray();
QByteArray ret; QByteArray ret;
QByteArray expandedKey = expandKey(key); QByteArray expandedKey = expandKey(key);
QByteArray alignedText(rawText);
QByteArray ivTemp(iv);
for(int i=0; i < alignedText.size(); i+= m_blocklen) switch(m_mode)
{ {
ret.append(invCipher(expandedKey, alignedText.mid(i, m_blocklen))); case ECB:
for(int i=0; i < rawText.size(); i+= m_blocklen)
if (m_mode == CBC) { ret.append(invCipher(expandedKey, rawText.mid(i, m_blocklen)));
ret.replace(i, m_blocklen, ivXor(ret.mid(i, m_blocklen),ivTemp)); break;
ivTemp = alignedText.mid(i, m_blocklen); case CBC: {
QByteArray ivTemp(iv);
for(int i=0; i < rawText.size(); i+= m_blocklen){
ret.append(invCipher(expandedKey, rawText.mid(i, m_blocklen)));
ret.replace(i, m_blocklen, byteXor(ret.mid(i, m_blocklen),ivTemp));
ivTemp = rawText.mid(i, m_blocklen);
} }
} }
break;
case CFB: {
ret.append(byteXor(rawText.mid(0, m_blocklen), cipher(expandedKey, iv)));
for(int i=0; i < rawText.size(); i+= m_blocklen){
if (i+m_blocklen < rawText.size()) {
ret.append(byteXor(rawText.mid(i+m_blocklen, m_blocklen),
cipher(expandedKey, rawText.mid(i, m_blocklen))));
}
}
}
break;
case OFB: {
QByteArray ofbTemp;
ofbTemp.append(cipher(expandedKey, iv));
for (int i=m_blocklen; i < rawText.size(); i += m_blocklen){
ofbTemp.append(cipher(expandedKey, ofbTemp.right(m_blocklen)));
}
ret.append(byteXor(rawText, ofbTemp));
}
break;
default:
//do nothing
break;
}
return ret; return ret;
} }
QByteArray QAESEncryption::removePadding(const QByteArray &rawText)
{
return RemovePadding(rawText, (Padding) m_padding);
}

View File

@ -4,77 +4,104 @@
#include <QObject> #include <QObject>
#include <QByteArray> #include <QByteArray>
class QAESEncryption : public QObject #ifdef __linux__
#ifndef __LP64__
#define do_rdtsc _do_rdtsc
#endif
#endif
#include "./../qtsecret_global.h"
class Qt_SECRETSHARED_EXPORT QAESEncryption : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
typedef enum { enum Aes {
AES_128, AES_128,
AES_192, AES_192,
AES_256 AES_256
} AES; };
typedef enum { enum Mode {
ECB, ECB,
CBC CBC,
} MODE; CFB,
OFB
};
static QByteArray Crypt(QAESEncryption::AES level, QAESEncryption::MODE mode, const QByteArray rawText, const QByteArray key, const QByteArray iv = NULL); enum Padding {
static QByteArray Decrypt(QAESEncryption::AES level, QAESEncryption::MODE mode, const QByteArray rawText, const QByteArray key, const QByteArray iv = NULL); ZERO,
static QByteArray ExpandKey(QAESEncryption::AES level, QAESEncryption::MODE mode, const QByteArray key); PKCS7,
ISO
};
QAESEncryption(QAESEncryption::AES level, QAESEncryption::MODE mode); static QByteArray Crypt(QAESEncryption::Aes level, QAESEncryption::Mode mode, const QByteArray &rawText, const QByteArray &key,
const QByteArray &iv = QByteArray(), QAESEncryption::Padding padding = QAESEncryption::ISO);
static QByteArray Decrypt(QAESEncryption::Aes level, QAESEncryption::Mode mode, const QByteArray &rawText, const QByteArray &key,
const QByteArray &iv = QByteArray(), QAESEncryption::Padding padding = QAESEncryption::ISO);
static QByteArray ExpandKey(QAESEncryption::Aes level, QAESEncryption::Mode mode, const QByteArray &key);
static QByteArray RemovePadding(const QByteArray &rawText, QAESEncryption::Padding padding = QAESEncryption::ISO);
QByteArray encode(const QByteArray rawText, const QByteArray key, const QByteArray iv = NULL); QAESEncryption(QAESEncryption::Aes level, QAESEncryption::Mode mode,
QByteArray decode(const QByteArray rawText, const QByteArray key, const QByteArray iv = NULL); QAESEncryption::Padding padding = QAESEncryption::ISO);
QByteArray expandKey(const QByteArray key);
QByteArray encode(const QByteArray &rawText, const QByteArray &key, const QByteArray &iv = QByteArray());
QByteArray decode(const QByteArray &rawText, const QByteArray &key, const QByteArray &iv = QByteArray());
QByteArray removePadding(const QByteArray &rawText);
QByteArray expandKey(const QByteArray &key);
QByteArray printArray(uchar *arr, int size);
signals: signals:
public slots: public slots:
private: private:
//typedef uint8_t state[4][4]; int m_nb;
int m_blocklen;
int m_nb, m_blocklen, m_level, m_mode; int m_level;
int m_mode;
int m_nk;
int m_keyLen;
int m_nr;
int m_expandedKey;
int m_padding;
bool m_aesNIAvailable;
QByteArray* m_state; QByteArray* m_state;
int m_nk, m_keyLen, m_nr, m_expandedKey; struct AES256{
typedef struct{
int nk = 8; int nk = 8;
int keylen = 32; int keylen = 32;
int nr = 14; int nr = 14;
int expandedKey = 240; int expandedKey = 240;
} AES256; };
typedef struct{ struct AES192{
int nk = 6; int nk = 6;
int keylen = 24; int keylen = 24;
int nr = 12; int nr = 12;
int expandedKey = 209; int expandedKey = 209;
} AES192; };
typedef struct{ struct AES128{
int nk = 4; int nk = 4;
int keylen = 16; int keylen = 16;
int nr = 10; int nr = 10;
int expandedKey = 176; int expandedKey = 176;
} AES128; };
quint8 getSBoxValue(quint8 num){return sbox[num];} quint8 getSBoxValue(quint8 num){return sbox[num];}
quint8 getSBoxInvert(quint8 num){return rsbox[num];} quint8 getSBoxInvert(quint8 num){return rsbox[num];}
void addRoundKey(const quint8 round, const QByteArray expKey); void addRoundKey(const quint8 round, const QByteArray &expKey);
void subBytes(); void subBytes();
void shiftRows(); void shiftRows();
void mixColumns(); void mixColumns();
void invMixColumns(); void invMixColumns();
void invSubBytes(); void invSubBytes();
void invShiftRows(); void invShiftRows();
QByteArray cipher(const QByteArray expKey, const QByteArray plainText); QByteArray getPadding(int currSize, int alignment);
QByteArray invCipher(const QByteArray expKey, const QByteArray plainText); QByteArray cipher(const QByteArray &expKey, const QByteArray &in);
QByteArray ivXor(const QByteArray in, const QByteArray iv); QByteArray invCipher(const QByteArray &expKey, const QByteArray &in);
QByteArray byteXor(const QByteArray &a, const QByteArray &b);
const quint8 sbox[256] = { const quint8 sbox[256] = {
//0 1 2 3 4 5 6 7 8 9 A B C D E F //0 1 2 3 4 5 6 7 8 9 A B C D E F
@ -95,8 +122,8 @@ private:
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 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 }; 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
const quint8 rsbox[256] = const quint8 rsbox[256] = {
{ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 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, 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, 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, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
@ -115,35 +142,9 @@ private:
// The round constant word array, Rcon[i], contains the values given by // The round constant word array, Rcon[i], contains the values given by
// x to th e power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) // x to th e power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8)
const quint8 Rcon[256] = { // Only the first 14 elements are needed
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, const quint8 Rcon[14] = {
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab};
0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d };
inline quint8 xTime(quint8 x){
return ((x<<1) ^ (((x>>7) & 1) * 0x1b));
}
inline quint8 multiply(quint8 x, quint8 y){
return (((y & 1) * x) ^ ((y>>1 & 1) * xTime(x)) ^ ((y>>2 & 1) * xTime(xTime(x))) ^ ((y>>3 & 1) * xTime(xTime(xTime(x)))) ^ ((y>>4 & 1) * xTime(xTime(xTime(xTime(x))))));
}
inline int getPadding(int currSize, int alignment) {
return (alignment - currSize % alignment) % alignment;
}
}; };
#endif // QAESENCRYPTION_H #endif // QAESENCRYPTION_H

View File

@ -9,16 +9,21 @@ CONFIG -= app_bundle
TEMPLATE = app TEMPLATE = app
SOURCES += main.cpp \
qaesencryption.cpp \
unit_test/aestest.cpp
# The following define makes your compiler emit warnings if you use # The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked deprecated (the exact warnings # any feature of Qt which as been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the # depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it. # deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS DEFINES += QT_DEPRECATED_WARNINGS
DEFINES += USE_INTEL_AES_IF_AVAILABLE
QMAKE_CXXFLAGS += -maes
CONFIG(release, debug|release): {
DESTDIR="$$PWD/build/release"
} else {
DESTDIR="$$PWD/build/debug"
}
# You can also make your code fail to compile if you use deprecated APIs. # You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line. # In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt. # You can also select to disable deprecated APIs only up to a certain version of Qt.
@ -26,5 +31,19 @@ DEFINES += QT_DEPRECATED_WARNINGS
HEADERS += \ HEADERS += \
qaesencryption.h \ qaesencryption.h \
aesni/aesni-key-exp.h \
aesni/aesni-enc-ecb.h \
aesni/aesni-enc-cbc.h \
unit_test/aestest.h unit_test/aestest.h
SOURCES += main.cpp \
qaesencryption.cpp \
unit_test/aestest.cpp
DISTFILES += \
unit_test/longText.txt
RESOURCES += \
res.qrc
include(ccache.pri)

12
qtsecret_global.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef QTSECRET_GLOBAL_H
#define QTSECRET_GLOBAL_H
#include <QtCore/qglobal.h>
#if defined(Qt_SECRET_LIBRARY)
# define Qt_SECRETSHARED_EXPORT Q_DECL_EXPORT
#else
# define Qt_SECRETSHARED_EXPORT Q_DECL_IMPORT
#endif
#endif // QTSECRET_GLOBAL_H

5
res.qrc Normal file
View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>unit_test/longText.txt</file>
</qresource>
</RCC>

View File

@ -1,11 +1,16 @@
#include "aestest.h" #include "aestest.h"
#include <QDebug>
#include <QByteArray> #include <QByteArray>
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QFile>
#include "qaesencryption.h" #include "qaesencryption.h"
void AesTest::initTestCase() void AesTest::initTestCase()
{ {
#ifdef USE_INTEL_AES_IF_AVAILABLE
qDebug() << "AESNI Enabled";
#endif
quint8 key_16[16] = {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c}; quint8 key_16[16] = {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c};
for (int i=0; i<16; i++) for (int i=0; i<16; i++)
key16.append(key_16[i]); key16.append(key_16[i]);
@ -28,12 +33,14 @@ void AesTest::initTestCase()
quint8 out_text[16] = { 0x3a, 0xd7, 0x7b, 0xb4, 0x0d, 0x7a, 0x36, 0x60, 0xa8, 0x9e, 0xca, 0xf3, 0x24, 0x66, 0xef, 0x97 }; quint8 out_text[16] = { 0x3a, 0xd7, 0x7b, 0xb4, 0x0d, 0x7a, 0x36, 0x60, 0xa8, 0x9e, 0xca, 0xf3, 0x24, 0x66, 0xef, 0x97 };
quint8 out_text_2[16] = { 0xbd, 0x33, 0x4f, 0x1d, 0x6e, 0x45, 0xf2, 0x5f, 0xf7, 0x12, 0xa2, 0x14, 0x57, 0x1f, 0xa5, 0xcc }; quint8 out_text_2[16] = { 0xbd, 0x33, 0x4f, 0x1d, 0x6e, 0x45, 0xf2, 0x5f, 0xf7, 0x12, 0xa2, 0x14, 0x57, 0x1f, 0xa5, 0xcc };
quint8 out_text_3[16] = { 0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c, 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1, 0x81, 0xf8 }; quint8 out_text_3[16] = { 0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c, 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1, 0x81, 0xf8 };
quint8 out_text_4[16] = { 0x3b, 0x3f, 0xd9, 0x2e, 0xb7, 0x2d, 0xad, 0x20, 0x33, 0x34, 0x49, 0xf8, 0xe8, 0x3c, 0xfb, 0x4a };
for (int i=0; i<16; i++){ for (int i=0; i<16; i++){
in.append(in_text[i]); in.append(in_text[i]);
outECB128.append(out_text[i]); outECB128.append(out_text[i]);
outECB192.append(out_text_2[i]); outECB192.append(out_text_2[i]);
outECB256.append(out_text_3[i]); outECB256.append(out_text_3[i]);
outOFB128.append(out_text_4[i]);
} }
quint8 text_cbc[64] = { 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, quint8 text_cbc[64] = { 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
@ -50,6 +57,7 @@ void AesTest::initTestCase()
inCBC128.append(text_cbc[i]); inCBC128.append(text_cbc[i]);
outCBC128.append(output_cbc[i]); outCBC128.append(output_cbc[i]);
} }
} }
@ -59,13 +67,11 @@ void AesTest::ECB128Crypt()
{ {
QByteArray hexText, outputHex; QByteArray hexText, outputHex;
QAESEncryption encryption(QAESEncryption::AES_128, QAESEncryption::ECB); QAESEncryption encryption(QAESEncryption::AES_128, QAESEncryption::ECB);
QCOMPARE(encryption.encode(in, key16), outECB128); QCOMPARE(encryption.encode(in, key16), outECB128);
} }
void AesTest::ECB128Decrypt() void AesTest::ECB128Decrypt()
{ {
QByteArray hexText, outputHex;
QAESEncryption encryption(QAESEncryption::AES_128, QAESEncryption::ECB); QAESEncryption encryption(QAESEncryption::AES_128, QAESEncryption::ECB);
QCOMPARE(encryption.decode(outECB128, key16), in); QCOMPARE(encryption.decode(outECB128, key16), in);
@ -103,7 +109,7 @@ void AesTest::ECB256Decrypt()
void AesTest::ECB256String() void AesTest::ECB256String()
{ {
QAESEncryption encryption(QAESEncryption::AES_256, QAESEncryption::ECB); QAESEncryption encryption(QAESEncryption::AES_256, QAESEncryption::ECB, QAESEncryption::Padding::ISO);
QString inputStr("The Advanced Encryption Standard (AES), also known by its original name Rijndael " QString inputStr("The Advanced Encryption Standard (AES), also known by its original name Rijndael "
"is a specification for the encryption of electronic data established by the U.S. " "is a specification for the encryption of electronic data established by the U.S. "
@ -113,12 +119,13 @@ void AesTest::ECB256String()
QByteArray hashKey = QCryptographicHash::hash(key.toLocal8Bit(), QCryptographicHash::Sha256); QByteArray hashKey = QCryptographicHash::hash(key.toLocal8Bit(), QCryptographicHash::Sha256);
QByteArray encodeText = encryption.encode(inputStr.toLocal8Bit(), hashKey); QByteArray encodeText = encryption.encode(inputStr.toLocal8Bit(), hashKey);
QByteArray decodedText = encryption.removePadding(encryption.decode(encodeText, hashKey));
QCOMPARE(QString(encryption.decode(encodeText, hashKey)), inputStr); QCOMPARE(QString(decodedText), inputStr);
} }
//==================CBC TESTING========================= ////==================CBC TESTING=========================
void AesTest::CBC128Crypt() void AesTest::CBC128Crypt()
{ {
@ -133,3 +140,98 @@ void AesTest::CBC128Decrypt()
QCOMPARE(encryption.decode(outCBC128, key16, iv), inCBC128); QCOMPARE(encryption.decode(outCBC128, key16, iv), inCBC128);
} }
//=================== CFB TESTING ============================
void AesTest::CFB256String()
{
QAESEncryption encryption(QAESEncryption::AES_256, QAESEncryption::CFB, QAESEncryption::PKCS7);
QString inputStr("The Advanced Encryption Standard (AES), also known by its original name Rijndael "
"is a specification for the encryption of electronic data established by the U.S. "
"National Institute of Standards and Technology (NIST) in 2001");
QString key("123456789123");
QByteArray hashKey = QCryptographicHash::hash(key.toLocal8Bit(), QCryptographicHash::Sha256);
QByteArray encodeText = encryption.encode(inputStr.toLocal8Bit(), hashKey, iv);
QByteArray decodedText = encryption.removePadding(encryption.decode(encodeText, hashKey, iv));
QCOMPARE(QString(decodedText), inputStr);
}
void AesTest::CFB256LongText()
{
QAESEncryption encryption(QAESEncryption::AES_256, QAESEncryption::CFB);
QFile textFile(":/unit_test/longText.txt");
QByteArray input;
if (textFile.open(QFile::ReadOnly))
input = textFile.readAll();
else
QFAIL("File longText.txt not found!");
QString key("123456789123");
QByteArray hashKey = QCryptographicHash::hash(key.toLocal8Bit(), QCryptographicHash::Sha256);
QByteArray encodeText = encryption.encode(input, hashKey, iv);
QByteArray decodedText = encryption.removePadding(encryption.decode(encodeText, hashKey, iv));
QCOMPARE(decodedText, input);
}
void AesTest::OFB128Crypt()
{
QAESEncryption encryption(QAESEncryption::AES_128, QAESEncryption::OFB);
QCOMPARE(encryption.encode(in, key16, iv), outOFB128);
}
void AesTest::OFB256String()
{
QAESEncryption encryption(QAESEncryption::AES_128, QAESEncryption::OFB, QAESEncryption::PKCS7);
QString inputStr("The Advanced Encryption Standard (AES), also known by its original name Rijndael "
"is a specification for the encryption of electronic data established by the U.S. "
"National Institute of Standards and Technology (NIST) in 2001");
QString key("123456789123");
QByteArray hashKey = QCryptographicHash::hash(key.toLocal8Bit(), QCryptographicHash::Sha256);
QByteArray encodeText = encryption.encode(inputStr.toLocal8Bit(), hashKey, iv);
QByteArray decodedText = encryption.removePadding(encryption.decode(encodeText, hashKey, iv));
QCOMPARE(inputStr, QString(decodedText));
}
void AesTest::CBC256StringEvenISO()
{
QAESEncryption encryption(QAESEncryption::AES_256, QAESEncryption::CBC);
//16 byte string
QString inputStr("1234567890123456");
QString key("123456789123");
QByteArray hashKey = QCryptographicHash::hash(key.toLocal8Bit(), QCryptographicHash::Sha256);
QByteArray encodeText = encryption.encode(inputStr.toLocal8Bit(), hashKey, iv);
QByteArray decodeText = encryption.decode(encodeText, hashKey, iv);
QString decodedString = QString(encryption.removePadding(decodeText));
QCOMPARE(QString(decodeText), decodedString);
}
void AesTest::CBC256StringEvenPKCS7()
{
QAESEncryption encryption(QAESEncryption::AES_256, QAESEncryption::CBC, QAESEncryption::PKCS7);
//16 byte string
QString inputStr("1234567890123456");
int blockLen = 16;
QString key("123456789123");
QByteArray hashKey = QCryptographicHash::hash(key.toLocal8Bit(), QCryptographicHash::Sha256);
QByteArray encodeText = encryption.encode(inputStr.toLocal8Bit(), hashKey, iv);
QByteArray decodeText = encryption.decode(encodeText, hashKey, iv);
QByteArray padding = decodeText.remove(0, encryption.removePadding(decodeText).length());
QCOMPARE(padding.size(), blockLen);
}

View File

@ -25,13 +25,30 @@ private slots:
void CBC128Crypt(); void CBC128Crypt();
void CBC128Decrypt(); void CBC128Decrypt();
void CFB256String();
void CFB256LongText();
void OFB128Crypt();
void OFB256String();
void CBC256StringEvenISO();
void CBC256StringEvenPKCS7();
void cleanupTestCase(){} void cleanupTestCase(){}
private: private:
QByteArray key16, key24, key32; QByteArray key16;
QByteArray key24;
QByteArray key32;
QByteArray iv; QByteArray iv;
QByteArray in, outECB128, outECB192, outECB256; QByteArray in;
QByteArray inCBC128, outCBC128; QByteArray outECB128;
QByteArray outECB192;
QByteArray outECB256;
QByteArray inCBC128;
QByteArray outCBC128;
QByteArray outOFB128;
}; };
#endif // AESTEST_H #endif // AESTEST_H

665
unit_test/longText.txt Normal file
View File

@ -0,0 +1,665 @@
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
rFNmuB2892Bb53Y3aIkONNC2Sh1uUua9JxUDjThJSl1eKWX2VTeXjJrS+xrMK5dnP6CK3reuFocI
nVlTGucuHzQJPVylSFjIgDJLp2SbhMkDuTNpklMXIBxZJZcklXIS5LCKQIpKtsk2NyayQyJLkCuS
ylyZ0+5LSzyaxdScurkyZ1k6kkn2DiScqp8litMincmUfJanlySBJDfJE//EAC4QAAICAgEDAwQB
BAMBAQAAAAABAhEDEiEEEBMgIjEUMDJBBSMzQEIVNFBEgP/aAAgBAQABBQL/APUNf+tXejXukamp
qaigKNE/kfAnZkySWT62acOtgyM4T/8AMoUSho0FEkjQUDUrtqa9mZMkmYYPKZNavFeTHk8mfFT5
TjOUXg6y/wDHr/ErvXevs5eU8S1hDQkrXgh05LH1OchHqMUeoxqEu3RZr9FFFd9Ga0UV6qKKOPs6
mpRTNWaM0ZozQ0NSiyyzY2NmbM2Zsy2bG5ubGw/y7M6lSlCXXMn1eSZ1C0x9oS0lH3RortVjVGol
6KKNTQ1RwV2oo17UUUaigartRRX2aKKNSjUo1ZqzU1Gu1WRjzL8+2WekY9VvkyrHMThiMno6GW+C
mc9kxyNjY2ZbOTntZsWWX6qK7WWWWbFlllmxZfoor7qJfJKSSy52RdPIsmSWsqeI8EiWKce38Yv6
IzU0NDQ0NTU1K7UUUV34+xwWi/s3/hTZPNb0nOM/psJLrUiXU5Zjb7qc0b2dD1ePEoyUl6LLLLLL
7X6OSiiimUyjU1NTQ0NTU1NTU1NTU1NTUr7+TIscJZ8WUl1eGBm63NMdsoooo4OCk1/GYVknFKC9
NFFFFf8Alc9v5Ny8GHWMck9RzkzZlnJbLKRwM/i8tL/Bv/Gv7zfH1EpkOjxzOu6R4EM+CzTgss/X
8fx1H+JZZZZZf+Y+Vzghgfs6vF5sHji1KLR8ijbkiUeVE4qMbP47++WiyzaJaNl6bLRsjdd79G8R
5IHmieWJ5IjzRPNE8sR5Uec8shzZ5GeVnmZ5kec855yyyyyyyyyyyyyzYljjkIe3tPocM31OCWOf
iOh6NZX1H8fOCo1QooR0eRwyvJN9qkOxd9meWZ5ZjySZbL77SNpnuLmbZDllGpqao9v2q78Fm6N0
bo3N0bm5ubm5ubm7NmLJUU1aY5UdTm4/J4cn0+DN/IRlGU43ZEbOlxpQq/Xx3ooopd7G79Nll9qR
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
UjQ1aWFhEJeiJeFywdwdhbFsDU6iILDNuC1wbiE/QnuG3pjehhd0hNR4gHGmNuQMWwmkxiEFGqMu
ixIWVE4WEh3R7SY3NlqqwlNmu0JTo29jij2JP2Pej+CeCxkklEMKSMaaceBIhhlyFWiJaYmJzg/D
ZRLcD9GQlEIFGmNMOtCNvgiDIqL8I8LDlK6YWNl8FMc1ICkHBYfoFHaJpRKnDpJwY40UbR8BtNj+
GX78EIuH4oQhqMon9N0zqWCbuJETQ0NooqUXCoaT6QniuFKP/B7RBtSEHgUE+0LSITYyQWyQmxDw
9/IuZeGP/AWfOfs4PYZ0PHIuY//EACgQAQACAgICAgICAwEBAQAAAAEAESExQVFhcRCBkbGhwdHh
8PEgMP/aAAgBAQABPxCpUqJKlSpUqVKlXKlSpUqVKlSuZWJUqVKlSpUqVKlSpUqVKldypUqVKh8V
KlYlSpVzcqVKZUqVKlSpUqVK+FSpUqVKlSsSsSpUqVK6lSpUqVKlcysypUqVKgSpUqVKlSpUqVzK
lcSuIEqVKlSoEqVKlSqgSpUqVKlYmJUruUSpUqVKlSpUqVKlSpUqV8KlSupUqVMJUqVKlSpUqVKl
fFSpUqV8V1KlSpUqVKlVKlSpWK+KiSrlSpUqVKzK5iSvipUCVKlSpUqV3KldypUr4VKlSokqVKlE
qBKlSpUqVKiSpUqUSpXUSVK+AlQJUrHxVSpUqZfFROJUqV8VKlSpRKlSpUqVKlSpUqVKlSupUr4V
iVKlcSpUqVKlSpUqVKlSpVypUqVAlSpTKlSpUqVKlSpUqVKlSpWJUqVK+KlMqVKlSpUqVKlSpVSp
UqVKlSpUqVKlSpXEqVKlSpUqVKlSpUqV8VKlSmVKlSpUqVAlSpTKZUqVKudJUqUyuZXyVKifAIkL
S8t/8gIYIo3K/wDgVKlSpUqVK+FfCpUqBK+FRipUqVKohDGEqVKlcSvhUqVKlSpUqVKlSpUYq4JL
CCSoiomQT4CiOICskc9TJLGqm0Jggj1MsUpcqLNyvhidNcTTrc2KbHwwOqaIXXiVwy7MkqNPDmVK
lSpUqVKlSpUqVE4lcSu5UqVKlSpUqVKlSpUqVKlSpUqVKlSqlSpRFxUXD2QsDGobdQGllhIjcA1M
2cISxmLygYlW6B9QRxfPMa6LgbfcQ3OCziVHmtLUSuYW4L8XLr04sV9/AAUJcqoGBrgGvuFAch+F
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