From 6342884e86668738891815068ff5f1bd45d6aa4e Mon Sep 17 00:00:00 2001 From: "S. Razi Alavizadeh" Date: Sat, 8 Jun 2013 14:51:19 +0430 Subject: [PATCH] Added AES encryption/decryption ability (version 1) -AesInterface is a Qt interface for AES encryption/decryption that uses AES 256 CBC mode. --- src/lib/lib.pro | 9 +- src/lib/tools/aesinterface.cpp | 203 +++++++++++++++++++++++++++++++++ src/lib/tools/aesinterface.h | 58 ++++++++++ 3 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 src/lib/tools/aesinterface.cpp create mode 100644 src/lib/tools/aesinterface.h diff --git a/src/lib/lib.pro b/src/lib/lib.pro index ccb8b0f8f..217350163 100644 --- a/src/lib/lib.pro +++ b/src/lib/lib.pro @@ -243,7 +243,9 @@ SOURCES += \ tools/delayedfilewatcher.cpp \ autofill/passwordmanager.cpp \ autofill/passwordbackends/databasepasswordbackend.cpp \ - autofill/passwordbackends/passwordbackend.cpp + autofill/passwordbackends/passwordbackend.cpp \ + tools/aesinterface.cpp + HEADERS += \ webview/tabpreview.h \ @@ -422,7 +424,8 @@ HEADERS += \ tools/delayedfilewatcher.h \ autofill/passwordmanager.h \ autofill/passwordbackends/passwordbackend.h \ - autofill/passwordbackends/databasepasswordbackend.h + autofill/passwordbackends/databasepasswordbackend.h \ + tools/aesinterface.h FORMS += \ preferences/autofillmanager.ui \ @@ -510,6 +513,8 @@ mac { LIBS += -framework CoreServices } +LIBS += -llibeay32 + message(===========================================) message( Using following defines:) message( $$DEFINES) diff --git a/src/lib/tools/aesinterface.cpp b/src/lib/tools/aesinterface.cpp new file mode 100644 index 000000000..884e84db0 --- /dev/null +++ b/src/lib/tools/aesinterface.cpp @@ -0,0 +1,203 @@ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2013 S. Razi Alavizadeh +* Copyright (C) 2013 David Rosca +* +* This is based on a work by Saju Pillai +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ + +#include "aesinterface.h" + +#include +#include +#include + +#include +#include +#include +#include + +////////////////////////////////////////////// +/// Version 1: +/// init(): n=5, EVP_CIPHER=EVP_aes_256_cbc(), EVP_MD=EVP_sha256(), Random IV +/// Encrypted data structure: Version$InitializationVector_base64$EncryptedData_base64 +const int AesInterface::VERSION = 1; + +AesInterface::AesInterface(QObject* parent) + : QObject(parent) + , m_ok(false) +{ + EVP_CIPHER_CTX_init(&m_encodeCTX); + EVP_CIPHER_CTX_init(&m_decodeCTX); +} + +AesInterface::~AesInterface() +{ + EVP_CIPHER_CTX_cleanup(&m_encodeCTX); + EVP_CIPHER_CTX_cleanup(&m_decodeCTX); +} + +// Create an 256 bit 'key' using the supplied password, and creates a random 'iv'. +// saltArray is an array of 8 bytes can be added for taste. +// Fills in the encryption and decryption ctx objects and returns true on success +bool AesInterface::init(int evpMode, const QByteArray &password, const QByteArray &iVector) +{ + m_iVector.clear(); + + int i; + const int nrounds = 5; + uchar key[EVP_MAX_KEY_LENGTH]; + + // Gen "key" for AES 256 CBC mode. A SHA1 digest is used to hash the supplied + // key material. nrounds is the number of times that we hash the material. + // More rounds are more secure but slower. + i = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha256(), 0, (uchar*)password.data(), password.size(), nrounds, key, 0); + + if (i != 32) { + qWarning("Key size is %d bits - should be 256 bits", i * 8); + return false; + } + + int result; + if (evpMode == EVP_PKEY_MO_ENCRYPT) { + m_iVector = createRandomData(EVP_MAX_IV_LENGTH); + result = EVP_EncryptInit_ex(&m_encodeCTX, EVP_aes_256_cbc(), NULL, key, (uchar*)m_iVector.constData()); + } + else if (evpMode == EVP_PKEY_MO_DECRYPT) { + result = EVP_DecryptInit_ex(&m_decodeCTX, EVP_aes_256_cbc(), NULL, key, (uchar*)iVector.constData()); + } + + if (result == 0) { + qWarning("EVP is not initialized!"); + return false; + } + + return true; +} + +QByteArray AesInterface::encrypt(const QByteArray &plainData, const QByteArray &password) +{ + if (!init(EVP_PKEY_MO_ENCRYPT, password)) { + m_ok = false; + return plainData; + } + + // max ciphertext len for a n bytes of plaintext is n + AES_BLOCK_SIZE -1 bytes + int dataLength = plainData.size(); + int cipherlength = dataLength + AES_BLOCK_SIZE; + int finalLength = 0; + uchar* ciphertext = (uchar*)malloc(cipherlength); + + // allows reusing of 'm_encodeCTX' for multiple encryption cycles + EVP_EncryptInit_ex(&m_encodeCTX, NULL, NULL, NULL, NULL); + + // update ciphertext, c_len is filled with the length of ciphertext generated, + // dataLength is the size of plaintext in bytes + EVP_EncryptUpdate(&m_encodeCTX, ciphertext, &cipherlength, (uchar*)plainData.data(), dataLength); + + // update ciphertext with the final remaining bytes + EVP_EncryptFinal_ex(&m_encodeCTX, ciphertext + cipherlength, &finalLength); + + dataLength = cipherlength + finalLength; + QByteArray out((char*)ciphertext, dataLength); + out = QByteArray::number(AesInterface::VERSION) + '$' + m_iVector.toBase64() + '$' + out.toBase64(); + free(ciphertext); + + m_ok = true; + return out; +} + +QByteArray AesInterface::decrypt(const QByteArray &cipherData, const QByteArray &password) +{ + m_ok = false; + + if (cipherData.isEmpty()) { + m_ok = true; + return QByteArray(); + } + + QList cipherSections(cipherData.split('$')); + if (cipherSections.size() != 3) { + qWarning() << "Decrypt error: It seems data is corrupted"; + return QByteArray(); + } + + if (cipherSections.at(0).toInt() > AesInterface::VERSION) { + QMessageBox::information(0, tr("Warning!"), tr("Data has been encrypted with a newer version of QupZilla." + "\nPlease install latest version of QupZilla.")); + return QByteArray(); + } + + if (cipherSections.at(0).toInt() != 1) { + qWarning() << Q_FUNC_INFO << "There is just version 1 of decoder, yet ;-)"; + return QByteArray(); + } + + if (!init(EVP_PKEY_MO_DECRYPT, password, QByteArray::fromBase64(cipherSections.at(1)))) { + return QByteArray(); + } + + QByteArray cipherArray = QByteArray::fromBase64(cipherSections.at(2)); + int cipherLength = cipherArray.size(); + int plainTextLength = cipherLength; + int finalLength = 0; + + uchar* cipherText = (uchar*)cipherArray.data(); + // because we have padding ON, we must allocate an extra cipher block size of memory + uchar* plainText = (uchar*)malloc(plainTextLength + AES_BLOCK_SIZE); + + EVP_DecryptInit_ex(&m_decodeCTX, NULL, NULL, NULL, NULL); + EVP_DecryptUpdate(&m_decodeCTX, plainText, &plainTextLength, cipherText, cipherLength); + int success = EVP_DecryptFinal_ex(&m_decodeCTX, plainText + plainTextLength, &finalLength); + + cipherLength = plainTextLength + finalLength; + + QByteArray result((char*)plainText, cipherLength); + free(plainText); + + if (success != 1) { + return QByteArray(); + } + + m_ok = true; + return result; +} + +QByteArray AesInterface::passwordToHash(const QString &masterPassword) +{ + if (!masterPassword.isEmpty()) { + QByteArray result = masterPassword.toUtf8(); + result = QCryptographicHash::hash(result, QCryptographicHash::Sha1) + result; + result = QCryptographicHash::hash(result, QCryptographicHash::Sha1); + return result.toBase64(); + } + else { + return QByteArray(); + } +} + +QByteArray AesInterface::createRandomData(int length) +{ + uchar* randomData = (uchar*)malloc(length); + + if (RAND_bytes(randomData, length) != 1) { + RAND_pseudo_bytes(randomData, length); + } + QByteArray data((char*)randomData, length); + free(randomData); + + return data; +} diff --git a/src/lib/tools/aesinterface.h b/src/lib/tools/aesinterface.h new file mode 100644 index 000000000..1e08b95d3 --- /dev/null +++ b/src/lib/tools/aesinterface.h @@ -0,0 +1,58 @@ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2013 S. Razi Alavizadeh +* Copyright (C) 2013 David Rosca +* +* This is based on a work by Saju Pillai +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#ifndef AESINTERFACE_H +#define AESINTERFACE_H + +#include "qz_namespace.h" + +#include +#include + +#include +#include +class QT_QUPZILLA_EXPORT AesInterface : public QObject +{ +public: + static const int VERSION; + + explicit AesInterface(QObject* parent = 0); + ~AesInterface(); + + inline bool isOk() { return m_ok; } + + QByteArray encrypt(const QByteArray &plainData, const QByteArray &password); + QByteArray decrypt(const QByteArray &cipherData, const QByteArray &password); + + static void encryptSomeData(const QByteArray &pass = QByteArray()); + static QByteArray passwordToHash(const QString &masterPassword); + + static QByteArray createRandomData(int length); + +private: + bool init(int evpMode, const QByteArray &password, const QByteArray &iVector = QByteArray()); + + EVP_CIPHER_CTX m_encodeCTX; + EVP_CIPHER_CTX m_decodeCTX; + + bool m_ok; + QByteArray m_iVector; +}; +#endif //AESINTERFACE_H