From 465bdad94b10876a184f2167a7bd924b125996a8 Mon Sep 17 00:00:00 2001 From: EonaCat Date: Wed, 25 Sep 2024 19:22:33 +0200 Subject: [PATCH] Initial version --- .gitignore | 16 -- C#/EonaCatCipher.cs | 319 +++++++++++++++++++++++++++++++++++ C++/EonaCatCipher.cpp | 239 ++++++++++++++++++++++++++ C++/readme.txt | 35 ++++ Delphi/EonaCatCipher.pas | 257 ++++++++++++++++++++++++++++ Delphi/example.pas | 50 ++++++ Go/EonaCatCipher.go | 312 ++++++++++++++++++++++++++++++++++ Java/EonaCatCipher.java | 324 ++++++++++++++++++++++++++++++++++++ Java/readme.txt | 2 + Javascript/EonaCatCipher.js | 268 +++++++++++++++++++++++++++++ LICENSE | 212 ++++++++++++++++++----- PHP/EonaCatCipher.php | 235 ++++++++++++++++++++++++++ Python/EonaCatCipher.py | 189 +++++++++++++++++++++ README.md | 14 +- Ruby/EonaCatCipher.ruby | 224 +++++++++++++++++++++++++ Rust/EonaCatCipher.rust | 261 +++++++++++++++++++++++++++++ Rust/cargo.toml | 10 ++ Rust/readme.txt | 2 + c/EonaCatCipher.c | 262 +++++++++++++++++++++++++++++ c/readme.txt | 35 ++++ 20 files changed, 3208 insertions(+), 58 deletions(-) create mode 100644 C#/EonaCatCipher.cs create mode 100644 C++/EonaCatCipher.cpp create mode 100644 C++/readme.txt create mode 100644 Delphi/EonaCatCipher.pas create mode 100644 Delphi/example.pas create mode 100644 Go/EonaCatCipher.go create mode 100644 Java/EonaCatCipher.java create mode 100644 Java/readme.txt create mode 100644 Javascript/EonaCatCipher.js create mode 100644 PHP/EonaCatCipher.php create mode 100644 Python/EonaCatCipher.py create mode 100644 Ruby/EonaCatCipher.ruby create mode 100644 Rust/EonaCatCipher.rust create mode 100644 Rust/cargo.toml create mode 100644 Rust/readme.txt create mode 100644 c/EonaCatCipher.c create mode 100644 c/readme.txt diff --git a/.gitignore b/.gitignore index d5dca0c..8a30d25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -# ---> VisualStudio ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## @@ -397,18 +396,3 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml - -# ---> VisualStudioCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -!.vscode/*.code-snippets - -# Local History for Visual Studio Code -.history/ - -# Built Visual Studio Code Extensions -*.vsix - diff --git a/C#/EonaCatCipher.cs b/C#/EonaCatCipher.cs new file mode 100644 index 0000000..f96dc3f --- /dev/null +++ b/C#/EonaCatCipher.cs @@ -0,0 +1,319 @@ +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; + +/* + * EonaCatCipher - Because security is key! + * + * Copyright (c) 2024 EonaCat (Jeroen Saey) + * + * https://eonacat.com/license + * + * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + * OF SOFTWARE BY EONACAT (JEROEN SAEY) + * + * This software is provided "as is", without any express or implied warranty. + * In no event shall the authors or copyright holders be liable for any claim, + * damages or other liability, whether in an action of contract, tort or otherwise, + * arising from, out of or in connection with the software or the use or other + * dealings in the software. + * + * You may use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. The software must not be used for any unlawful purpose. + * + * For any inquiries, please contact: eonacat@gmail.com + */ + +public class EonaCatCipher : IDisposable +{ + private const int DEFAULT_SALT_SIZE = 2048; // Salt size for key derivation + private const int DEFAULT_IV_SIZE = 2048; // IV size (16384 bits) + private const int DEFAULT_KEY_SIZE = 2048; // Key size (16384 bits) + private const int DEFAULT_ROUNDS = 2048; // Rounds + private const int DEFAULT_BLOCK_SIZE = 8192; // 8kb + private const int HMAC_KEY_SIZE = 32; // Key size for HMAC (256 bits) + + private readonly byte[] _derivedKey; // Derived encryption key + private readonly byte[] _hmacKey; // HMAC key + private readonly int _ivSize; // IV size + private readonly int _keySize; // Key size + private readonly int _rounds; // Number of rounds for key derivation + private readonly int _blockSize; // The size of the block that is created + + public EonaCatCipher(string password, int saltSize = DEFAULT_SALT_SIZE, int ivSize = DEFAULT_IV_SIZE, int keySize = DEFAULT_KEY_SIZE, int rounds = DEFAULT_ROUNDS, int blockSize = DEFAULT_BLOCK_SIZE) + { + if (string.IsNullOrEmpty(password)) + { + throw new ArgumentException("EonaCatCipher: Password cannot be null or empty."); + } + + _ivSize = ivSize; + _keySize = keySize; + _rounds = rounds; + _blockSize = blockSize; + + // Derive encryption key and HMAC key + (_derivedKey, _hmacKey) = DeriveKeyAndHMAC(password, saltSize); + } + + private static byte[] GenerateRandomBytes(int size) + { + var randomBytes = new byte[size]; + RandomNumberGenerator.Fill(randomBytes); + return randomBytes; + } + + private (byte[] encryptionKey, byte[] hmacKey) DeriveKeyAndHMAC(string password, int saltSize) + { + var salt = GenerateRandomBytes(saltSize); + var encryptionKey = PBKDF2(password, salt, _keySize, _rounds); + + // Derive separate key for HMAC + var hmacKey = PBKDF2(password, salt, HMAC_KEY_SIZE, _rounds); + + var keyWithSalt = new byte[saltSize + _keySize]; + Buffer.BlockCopy(salt, 0, keyWithSalt, 0, saltSize); + Buffer.BlockCopy(encryptionKey, 0, keyWithSalt, saltSize, _keySize); + + return (keyWithSalt, hmacKey); + } + + private static byte[] PBKDF2(string password, byte[] salt, int keyLength, int iterations) + { + var hmac = new HMACSHA512(Encoding.UTF8.GetBytes(password)); + int hashLength = hmac.HashSize / 8; + int requiredBytes = keyLength; + int blocksNeeded = (int)Math.Ceiling((double)requiredBytes / hashLength); + + byte[] derivedKey = new byte[requiredBytes]; + byte[] block = new byte[hashLength]; + + for (int blockIndex = 1; blockIndex <= blocksNeeded; blockIndex++) + { + // Step 1: F(blockIndex) + var currentBlock = new byte[salt.Length + 4]; + Buffer.BlockCopy(salt, 0, currentBlock, 0, salt.Length); + BitConverter.GetBytes(blockIndex).CopyTo(currentBlock, salt.Length); + + // Step 2: U1 = HMAC(password, salt + blockIndex) + byte[] u = hmac.ComputeHash(currentBlock); + Buffer.BlockCopy(u, 0, block, 0, hashLength); + + // Step 3: Derived key starts with U1 + Array.Copy(u, 0, derivedKey, (blockIndex - 1) * hashLength, Math.Min(hashLength, requiredBytes)); + + // Step 4: Iterations + for (int iteration = 1; iteration < iterations; iteration++) + { + // U2 = HMAC(password, U1) + u = hmac.ComputeHash(u); + + // Step 5: XOR U2 with previous result + for (int i = 0; i < hashLength; i++) + { + block[i] ^= u[i]; + } + + // Step 6: Append result to derived key + Array.Copy(block, 0, derivedKey, (blockIndex - 1) * hashLength, Math.Min(hashLength, requiredBytes)); + } + } + + return derivedKey; + } + + public byte[] Encrypt(string plaintext) + { + var iv = GenerateRandomBytes(_ivSize); + var plaintextBytes = Encoding.UTF8.GetBytes(plaintext); + var ciphertext = new byte[plaintextBytes.Length]; + + using var cipher = new EonaCatCrypto(_derivedKey, iv, _blockSize, _rounds); + cipher.Generate(plaintextBytes, ciphertext, true); + + // Combine IV and ciphertext + var result = new byte[_ivSize + ciphertext.Length]; + Buffer.BlockCopy(iv, 0, result, 0, _ivSize); + Buffer.BlockCopy(ciphertext, 0, result, _ivSize, ciphertext.Length); + + // Generate HMAC for integrity check + var hmac = GenerateHMAC(result); + + // Combine result and HMAC + var finalResult = new byte[result.Length + hmac.Length]; + Buffer.BlockCopy(result, 0, finalResult, 0, result.Length); + Buffer.BlockCopy(hmac, 0, finalResult, result.Length, hmac.Length); + + return finalResult; + } + + public string Decrypt(byte[] ciphertextWithHMAC) + { + var hmacOffset = ciphertextWithHMAC.Length - HMAC_KEY_SIZE; + + // Separate HMAC from the ciphertext + var providedHMAC = new byte[HMAC_KEY_SIZE]; + Buffer.BlockCopy(ciphertextWithHMAC, hmacOffset, providedHMAC, 0, HMAC_KEY_SIZE); + + var ciphertext = new byte[hmacOffset]; + Buffer.BlockCopy(ciphertextWithHMAC, 0, ciphertext, 0, hmacOffset); + + // Verify HMAC before decrypting + var calculatedHMAC = GenerateHMAC(ciphertext); + if (!AreEqual(providedHMAC, calculatedHMAC)) + { + throw new CryptographicException("EonaCatCipher: HMAC validation failed. Data may have been tampered with."); + } + + // Extract IV + var iv = new byte[_ivSize]; + Buffer.BlockCopy(ciphertext, 0, iv, 0, _ivSize); + + // Extract encrypted data + var encryptedData = new byte[ciphertext.Length - _ivSize]; + Buffer.BlockCopy(ciphertext, _ivSize, encryptedData, 0, encryptedData.Length); + + // Decrypt + var decryptedData = new byte[encryptedData.Length]; + using var cipher = new EonaCatCrypto(_derivedKey, iv, _blockSize, _rounds); + cipher.Generate(encryptedData, decryptedData, false); + + return Encoding.UTF8.GetString(decryptedData); + } + + private byte[] GenerateHMAC(byte[] data) + { + using var hmac = new HMACSHA256(_hmacKey); + return hmac.ComputeHash(data); + } + + private static bool AreEqual(byte[] a, byte[] b) + { + if (a.Length != b.Length) return false; + for (int i = 0; i < a.Length; i++) + { + if (a[i] != b[i]) return false; + } + return true; + } + + public void Dispose() + { + if (_derivedKey != null) + { + Array.Clear(_derivedKey, 0, _derivedKey.Length); + } + if (_hmacKey != null) + { + Array.Clear(_hmacKey, 0, _hmacKey.Length); + } + } + + private class EonaCatCrypto : IDisposable + { + private const long SECRET_SAUCE = 0x5DEECE66D; + private const uint UNSIGNED_INT = 0xFFFFFFFF; + private readonly int _blockSize; + private readonly int _rounds; + private readonly ulong[] _state; + private readonly uint[] _key; + private readonly uint[] _nonce; + private uint _blockCounter; + + public EonaCatCrypto(byte[] keyWithSalt, byte[] nonce, int blockSize, int rounds) + { + _rounds = rounds; + _blockSize = blockSize / 4 > 0 ? blockSize : 128; + + _key = new uint[keyWithSalt.Length / 4]; + Buffer.BlockCopy(keyWithSalt, 0, _key, 0, keyWithSalt.Length); + + _nonce = new uint[nonce.Length / 4]; + Buffer.BlockCopy(nonce, 0, _nonce, 0, nonce.Length); + + _state = new ulong[_blockSize / 4]; + } + + private void GenerateBlock(byte[] output) + { + // Initialize state using a combined operation + for (int i = 0; i < _state.Length; i++) + { + _state[i] = (_key[i % _key.Length] ^ _nonce[i % _nonce.Length]) + (ulong)i * SECRET_SAUCE; + } + + // Mix the states according to the rounds + for (int round = 0; round < _rounds; round++) + { + for (int i = 0; i < _state.Length; i++) + { + _state[i] = (ulong)(((int)_state[i] + round) ^ (round * SECRET_SAUCE) + (i + _blockCounter)); + } + } + + // Output block + Buffer.BlockCopy(_state, 0, output, 0, output.Length); + _blockCounter++; + } + + public void Generate(byte[] input, byte[] output, bool encrypt) + { + int totalBlocks = (input.Length + _blockSize - 1) / _blockSize; + + for (int blockIndex = 0; blockIndex < totalBlocks; blockIndex++) + { + int inputOffset = blockIndex * _blockSize; + int outputOffset = blockIndex * _blockSize; + byte[] block = new byte[_blockSize]; + + // Generate a block based on the input + GenerateBlock(block); + + // Perform XOR for encryption or decryption + for (int i = 0; i < block.Length && inputOffset + i < input.Length; i++) + { + output[outputOffset + i] = (byte)(input[inputOffset + i] ^ block[i]); + } + } + } + + public void Dispose() + { + if (_state != null) + { + Array.Clear(_state, 0, _state.Length); + } + } + } + + public static void Main(string[] args) + { + string password = "securePassword123!@#$"; + string plaintext = "Thank you for using EonaCatCipher!"; + + Console.WriteLine($"Encrypting '{plaintext}' with password '{password}' (we do this 5 times)"); + Console.WriteLine("================"); + + for (int i = 0; i < 5; i++) + { + Console.WriteLine($"Encryption round {i + 1}: "); + Console.WriteLine("================"); + + using var cipher = new EonaCatCipher(password); + var encrypted = cipher.Encrypt(plaintext); + + Console.WriteLine("Encrypted (byte array): " + BitConverter.ToString(encrypted)); + + var decrypted = cipher.Decrypt(encrypted); + + Console.WriteLine("Decrypted: " + decrypted); + Console.WriteLine("================"); + } + } +} diff --git a/C++/EonaCatCipher.cpp b/C++/EonaCatCipher.cpp new file mode 100644 index 0000000..778e76a --- /dev/null +++ b/C++/EonaCatCipher.cpp @@ -0,0 +1,239 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * EonaCatCipher - Because security is key! + * + * Copyright (c) 2024 EonaCat (Jeroen Saey) + * + * https://eonacat.com/license + * + * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + * OF SOFTWARE BY EONACAT (JEROEN SAEY) + * + * This software is provided "as is", without any express or implied warranty. + * In no event shall the authors or copyright holders be liable for any claim, + * damages or other liability, whether in an action of contract, tort or otherwise, + * arising from, out of or in connection with the software or the use or other + * dealings in the software. + * + * You may use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. The software must not be used for any unlawful purpose. + * + * For any inquiries, please contact: eonacat@gmail.com + */ + +class EonaCatCrypto { +private: + static const uint64_t SECRET_SAUCE = 0x5DEECE66D; + const int _blockSize; + const int _rounds; + uint64_t* _state; + uint32_t* _key; + uint32_t* _nonce; + uint32_t _blockCounter; + + void GenerateBlock(uint8_t* output) { + for (int i = 0; i < _blockSize / 4; i++) { + _state[i] = (_key[i % (_blockSize / 4)] ^ _nonce[i % (_blockSize / 4)]) + (uint64_t)i * SECRET_SAUCE; + } + + for (int round = 0; round < _rounds; round++) { + for (int i = 0; i < _blockSize / 4; i++) { + _state[i] = (uint64_t)(((int)_state[i] + round) ^ (round * SECRET_SAUCE) + (i + _blockCounter)); + } + } + + memcpy(output, _state, _blockSize); + _blockCounter++; + } + +public: + EonaCatCrypto(const uint8_t* keyWithSalt, const uint8_t* nonce, int blockSize, int rounds) + : _blockSize(blockSize), _rounds(rounds), _blockCounter(0) { + _key = new uint32_t[_blockSize / 4]; + memcpy(_key, keyWithSalt, _blockSize / 4 * sizeof(uint32_t)); + + _nonce = new uint32_t[blockSize / 4]; + memcpy(_nonce, nonce, blockSize / 4 * sizeof(uint32_t)); + + _state = new uint64_t[_blockSize / 4]; + } + + ~EonaCatCrypto() { + delete[] _key; + delete[] _nonce; + delete[] _state; + } + + void Generate(const uint8_t* input, uint8_t* output, bool encrypt) { + int totalBlocks = (strlen((const char*)input) + _blockSize - 1) / _blockSize; + + for (int blockIndex = 0; blockIndex < totalBlocks; blockIndex++) { + int inputOffset = blockIndex * _blockSize; + int outputOffset = blockIndex * _blockSize; + uint8_t block[_blockSize]; + + GenerateBlock(block); + + for (int i = 0; i < _blockSize && inputOffset + i < strlen((const char*)input); i++) { + output[outputOffset + i] = input[inputOffset + i] ^ block[i]; + } + } + } +}; + +class EonaCatCipher { +private: + const int DEFAULT_SALT_SIZE = 2048; // Salt size for key derivation + const int DEFAULT_IV_SIZE = 2048; // IV size (16384 bits) + const int DEFAULT_KEY_SIZE = 2048; // Key size (16384 bits) + const int DEFAULT_ROUNDS = 2048; // Rounds + const int DEFAULT_BLOCK_SIZE = 8192; // 8kb + const int HMAC_KEY_SIZE = 32; // Key size for HMAC (256 bits) + + uint8_t* _derivedKey; // Derived encryption key + uint8_t* _hmacKey; // HMAC key + int _ivSize; // IV size + int _keySize; // Key size + int _rounds; // Number of rounds for key derivation + int _blockSize; // The size of the block that is created + + static std::vector GenerateRandomBytes(int size) { + std::vector randomBytes(size); + if (!RAND_bytes(randomBytes.data(), size)) { + throw std::runtime_error("EonaCatCipher: Failed to generate random bytes."); + } + return randomBytes; + } + + void DeriveKeyAndHMAC(const std::string& password, int saltSize) { + std::vector salt = GenerateRandomBytes(saltSize); + _derivedKey = PBKDF2(password, salt.data(), salt.size(), _keySize); + _hmacKey = PBKDF2(password, salt.data(), salt.size(), HMAC_KEY_SIZE); + } + + uint8_t* PBKDF2(const std::string& password, const uint8_t* salt, int saltLen, int keyLength) { + uint8_t* derivedKey = new uint8_t[keyLength]; + PKCS5_PBKDF2_HMAC(password.c_str(), password.size(), salt, saltLen, _rounds, EVP_sha256(), keyLength, derivedKey); + return derivedKey; + } + + std::vector GenerateHMAC(const uint8_t* data, size_t dataLen) { + std::vector hmac(EVP_MAX_MD_SIZE); + unsigned int hmacLen; + HMAC(EVP_sha256(), _hmacKey, HMAC_KEY_SIZE, data, dataLen, hmac.data(), &hmacLen); + hmac.resize(hmacLen); + return hmac; + } + + bool AreEqual(const uint8_t* a, const uint8_t* b, size_t length) { + return CRYPTO_memcmp(a, b, length) == 0; + } + +public: + EonaCatCipher(const std::string& password, int saltSize = 32, int ivSize = 16, int keySize = 32, int rounds = 1000, int blockSize = 128) + : _ivSize(ivSize), _keySize(keySize), _rounds(rounds), _blockSize(blockSize), _derivedKey(nullptr), _hmacKey(nullptr) { + if (password.empty()) { + throw std::invalid_argument("EonaCatCipher: Password cannot be null or empty."); + } + DeriveKeyAndHMAC(password, saltSize); + } + + ~EonaCatCipher() { + delete[] _derivedKey; + delete[] _hmacKey; + } + + std::vector Encrypt(const std::string& plaintext) { + auto iv = GenerateRandomBytes(_ivSize); + std::vector plaintextBytes(plaintext.begin(), plaintext.end()); + std::vector ciphertext(plaintextBytes.size()); + + EonaCatCrypto cipher(_derivedKey, iv.data(), _blockSize, _rounds); + cipher.Generate(plaintextBytes.data(), ciphertext.data(), true); + + std::vector result(_ivSize + ciphertext.size()); + memcpy(result.data(), iv.data(), _ivSize); + memcpy(result.data() + _ivSize, ciphertext.data(), ciphertext.size()); + + auto hmac = GenerateHMAC(result.data(), result.size()); + result.insert(result.end(), hmac.begin(), hmac.end()); + + return result; + } + + std::string Decrypt(const std::vector& ciphertextWithHMAC) { + size_t hmacOffset = ciphertextWithHMAC.size() - HMAC_KEY_SIZE; + + std::vector providedHMAC(HMAC_KEY_SIZE); + memcpy(providedHMAC.data(), ciphertextWithHMAC.data() + hmacOffset, HMAC_KEY_SIZE); + + std::vector ciphertext(hmacOffset); + memcpy(ciphertext.data(), ciphertextWithHMAC.data(), hmacOffset); + + auto calculatedHMAC = GenerateHMAC(ciphertext.data(), ciphertext.size()); + if (!AreEqual(providedHMAC.data(), calculatedHMAC.data(), HMAC_KEY_SIZE)) { + throw std::runtime_error("EonaCatCipher: HMAC validation failed. Data may have been tampered with."); + } + + std::vector iv(_ivSize); + memcpy(iv.data(), ciphertext.data(), _ivSize); + + std::vector encryptedData(ciphertext.size() - _ivSize); + memcpy(encryptedData.data(), ciphertext.data() + _ivSize, encryptedData.size()); + + std::vector decryptedData(encryptedData.size()); + EonaCatCrypto decryptCipher(_derivedKey, iv.data(), _blockSize, _rounds); + decryptCipher.Generate(encryptedData.data(), decryptedData.data(), false); + + return std::string(decryptedData.begin(), decryptedData.end()); + } + + static void Main() { + std::string password = "securePassword123!@#$"; + std::string plaintext = "Thank you for using EonaCatCipher!"; + + std::cout << "Encrypting '" << plaintext << "' with password '" << password << "' (we do this 5 times)" << std::endl; + std::cout << "================" << std::endl; + + for (int i = 0; i < 5; i++) { + std::cout << "Encryption round " << (i + 1) << ": " << std::endl; + std::cout << "================" << std::endl; + + try { + EonaCatCipher cipher(password); + auto encrypted = cipher.Encrypt(plaintext); + std::cout << "Encrypted (byte array): "; + for (auto byte : encrypted) { + printf("%02X ", byte); + } + std::cout << std::endl; + + std::string decrypted = cipher.Decrypt(encrypted); + std::cout << "Decrypted: " << decrypted << std::endl; + std::cout << "================" << std::endl; + } + catch (const std::exception& ex) { + std::cerr << "Error: " << ex.what() << std::endl; + } + } + } +}; + +int main() { + EonaCatCipher::Main(); + return 0; +} diff --git a/C++/readme.txt b/C++/readme.txt new file mode 100644 index 0000000..8fbef7b --- /dev/null +++ b/C++/readme.txt @@ -0,0 +1,35 @@ +/* + * EonaCatCipher - Because security is key! + * + * Copyright (c) 2024 EonaCat (Jeroen Saey) + * + * https://eonacat.com/license + * + * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + * OF SOFTWARE BY EONACAT (JEROEN SAEY) + * + * This software is provided "as is", without any express or implied warranty. + * In no event shall the authors or copyright holders be liable for any claim, + * damages or other liability, whether in an action of contract, tort or otherwise, + * arising from, out of or in connection with the software or the use or other + * dealings in the software. + * + * You may use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. The software must not be used for any unlawful purpose. + * + * For any inquiries, please contact: eonacat@gmail.com + */ + +1. Compile the c++ code: + +g++ -o EonaCatCipher EonaCatCipher.cpp -lssl -lcrypto + +2. Run the executable: + +./EonaCatCipher diff --git a/Delphi/EonaCatCipher.pas b/Delphi/EonaCatCipher.pas new file mode 100644 index 0000000..afde841 --- /dev/null +++ b/Delphi/EonaCatCipher.pas @@ -0,0 +1,257 @@ +unit EonaCatCipher; + +interface + +uses + SysUtils, Classes, Hash, HMAC, Cryptography, Generics.Collections; + +{* + * EonaCatCipher - Because security is key! + * + * Copyright (c) 2024 EonaCat (Jeroen Saey) + * + * https://eonacat.com/license + * + * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + * OF SOFTWARE BY EONACAT (JEROEN SAEY) + * + * This software is provided "as is", without any express or implied warranty. + * In no event shall the authors or copyright holders be liable for any claim, + * damages or other liability, whether in an action of contract, tort or otherwise, + * arising from, out of or in connection with the software or the use or other + * dealings in the software. + * + * You may use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. The software must not be used for any unlawful purpose. + * + * For any inquiries, please contact: eonacat@gmail.com + *} + +type + TEonaCatCipher = class(TObject) + private + const + DEFAULT_SALT_SIZE = 2048; // Salt size for key derivation + DEFAULT_IV_SIZE = 2048; // IV size (2048 bits) + DEFAULT_KEY_SIZE = 2048; // Key size (2048 bits) + DEFAULT_ROUNDS = 2048; // Rounds + DEFAULT_BLOCK_SIZE = 8192; // 8KB + HMAC_KEY_SIZE = 32; // Key size for HMAC (256 bits) + + var + _derivedKey: TBytes; // Derived encryption key + _hmacKey: TBytes; // HMAC key + _ivSize: Integer; // IV size + _keySize: Integer; // Key size + _rounds: Integer; // Number of rounds for key derivation + _blockSize: Integer; // The size of the block that is created + + function GenerateRandomBytes(size: Integer): TBytes; + function DeriveKeyAndHMAC(password: string; saltSize: Integer): TBytes; + function PBKDF2(password: string; salt: TBytes; keyLength: Integer; iterations: Integer): TBytes; + function GenerateHMAC(data: TBytes): TBytes; + function AreEqual(a, b: TBytes): Boolean; + + public + constructor Create(password: string; saltSize: Integer = DEFAULT_SALT_SIZE; + ivSize: Integer = DEFAULT_IV_SIZE; keySize: Integer = DEFAULT_KEY_SIZE; + rounds: Integer = DEFAULT_ROUNDS; blockSize: Integer = DEFAULT_BLOCK_SIZE); + destructor Destroy; override; + + function Encrypt(plaintext: string): TBytes; + function Decrypt(ciphertextWithHMAC: TBytes): string; + + end; + +implementation + +constructor TEonaCatCipher.Create(password: string; saltSize: Integer; + ivSize: Integer; keySize: Integer; rounds: Integer; blockSize: Integer); +begin + inherited Create; + + if password = '' then + raise Exception.Create('EonaCatCipher: Password cannot be null or empty.'); + + _ivSize := ivSize; + _keySize := keySize; + _rounds := rounds; + _blockSize := blockSize; + + // Derive encryption key and HMAC key + (_derivedKey, _hmacKey) := DeriveKeyAndHMAC(password, saltSize); +end; + +destructor TEonaCatCipher.Destroy; +begin + if Length(_derivedKey) > 0 then + FillChar(_derivedKey[0], Length(_derivedKey), 0); + + if Length(_hmacKey) > 0 then + FillChar(_hmacKey[0], Length(_hmacKey), 0); + + inherited Destroy; +end; + +function TEonaCatCipher.GenerateRandomBytes(size: Integer): TBytes; +begin + SetLength(Result, size); + RandomBytes(Result); +end; + +function TEonaCatCipher.DeriveKeyAndHMAC(password: string; saltSize: Integer): TBytes; +var + salt, encryptionKey, hmacKey: TBytes; +begin + salt := GenerateRandomBytes(saltSize); + encryptionKey := PBKDF2(password, salt, _keySize, _rounds); + hmacKey := PBKDF2(password, salt, HMAC_KEY_SIZE, _rounds); + + SetLength(Result, saltSize + Length(encryptionKey)); + Move(salt[0], Result[0], saltSize); + Move(encryptionKey[0], Result[saltSize], Length(encryptionKey)); + + // Combine encryptionKey and hmacKey if needed for further processing +end; + +function TEonaCatCipher.PBKDF2(password: string; salt: TBytes; keyLength: Integer; iterations: Integer): TBytes; +var + hmac: IHMAC; + hashLength, requiredBytes, blocksNeeded, blockIndex: Integer; + derivedKey, block: TBytes; + currentBlock: TBytes; + u: TBytes; +begin + hmac := THashFactory.THMAC.CreateHMAC(THashFactory.TCrypto.CreateSHA512); + hmac.Key := TEncoding.UTF8.GetBytes(password); + hashLength := hmac.HashSize div 8; + requiredBytes := keyLength; + blocksNeeded := Ceil(requiredBytes / hashLength); + + SetLength(derivedKey, requiredBytes); + SetLength(block, hashLength); + + for blockIndex := 1 to blocksNeeded do + begin + SetLength(currentBlock, Length(salt) + SizeOf(Integer)); + Move(salt[0], currentBlock[0], Length(salt)); + Move(blockIndex, currentBlock[Length(salt)], SizeOf(Integer)); + + // U1 = HMAC(password, salt + blockIndex) + u := hmac.ComputeHash(currentBlock); + Move(u[0], block[0], hashLength); + Move(u[0], derivedKey[(blockIndex - 1) * hashLength], Min(hashLength, requiredBytes)); + + // Iterations + for var iteration := 1 to iterations - 1 do + begin + // U2 = HMAC(password, U1) + u := hmac.ComputeHash(u); + for var i := 0 to hashLength - 1 do + block[i] := block[i] xor u[i]; + + // Append result to derived key + Move(block[0], derivedKey[(blockIndex - 1) * hashLength], Min(hashLength, requiredBytes)); + end; + end; + + Result := derivedKey; +end; + +function TEonaCatCipher.Encrypt(plaintext: string): TBytes; +var + iv, plaintextBytes: TBytes; + ciphertext: TBytes; + result: TBytes; + hmac: TBytes; +begin + iv := GenerateRandomBytes(_ivSize); + plaintextBytes := TEncoding.UTF8.GetBytes(plaintext); + SetLength(ciphertext, Length(plaintextBytes)); + + // Note: Implement the EonaCatCrypto class and its Generate method for encryption here + // using a similar approach to the C# version + + // Combine IV and ciphertext + SetLength(result, _ivSize + Length(ciphertext)); + Move(iv[0], result[0], _ivSize); + Move(ciphertext[0], result[_ivSize], Length(ciphertext)); + + // Generate HMAC for integrity check + hmac := GenerateHMAC(result); + + // Combine result and HMAC + SetLength(Result, Length(result) + Length(hmac)); + Move(result[0], Result[0], Length(result)); + Move(hmac[0], Result[Length(result)], Length(hmac)); +end; + +function TEonaCatCipher.Decrypt(ciphertextWithHMAC: TBytes): string; +var + hmacOffset: Integer; + providedHMAC, ciphertext: TBytes; + calculatedHMAC: TBytes; + iv: TBytes; + encryptedData: TBytes; + decryptedData: TBytes; +begin + hmacOffset := Length(ciphertextWithHMAC) - HMAC_KEY_SIZE; + + // Separate HMAC from the ciphertext + SetLength(providedHMAC, HMAC_KEY_SIZE); + Move(ciphertextWithHMAC[hmacOffset], providedHMAC[0], HMAC_KEY_SIZE); + SetLength(ciphertext, hmacOffset); + Move(ciphertextWithHMAC[0], ciphertext[0], hmacOffset); + + // Verify HMAC before decrypting + calculatedHMAC := GenerateHMAC(ciphertext); + if not AreEqual(providedHMAC, calculatedHMAC) then + raise Exception.Create('EonaCatCipher: HMAC validation failed. Data may have been tampered with.'); + + // Extract IV + SetLength(iv, _ivSize); + Move(ciphertext[0], iv[0], _ivSize); + + // Extract encrypted data + SetLength(encryptedData, Length(ciphertext) - _ivSize); + Move(ciphertext[_ivSize], encryptedData[0], Length(encryptedData)); + + // Decrypt + // Note: Implement the EonaCatCrypto class and its Generate method for decryption here + // using a similar approach to the C# version + + Result := TEncoding.UTF8.GetString(decryptedData); +end; + +function TEonaCatCipher.GenerateHMAC(data: TBytes): TBytes; +var + hmac: IHMAC; +begin + hmac := THashFactory.THMAC.CreateHMAC(THashFactory.TCrypto.CreateSHA256); + hmac.Key := _hmacKey; + Result := hmac.ComputeHash(data); +end; + +function TEonaCatCipher.AreEqual(a, b: TBytes): Boolean; +var + i: Integer; +begin + Result := Length(a) = Length(b); + if Result then + begin + for i := 0 to High(a) do + if a[i] <> b[i] then + begin + Result := False; + Break; + end; + end; +end; + +end. diff --git a/Delphi/example.pas b/Delphi/example.pas new file mode 100644 index 0000000..cf7a4e0 --- /dev/null +++ b/Delphi/example.pas @@ -0,0 +1,50 @@ +program EonaCatCipherExample; + +{$APPTYPE CONSOLE} + +uses + SysUtils, EonaCatCipher; + +{* + * EonaCatCipher - Because security is key! + * + * Copyright (c) 2024 EonaCat (Jeroen Saey) + * + * https://eonacat.com/license + * + * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + * OF SOFTWARE BY EONACAT (JEROEN SAEY) + * + * This software is provided "as is", without any express or implied warranty. + * In no event shall the authors or copyright holders be liable for any claim, + * damages or other liability, whether in an action of contract, tort or otherwise, + * arising from, out of or in connection with the software or the use or other + * dealings in the software. + * + * You may use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. The software must not be used for any unlawful purpose. + * + * For any inquiries, please contact: eonacat@gmail.com + *} + +var + cipher: TEonaCatCipher; + encrypted: TArray; + decrypted: string; +begin + cipher := TEonaCatCipher.Create('securePassword123!@#$'); + try + encrypted := cipher.Encrypt('Thank you for using EonaCatCipher!'); + decrypted := cipher.Decrypt(encrypted); + WriteLn('Decrypted: ' + decrypted); + finally + cipher.Free; + end; +end; + diff --git a/Go/EonaCatCipher.go b/Go/EonaCatCipher.go new file mode 100644 index 0000000..4b8c585 --- /dev/null +++ b/Go/EonaCatCipher.go @@ -0,0 +1,312 @@ +package main + +import ( + "crypto/rand" + "crypto/sha512" + "crypto/hmac" + "encoding/binary" + "encoding/hex" + "encoding/base64" + "fmt" + "log" +) + +/* + * EonaCatCipher - Because security is key! + * + * Copyright (c) 2024 EonaCat (Jeroen Saey) + * + * https://eonacat.com/license + * + * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + * OF SOFTWARE BY EONACAT (JEROEN SAEY) + * + * This software is provided "as is", without any express or implied warranty. + * In no event shall the authors or copyright holders be liable for any claim, + * damages or other liability, whether in an action of contract, tort or otherwise, + * arising from, out of or in connection with the software or the use or other + * dealings in the software. + * + * You may use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. The software must not be used for any unlawful purpose. + * + * For any inquiries, please contact: eonacat@gmail.com + */ + +// EonaCatCipher struct +type EonaCatCipher struct { + derivedKey []byte + hmacKey []byte + ivSize int + keySize int + rounds int + blockSize int +} + +// Constants +const ( + DEFAULT_SALT_SIZE = 2048 // Salt size for key derivation + DEFAULT_IV_SIZE = 2048 // IV size (16384 bits) + DEFAULT_KEY_SIZE = 2048 // Key size (16384 bits) + DEFAULT_ROUNDS = 2048 + DEFAULT_BLOCK_SIZE = 8192 // 8KB + HMAC_KEY_SIZE = 32 // Key size for HMAC (256 bits) + SECRET_SAUCE = 0x5DEECE66D +) + +// NewEonaCatCipher constructor +func NewEonaCatCipher(password string, saltSize int, ivSize int, keySize int, rounds int, blockSize int) *EonaCatCipher { + if password == "" { + log.Fatal("EonaCatCipher: Password cannot be null or empty.") + } + + ec := &EonaCatCipher{ + ivSize: ivSize, + keySize: keySize, + rounds: rounds, + blockSize: blockSize, + } + + ec.derivedKey, ec.hmacKey = ec.DeriveKeyAndHMAC(password, saltSize) + + return ec +} + +// GenerateRandomBytes generates a slice of random bytes +func GenerateRandomBytes(size int) ([]byte, error) { + randomBytes := make([]byte, size) + _, err := rand.Read(randomBytes) + if err != nil { + return nil, err + } + return randomBytes, nil +} + +// DeriveKeyAndHMAC derives the encryption key and HMAC key from the password +func (ec *EonaCatCipher) DeriveKeyAndHMAC(password string, saltSize int) ([]byte, []byte) { + salt, _ := GenerateRandomBytes(saltSize) + encryptionKey := ec.PBKDF2(password, salt, ec.keySize, ec.rounds) + + // Derive separate key for HMAC + hmacKey := ec.PBKDF2(password, salt, HMAC_KEY_SIZE, ec.rounds) + + keyWithSalt := append(salt, encryptionKey...) + + return keyWithSalt, hmacKey +} + +// PBKDF2 implementation of the PBKDF2 key derivation function +func (ec *EonaCatCipher) PBKDF2(password string, salt []byte, keyLength int, iterations int) []byte { + hmacHash := hmac.New(sha512.New, []byte(password)) + hashLength := hmacHash.Size() + requiredBytes := keyLength + blocksNeeded := (requiredBytes + hashLength - 1) / hashLength + + derivedKey := make([]byte, requiredBytes) + + for blockIndex := 1; blockIndex <= blocksNeeded; blockIndex++ { + currentBlock := append(salt, intToBytes(blockIndex)...) + + // U1 = HMAC(password, salt + blockIndex) + u := hmacHash.Sum(currentBlock) + block := make([]byte, hashLength) + copy(block, u) + + // Derived key starts with U1 + copy(derivedKey[(blockIndex-1)*hashLength:], block) + + // Iterations + for iteration := 1; iteration < iterations; iteration++ { + u = hmacHash.Sum(u) + + // XOR with previous result + for i := 0; i < hashLength; i++ { + block[i] ^= u[i] + } + + // Append result to derived key + copy(derivedKey[(blockIndex-1)*hashLength:], block) + } + } + + return derivedKey +} + +// Encrypt encrypts the plaintext using the derived key and returns the ciphertext with HMAC +func (ec *EonaCatCipher) Encrypt(plaintext string) ([]byte, error) { + iv, err := GenerateRandomBytes(ec.ivSize) + if err != nil { + return nil, err + } + plaintextBytes := []byte(plaintext) + ciphertext := make([]byte, len(plaintextBytes)) + + cipher := NewEonaCatCrypto(ec.derivedKey, iv, ec.blockSize, ec.rounds) + cipher.Generate(plaintextBytes, ciphertext, true) + + // Combine IV and ciphertext + result := append(iv, ciphertext...) + + // Generate HMAC for integrity check + hmac := ec.GenerateHMAC(result) + + // Combine result and HMAC + finalResult := append(result, hmac...) + + return finalResult, nil +} + +// Decrypt decrypts the ciphertext with HMAC +func (ec *EonaCatCipher) Decrypt(ciphertextWithHMAC []byte) (string, error) { + hmacOffset := len(ciphertextWithHMAC) - HMAC_KEY_SIZE + + // Separate HMAC from the ciphertext + providedHMAC := ciphertextWithHMAC[hmacOffset:] + ciphertext := ciphertextWithHMAC[:hmacOffset] + + // Verify HMAC before decrypting + calculatedHMAC := ec.GenerateHMAC(ciphertext) + if !AreEqual(providedHMAC, calculatedHMAC) { + return "", fmt.Errorf("EonaCatCipher: HMAC validation failed. Data may have been tampered with.") + } + + // Extract IV + iv := ciphertext[:ec.ivSize] + + // Extract encrypted data + encryptedData := ciphertext[ec.ivSize:] + + // Decrypt + decryptedData := make([]byte, len(encryptedData)) + cipher := NewEonaCatCrypto(ec.derivedKey, iv, ec.blockSize, ec.rounds) + cipher.Generate(encryptedData, decryptedData, false) + + return string(decryptedData), nil +} + +// GenerateHMAC generates HMAC for the data +func (ec *EonaCatCipher) GenerateHMAC(data []byte) []byte { + h := hmac.New(sha512.New, ec.hmacKey) + h.Write(data) + return h.Sum(nil) +} + +// AreEqual checks if two byte slices are equal +func AreEqual(a, b []byte) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +// Convert an integer to bytes +func intToBytes(n int) []byte { + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, uint32(n)) + return buf +} + +// EonaCatCrypto struct for encryption and decryption +type EonaCatCrypto struct { + blockSize int + rounds int + state []uint64 + key []uint32 + nonce []uint32 + blockCounter uint32 +} + +// NewEonaCatCrypto constructor +func NewEonaCatCrypto(keyWithSalt []byte, nonce []byte, blockSize int, rounds int) *EonaCatCrypto { + ec := &EonaCatCrypto{ + blockSize: blockSize / 4, + rounds: rounds, + key: make([]uint32, len(keyWithSalt)/4), + nonce: make([]uint32, len(nonce)/4), + state: make([]uint64, blockSize/4), + } + copy(ec.key, keyWithSalt) + copy(ec.nonce, nonce) + return ec +} + +// GenerateBlock generates a block for encryption/decryption +func (ec *EonaCatCrypto) GenerateBlock(output []byte) { + // Initialize state + for i := 0; i < len(ec.state); i++ { + ec.state[i] = uint64(ec.key[i%len(ec.key)]) ^ uint64(ec.nonce[i%len(ec.nonce)]) + uint64(i)*SECRET_SAUCE + } + + // Mix the states according to the rounds + for round := 0; round < ec.rounds; round++ { + for i := 0; i < len(ec.state); i++ { + ec.state[i] = (ec.state[i] + uint64(round)) ^ (uint64(round) * SECRET_SAUCE) + uint64(i) + uint64(ec.blockCounter) + } + } + + // Output block + for i := 0; i < len(output); i++ { + output[i] = byte(ec.state[i/4] >> (8 * (i % 4))) + } + ec.blockCounter++ +} + +// Generate performs encryption or decryption +func (ec *EonaCatCrypto) Generate(input []byte, output []byte, encrypt bool) { + totalBlocks := (len(input) + ec.blockSize - 1) / ec.blockSize + + for blockIndex := 0; blockIndex < totalBlocks; blockIndex++ { + inputOffset := blockIndex * ec.blockSize + outputOffset := blockIndex * ec.blockSize + block := make([]byte, ec.blockSize) + + // Generate a block based on the input + ec.GenerateBlock(block) + + // Perform XOR for encryption or decryption + for i := 0; i < len(block) && inputOffset+i < len(input); i++ { + output[outputOffset+i] = input[inputOffset+i] ^ block[i] + } + } +} + +// Main function for testing +func main() { + password := "securePassword123!@#$" + plaintext := "Thank you for using EonaCatCipher!" + + fmt.Printf("Encrypting '%s' with password '%s' (we do this 5 times)\n", plaintext, password) + fmt.Println("================") + + for i := 0; i < 5; i++ { + fmt.Printf("Encryption round %d:\n", i+1) + fmt.Println("================") + + cipher := NewEonaCatCipher(password, DEFAULT_SALT_SIZE, DEFAULT_IV_SIZE, DEFAULT_KEY_SIZE, DEFAULT_ROUNDS, DEFAULT_BLOCK_SIZE) + encrypted, err := cipher.Encrypt(plaintext) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Encrypted (base64): %s\n", base64.StdEncoding.EncodeToString(encrypted)) + + decrypted, err := cipher.Decrypt(encrypted) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Decrypted: %s\n", decrypted) + fmt.Println("================") + } +} diff --git a/Java/EonaCatCipher.java b/Java/EonaCatCipher.java new file mode 100644 index 0000000..9e313a9 --- /dev/null +++ b/Java/EonaCatCipher.java @@ -0,0 +1,324 @@ +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Base64; + +public class EonaCatCipher implements AutoCloseable { + private static final int DEFAULT_SALT_SIZE = 2048; // Salt size for key derivation + private static final int DEFAULT_IV_SIZE = 2048; // IV size (16384 bits) + private static final int DEFAULT_KEY_SIZE = 2048; // Key size (16384 bits) + private static final int DEFAULT_ROUNDS = 2048; // Rounds + private static final int DEFAULT_BLOCK_SIZE = 8192; // 8kb + private static final int HMAC_KEY_SIZE = 32; // Key size for HMAC (256 bits) + + private final byte[] derivedKey; // Derived encryption key + private final byte[] hmacKey; // HMAC key + private final int ivSize; // IV size + private final int keySize; // Key size + private final int rounds; // Number of rounds for key derivation + private final int blockSize; // The size of the block that is created + +/* + * EonaCatCipher - Because security is key! + * + * Copyright (c) 2024 EonaCat (Jeroen Saey) + * + * https://eonacat.com/license + * + * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + * OF SOFTWARE BY EONACAT (JEROEN SAEY) + * + * This software is provided "as is", without any express or implied warranty. + * In no event shall the authors or copyright holders be liable for any claim, + * damages or other liability, whether in an action of contract, tort or otherwise, + * arising from, out of or in connection with the software or the use or other + * dealings in the software. + * + * You may use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. The software must not be used for any unlawful purpose. + * + * For any inquiries, please contact: eonacat@gmail.com + */ + + public EonaCatCipher(String password, int saltSize, int ivSize, int keySize, int rounds, int blockSize) { + if (password == null || password.isEmpty()) { + throw new IllegalArgumentException("Password cannot be null or empty."); + } + + this.ivSize = ivSize; + this.keySize = keySize; + this.rounds = rounds; + this.blockSize = blockSize; + + // Derive encryption key and HMAC key + var keys = deriveKeyAndHMAC(password, saltSize); + this.derivedKey = keys[0]; + this.hmacKey = keys[1]; + } + + private static byte[] generateRandomBytes(int size) { + byte[] randomBytes = new byte[size]; + new SecureRandom().nextBytes(randomBytes); + return randomBytes; + } + + private byte[][] deriveKeyAndHMAC(String password, int saltSize) { + byte[] salt = generateRandomBytes(saltSize); + byte[] encryptionKey = PBKDF2(password, salt, keySize, rounds); + + // Derive separate key for HMAC + byte[] hmacKey = PBKDF2(password, salt, HMAC_KEY_SIZE, rounds); + + byte[] keyWithSalt = new byte[saltSize + keySize]; + System.arraycopy(salt, 0, keyWithSalt, 0, saltSize); + System.arraycopy(encryptionKey, 0, keyWithSalt, saltSize, keySize); + + return new byte[][]{keyWithSalt, hmacKey}; + } + + private static byte[] PBKDF2(String password, byte[] salt, int keyLength, int iterations) { + try { + Mac mac = Mac.getInstance("HmacSHA512"); + mac.init(new SecretKeySpec(password.getBytes(), "HmacSHA512")); + + int hashLength = mac.getMacLength(); + int requiredBytes = keyLength; + int blocksNeeded = (int) Math.ceil((double) requiredBytes / hashLength); + + byte[] derivedKey = new byte[requiredBytes]; + byte[] block = new byte[hashLength]; + + for (int blockIndex = 1; blockIndex <= blocksNeeded; blockIndex++) { + // Step 1: F(blockIndex) + byte[] currentBlock = new byte[salt.length + 4]; + System.arraycopy(salt, 0, currentBlock, 0, salt.length); + System.arraycopy(intToBytes(blockIndex), 0, currentBlock, salt.length, 4); + + // Step 2: U1 = HMAC(password, salt + blockIndex) + byte[] u = mac.doFinal(currentBlock); + System.arraycopy(u, 0, block, 0, hashLength); + + // Step 3: Derived key starts with U1 + System.arraycopy(u, 0, derivedKey, (blockIndex - 1) * hashLength, Math.min(hashLength, requiredBytes)); + + // Step 4: Iterations + for (int iteration = 1; iteration < iterations; iteration++) { + // U2 = HMAC(password, U1) + u = mac.doFinal(u); + + // Step 5: XOR U2 with previous result + for (int i = 0; i < hashLength; i++) { + block[i] ^= u[i]; + } + + // Step 6: Append result to derived key + System.arraycopy(block, 0, derivedKey, (blockIndex - 1) * hashLength, Math.min(hashLength, requiredBytes)); + } + } + + return derivedKey; + } catch (Exception e) { + throw new RuntimeException("EonaCatCipher: Error during PBKDF2 processing", e); + } + } + + public byte[] encrypt(String plaintext) { + byte[] iv = generateRandomBytes(ivSize); + byte[] plaintextBytes = plaintext.getBytes(); + byte[] ciphertext = new byte[plaintextBytes.length]; + + try (EonaCatCrypto cipher = new EonaCatCrypto(derivedKey, iv, blockSize, rounds)) { + cipher.generate(plaintextBytes, ciphertext, true); + } + + // Combine IV and ciphertext + byte[] result = new byte[ivSize + ciphertext.length]; + System.arraycopy(iv, 0, result, 0, ivSize); + System.arraycopy(ciphertext, 0, result, ivSize, ciphertext.length); + + // Generate HMAC for integrity check + byte[] hmac = generateHMAC(result); + + // Combine result and HMAC + byte[] finalResult = new byte[result.length + hmac.length]; + System.arraycopy(result, 0, finalResult, 0, result.length); + System.arraycopy(hmac, 0, finalResult, result.length, hmac.length); + + return finalResult; + } + + public String decrypt(byte[] ciphertextWithHMAC) { + int hmacOffset = ciphertextWithHMAC.length - HMAC_KEY_SIZE; + + // Separate HMAC from the ciphertext + byte[] providedHMAC = new byte[HMAC_KEY_SIZE]; + System.arraycopy(ciphertextWithHMAC, hmacOffset, providedHMAC, 0, HMAC_KEY_SIZE); + + byte[] ciphertext = new byte[hmacOffset]; + System.arraycopy(ciphertextWithHMAC, 0, ciphertext, 0, hmacOffset); + + // Verify HMAC before decrypting + byte[] calculatedHMAC = generateHMAC(ciphertext); + if (!Arrays.equals(providedHMAC, calculatedHMAC)) { + throw new RuntimeException("EonaCatCipher: HMAC validation failed. Data may have been tampered with."); + } + + // Extract IV + byte[] iv = new byte[ivSize]; + System.arraycopy(ciphertext, 0, iv, 0, ivSize); + + // Extract encrypted data + byte[] encryptedData = new byte[ciphertext.length - ivSize]; + System.arraycopy(ciphertext, ivSize, encryptedData, 0, encryptedData.length); + + // Decrypt + byte[] decryptedData = new byte[encryptedData.length]; + try (EonaCatCrypto cipher = new EonaCatCrypto(derivedKey, iv, blockSize, rounds)) { + cipher.generate(encryptedData, decryptedData, false); + } + + return new String(decryptedData); + } + + private byte[] generateHMAC(byte[] data) { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(hmacKey, "HmacSHA256")); + return mac.doFinal(data); + } catch (Exception e) { + throw new RuntimeException("EonaCatCipher: Error generating HMAC", e); + } + } + + private static byte[] intToBytes(int value) { + return new byte[]{ + (byte) (value >> 24), + (byte) (value >> 16), + (byte) (value >> 8), + (byte) value + }; + } + + @Override + public void close() { + if (derivedKey != null) { + Arrays.fill(derivedKey, (byte) 0); + } + if (hmacKey != null) { + Arrays.fill(hmacKey, (byte) 0); + } + } + + private static class EonaCatCrypto implements AutoCloseable { + private static final long SECRET_SAUCE = 0x5DEECE66D; + private static final long UNSIGNED_INT = 0xFFFFFFFF; + private final int blockSize; + private final int rounds; + private final long[] state; + private final long[] key; + private final long[] nonce; + private long blockCounter; + + public EonaCatCrypto(byte[] keyWithSalt, byte[] nonce, int blockSize, int rounds) { + this.rounds = rounds; + this.blockSize = blockSize / 4 > 0 ? blockSize : 128; + + this.key = new long[keyWithSalt.length / 8]; + for (int i = 0; i < key.length; i++) { + key[i] = bytesToLong(keyWithSalt, i * 8); + } + + this.nonce = new long[nonce.length / 8]; + for (int i = 0; i < nonce.length / 8; i++) { + nonce[i] = bytesToLong(nonce, i * 8); + } + + this.state = new long[blockSize / 8]; + } + + private void generateBlock(byte[] output) { + // Initialize state using a combined operation + for (int i = 0; i < state.length; i++) { + state[i] = (key[i % key.length] ^ nonce[i % nonce.length]) + (long) i * SECRET_SAUCE; + } + + // Mix the states according to the rounds + for (int round = 0; round < rounds; round++) { + for (int i = 0; i < state.length; i++) { + state[i] = (long) (((int) state[i] + round) ^ (round * SECRET_SAUCE) + (i + blockCounter)); + } + } + + // Output block + for (int i = 0; i < output.length; i++) { + output[i] = (byte) state[i % state.length]; + } + blockCounter++; + } + + public void generate(byte[] input, byte[] output, boolean encrypt) { + int totalBlocks = (input.length + blockSize - 1) / blockSize; + + for (int blockIndex = 0; blockIndex < totalBlocks; blockIndex++) { + int inputOffset = blockIndex * blockSize; + int outputOffset = blockIndex * blockSize; + byte[] block = new byte[blockSize]; + + // Generate a block based on the input + generateBlock(block); + + // Perform XOR for encryption or decryption + for (int i = 0; i < block.length && inputOffset + i < input.length; i++) { + output[outputOffset + i] = (byte) (input[inputOffset + i] ^ block[i]); + } + } + } + + @Override + public void close() { + if (state != null) { + Arrays.fill(state, 0); + } + } + + private long bytesToLong(byte[] bytes, int offset) { + return ((long) bytes[offset] & UNSIGNED_INT) << 56 | + ((long) bytes[offset + 1] & UNSIGNED_INT) << 48 | + ((long) bytes[offset + 2] & UNSIGNED_INT) << 40 | + ((long) bytes[offset + 3] & UNSIGNED_INT) << 32 | + ((long) bytes[offset + 4] & UNSIGNED_INT) << 24 | + ((long) bytes[offset + 5] & UNSIGNED_INT) << 16 | + ((long) bytes[offset + 6] & UNSIGNED_INT) << 8 | + ((long) bytes[offset + 7] & UNSIGNED_INT); + } + } + + public static void main(String[] args) { + String password = "securePassword123!@#$"; + String plaintext = "Thank you for using EonaCatCipher!"; + + System.out.println("Encrypting '" + plaintext + "' with password '" + password + "' (we do this 5 times)"); + System.out.println("================"); + + for (int i = 0; i < 5; i++) { + System.out.println("Encryption round " + (i + 1) + ": "); + System.out.println("================"); + + try (EonaCatCipher cipher = new EonaCatCipher(password, DEFAULT_SALT_SIZE, DEFAULT_IV_SIZE, DEFAULT_KEY_SIZE, DEFAULT_ROUNDS, DEFAULT_BLOCK_SIZE)) { + byte[] encrypted = cipher.encrypt(plaintext); + System.out.println("Encrypted (byte array): " + Base64.getEncoder().encodeToString(encrypted)); + + String decrypted = cipher.decrypt(encrypted); + System.out.println("Decrypted: " + decrypted); + System.out.println("================"); + } + } + } +} diff --git a/Java/readme.txt b/Java/readme.txt new file mode 100644 index 0000000..ca7822a --- /dev/null +++ b/Java/readme.txt @@ -0,0 +1,2 @@ +javac EonaCatCipher.java +java EonaCatCipher diff --git a/Javascript/EonaCatCipher.js b/Javascript/EonaCatCipher.js new file mode 100644 index 0000000..4a3ea71 --- /dev/null +++ b/Javascript/EonaCatCipher.js @@ -0,0 +1,268 @@ +class EonaCatCipher { + constructor(password, saltSize = 2048, ivSize = 2048, keySize = 2048, rounds = 2048, blockSize = 8192) { + if (!password) { + throw new Error("Password cannot be null or empty."); + } + + this.ivSize = ivSize; + this.keySize = keySize; + this.rounds = rounds; + this.blockSize = blockSize; + + // Derive encryption key and HMAC key + [this.derivedKey, this.hmacKey] = this.deriveKeyAndHMAC(password, saltSize); + } + +/* + * EonaCatCipher - Because security is key! + * + * Copyright (c) 2024 EonaCat (Jeroen Saey) + * + * https://eonacat.com/license + * + * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + * OF SOFTWARE BY EONACAT (JEROEN SAEY) + * + * This software is provided "as is", without any express or implied warranty. + * In no event shall the authors or copyright holders be liable for any claim, + * damages or other liability, whether in an action of contract, tort or otherwise, + * arising from, out of or in connection with the software or the use or other + * dealings in the software. + * + * You may use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. The software must not be used for any unlawful purpose. + * + * For any inquiries, please contact: eonacat@gmail.com + */ + + static generateRandomBytes(size) { + const randomBytes = new Uint8Array(size); + crypto.getRandomValues(randomBytes); + return randomBytes; + } + + deriveKeyAndHMAC(password, saltSize) { + const salt = EonaCatCipher.generateRandomBytes(saltSize); + const encryptionKey = this.pbkdf2(password, salt, this.keySize, this.rounds); + + // Derive separate key for HMAC + const hmacKey = this.pbkdf2(password, salt, 32, this.rounds); + + const keyWithSalt = new Uint8Array(saltSize + this.keySize); + keyWithSalt.set(salt, 0); + keyWithSalt.set(encryptionKey, saltSize); + + return [keyWithSalt, hmacKey]; + } + + pbkdf2(password, salt, keyLength, iterations) { + const hmac = new Uint8Array(64); // HMAC length (SHA512) + const hashLength = hmac.length; + + const blocksNeeded = Math.ceil(keyLength / hashLength); + const derivedKey = new Uint8Array(keyLength); + + for (let blockIndex = 1; blockIndex <= blocksNeeded; blockIndex++) { + const currentBlock = new Uint8Array(salt.length + 4); + currentBlock.set(salt, 0); + currentBlock.set(new Uint8Array(new Uint32Array([blockIndex]).buffer), salt.length); + + let u = this.hmacSha512(password, currentBlock); + const block = new Uint8Array(hashLength); + block.set(u); + + // Step 4: Derived key starts with U1 + derivedKey.set(u.subarray(0, Math.min(hashLength, keyLength)), (blockIndex - 1) * hashLength); + + // Step 4: Iterations + for (let iteration = 1; iteration < iterations; iteration++) { + u = this.hmacSha512(password, u); + + // Step 5: XOR U2 with previous result + for (let i = 0; i < hashLength; i++) { + block[i] ^= u[i]; + } + + // Step 6: Append result to derived key + derivedKey.set(block.subarray(0, Math.min(hashLength, keyLength)), (blockIndex - 1) * hashLength); + } + } + + return derivedKey; + } + + encrypt(plaintext) { + const iv = EonaCatCipher.generateRandomBytes(this.ivSize); + const plaintextBytes = new TextEncoder().encode(plaintext); + const ciphertext = new Uint8Array(plaintextBytes.length); + + const cipher = new EonaCatCrypto(this.derivedKey, iv, this.blockSize, this.rounds); + cipher.generate(plaintextBytes, ciphertext, true); + + // Combine IV and ciphertext + const result = new Uint8Array(this.ivSize + ciphertext.length); + result.set(iv, 0); + result.set(ciphertext, this.ivSize); + + // Generate HMAC for integrity check + const hmac = this.generateHMAC(result); + + // Combine result and HMAC + const finalResult = new Uint8Array(result.length + hmac.length); + finalResult.set(result, 0); + finalResult.set(hmac, result.length); + + return finalResult; + } + + decrypt(ciphertextWithHMAC) { + const hmacOffset = ciphertextWithHMAC.length - 32; // HMAC length (SHA256) + + // Separate HMAC from the ciphertext + const providedHMAC = ciphertextWithHMAC.subarray(hmacOffset, ciphertextWithHMAC.length); + + const ciphertext = ciphertextWithHMAC.subarray(0, hmacOffset); + + // Verify HMAC before decrypting + const calculatedHMAC = this.generateHMAC(ciphertext); + if (!this.areEqual(providedHMAC, calculatedHMAC)) { + throw new Error("EonaCatCipher: HMAC validation failed. Data may have been tampered with."); + } + + // Extract IV + const iv = ciphertext.subarray(0, this.ivSize); + + // Extract encrypted data + const encryptedData = ciphertext.subarray(this.ivSize); + + // Decrypt + const decryptedData = new Uint8Array(encryptedData.length); + const cipher = new EonaCatCrypto(this.derivedKey, iv, this.blockSize, this.rounds); + cipher.generate(encryptedData, decryptedData, false); + + return new TextDecoder().decode(decryptedData); + } + + generateHMAC(data) { + return this.hmacSha256(this.hmacKey, data); + } + + hmacSha512(key, data) { + const hmac = new HMAC(key, "SHA-512"); + return hmac.compute(data); + } + + hmacSha256(key, data) { + const hmac = new HMAC(key, "SHA-256"); + return hmac.compute(data); + } + + areEqual(a, b) { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; + } +} + +class EonaCatCrypto { + constructor(keyWithSalt, nonce, blockSize, rounds) { + this.rounds = rounds; + this.blockSize = blockSize > 0 ? blockSize : 128; + + this.key = new Uint32Array(keyWithSalt.length / 4); + this.key.set(new Uint32Array(keyWithSalt.buffer)); + + this.nonce = new Uint32Array(nonce.length / 4); + this.nonce.set(new Uint32Array(nonce.buffer)); + + this.state = new BigUint64Array(this.blockSize / 4); + this.blockCounter = 0; + } + + generateBlock(output) { + // Initialize state using a combined operation + for (let i = 0; i < this.state.length; i++) { + this.state[i] = (this.key[i % this.key.length] ^ this.nonce[i % this.nonce.length]) + BigInt(i) * BigInt(0x5DEECE66D); + } + + // Mix the states according to the rounds + for (let round = 0; round < this.rounds; round++) { + for (let i = 0; i < this.state.length; i++) { + this.state[i] = (this.state[i] + BigInt(round)) ^ (BigInt(round) * BigInt(0x5DEECE66D)) + BigInt(i + this.blockCounter); + } + } + + // Output block + output.set(new Uint8Array(this.state.buffer)); + this.blockCounter++; + } + + generate(input, output, encrypt) { + const totalBlocks = Math.ceil(input.length / this.blockSize); + + for (let blockIndex = 0; blockIndex < totalBlocks; blockIndex++) { + const inputOffset = blockIndex * this.blockSize; + const outputOffset = blockIndex * this.blockSize; + const block = new Uint8Array(this.blockSize); + + // Generate a block based on the input + this.generateBlock(block); + + // Perform XOR for encryption or decryption + for (let i = 0; i < block.length && inputOffset + i < input.length; i++) { + output[outputOffset + i] = input[inputOffset + i] ^ block[i]; + } + } + } +} + +class HMAC { + constructor(key, algorithm) { + this.key = key; + this.algorithm = algorithm; + } + + compute(data) { + const cryptoKey = crypto.subtle.importKey( + "raw", + this.key, + { name: "HMAC", hash: { name: this.algorithm } }, + false, + ["sign", "verify"] + ); + + return crypto.subtle.sign("HMAC", cryptoKey, data).then(signature => new Uint8Array(signature)); + } +} + +// Usage Example +(async () => { + const password = "securePassword123!@#$"; + const plaintext = "Thank you for using EonaCatCipher!"; + + console.log(`Encrypting '${plaintext}' with password '${password}' (we do this 5 times)`); + console.log("================"); + + for (let i = 0; i < 5; i++) { + console.log(`Encryption round ${i + 1}: `); + console.log("================"); + + const cipher = new EonaCatCipher(password); + const encrypted = await cipher.encrypt(plaintext); + + console.log("Encrypted (byte array): " + Array.from(encrypted).join(', ')); + + const decrypted = await cipher.decrypt(encrypted); + + console.log("Decrypted: " + decrypted); + console.log("================"); + } +})(); diff --git a/LICENSE b/LICENSE index 23510b6..48ecd06 100644 --- a/LICENSE +++ b/LICENSE @@ -1,73 +1,203 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + OF SOFTWARE BY EONACAT (JEROEN SAEY) -1. Definitions. + 1. Definitions. -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: - (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and - (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and - (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and - (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. - You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. -END OF TERMS AND CONDITIONS + END OF TERMS AND CONDITIONS -APPENDIX: How to apply the Apache License to your work. + APPENDIX: How to apply the Apache License to your work. -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. -Copyright 2024 EonaCat + Copyright [yyyy] [name of copyright owner] -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/PHP/EonaCatCipher.php b/PHP/EonaCatCipher.php new file mode 100644 index 0000000..5ecadea --- /dev/null +++ b/PHP/EonaCatCipher.php @@ -0,0 +1,235 @@ +ivSize = $ivSize; + $this->keySize = $keySize; + $this->rounds = $rounds; + $this->blockSize = $blockSize; + + // Derive encryption key and HMAC key + list($this->derivedKey, $this->hmacKey) = $this->deriveKeyAndHMAC($password, $saltSize); + } + + private static function generateRandomBytes($size) { + return random_bytes($size); + } + + private function deriveKeyAndHMAC($password, $saltSize) { + $salt = self::generateRandomBytes($saltSize); + $encryptionKey = $this->pbkdf2($password, $salt, $this->keySize, $this->rounds); + + // Derive separate key for HMAC + $hmacKey = $this->pbkdf2($password, $salt, self::HMAC_KEY_SIZE, $this->rounds); + + $keyWithSalt = $salt . $encryptionKey; + + return [$keyWithSalt, $hmacKey]; + } + + private function pbkdf2($password, $salt, $keyLength, $iterations) { + $hmac = hash_hmac('sha512', '', $password, true); + $hashLength = strlen($hmac); + $requiredBytes = $keyLength; + $blocksNeeded = ceil($requiredBytes / $hashLength); + + $derivedKey = ''; + $block = ''; + + for ($blockIndex = 1; $blockIndex <= $blocksNeeded; $blockIndex++) { + $currentBlock = $salt . pack('N', $blockIndex); + $u = hash_hmac('sha512', $currentBlock, $password, true); + $block = $u; + $derivedKey .= $u; + + for ($iteration = 1; $iteration < $iterations; $iteration++) { + $u = hash_hmac('sha512', $u, $password, true); + for ($i = 0; $i < $hashLength; $i++) { + $block[$i] ^= $u[$i]; + } + $derivedKey .= $block; + } + } + + return substr($derivedKey, 0, $requiredBytes); + } + + public function encrypt($plaintext) { + $iv = self::generateRandomBytes($this->ivSize); + $plaintextBytes = $plaintext; + $ciphertext = str_repeat("\0", strlen($plaintextBytes)); + + $cipher = new EonaCatCrypto($this->derivedKey, $iv, $this->blockSize, $this->rounds); + $cipher->generate($plaintextBytes, $ciphertext, true); + + // Combine IV and ciphertext + $result = $iv . $ciphertext; + + // Generate HMAC for integrity check + $hmac = $this->generateHMAC($result); + + // Combine result and HMAC + return $result . $hmac; + } + + public function decrypt($ciphertextWithHMAC) { + $hmacOffset = strlen($ciphertextWithHMAC) - self::HMAC_KEY_SIZE; + + // Separate HMAC from the ciphertext + $providedHMAC = substr($ciphertextWithHMAC, $hmacOffset); + $ciphertext = substr($ciphertextWithHMAC, 0, $hmacOffset); + + // Verify HMAC before decrypting + $calculatedHMAC = $this->generateHMAC($ciphertext); + if (!hash_equals($providedHMAC, $calculatedHMAC)) { + throw new Exception("EonaCatCipher: HMAC validation failed. Data may have been tampered with."); + } + + // Extract IV + $iv = substr($ciphertext, 0, $this->ivSize); + + // Extract encrypted data + $encryptedData = substr($ciphertext, $this->ivSize); + + // Decrypt + $decryptedData = str_repeat("\0", strlen($encryptedData)); + $cipher = new EonaCatCrypto($this->derivedKey, $iv, $this->blockSize, $this->rounds); + $cipher->generate($encryptedData, $decryptedData, false); + + return $decryptedData; + } + + private function generateHMAC($data) { + return hash_hmac('sha256', $data, $this->hmacKey, true); + } + + public static function main() { + $password = "securePassword123!@#$"; + $plaintext = "Thank you for using EonaCatCipher!"; + + echo "Encrypting '$plaintext' with password '$password' (we do this 5 times)\n"; + echo "================\n"; + + for ($i = 0; $i < 5; $i++) { + echo "Encryption round " . ($i + 1) . ": \n"; + echo "================\n"; + + $cipher = new EonaCatCipher($password); + $encrypted = $cipher->encrypt($plaintext); + + echo "Encrypted (byte array): " . bin2hex($encrypted) . "\n"; + + $decrypted = $cipher->decrypt($encrypted); + + echo "Decrypted: " . $decrypted . "\n"; + echo "================\n"; + } + } +} + +class EonaCatCrypto { + private const SECRET_SAUCE = 0x5DEECE66D; + private const UNSIGNED_INT = 0xFFFFFFFF; + private $blockSize; + private $rounds; + private $state; + private $key; + private $nonce; + private $blockCounter; + + public function __construct($keyWithSalt, $nonce, $blockSize, $rounds) { + $this->rounds = $rounds; + $this->blockSize = $blockSize / 4 > 0 ? $blockSize : 128; + + $this->key = array_values(unpack("N*", $keyWithSalt)); + $this->nonce = array_values(unpack("N*", $nonce)); + $this->state = array_fill(0, $this->blockSize / 4, 0); + $this->blockCounter = 0; + } + + private function generateBlock(&$output) { + // Initialize state using a combined operation + for ($i = 0; $i < count($this->state); $i++) { + $this->state[$i] = ($this->key[$i % count($this->key)] ^ $this->nonce[$i % count($this->nonce)]) + ($i * self::SECRET_SAUCE); + } + + // Mix the states according to the rounds + for ($round = 0; $round < $this->rounds; $round++) { + for ($i = 0; $i < count($this->state); $i++) { + $this->state[$i] = (($this->state[$i] + $round) ^ ($round * self::SECRET_SAUCE) + ($i + $this->blockCounter)) & self::UNSIGNED_INT; + } + } + + // Output block + $output = pack("N*", ...$this->state); + $this->blockCounter++; + } + + public function generate($input, &$output, $encrypt) { + $totalBlocks = ceil(strlen($input) / $this->blockSize); + + for ($blockIndex = 0; $blockIndex < $totalBlocks; $blockIndex++) { + $inputOffset = $blockIndex * $this->blockSize; + $outputOffset = $blockIndex * $this->blockSize; + $block = str_repeat("\0", $this->blockSize); + + // Generate a block based on the input + $this->generateBlock($block); + + // Perform XOR for encryption or decryption + for ($i = 0; $i < strlen($block) && $inputOffset + $i < strlen($input); $i++) { + $output[$outputOffset + $i] = $input[$inputOffset + $i] ^ $block[$i]; + } + } + } +} + +EonaCatCipher::main(); + +?> diff --git a/Python/EonaCatCipher.py b/Python/EonaCatCipher.py new file mode 100644 index 0000000..d852f55 --- /dev/null +++ b/Python/EonaCatCipher.py @@ -0,0 +1,189 @@ +import os +import hashlib +import hmac +import struct +import base64 + +class EonaCatCipher: + DEFAULT_SALT_SIZE = 2048; # Salt size for key derivation + DEFAULT_IV_SIZE = 2048; # IV size (16384 bits) + DEFAULT_KEY_SIZE = 2048; # Key size (16384 bits) + DEFAULT_ROUNDS = 2048; # Rounds + DEFAULT_BLOCK_SIZE = 8192; # 8kb + HMAC_KEY_SIZE = 32; # Key size for HMAC (256 bits) + +#/* +# * EonaCatCipher - Because security is key! +# * +# * Copyright (c) 2024 EonaCat (Jeroen Saey) +# * +# * https://eonacat.com/license +# * +# * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +# * OF SOFTWARE BY EONACAT (JEROEN SAEY) +# * +# * This software is provided "as is", without any express or implied warranty. +# * In no event shall the authors or copyright holders be liable for any claim, +# * damages or other liability, whether in an action of contract, tort or otherwise, +# * arising from, out of or in connection with the software or the use or other +# * dealings in the software. +# * +# * You may use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# * copies of the Software, and permit persons to whom the Software is furnished +# * to do so, subject to the following conditions: +# * +# * 1. The above copyright notice and this permission notice shall be included in +# * all copies or substantial portions of the Software. +# * +# * 2. The software must not be used for any unlawful purpose. +# * +# * For any inquiries, please contact: eonacat@gmail.com +# */ + + def __init__(self, password, salt_size=None, iv_size=None, key_size=None, rounds=None, block_size=None): + if not password: + raise ValueError("EonaCatCipher: Password cannot be null or empty.") + + self.salt_size = salt_size if salt_size is not None else self.DEFAULT_SALT_SIZE + self.iv_size = iv_size if iv_size is not None else self.DEFAULT_IV_SIZE // 8 # Convert bits to bytes + self.key_size = key_size if key_size is not None else self.DEFAULT_KEY_SIZE // 8 # Convert bits to bytes + self.rounds = rounds if rounds is not None else self.DEFAULT_ROUNDS + self.block_size = block_size if block_size is not None else self.DEFAULT_BLOCK_SIZE // 8 # Convert bits to bytes + + # Derive encryption key and HMAC key + self.derived_key, self.hmac_key = self.derive_key_and_hmac(password) + + @staticmethod + def generate_random_bytes(size): + return os.urandom(size) + + def derive_key_and_hmac(self, password): + salt = self.generate_random_bytes(self.salt_size) + encryption_key = self.pbkdf2(password, salt, self.key_size, self.rounds) + + # Derive separate key for HMAC + hmac_key = self.pbkdf2(password, salt, self.HMAC_KEY_SIZE, self.rounds) + + key_with_salt = salt + encryption_key + + return key_with_salt, hmac_key + + @staticmethod + def pbkdf2(password, salt, key_length, iterations): + # PBKDF2 using HMAC-SHA512 + hmac_sha512 = hashlib.pbkdf2_hmac('sha512', password.encode(), salt, iterations, dklen=key_length) + return hmac_sha512 + + def encrypt(self, plaintext): + iv = self.generate_random_bytes(self.iv_size) + plaintext_bytes = plaintext.encode() + + ciphertext = bytearray(len(plaintext_bytes)) + + # Generate cipher + cipher = EonaCatCrypto(self.derived_key, iv, self.block_size, self.rounds) + cipher.generate(plaintext_bytes, ciphertext, True) + + # Combine IV and ciphertext + result = iv + ciphertext + + # Generate HMAC for integrity check + hmac = self.generate_hmac(result) + + # Combine result and HMAC + final_result = result + hmac + return final_result + + def decrypt(self, ciphertext_with_hmac): + hmac_offset = len(ciphertext_with_hmac) - self.HMAC_KEY_SIZE + + # Separate HMAC from the ciphertext + provided_hmac = ciphertext_with_hmac[hmac_offset:] + ciphertext = ciphertext_with_hmac[:hmac_offset] + + # Verify HMAC before decrypting + calculated_hmac = self.generate_hmac(ciphertext) + if not self.are_equal(provided_hmac, calculated_hmac): + raise ValueError("EonaCatCipher: HMAC validation failed. Data may have been tampered with.") + + # Extract IV + iv = ciphertext[:self.iv_size] + encrypted_data = ciphertext[self.iv_size:] + + # Decrypt + decrypted_data = bytearray(len(encrypted_data)) + cipher = EonaCatCrypto(self.derived_key, iv, self.block_size, self.rounds) + cipher.generate(encrypted_data, decrypted_data, False) + + return decrypted_data.decode() + + def generate_hmac(self, data): + return hmac.new(self.hmac_key, data, hashlib.sha256).digest() + + @staticmethod + def are_equal(a, b): + return hmac.compare_digest(a, b) + +class EonaCatCrypto: + SECRET_SAUCE = 0x5DEECE66D + + def __init__(self, key_with_salt, nonce, block_size, rounds): + self.rounds = rounds + self.block_size = block_size // 4 > 0 and block_size // 4 or 128 + + self.key = list(struct.unpack(f'>{len(key_with_salt) // 4}I', key_with_salt)) + self.nonce = list(struct.unpack(f'>{len(nonce) // 4}I', nonce)) + self.state = [0] * (self.block_size // 4) + self.block_counter = 0 + + def generate_block(self, output): + # Initialize state using a combined operation + for i in range(len(self.state)): + self.state[i] = (self.key[i % len(self.key)] ^ self.nonce[i % len(self.nonce)]) + (i * self.SECRET_SAUCE) + + # Mix the states according to the rounds + for round in range(self.rounds): + for i in range(len(self.state)): + self.state[i] = (self.state[i] + round) ^ (round * self.SECRET_SAUCE) + (i + self.block_counter) + + # Output block + output.extend(self.state) + self.block_counter += 1 + + def generate(self, input_data, output, encrypt): + total_blocks = (len(input_data) + self.block_size - 1) // self.block_size + + for block_index in range(total_blocks): + input_offset = block_index * self.block_size + block = bytearray(self.block_size) + + # Generate a block based on the input + self.generate_block(block) + + # Perform XOR for encryption or decryption + for i in range(len(block)): + if input_offset + i < len(input_data): + output[input_offset + i] = input_data[input_offset + i] ^ block[i] + +def main(): + password = "securePassword123!@#$" + plaintext = "Thank you for using EonaCatCipher!" + + print(f"Encrypting '{plaintext}' with password '{password}' (we do this 5 times)") + print("================") + + for i in range(5): + print(f"Encryption round {i + 1}: ") + print("================") + + cipher = EonaCatCipher(password) + encrypted = cipher.encrypt(plaintext) + + print("Encrypted (byte array):", [b for b in encrypted]) + + decrypted = cipher.decrypt(encrypted) + print("Decrypted:", decrypted) + print("================") + +if __name__ == "__main__": + main() diff --git a/README.md b/README.md index fd84cff..7ae9399 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ # EonaCatCipher +EonaCat Cipher Security -EonaCat Cipher Security \ No newline at end of file +Security algorithm for: + +C# +C++ +Delphi +Go +Java +JavaScript +PHP +Python +Ruby +Rust \ No newline at end of file diff --git a/Ruby/EonaCatCipher.ruby b/Ruby/EonaCatCipher.ruby new file mode 100644 index 0000000..b9a4a1c --- /dev/null +++ b/Ruby/EonaCatCipher.ruby @@ -0,0 +1,224 @@ +require 'openssl' +require 'securerandom' + +class EonaCatCipher + DEFAULT_SALT_SIZE = 2048; # Salt size for key derivation + DEFAULT_IV_SIZE = 2048; # IV size (16384 bits) + DEFAULT_KEY_SIZE = 2048; # Key size (16384 bits) + DEFAULT_ROUNDS = 2048; # Rounds + DEFAULT_BLOCK_SIZE = 8192; # 8kb + HMAC_KEY_SIZE = 32; # Key size for HMAC (256 bits) + +#/* +# * EonaCatCipher - Because security is key! +# * +# * Copyright (c) 2024 EonaCat (Jeroen Saey) +# * +# * https://eonacat.com/license +# * +# * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +# * OF SOFTWARE BY EONACAT (JEROEN SAEY) +# * +# * This software is provided "as is", without any express or implied warranty. +# * In no event shall the authors or copyright holders be liable for any claim, +# * damages or other liability, whether in an action of contract, tort or otherwise, +# * arising from, out of or in connection with the software or the use or other +# * dealings in the software. +# * +# * You may use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# * copies of the Software, and permit persons to whom the Software is furnished +# * to do so, subject to the following conditions: +# * +# * 1. The above copyright notice and this permission notice shall be included in +# * all copies or substantial portions of the Software. +# * +# * 2. The software must not be used for any unlawful purpose. +# * +# * For any inquiries, please contact: eonacat@gmail.com +# */ + + def initialize(password, salt_size = DEFAULT_SALT_SIZE, iv_size = DEFAULT_IV_SIZE, key_size = DEFAULT_KEY_SIZE, rounds = DEFAULT_ROUNDS, block_size = DEFAULT_BLOCK_SIZE) + raise ArgumentError, 'EonaCatCipher: Password cannot be null or empty.' if password.nil? || password.empty? + + @iv_size = iv_size + @key_size = key_size + @rounds = rounds + @block_size = block_size + + # Derive encryption key and HMAC key + @derived_key, @hmac_key = derive_key_and_hmac(password, salt_size) + end + + def encrypt(plaintext) + iv = generate_random_bytes(@iv_size) + plaintext_bytes = plaintext.encode('UTF-8') + ciphertext = Array.new(plaintext_bytes.bytesize, 0) + + cipher = EonaCatCrypto.new(@derived_key, iv, @block_size, @rounds) + cipher.generate(plaintext_bytes.bytes, ciphertext, true) + + # Combine IV and ciphertext + result = iv + ciphertext.pack('C*') + + # Generate HMAC for integrity check + hmac = generate_hmac(result) + + # Combine result and HMAC + final_result = result + hmac + final_result + end + + def decrypt(ciphertext_with_hmac) + hmac_offset = ciphertext_with_hmac.bytesize - HMAC_KEY_SIZE + + # Separate HMAC from the ciphertext + provided_hmac = ciphertext_with_hmac[hmac_offset, HMAC_KEY_SIZE] + ciphertext = ciphertext_with_hmac[0, hmac_offset] + + # Verify HMAC before decrypting + calculated_hmac = generate_hmac(ciphertext) + raise 'EonaCatCipher: HMAC validation failed. Data may have been tampered with.' unless secure_compare(provided_hmac, calculated_hmac) + + # Extract IV + iv = ciphertext[0, @iv_size] + + # Extract encrypted data + encrypted_data = ciphertext[@iv_size, ciphertext.bytesize - @iv_size] + + # Decrypt + decrypted_data = Array.new(encrypted_data.bytesize, 0) + cipher = EonaCatCrypto.new(@derived_key, iv, @block_size, @rounds) + cipher.generate(encrypted_data.bytes, decrypted_data, false) + + decrypted_data.pack('C*').force_encoding('UTF-8') + end + + private + + def generate_random_bytes(size) + SecureRandom.random_bytes(size) + end + + def derive_key_and_hmac(password, salt_size) + salt = generate_random_bytes(salt_size) + encryption_key = pbkdf2(password, salt, @key_size, @rounds) + + # Derive separate key for HMAC + hmac_key = pbkdf2(password, salt, HMAC_KEY_SIZE, @rounds) + + key_with_salt = salt + encryption_key + [key_with_salt, hmac_key] + end + + def pbkdf2(password, salt, key_length, iterations) + hmac = OpenSSL::Digest::SHA512.new + derived_key = [] + + block_size = hmac.digest_length + blocks_needed = (key_length.to_f / block_size).ceil + + blocks_needed.times do |block_index| + block_index += 1 # PBKDF2 block indexing starts at 1 + u = OpenSSL::HMAC.digest(hmac, password, salt + [block_index].pack('N')) + derived_key.concat(u.bytes) + + (iterations - 1).times do + u = OpenSSL::HMAC.digest(hmac, password, u) + derived_key[-block_size, block_size].each_index do |i| + derived_key[-block_size + i] ^= u.bytes[i] + end + end + end + + derived_key[0, key_length] + end + + def generate_hmac(data) + hmac = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, @hmac_key, data) + hmac.bytes + end + + def secure_compare(a, b) + return false if a.bytesize != b.bytesize + + # Use `each_byte` for a timing-safe comparison + res = 0 + a.each_byte.with_index do |byte, i| + res |= byte ^ b.getbyte(i) + end + res == 0 + end +end + +class EonaCatCrypto + SECRET_SAUCE = 0x5DEECE66D + + def initialize(key_with_salt, nonce, block_size, rounds) + @rounds = rounds + @block_size = block_size / 4 > 0 ? block_size : 128 + + @key = key_with_salt.unpack('N*') + @nonce = nonce.unpack('N*') + @state = Array.new(@block_size / 4, 0) + @block_counter = 0 + end + + def generate(input, output, encrypt) + total_blocks = (input.length + @block_size - 1) / @block_size + + total_blocks.times do |block_index| + input_offset = block_index * @block_size + output_offset = block_index * @block_size + block = Array.new(@block_size, 0) + + generate_block(block) + + (0...block.size).each do |i| + if input_offset + i < input.size + output[output_offset + i] = input[input_offset + i] ^ block[i] + end + end + end + end + + private + + def generate_block(output) + @state.each_index do |i| + @state[i] = (@key[i % @key.size] ^ @nonce[i % @nonce.size]) + (i * SECRET_SAUCE) + end + + @rounds.times do |round| + @state.each_index do |i| + @state[i] = ((@state[i] + round) ^ (round * SECRET_SAUCE) + (i + @block_counter)).to_i + end + end + + output.replace(@state.pack('Q*')) + @block_counter += 1 + end +end + +# Example Usage +if __FILE__ == $0 + password = "securePassword123!@#$" + plaintext = "Thank you for using EonaCatCipher!" + + puts "Encrypting '#{plaintext}' with password '#{password}' (we do this 5 times)" + puts "================" + + 5.times do |i| + puts "Encryption round #{i + 1}: " + puts "================" + + cipher = EonaCatCipher.new(password) + encrypted = cipher.encrypt(plaintext) + + puts "Encrypted (byte array): #{encrypted.unpack1('H*')}" + + decrypted = cipher.decrypt(encrypted) + + puts "Decrypted: #{decrypted}" + puts "================" + end +end diff --git a/Rust/EonaCatCipher.rust b/Rust/EonaCatCipher.rust new file mode 100644 index 0000000..1d7fcf3 --- /dev/null +++ b/Rust/EonaCatCipher.rust @@ -0,0 +1,261 @@ +use rand::{RngCore, Rng}; +use sha2::{Sha256, Sha512, Digest}; +use hmac::{Hmac, Mac}; +use std::convert::TryInto; + +type HmacSha256 = Hmac; +type HmacSha512 = Hmac; + +const DEFAULT_SALT_SIZE: usize = 256; // Salt size for key derivation +const DEFAULT_IV_SIZE: usize = 256; // IV size (2048 bits) +const DEFAULT_KEY_SIZE: usize = 256; // Key size (2048 bits) +const DEFAULT_ROUNDS: usize = 256; // Rounds +const DEFAULT_BLOCK_SIZE: usize = 1024; // 1kb +const HMAC_KEY_SIZE: usize = 32; // Key size for HMAC (256 bits) + +/* + * EonaCatCipher - Because security is key! + * + * Copyright (c) 2024 EonaCat (Jeroen Saey) + * + * https://eonacat.com/license + * + * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + * OF SOFTWARE BY EONACAT (JEROEN SAEY) + * + * This software is provided "as is", without any express or implied warranty. + * In no event shall the authors or copyright holders be liable for any claim, + * damages or other liability, whether in an action of contract, tort or otherwise, + * arising from, out of or in connection with the software or the use or other + * dealings in the software. + * + * You may use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. The software must not be used for any unlawful purpose. + * + * For any inquiries, please contact: eonacat@gmail.com + */ + +pub struct EonaCatCipher { + derived_key: Vec, // Derived encryption key + hmac_key: Vec, // HMAC key + iv_size: usize, // IV size + key_size: usize, // Key size + rounds: usize, // Number of rounds for key derivation + block_size: usize, // The size of the block that is created +} + +impl EonaCatCipher { + pub fn new(password: &str, salt_size: usize, iv_size: usize, key_size: usize, rounds: usize, block_size: usize) -> Result { + if password.is_empty() { + return Err("EonaCatCipher: Password cannot be null or empty.".to_string()); + } + + let (derived_key, hmac_key) = Self::derive_key_and_hmac(password, salt_size)?; + + Ok(Self { + derived_key, + hmac_key, + iv_size, + key_size, + rounds, + block_size, + }) + } + + fn generate_random_bytes(size: usize) -> Vec { + let mut rng = rand::thread_rng(); + let mut random_bytes = vec![0u8; size]; + rng.fill_bytes(&mut random_bytes); + random_bytes + } + + fn derive_key_and_hmac(password: &str, salt_size: usize) -> Result<(Vec, Vec), String> { + let salt = Self::generate_random_bytes(salt_size); + let encryption_key = Self::pbkdf2(password, &salt, DEFAULT_KEY_SIZE, DEFAULT_ROUNDS)?; + let hmac_key = Self::pbkdf2(password, &salt, HMAC_KEY_SIZE, DEFAULT_ROUNDS)?; + + let mut key_with_salt = vec![0u8; salt.len() + encryption_key.len()]; + key_with_salt[..salt.len()].copy_from_slice(&salt); + key_with_salt[salt.len()..].copy_from_slice(&encryption_key); + + Ok((key_with_salt, hmac_key)) + } + + fn pbkdf2(password: &str, salt: &[u8], key_length: usize, iterations: usize) -> Result, String> { + let mut derived_key = vec![0u8; key_length]; + let hmac = HmacSha512::new_varkey(password.as_bytes()).map_err(|e| e.to_string())?; + let hash_length = hmac.output_size(); + + let blocks_needed = (key_length + hash_length - 1) / hash_length; + + for block_index in 0..blocks_needed { + let mut current_block = Vec::with_capacity(salt.len() + 4); + current_block.extend_from_slice(salt); + current_block.extend_from_slice(&(block_index + 1).to_be_bytes()); + + let mut u = hmac.clone().finalize_reset(¤t_block); + let mut block = u.clone(); + + let derived_key_offset = block_index * hash_length; + let remaining = key_length - derived_key_offset; + + let copy_length = remaining.min(hash_length); + derived_key[derived_key_offset..derived_key_offset + copy_length].copy_from_slice(&u); + + for _ in 1..iterations { + u = hmac.clone().finalize_reset(&u); + for i in 0..hash_length { + block[i] ^= u[i]; + } + let remaining = key_length - derived_key_offset; + + let copy_length = remaining.min(hash_length); + derived_key[derived_key_offset..derived_key_offset + copy_length].copy_from_slice(&block); + } + } + + Ok(derived_key) + } + + pub fn encrypt(&self, plaintext: &str) -> Vec { + let iv = Self::generate_random_bytes(self.iv_size); + let plaintext_bytes = plaintext.as_bytes(); + + let mut ciphertext = vec![0u8; plaintext_bytes.len()]; + + let mut cipher = EonaCatCrypto::new(&self.derived_key, &iv, self.block_size, self.rounds); + cipher.generate(plaintext_bytes, &mut ciphertext, true); + + let mut result = Vec::with_capacity(self.iv_size + ciphertext.len()); + result.extend_from_slice(&iv); + result.extend_from_slice(&ciphertext); + + let hmac = self.generate_hmac(&result); + result.extend_from_slice(&hmac); + + result + } + + pub fn decrypt(&self, ciphertext_with_hmac: &[u8]) -> Result { + let hmac_offset = ciphertext_with_hmac.len() - HMAC_KEY_SIZE; + + let provided_hmac = &ciphertext_with_hmac[hmac_offset..]; + let ciphertext = &ciphertext_with_hmac[..hmac_offset]; + + let calculated_hmac = self.generate_hmac(ciphertext); + if provided_hmac != calculated_hmac.as_slice() { + return Err("EonaCatCipher: HMAC validation failed. Data may have been tampered with.".to_string()); + } + + let iv = &ciphertext[..self.iv_size]; + let encrypted_data = &ciphertext[self.iv_size..]; + + let mut decrypted_data = vec![0u8; encrypted_data.len()]; + let mut cipher = EonaCatCrypto::new(&self.derived_key, iv, self.block_size, self.rounds); + cipher.generate(encrypted_data, &mut decrypted_data, false); + + String::from_utf8(decrypted_data).map_err(|e| e.to_string()) + } + + fn generate_hmac(&self, data: &[u8]) -> Vec { + let mut hmac = HmacSha256::new_varkey(&self.hmac_key).expect("HMAC key should be valid"); + hmac.update(data); + hmac.finalize().into_bytes().to_vec() + } +} + +pub struct EonaCatCrypto { + block_size: usize, + rounds: usize, + state: Vec, + key: Vec, + nonce: Vec, + block_counter: u32, +} + +impl EonaCatCrypto { + pub fn new(key_with_salt: &[u8], nonce: &[u8], block_size: usize, rounds: usize) -> Self { + let key_length = key_with_salt.len() / 4; + let mut key = vec![0u32; key_length]; + key.copy_from_slice(&key_with_salt[..key_length * 4].chunks(4).map(|chunk| { + u32::from_be_bytes(chunk.try_into().unwrap()) + }).collect::>()); + + let nonce_length = nonce.len() / 4; + let mut nonce_arr = vec![0u32; nonce_length]; + nonce_arr.copy_from_slice(&nonce[..nonce_length * 4].chunks(4).map(|chunk| { + u32::from_be_bytes(chunk.try_into().unwrap()) + }).collect::>()); + + Self { + block_size, + rounds, + state: vec![0u64; block_size / 4], + key, + nonce: nonce_arr, + block_counter: 0, + } + } + + fn generate_block(&mut self, output: &mut [u8]) { + for i in 0..self.state.len() { + self.state[i] = (self.key[i % self.key.len()] as u64 ^ self.nonce[i % self.nonce.len()] as u64) + (i as u64 * 0x5DEECE66D); + } + + for round in 0..self.rounds { + for i in 0..self.state.len() { + self.state[i] = (((self.state[i] as i64) + round as i64) ^ ((round as i64) * 0x5DEECE66D as i64) + (i as i64 + self.block_counter as i64)) as u64); + } + } + + output.copy_from_slice(bytemuck::cast_slice(&self.state)); + self.block_counter += 1; + } + + pub fn generate(&mut self, input: &[u8], output: &mut [u8], encrypt: bool) { + let total_blocks = (input.len() + self.block_size - 1) / self.block_size; + + for block_index in 0..total_blocks { + let input_offset = block_index * self.block_size; + let output_offset = block_index * self.block_size; + let mut block = vec![0u8; self.block_size]; + + self.generate_block(&mut block); + + for i in 0..block.len() { + if input_offset + i < input.len() { + output[output_offset + i] = input[input_offset + i] ^ block[i]; + } + } + } + } +} + +fn main() { + let password = "securePassword123!@#$"; + let plaintext = "Thank you for using EonaCatCipher!"; + + println!("Encrypting '{}' with password '{}' (we do this 5 times)", plaintext, password); + println!("================"); + + for i in 0..5 { + println!("Encryption round {}: ", i + 1); + println!("================"); + + let cipher = EonaCatCipher::new(password, DEFAULT_SALT_SIZE, DEFAULT_IV_SIZE, DEFAULT_KEY_SIZE, DEFAULT_ROUNDS, DEFAULT_BLOCK_SIZE) + .expect("Failed to create cipher"); + + let encrypted = cipher.encrypt(plaintext); + println!("Encrypted (byte array): {:?}", encrypted); + + let decrypted = cipher.decrypt(&encrypted).expect("Failed to decrypt"); + println!("Decrypted: {}", decrypted); + println!("================"); + } +} diff --git a/Rust/cargo.toml b/Rust/cargo.toml new file mode 100644 index 0000000..fbb9bf6 --- /dev/null +++ b/Rust/cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "EonaCatCipher" +version = "1.0.0" +edition = "2024" + +[dependencies] +rand = "0.8" +sha2 = "0.10" +hmac = "0.11" +bytemuck = "1.7" diff --git a/Rust/readme.txt b/Rust/readme.txt new file mode 100644 index 0000000..4cd47a8 --- /dev/null +++ b/Rust/readme.txt @@ -0,0 +1,2 @@ +cargo build +cargo run \ No newline at end of file diff --git a/c/EonaCatCipher.c b/c/EonaCatCipher.c new file mode 100644 index 0000000..f3fd8b3 --- /dev/null +++ b/c/EonaCatCipher.c @@ -0,0 +1,262 @@ +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_SALT_SIZE 32 // Salt size for key derivation +#define DEFAULT_IV_SIZE 16 // IV size (128 bits) +#define DEFAULT_KEY_SIZE 32 // Key size (256 bits) +#define DEFAULT_ROUNDS 10000 // Rounds +#define DEFAULT_BLOCK_SIZE 16 // 128 bits +#define HMAC_KEY_SIZE 32 // Key size for HMAC (256 bits) + +// Function prototypes +void generate_random_bytes(uint8_t *buffer, size_t size); +void pbkdf2(const char *password, uint8_t *salt, int salt_len, uint8_t *output, int output_len, int iterations); +void xor_buffers(uint8_t *a, uint8_t *b, uint8_t *result, size_t length); +void generate_hmac(uint8_t *data, size_t data_len, uint8_t *key, size_t key_len, uint8_t *hmac_out); +int are_equal(const uint8_t *a, const uint8_t *b, size_t length); + +// EonaCatCipher structure +typedef struct { + uint8_t derived_key[DEFAULT_KEY_SIZE]; + uint8_t hmac_key[HMAC_KEY_SIZE]; + int iv_size; + int key_size; + int rounds; + int block_size; +} EonaCatCipher; + +// EonaCatCrypto structure +typedef struct { + uint8_t *key; + uint8_t *nonce; + int block_size; + int rounds; + uint64_t *state; + uint32_t block_counter; +} EonaCatCrypto; + +// Function to create and initialize EonaCatCipher +EonaCatCipher* eonacat_cipher_create(const char *password) { + if (password == NULL || strlen(password) == 0) { + fprintf(stderr, "EonaCatCipher: Password cannot be null or empty.\n"); + return NULL; + } + + EonaCatCipher *cipher = malloc(sizeof(EonaCatCipher)); + if (!cipher) { + fprintf(stderr, "EonaCatCipher: Memory allocation failed.\n"); + return NULL; + } + + cipher->iv_size = DEFAULT_IV_SIZE; + cipher->key_size = DEFAULT_KEY_SIZE; + cipher->rounds = DEFAULT_ROUNDS; + cipher->block_size = DEFAULT_BLOCK_SIZE; + + uint8_t salt[DEFAULT_SALT_SIZE]; + generate_random_bytes(salt, sizeof(salt)); + pbkdf2(password, salt, sizeof(salt), cipher->derived_key, sizeof(cipher->derived_key), cipher->rounds); + pbkdf2(password, salt, sizeof(salt), cipher->hmac_key, sizeof(cipher->hmac_key), cipher->rounds); + + return cipher; +} + +// Function to clean up and free EonaCatCipher +void eonacat_cipher_destroy(EonaCatCipher *cipher) { + if (cipher) { + memset(cipher, 0, sizeof(EonaCatCipher)); + free(cipher); + } +} + +// PBKDF2 implementation +void pbkdf2(const char *password, uint8_t *salt, int salt_len, uint8_t *output, int output_len, int iterations) { + int hash_len = SHA256_DIGEST_LENGTH; + int blocks_needed = (output_len + hash_len - 1) / hash_len; + + uint8_t u[hash_len]; + uint8_t t[hash_len]; + + for (int block_index = 1; block_index <= blocks_needed; block_index++) { + // Step 1: F(block_index) + uint8_t block[salt_len + 4]; + memcpy(block, salt, salt_len); + uint32_t block_index_network = htonl(block_index); + memcpy(block + salt_len, &block_index_network, 4); + + // Step 2: U1 = HMAC(password, salt + block_index) + unsigned int len = 0; + HMAC(EVP_sha256(), password, strlen(password), block, sizeof(block), u, &len); + + memcpy(t, u, hash_len); + memcpy(output + (block_index - 1) * hash_len, t, (block_index == blocks_needed && output_len % hash_len != 0) ? output_len % hash_len : hash_len); + + // Step 4: Iterations + for (int iteration = 1; iteration < iterations; iteration++) { + // U2 = HMAC(password, U1) + HMAC(EVP_sha256(), password, strlen(password), u, hash_len, u, &len); + xor_buffers(t, u, t, hash_len); + memcpy(output + (block_index - 1) * hash_len, t, (block_index == blocks_needed && output_len % hash_len != 0) ? output_len % hash_len : hash_len); + } + } +} + +// Function to generate random bytes +void generate_random_bytes(uint8_t *buffer, size_t size) { + RAND_bytes(buffer, size); +} + +// Function to perform XOR operation on two buffers +void xor_buffers(uint8_t *a, uint8_t *b, uint8_t *result, size_t length) { + for (size_t i = 0; i < length; i++) { + result[i] = a[i] ^ b[i]; + } +} + +// Function to generate HMAC +void generate_hmac(uint8_t *data, size_t data_len, uint8_t *key, size_t key_len, uint8_t *hmac_out) { + unsigned int len = 0; + HMAC(EVP_sha256(), key, key_len, data, data_len, hmac_out, &len); +} + +// Function to compare two buffers +int are_equal(const uint8_t *a, const uint8_t *b, size_t length) { + return (memcmp(a, b, length) == 0); +} + +// Function to encrypt plaintext +uint8_t* eonacat_cipher_encrypt(EonaCatCipher *cipher, const char *plaintext, size_t *out_len) { + uint8_t iv[cipher->iv_size]; + generate_random_bytes(iv, cipher->iv_size); + + size_t plaintext_len = strlen(plaintext); + uint8_t *ciphertext = malloc(plaintext_len + cipher->iv_size); + if (!ciphertext) { + fprintf(stderr, "Memory allocation failed.\n"); + return NULL; + } + + memcpy(ciphertext, iv, cipher->iv_size); // Combine IV and ciphertext + uint8_t encrypted[plaintext_len]; + + EonaCatCrypto crypto; + crypto.key = cipher->derived_key; + crypto.nonce = iv; + crypto.block_size = cipher->block_size; + crypto.rounds = cipher->rounds; + crypto.block_counter = 0; + crypto.state = calloc(cipher->block_size / 4, sizeof(uint64_t)); + + // Encrypt the plaintext (dummy XOR for demonstration purposes) + for (size_t i = 0; i < plaintext_len; i++) { + encrypted[i] = plaintext[i] ^ iv[i % cipher->iv_size]; + } + + memcpy(ciphertext + cipher->iv_size, encrypted, plaintext_len); // Append ciphertext + + // Generate HMAC for integrity check + uint8_t hmac[HMAC_KEY_SIZE]; + generate_hmac(ciphertext, plaintext_len + cipher->iv_size, cipher->hmac_key, sizeof(cipher->hmac_key), hmac); + + *out_len = plaintext_len + cipher->iv_size + HMAC_KEY_SIZE; + uint8_t *final_result = realloc(ciphertext, *out_len); + if (!final_result) { + free(ciphertext); + fprintf(stderr, "Memory allocation failed.\n"); + return NULL; + } + + memcpy(final_result + *out_len - HMAC_KEY_SIZE, hmac, HMAC_KEY_SIZE); // Combine result and HMAC + + return final_result; +} + +// Function to decrypt ciphertext +char* eonacat_cipher_decrypt(EonaCatCipher *cipher, uint8_t *ciphertext_with_hmac, size_t ciphertext_len) { + uint8_t provided_hmac[HMAC_KEY_SIZE]; + memcpy(provided_hmac, ciphertext_with_hmac + ciphertext_len - HMAC_KEY_SIZE, HMAC_KEY_SIZE); + + size_t ciphertext_size = ciphertext_len - HMAC_KEY_SIZE; + uint8_t *ciphertext = malloc(ciphertext_size); + if (!ciphertext) { + fprintf(stderr, "EonaCatCipher: Memory allocation failed.\n"); + return NULL; + } + + memcpy(ciphertext, ciphertext_with_hmac, ciphertext_size); // Separate HMAC from the ciphertext + + // Verify HMAC before decrypting + uint8_t calculated_hmac[HMAC_KEY_SIZE]; + generate_hmac(ciphertext, ciphertext_size, cipher->hmac_key, sizeof(cipher->hmac_key), calculated_hmac); + if (!are_equal(provided_hmac, calculated_hmac, HMAC_KEY_SIZE)) { + free(ciphertext); + fprintf(stderr, "EonaCatCipher: HMAC validation failed. Data may have been tampered with.\n"); + return NULL; + } + + uint8_t iv[cipher->iv_size]; + memcpy(iv, ciphertext, cipher->iv_size); // Extract IV + + size_t plaintext_len = ciphertext_size - cipher->iv_size; + char *plaintext = malloc(plaintext_len + 1); + if (!plaintext) { + free(ciphertext); + fprintf(stderr, "EonaCatCipher: Memory allocation failed.\n"); + return NULL; + } + + // Decrypt + for (size_t i = 0; i < plaintext_len; i++) { + plaintext[i] = ciphertext[cipher->iv_size + i] ^ iv[i % cipher->iv_size]; + } + + plaintext[plaintext_len] = '\0'; // Null-terminate the plaintext + + free(ciphertext); + return plaintext; +} + +// Main function to demonstrate encryption and decryption +int main() { + const char *password = "securePassword123!@#$"; + const char *plaintext = "Thank you for using EonaCatCipher!"; + size_t out_len; + + printf("Encrypting '%s' with password '%s'\n", plaintext, password); + printf("================\n"); + + EonaCatCipher *cipher = eonacat_cipher_create(password); + if (!cipher) return 1; + + for (int i = 0; i < 5; i++) { + printf("Encryption round %d: \n", i + 1); + printf("================\n"); + + uint8_t *encrypted = eonacat_cipher_encrypt(cipher, plaintext, &out_len); + if (encrypted) { + printf("Encrypted (byte array): "); + for (size_t j = 0; j < out_len; j++) { + printf("%02X", encrypted[j]); + } + printf("\n"); + + char *decrypted = eonacat_cipher_decrypt(cipher, encrypted, out_len); + if (decrypted) { + printf("Decrypted: %s\n", decrypted); + free(decrypted); + } + + free(encrypted); + } + + printf("================\n"); + } + + eonacat_cipher_destroy(cipher); + return 0; +} diff --git a/c/readme.txt b/c/readme.txt new file mode 100644 index 0000000..587a007 --- /dev/null +++ b/c/readme.txt @@ -0,0 +1,35 @@ +/* + * EonaCatCipher - Because security is key! + * + * Copyright (c) 2024 EonaCat (Jeroen Saey) + * + * https://eonacat.com/license + * + * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + * OF SOFTWARE BY EONACAT (JEROEN SAEY) + * + * This software is provided "as is", without any express or implied warranty. + * In no event shall the authors or copyright holders be liable for any claim, + * damages or other liability, whether in an action of contract, tort or otherwise, + * arising from, out of or in connection with the software or the use or other + * dealings in the software. + * + * You may use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * 1. The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 2. The software must not be used for any unlawful purpose. + * + * For any inquiries, please contact: eonacat@gmail.com + */ + +1. Compile the c code: + +gcc -o EonaCatCipher EonaCatCipher.c -lcrypto + +2. Run the executable: + +./EonaCatCipher \ No newline at end of file