EonaCatCipher/Python/EonaCatCipher.py

190 lines
7.0 KiB
Python

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()