320 lines
11 KiB
C#
320 lines
11 KiB
C#
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("================");
|
|
}
|
|
}
|
|
}
|