diff --git a/src/crypto.cpp b/src/crypto.cpp index d125e1e..0f0e078 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -12,28 +12,8 @@ #include "utils.h" namespace horcrux { -/** Cipher context class - * This is a wrapper for OpenSSL context object, just for RAII pattern - */ -class EvpCipherCtx { - EVP_CIPHER_CTX *ptr; -public: - EvpCipherCtx() { - ptr = EVP_CIPHER_CTX_new(); - } - - ~EvpCipherCtx() { - EVP_CIPHER_CTX_free(ptr); - } - - EVP_CIPHER_CTX* get() { - return ptr; - } -}; - - -EvpCipherCtx AES256_CBC::init(Mode mode, const raw_data& key, +void AES256_CBC::init(Mode mode, const raw_data& key, const raw_data& iv) { if (key.size() != kKeySize) { throw std::runtime_error("Wrong key size"); @@ -42,24 +22,54 @@ EvpCipherCtx AES256_CBC::init(Mode mode, const raw_data& key, throw std::runtime_error("Wrong IV size"); } - EvpCipherCtx ctx; + ctx = std::make_unique(); if (mode == Mode::kEncrypt) { - if (EVP_EncryptInit(ctx.get(), EVP_aes_256_cbc(), + if (EVP_EncryptInit(ctx->get(), EVP_aes_256_cbc(), key.data(), iv.data()) == 0) { throw std::runtime_error("EVP_EncryptInit"); } } else if (mode == Mode::kDecrypt) { - if (EVP_DecryptInit(ctx.get(), EVP_aes_256_cbc(), + if (EVP_DecryptInit(ctx->get(), EVP_aes_256_cbc(), key.data(), iv.data()) == 0) { throw std::runtime_error("EVP_DecryptInit"); } } else { throw std::invalid_argument("Invalid Cipher mode"); } - return ctx; } -size_t AES256_CBC::process_chunk(Mode mode, EvpCipherCtx& ctx, +size_t AES256_CBC::process_start(Mode mode, + raw_data::const_iterator begin, + raw_data::const_iterator end, + raw_data& output, + bool resize_in, bool resize_out) { + auto current = begin; + size_t output_offset = 0; + raw_data iv(kIvSize); + + if (mode == Mode::kEncrypt) { + // Make sure output is large enough to add IV + output.resize(kIvSize); + // generate IV + iv = generate_random(kIvSize); + // write IV to output + std::copy(iv.begin(), iv.end(), output.begin()); + output_offset += kIvSize; + } else { + // kDecrypt + if (end - begin < kIvSize){ + throw std::invalid_argument( + "First encrypted chunk must contain the whole IV"); + } + // read IV from input + std::copy(current, current + kIvSize, iv.begin()); + current += kIvSize; + } + init(mode, encryption_key, iv); + return output_offset + process_chunk(mode, current, end, output, output_offset, resize_in, resize_out); +} + +size_t AES256_CBC::process_chunk(Mode mode, raw_data::const_iterator begin, raw_data::const_iterator end, raw_data& output, size_t output_offset, @@ -71,13 +81,13 @@ size_t AES256_CBC::process_chunk(Mode mode, EvpCipherCtx& ctx, output.resize(output_offset + chunk_size + kIvSize); } if (mode == Mode::kEncrypt) { - if (1 != EVP_EncryptUpdate(ctx.get(), + if (1 != EVP_EncryptUpdate(ctx->get(), output.data() + output_offset, &len, &*begin, chunk_size)) { throw std::runtime_error("EVP_EncryptUpdate"); } } else { - if (1 != EVP_DecryptUpdate(ctx.get(), + if (1 != EVP_DecryptUpdate(ctx->get(), output.data() + output_offset, &len, &*begin, chunk_size)) { throw std::runtime_error("EVP_DecryptUpdate"); @@ -88,7 +98,7 @@ size_t AES256_CBC::process_chunk(Mode mode, EvpCipherCtx& ctx, return len; } -size_t AES256_CBC::process_final(Mode mode, EvpCipherCtx& ctx, +size_t AES256_CBC::process_final(Mode mode, raw_data& output, size_t output_offset, bool resize_in, bool resize_out) { int len; @@ -97,12 +107,12 @@ size_t AES256_CBC::process_final(Mode mode, EvpCipherCtx& ctx, output.resize(output_offset + kIvSize); } if (mode == Mode::kEncrypt) { - if (1 != EVP_EncryptFinal_ex(ctx.get(), output.data() + output_offset, + if (1 != EVP_EncryptFinal_ex(ctx->get(), output.data() + output_offset, &len)) { throw std::runtime_error("EVP_EncryptFinal"); } } else { - if (1 != EVP_DecryptFinal_ex(ctx.get(), (output.data()) + output_offset, + if (1 != EVP_DecryptFinal_ex(ctx->get(), (output.data()) + output_offset, &len)) { throw std::runtime_error("EVP_DecryptFinal"); } @@ -112,62 +122,78 @@ size_t AES256_CBC::process_final(Mode mode, EvpCipherCtx& ctx, return len; } -size_t AES256_CBC::process_all(Mode mode, EvpCipherCtx& ctx, +size_t AES256_CBC::process_all(Mode mode, raw_data::const_iterator begin, raw_data::const_iterator end, raw_data& output, size_t output_offset, bool resize_in, bool resize_out) { - int len = process_chunk(mode, ctx, begin, end, output, output_offset, + int len = process_chunk(mode, begin, end, output, output_offset, resize_in, false); - len += process_final(mode, ctx, output, output_offset + len, false, + len += process_final(mode, output, output_offset + len, false, resize_out); return len; } -raw_data AES256_CBC::process(const raw_data& inputdata) { - if (mode == Mode::kEncrypt) - return encrypt(inputdata); +raw_data AES256_CBC::process(const raw_data& inputdata, bool begin, bool end) { + //if (mode == Mode::kEncrypt) + // return encrypt(inputdata, begin, end); + //else + // return decrypt(inputdata, begin, end); + raw_data output; + size_t len; + if (begin) + len = process_start(mode, inputdata.begin(), inputdata.end(), output); else - return decrypt(inputdata); + len = process_chunk(mode, inputdata.begin(), inputdata.end(), output, 0); + if (end) + process_final(mode, output, len); + return output; } -raw_data AES256_CBC::encrypt(const raw_data& plaintext) { - return encrypt(encryption_key, horcrux::generate_random(kIvSize), - plaintext); +raw_data AES256_CBC::encrypt(const raw_data& plaintext, bool begin, bool end) { + //return encrypt(encryption_key, horcrux::generate_random(kIvSize), + // plaintext); + raw_data output; + size_t len; + if (begin) + len = process_start(Mode::kEncrypt, plaintext.begin(), plaintext.end(), output); + else + len = process_chunk(Mode::kEncrypt, plaintext.begin(), plaintext.end(), output, 0); + if (end) + process_final(Mode::kEncrypt, output, len); + return output; } -raw_data AES256_CBC::decrypt(const raw_data& ciphertext) { - return decrypt(encryption_key, ciphertext); +raw_data AES256_CBC::decrypt(const raw_data& ciphertext, bool begin, bool end) { + //return decrypt(encryption_key, ciphertext); + //raw_data output; + //init(Mode::kDecrypt, key, iv); + //process_all(Mode::kDecrypt, begin, end, output, 0); + //auto len = process_start(Mode::kDecrypt, ciphertext.begin(), ciphertext.end(), output); + //process_final(Mode::kDecrypt, output, len); + //return output; + raw_data output; + size_t len; + if (begin) + len = process_start(Mode::kDecrypt, ciphertext.begin(), ciphertext.end(), output); + else + len = process_chunk(Mode::kDecrypt, ciphertext.begin(), ciphertext.end(), output, 0); + if (end) + process_final(Mode::kDecrypt, output, len); + return output; } raw_data AES256_CBC::encrypt(const raw_data& key, const raw_data& iv, const raw_data& input) { - auto ctx = init(Mode::kEncrypt, key, iv); + init(Mode::kEncrypt, key, iv); // Make sure ouput is large enough to contain IV + encrypted data + padding raw_data output(input.size() + (2 * kIvSize)); std::copy(iv.begin(), iv.end(), output.begin()); - process_all(Mode::kEncrypt, ctx, input.begin(), input.end(), output, + process_all(Mode::kEncrypt, input.begin(), input.end(), output, kIvSize); return output; } -/* - -int AES256_CBC::encrypt(const raw_data& key, const raw_data& iv, - std::istream input, const size_t input_len, - std::ostream output, size_t& output_len) { - auto inbuf = raw_data(input_len); - auto outbuf = raw_data(input_len + 16); - input.read(reinterpret_cast(inbuf.data()), input_len); - - encrypt(key, iv, inbuf, outbuf); - - output.write(reinterpret_cast(outbuf.data()), outbuf.size()); - - return 0; -} - -*/ raw_data AES256_CBC::decrypt(const raw_data& key, const raw_data& ciphertext) { raw_data iv(ciphertext.begin(), ciphertext.begin() + kIvSize); return decrypt(key, iv, ciphertext.begin() + kIvSize, ciphertext.end()); @@ -181,9 +207,9 @@ raw_data AES256_CBC::decrypt(const raw_data& key, const raw_data& iv, raw_data AES256_CBC::decrypt(const raw_data& key, const raw_data& iv, raw_data::const_iterator begin, raw_data::const_iterator end) { - auto ctx = init(Mode::kDecrypt, key, iv); raw_data output; - process_all(Mode::kDecrypt, ctx, begin, end, output, 0); + init(Mode::kDecrypt, key, iv); + process_all(Mode::kDecrypt, begin, end, output, 0); return output; } }; // namespace horcrux diff --git a/src/crypto.h b/src/crypto.h index 97a5336..547953c 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -1,5 +1,6 @@ #ifndef HORCRUX_SRC_CRYPTO_H #define HORCRUX_SRC_CRYPTO_H +#include #include #include #include @@ -48,28 +49,51 @@ public: /** @brief process inputdata according to Cipher mode * @param inputdata buffer containing data to process + * @param begin set to true if this is the first chunk of data to process. + * @param end set to true if this is the last chunk of data to process. * @return processed data */ virtual raw_data process( - const raw_data& inputdata) = 0; + const raw_data& inputdata, bool begin = true, bool end = true) = 0; /** @brief encrypt the content of a buffer using Cipher key * @param plaintext the input buffer + * @param begin set to true if this is the first chunk of data to process. + * @param end set to true if this is the last chunk of data to process. * @return encrypted data */ virtual raw_data encrypt( - const raw_data& plaintext) = 0; + const raw_data& plaintext, bool begin = true, bool end = true) = 0; /** @brief decrypt the content of a buffer using Cipher key * @param decrypt the input buffer + * @param begin set to true if this is the first chunk of data to process. + * @param end set to true if this is the last chunk of data to process. * @return decrypted data */ virtual raw_data decrypt( - const raw_data& ciphertext) = 0; + const raw_data& ciphertext, bool begin = true, bool end = true) = 0; }; -// forward declaration -class EvpCipherCtx; +/** Cipher context class + * This is a wrapper for OpenSSL context object, just for RAII pattern + */ +class EvpCipherCtx { + EVP_CIPHER_CTX *ptr; + +public: + EvpCipherCtx() { + ptr = EVP_CIPHER_CTX_new(); + } + + ~EvpCipherCtx() { + EVP_CIPHER_CTX_free(ptr); + } + + EVP_CIPHER_CTX* get() { + return ptr; + } +}; /** @brief AES256 Cipher * @details @@ -90,6 +114,8 @@ class AES256_CBC : public Cipher { /** AES256 CBC initialization vector size, from AES256 specs */ const size_t kIvSize = 16; + std::unique_ptr ctx = nullptr; + public: /** @brief Create cipher in encrypt mode with random key */ AES256_CBC() : Cipher(Mode::kEncrypt) { @@ -108,24 +134,36 @@ public: /** @brief process inputdata according to Cipher mode * @param inputdata buffer containing data to process + * @param begin set to true if this is the first chunk of data to process. + * (deals with IV) + * @param end set to true if this is the last chunk of data to process. + * (deals with padding) * @return processed data */ raw_data process( - const raw_data& inputdata) override; + const raw_data& inputdata, bool begin = true, bool end = true) override; /** @brief encrypt the content of a buffer using Cipher key * @param plaintext the input buffer + * @param begin set to true if this is the first chunk of data to process. + * (deals with IV) + * @param end set to true if this is the last chunk of data to process. + * (deals with padding) * @return encrypted data */ raw_data encrypt( - const raw_data& plaintext) override; + const raw_data& plaintext, bool begin = true, bool end = true) override; /** @brief decrypt the content of a buffer using Cipher key * @param decrypt the input buffer + * @param begin set to true if this is the first chunk of data to process. + * (deals with IV) + * @param end set to true if this is the last chunk of data to process. + * (deals with padding) * @return decrypted data */ raw_data decrypt( - const raw_data& ciphertext) override; + const raw_data& ciphertext, bool begin = true, bool end = true) override; /** @brief Read the whole input and return a buffer with the encrypted data * @param key the encryption key @@ -182,18 +220,16 @@ public: raw_data::const_iterator end); private: - /** @brief Create a Cipher context object needed for enc/dec operations + /** @brief Initialize the Cipher context object needed for enc/dec operations * @param mode encrypt or decrypt * @param key the enc/dec key * @param the initialization vector - * @return the Cipher context object */ - EvpCipherCtx init(Mode mode, const raw_data& key, + void init(Mode mode, const raw_data& key, const raw_data& iv); /** @brief process a chunk of input * @param mode encrypt or decrypt - * @param ctx the Cipher context object * @param begin beginning of the input chunk * @param end end of the input chunk * @param output output buffer @@ -203,17 +239,21 @@ private: * order to make sure there is enough room. * @param resize_out shrink the output buffer at the end */ - size_t process_chunk(Mode mode, EvpCipherCtx& ctx, + size_t process_chunk(Mode mode, raw_data::const_iterator begin, raw_data::const_iterator end, raw_data& output, size_t output_offset, bool resize_in = true, bool resize_out = true); + size_t process_start(Mode mode, + raw_data::const_iterator begin, + raw_data::const_iterator end, + raw_data& output, + bool resize_in = true, bool resize_out = true); /** @brief Finalize a encryption/decryption process. Write remaining bytes * to the output * @param mode encrypt or decrypt - * @param ctx the Cipher context object * @param output output buffer * @param output_offset where to start to append the last data within * the output buffer @@ -221,14 +261,13 @@ private: * order to make sure there is enough room. * @param resize_out shrink the output buffer at the end */ - size_t process_final(Mode mode, EvpCipherCtx& ctx, + size_t process_final(Mode mode, raw_data& output, size_t output_offset, bool resize_in = true, bool resize_out = true); /** @brief process a input and finalize it. * @param mode encrypt or decrypt - * @param ctx the Cipher context object * @param begin beginning of the input chunk * @param end end of the input chunk * @param output output buffer @@ -238,7 +277,7 @@ private: * order to make sure there is enough room. * @param resize_out shrink the output buffer at the end */ - size_t process_all(Mode mode, EvpCipherCtx& ctx, + size_t process_all(Mode mode, raw_data::const_iterator begin, raw_data::const_iterator end, raw_data& output, size_t output_offset, diff --git a/src/horcrux.cpp b/src/horcrux.cpp index 0a64cf2..ad53ff6 100644 --- a/src/horcrux.cpp +++ b/src/horcrux.cpp @@ -5,6 +5,15 @@ namespace horcrux { +void Horcrux::read_chunks(size_t size) { + raw_data buf = input->read(size); + output->write_chunk(cipher->process(buf, true, false)); + while ((buf = input->read(size)).size() == size) { + output->write_chunk(cipher->process(buf, false, false)); + } + output->write_chunk(cipher->process(buf, false, true)); +} + void Horcrux::init() { if (options.count <= 0) { throw std::invalid_argument("Invalid horcrux count"); @@ -32,7 +41,8 @@ void Horcrux::init() { void Horcrux::run() { if (options.mode == Mode::kEncrypt) - output->write(cipher->encrypt(input->read())); + //output->write(cipher->encrypt(input->read())); + read_chunks(512); else output->write(cipher->decrypt(input->read())); } diff --git a/src/horcrux.h b/src/horcrux.h index dfe9261..89e2d6d 100644 --- a/src/horcrux.h +++ b/src/horcrux.h @@ -38,6 +38,7 @@ class Horcrux { * Used cipher, input and output are generic and the actual implementation * can be selected in this function */ void init(); + void read_chunks(size_t size); public: /** remove default constructor */ diff --git a/src/io.cpp b/src/io.cpp index 7a644b8..d95c55f 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -68,7 +68,9 @@ raw_data FsCryptoInput::read(size_t size) { while (data_read < size && current_file < file_paths.size()) { if (!file_stream.is_open()) file_stream.open(file_paths[current_file].first, std::ios::binary); - file_stream.read(reinterpret_cast(result.data()) + data_read, size - data_read); + file_stream.read( + reinterpret_cast(result.data()) + data_read, + size - data_read); data_read += file_stream.gcount(); if(file_stream.eof()) { file_stream.close(); @@ -93,7 +95,8 @@ size_t FsPlainOutput::write(const raw_data& to_write) { } size_t FsPlainOutput::write_chunk(const raw_data& to_write) { file_stream.open(file_path, std::ios::binary); - file_stream.write(reinterpret_cast(to_write.data()), to_write.size()); + file_stream.write(reinterpret_cast(to_write.data()), + to_write.size()); return to_write.size(); } @@ -129,29 +132,37 @@ size_t FsCryptoOutput::write(const raw_data& to_write) { // } // return data_written; } + size_t FsCryptoOutput::write_chunk(const raw_data& to_write) { + // horcrux file size must be known in advance if (size <= 0) { throw std::invalid_argument("Invalid horcrux size"); } size_t data_written{0}; - std::cout << to_write.size() << " " << data_written << std::endl; while (data_written < to_write.size()){ if(!file_stream.is_open()){ - std::string name = base_name + '.' + std::to_string(created_files.size()) + ".enc"; + // create a new file + std::string name = base_name + + '.' + std::to_string(created_files.size()) + ".enc"; created_files.emplace_back(folder_path / name); file_stream.open(created_files.back(), - std::ios::binary | std::ios::trunc); + std::ios::binary | std::ios::trunc); } + // write up to fill the file size or consume the input buffer + auto writable = std::min(size - file_stream.tellp(), + to_write.size() - data_written); // the last file may contain more data (size % num) - auto writable = std::min(size - file_stream.tellp(), to_write.size() - data_written); - if (num == created_files.size()) { writable = to_write.size() - data_written; } - file_stream.write(reinterpret_cast(to_write.data()) + data_written, + if (num == created_files.size()) { + writable = to_write.size() - data_written; + } + file_stream.write( + reinterpret_cast(to_write.data()) + data_written, writable); data_written += writable; if (file_stream.tellp() >= size){ + // close the file if it has been filled file_stream.close(); } - std::cout << to_write.size() << " " << data_written << std::endl; } return data_written; } diff --git a/src/io.h b/src/io.h index b7d197a..49054fb 100644 --- a/src/io.h +++ b/src/io.h @@ -19,12 +19,7 @@ public: virtual raw_data read() = 0; virtual raw_data read(size_t size) = 0; -// template -// void read_chunks(size_t size, UnaryFunction f) { -// while ((auto buf = read(size)) == size) { -// f(buf); -// } -// } + }; /** @brief Base Class for Output