diff --git a/CMakeLists.txt b/CMakeLists.txt index 8db9e9c..15ca0cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,9 @@ target_sources(${EXE_NAME} PRIVATE src/ecbencryptor.cpp src/evpwrapper.cpp src/bitmapreader.cpp - src/filewriter.cpp) + src/filewriter.cpp + src/onetimepad.cpp + src/qrngprocessor.cpp) target_include_directories(${EXE_NAME} PRIVATE ${OpenSSL_INCLUDE_DIRS} diff --git a/MP_README.md b/MP_README.md new file mode 100644 index 0000000..50729ab --- /dev/null +++ b/MP_README.md @@ -0,0 +1,23 @@ +# Qrypt One-Time Pad Penguinify challenge + + +_Maria Perepechaenko_ + +## Notes: + +- The current program supports both, AES ECB encryption as well as One-Time Pad(OTP) encryption with QRNG random used as a key. + - To produce an encrypted version of the bitmap file `Tux.bmp` using AES ECB run + `./penguinify --bitmap Tux.bmp --key 0123456789abcdef --out ecb_out.bmp` + - To produce an encrypted version of the bitmap file `Tux.bmp` using OTP with QRNG random run + `./penguinify --bitmap Tux.bmp --otp quantum.qrand --out otp_out.bmp` + - The program distinguishes between AES ECB and OTP based on the keyword "key" or "otp". + + +- Encryption of the bitmap file is performed using `otpencrypt` method of the `otpencryptor` class. The main functionality is as follows: + 1. Get the header of the bitmap file to be encrypted, and write it into the output file without any changes to the header + 2. Get the body of the bitmap file, and a qrng file that contains quantum randomness, and return the content of the qrng file equal in size to the body of the bitmap file. + 3. XOR the body of the bitmap file with the content of the qrng file returned by the above described method. + 4. Write the encrypted body of the bitmap file into the output file. + + +- A new `mocks_otp.h` and `test_otpencryptor.cpp` files are available for convenience and are used for testing. diff --git a/include/onetimepad.h b/include/onetimepad.h new file mode 100644 index 0000000..7114bf2 --- /dev/null +++ b/include/onetimepad.h @@ -0,0 +1,27 @@ +#pragma once + +#include "bitmapreader.h" +#include "filewriter.h" +#include "qrngprocessor.h" + +#include + + +/* +* otpencryptor class that facilitates One-Time Pad encryption of a given bitmap file, while keeping the header unchanged +* otpencryptor class contains qrngprocessor class as a private member, and q_xor as a private method (q_xor method computes and returns XOR result of two inputs) +* otpencryptor class contains otpencrypt as a public method that encrypts a body of a given bitmap file using One-Time Pad encryption, leaving the header intact, and writing the header and the encrypted result into an output file +*/ +class otpencryptor { + +private: + qrngprocessor &_processor; + std::vector q_xor(const std::vector &bytes, + const std::vector &qrng); + +public: + otpencryptor(qrngprocessor &processor) : _processor{processor} {} + virtual ~otpencryptor() = default; + virtual void otpencrypt(bitmapreader &in, const std::string &qrngfile, + filewriter &out); +}; diff --git a/include/qrngprocessor.h b/include/qrngprocessor.h new file mode 100644 index 0000000..cf70450 --- /dev/null +++ b/include/qrngprocessor.h @@ -0,0 +1,22 @@ +#pragma once + +#include "bitmapreader.h" + +#include +#include +#include +#include + + +/* +* qrngprocessor class processes files containing qrng by opening and reading them, then returns a sufficient quantity of random bytes from qrng file, that corresponds to the body size of the corresponding bitmap files to be encrypted +* qrngprocessor class contains processqrngfile as a public method that takes as input a bitmap file to be encrypted along with a qrng file, and returns random bytes from qrng file equal in size to the body of the input bitmap file intended for encryption +*/ + +class qrngprocessor { + +public: + qrngprocessor() {} + virtual ~qrngprocessor() = default; + virtual std::vector processqrngfile(bitmapreader &in, const std::string &qrngfile); +}; diff --git a/src/onetimepad.cpp b/src/onetimepad.cpp new file mode 100644 index 0000000..54e48c0 --- /dev/null +++ b/src/onetimepad.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include + +#include "onetimepad.h" +#include "qrngprocessor.h" + +/* +* Function computes and returns XOR of two inputs +* INPUT: two constant vectors of unsigned 8-bit integers +* OUTPUT: vector of unsigned 8-bit integers +*/ +std::vector otpencryptor::q_xor(const std::vector &bytes, const std::vector &qrng){ + //first want to make sure the inputs have the same size + if(bytes.size() != qrng.size()){ + throw std::runtime_error("Inputs provided must have the same size."); + } + //initialize an output vector + std::vector cipher(bytes.size()); + //cipher = bytes XOR qrng + std::transform(bytes.begin(), bytes.end(), qrng.begin(), cipher.begin(), std::bit_xor()); + + return cipher; + +} + +/* +* Function performs One-Time Pad encryption of an input bitmap file, while keeping the header intact, and writing the header as well as encrypted body into an output file +* INPUT: an instance of a bitmapreader, name of the qrng file, an instance of a filewriter +* OUTPUT: none +*/ +void otpencryptor::otpencrypt(bitmapreader &in, const std::string &qrngfile, + filewriter &out) { + + out.write(in.getHeader()); //writing the header unchanged + out.write(q_xor(in.getBody(), _processor.processqrngfile(in, qrngfile))); //writing the OTP encrypted version of the bitmap file body +} diff --git a/src/penguinify.cpp b/src/penguinify.cpp index 82f8fde..d5ed38a 100644 --- a/src/penguinify.cpp +++ b/src/penguinify.cpp @@ -1,4 +1,12 @@ // Copyright © 2020, Qrypt, Inc., All rights reserved. + +/* +* There is an option to pass command ./penguinify --bitmap Tux.bmp --key 0123456789abcdef --out bar.bmp +* There is an option to pass command ./penguinify --bitmap Tux.bmp --otp quantum.qrand --out bar.bmp +* We differentiate based on the identifier "key" vs "otp" +*/ + + #include #include #include @@ -7,15 +15,17 @@ #include "ecbencryptor.h" #include "evpwrapper.h" #include "filewriter.h" +#include "onetimepad.h" +#include "qrngprocessor.h" void printUsage() { std::cout << "penguinify.exe" << std::endl; std::cout - << "Creates ecb encrypted bitmaps that leave the file header in tact." + << "Creates encrypted bitmaps that leave the file header intact." << std::endl << std::endl; std::cout << "Usage:" << std::endl; - std::cout << "penguinify --bitmap --key <16-char string> " + std::cout << "penguinify --bitmap -- <16-char string> " "--out " << std::endl; } @@ -32,30 +42,51 @@ int main(int argc, char **argv) { } std::string bitmap_name{argv[2]}; + std::string identifier{argv[3]}; std::string key{argv[4]}; std::string out_name{argv[6]}; + + if (identifier.substr(2) == "key") { - if (18 == key.size() && surroundedByQuotes(key)) { - key.erase(key.begin(), key.begin() + 1); - key.erase(key.end() - 1, key.end()); - } + if (18 == key.size() && surroundedByQuotes(key)) { + key.erase(key.begin(), key.begin() + 1); + key.erase(key.end() - 1, key.end()); + } - if (key.size() != 16) { - printUsage(); - return 1; + if (key.size() != 16) { + printUsage(); + return 1; + } + + try { + bitmapreader bmrIn; + evpwrapper evp; + filewriter fwOut; + ecbencryptor ecb(evp); + bmrIn.open(bitmap_name); + fwOut.open(out_name); + ecb.encrypt(bmrIn, key, fwOut); + } catch (std::exception e) { + std::cout << e.what() << std::endl; + printUsage(); + return 1; + } } - try { - bitmapreader bmrIn; - evpwrapper evp; - filewriter fwOut; - ecbencryptor ecb(evp); - bmrIn.open(bitmap_name); - fwOut.open(out_name); - ecb.encrypt(bmrIn, key, fwOut); - } catch (std::exception e) { - std::cout << e.what() << std::endl; - printUsage(); - return 1; + if (identifier.substr(2) == "otp") { + try { + bitmapreader bmrIn; + filewriter fwOut; + qrngprocessor QRNG; + otpencryptor OTP(QRNG); + bmrIn.open(bitmap_name); + fwOut.open(out_name); + OTP.otpencrypt(bmrIn, key, fwOut); + } catch (std::exception e) { + std::cout << e.what() << std::endl; + printUsage(); + return 1; + } } + } \ No newline at end of file diff --git a/src/qrngprocessor.cpp b/src/qrngprocessor.cpp new file mode 100644 index 0000000..9e39040 --- /dev/null +++ b/src/qrngprocessor.cpp @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include + +#include "qrngprocessor.h" + +/* +* Function takes a name of the qrng file and a bitmap file for encryption, and returns random from qrng file of the same size as the body of the bitmap file to be encrypted +* INPUT: a bitmap file for encryption, and a name of the qrng file containing quantum randomness +* OUTPUT: vector of unsigned 8-bit integers from the qrng file, with the same size as the body of the bitmap file for encryption +*/ +std::vector qrngprocessor::processqrngfile(bitmapreader &in, const std::string &qrngfile) { + + //get the size of the bitmap file body + std::vector file_body = in.getBody(); + size_t size_body = file_body.size(); + + //open and read the qrng file + std::ifstream file(qrngfile, std::ios::binary); + file >> std::noskipws; + if (!file.is_open()) { + throw std::runtime_error("Error opening QRNG file."); + } + std::istream_iterator begin(file), end; + std::vector buffer(begin, end); + + //check that the qrng file is large enough to contain enough random sufficient for encryption of the bitmap body + if (buffer.size() < size_body) { + throw std::runtime_error("QRNG file is too small."); + } + + //initialize the qrng vector and store the random from qrng in it, with the same size as the bitmap file body + std::vector qrng; + qrng.insert(qrng.begin(), buffer.begin(), buffer.begin() + size_body); + + return qrng; + +} + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cb33a84..fe66479 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,7 +18,11 @@ target_sources(${TEST_PROJECT} PRIVATE src/test_filewriter.cpp ../src/filewriter.cpp src/test_evpwrapper.cpp - ../src/evpwrapper.cpp) + ../src/evpwrapper.cpp + src/test_otpencryptor.cpp + ../src/onetimepad.cpp + ../src/qrngprocessor.cpp + ) target_include_directories(${TEST_PROJECT} PRIVATE ${GTest_INCLUDE_DIRS} diff --git a/tests/include/mocks_otp.h b/tests/include/mocks_otp.h new file mode 100644 index 0000000..523b5b5 --- /dev/null +++ b/tests/include/mocks_otp.h @@ -0,0 +1,27 @@ +#pragma once + +#include "bitmapreader.h" +#include "filewriter.h" +#include "qrngprocessor.h" +#include + +class MockReader : public bitmapreader { +public: + MockReader() : bitmapreader() {} + MOCK_METHOD(void, open, (const std::string &bmpFile), (override)); + MOCK_METHOD(const std::vector &, getHeader, (), (override)); + MOCK_METHOD(const std::vector &, getBody, (), (override)); +}; + +class MockWriter : public filewriter { +public: + MockWriter() : filewriter() {} + MOCK_METHOD(void, open, (const std::string &filename), (override)); + MOCK_METHOD(void, write, (const std::vector &bytes), (override)); +}; + +class MockProcessor : public qrngprocessor{ +public: + MockProcessor() : qrngprocessor() {} + MOCK_METHOD(std::vector, processqrngfile, (bitmapreader &in, const std::string &qrngfile), (override)); +}; \ No newline at end of file diff --git a/tests/src/test_otpencryptor.cpp b/tests/src/test_otpencryptor.cpp new file mode 100644 index 0000000..c8d8380 --- /dev/null +++ b/tests/src/test_otpencryptor.cpp @@ -0,0 +1,31 @@ +#include "mocks_otp.h" +#include +#include + +#include "onetimepad.h" + +using ::testing::InSequence; +using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::SaveArg; + +TEST(otpencryptor, encrypt) { + + std::vector header(0x36, 1); + std::vector body(1000, 2); + std::vector out1{}; + std::vector out2{}; + MockProcessor mp; + MockReader mr; + MockWriter mw; + InSequence seq; + EXPECT_CALL(mr, getHeader()).WillOnce(ReturnRef(header)); + EXPECT_CALL(mw, write).WillOnce(SaveArg<0>(&out1)); + EXPECT_CALL(mp, processqrngfile).WillOnce(Return(body)); + EXPECT_CALL(mr, getBody()).WillOnce(ReturnRef(body)); + EXPECT_CALL(mw, write).WillOnce(SaveArg<0>(&out2)); + otpencryptor otp(mp); + otp.otpencrypt(mr, "/workspaces/ecbPenguin/tests/data/quantum.qrand", mw); + EXPECT_EQ(header.size(), out1.size()); + EXPECT_EQ(body.size(), out2.size()); +}