From 9d3bfa3d51af1506bf8fe7badef47d2b048c625e Mon Sep 17 00:00:00 2001 From: Roman <22231294+Lutymane@users.noreply.github.com> Date: Thu, 29 Jun 2023 21:39:44 +0400 Subject: [PATCH] 1.0-beta --- .gitignore | 5 +- architecture.md | 6 +- integrity.hpp | 12 ++- kdf.hpp | 6 +- main.cpp | 205 +++++++++++++++++++++++++++++++++++++++++------- makefile | 3 +- pcbc.hpp | 10 +++ 7 files changed, 206 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index d9551fa..d2007e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,11 @@ main +mars test-suite .vscode *.mars *.tar.br -*.tar.gz \ No newline at end of file +*.tar.gz + +tf \ No newline at end of file diff --git a/architecture.md b/architecture.md index 01d5fd7..4a748cf 100644 --- a/architecture.md +++ b/architecture.md @@ -18,6 +18,8 @@ v2: v2 prevents from searching for file prefix signatures, since it starts with a random hash folder preparation: +https://unix.stackexchange.com/questions/533463/tar-contents-in-current-directory-to-stdout +tar will only write stdout to smth * `tar -c data` (`tar -x data.tar`) * `brotli --best data.tar -o data.tar.br` (`brotli -d`) @@ -34,4 +36,6 @@ https://www.ibm.com/docs/en/zos/2.4.0?topic=rules-pkcs-padding-method https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption -https://www.openssl.org/docs/man3.1/man3/EVP_EncryptUpdate.html \ No newline at end of file +https://www.openssl.org/docs/man3.1/man3/EVP_EncryptUpdate.html + +# overwrite files with 0s \ No newline at end of file diff --git a/integrity.hpp b/integrity.hpp index 5d07aba..2d9331a 100644 --- a/integrity.hpp +++ b/integrity.hpp @@ -5,7 +5,7 @@ const auto DIGEST_ALG = EVP_sha3_512(); const auto DIGEST_SIZE = EVP_MD_get_size(DIGEST_ALG); -void hash(byte *data, size_t data_size, byte *&digest_buffer) +void hash(const byte *data, const size_t data_size, byte *digest_buffer) { EVP_MD_CTX *ctx = EVP_MD_CTX_new(); @@ -13,8 +13,6 @@ void hash(byte *data, size_t data_size, byte *&digest_buffer) EVP_DigestUpdate(ctx, data, data_size); - digest_buffer = new byte[DIGEST_SIZE]; - unsigned int digest_size; EVP_DigestFinal(ctx, digest_buffer, &digest_size); @@ -22,15 +20,15 @@ void hash(byte *data, size_t data_size, byte *&digest_buffer) EVP_MD_CTX_free(ctx); } -bool verify(byte *data, size_t data_size, byte *digest_buffer) +bool verify(const byte *data, const size_t data_size, const byte *digest_buffer) { - byte *current_digest_buffer; + byte data_digest_buffer[DIGEST_SIZE]; - hash(data, data_size, current_digest_buffer); + hash(data, data_size, data_digest_buffer); for (size_t b = 0; b < DIGEST_SIZE; b += 1) { - if (digest_buffer[b] != current_digest_buffer[b]) + if (digest_buffer[b] != data_digest_buffer[b]) return false; } diff --git a/kdf.hpp b/kdf.hpp index f12a148..ae61c09 100644 --- a/kdf.hpp +++ b/kdf.hpp @@ -11,13 +11,15 @@ // aes256 #define KEY_SIZE (256 / 8) +#define SALT_SIZE (128 / 8) + #include using namespace std::chrono; #define MB(x) 1024ull * x #define GB(x) MB(1024ull) * x -void kdf(byte *password, byte password_size, byte *salt, byte salt_size, byte *&key) +void kdf(byte *password, byte password_size, byte *salt, byte salt_size, byte *key) { EVP_KDF *kdf = EVP_KDF_fetch(NULL, "ARGON2D", NULL); @@ -48,7 +50,7 @@ void kdf(byte *password, byte password_size, byte *salt, byte salt_size, byte *& printf("[KDF] Iterations: %zu, memcost: %zu, lanes: %zu\n", iterations, memcost, lanes); - key = new byte[KEY_SIZE]; + // key = new byte[KEY_SIZE]; // @todo error, undefined /* required if threads > 1 */ diff --git a/main.cpp b/main.cpp index b92ef16..f4c1d39 100644 --- a/main.cpp +++ b/main.cpp @@ -12,35 +12,56 @@ #include "./integrity.hpp" #include "./pcbc.hpp" -#define SALT_SIZE (128 / 8) +#define ENCRYPTED_EXT std::string(".mars") -int main(void) +void encrypt(const std::string filename, const std::string password) { -#pragma region File digest +#pragma region Create brotli compressed archive + + std::string tar_path = filename + ".tar.br"; + std::string command = "tar -cO " + filename + " | brotli --best -f - -o " + tar_path; - // size_t file_size = 500; - // byte *file = new byte[file_size]; - // RAND_bytes(file, file_size); + system(command.c_str()); - byte *file = (byte *)"some test file content"; - size_t file_size = strlen((char *)file); +#pragma endregion - byte *digest; +#pragma region Read tar.br + std::ifstream fs(tar_path, std::ios::binary); - hash(file, file_size, digest); + // extra parens are required???? + std::string file((std::istreambuf_iterator(fs)), + (std::istreambuf_iterator())); - // printf("Digest = %s\n", OPENSSL_buf2hexstr(digest_buffer, DIGEST_SIZE)); + fs.close(); + size_t file_size = file.length(); #pragma endregion -#pragma region Password input and key derivation - printf("Input password:\n"); +#pragma region Overwrite file and delete it - std::string password; - getline(std::cin, password); + std::ofstream of(tar_path); + for (size_t f = 0; f < file_size; f += 1) + { + of.put(0); + } + of.close(); - system("clear"); + // remove file + std::remove(tar_path.c_str()); +#pragma endregion + +#pragma region File digest + + byte digest[DIGEST_SIZE]; + + hash((byte *)file.data(), file_size, digest); + + // printf("Digest = %s\n", OPENSSL_buf2hexstr(digest_buffer, DIGEST_SIZE)); + +#pragma endregion + +#pragma region Key derivation byte salt[SALT_SIZE]; RAND_bytes(salt, SALT_SIZE); @@ -48,7 +69,7 @@ int main(void) // printf("Password (%zu) = %s\n", password.length(), password.c_str()); // printf("Salt = %s\n", OPENSSL_buf2hexstr(salt, SALT_SIZE)); - byte *key; + byte key[KEY_SIZE]; kdf((byte *)password.c_str(), password.length(), salt, SALT_SIZE, key); @@ -58,36 +79,162 @@ int main(void) #pragma region File encryption - byte *iv = new byte[IV_SIZE]; + byte iv[IV_SIZE]; RAND_bytes(iv, IV_SIZE); byte *ciphertext; size_t ciphertext_size; - encrypt_aes256_pcbc(file, file_size, key, iv, ciphertext, ciphertext_size); + encrypt_aes256_pcbc((byte *)file.data(), file_size, key, iv, ciphertext, ciphertext_size); - // erase key - memset(key, 0, KEY_SIZE); + OPENSSL_cleanse(key, KEY_SIZE); + OPENSSL_cleanse(file.data(), file_size); #pragma endregion #pragma region Writing to disk - std::ofstream encrypted_tar; - encrypted_tar.open("encrypted.mars", std::ios::binary | std::ios::trunc); + std::ofstream encrypted(filename + ".mars", std::ios::binary | std::ios::trunc); // << can't be used - encrypted_tar.write((char *)salt, SALT_SIZE); - encrypted_tar.write((char *)digest, DIGEST_SIZE); - encrypted_tar.write((char *)iv, IV_SIZE); - encrypted_tar.write((char *)ciphertext, ciphertext_size); + encrypted.write((char *)salt, SALT_SIZE); + encrypted.write((char *)digest, DIGEST_SIZE); + encrypted.write((char *)iv, IV_SIZE); + encrypted.write((char *)ciphertext, ciphertext_size); // vs code doesn't immediately update files in side bar, so it may appear ofstream didn't create the file - encrypted_tar.close(); + encrypted.close(); printf("File written! %zu bytes\n", SALT_SIZE + DIGEST_SIZE + IV_SIZE + ciphertext_size); +#pragma endregion +} + +void decrypt(const std::string filename, const std::string password) +{ +#pragma region Read data + + byte salt[SALT_SIZE]; + byte digest[DIGEST_SIZE]; + byte iv[IV_SIZE]; + + std::ifstream encrypted(filename, std::ios::binary); + encrypted.read((char *)salt, SALT_SIZE); + encrypted.read((char *)digest, DIGEST_SIZE); + encrypted.read((char *)iv, IV_SIZE); + + std::string ciphertext((std::istreambuf_iterator(encrypted)), + (std::istreambuf_iterator())); + + size_t ciphertext_size = ciphertext.length(); + +#pragma endregion + +#pragma region Key derivation + + byte key[KEY_SIZE]; + + kdf((byte *)password.c_str(), password.length(), salt, SALT_SIZE, key); + +#pragma endregion + +#pragma region Decryption + + byte *plaintext; + size_t plaintext_size; + + decrypt_aes256_pcbc((byte *)ciphertext.data(), ciphertext_size, key, iv, plaintext, plaintext_size); + + OPENSSL_cleanse(key, KEY_SIZE); + +#pragma endregion + +#pragma Integrity check + + if (!verify(plaintext, plaintext_size, digest)) + { + printf("Integrity check failed!\n"); + abort(); + } + +#pragma endregion + +#pragma region Decompress and untar + + std::string path = filename.substr(0, filename.length() - ENCRYPTED_EXT.length()) + ".tar.br"; + + std::ofstream fs; + + fs.open(path, std::ios::binary); + fs.write((char *)plaintext, plaintext_size); + fs.close(); + + OPENSSL_cleanse(plaintext, plaintext_size); + + std::string command = "brotli -d " + path + " --stdout | tar -x"; + + system(command.c_str()); + + // overwrite with 0s + fs.open(path, std::ios::binary); + for (size_t f = 0; f < plaintext_size; f += 1) + { + fs.put(0); + } + fs.close(); + + std::remove((path).c_str()); + + printf("File decrypted!\n"); + +#pragma endregion +} + +int main(int argc, char **argv) +{ + if (argc != 2) + { + printf( + "\e[33m\e[3m\e[1m" + "Usage:" + "\e[0m\n" + + " \e[41m" + "mars" + "\e[49m \e[36m" + "encrypted.mars\n" + + "\e[39m\e[3m" + "or" + "\e[0m\n" + + " \e[41m" + "mars" + "\e[49m \e[33m" + "path/to/folder\n"); + return 0; + } + + std::string argument(argv[1]); + +#pragma region Password input + printf("\e[33m\e[3m\e[1m" + "Input password:\n" + "\e[0m"); + + std::string password; + getline(std::cin, password); + + system("clear"); + #pragma endregion - return 0; + if (argument.ends_with(ENCRYPTED_EXT)) + { + decrypt(argument, password); + } + else + { + encrypt(argument, password); + } } \ No newline at end of file diff --git a/makefile b/makefile index 9818f83..1d7e213 100644 --- a/makefile +++ b/makefile @@ -3,7 +3,8 @@ OPENSSL_PATH=/home/ceo/src/openssl build: # source should go before libs, otherwise they are skipped # -L/usr/local/lib64/ - g++ main.cpp -pthread -L/usr/local/lib64/ -lcrypto -o main +# -std=c++2a for string.endsWith + g++ main.cpp -std=c++2a -pthread -L/usr/local/lib64/ -lcrypto -o mars test: g++ test.cpp -pthread -L/usr/local/lib64/ -lcrypto -o test-suite && ./test-suite \ No newline at end of file diff --git a/pcbc.hpp b/pcbc.hpp index f458c25..d5a06bd 100644 --- a/pcbc.hpp +++ b/pcbc.hpp @@ -154,6 +154,16 @@ void decrypt_aes256_pcbc(const byte *ciphertext, const size_t ciphertext_size, c } size_t padding_size = plaintext[ciphertext_size - 1]; + + // @note sometimes incorrect password may lead to incorrect decryption and thus + // extremely big plaintext_size values, when padding_size turns out to be bigger + // than ciphertext size (overflow) + if (padding_size > BLOCK_SIZE) + { + printf("Integrity is broken\n"); + abort(); + } + plaintext_size = ciphertext_size - padding_size; EVP_CIPHER_CTX_free(ctx);