Spaces:
Sleeping
Sleeping
Create core.py
Browse files
core.py
ADDED
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import io
|
2 |
+
import json
|
3 |
+
import os
|
4 |
+
import struct
|
5 |
+
import logging
|
6 |
+
|
7 |
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
8 |
+
from cryptography.hazmat.primitives import hashes
|
9 |
+
from cryptography.hazmat.primitives import serialization
|
10 |
+
from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
11 |
+
from PIL import Image, ImageDraw, ImageFont
|
12 |
+
import numpy as np
|
13 |
+
|
14 |
+
# --- Configure Logging ---
|
15 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
16 |
+
logger = logging.getLogger(__name__)
|
17 |
+
|
18 |
+
# --- Constants ---
|
19 |
+
AES_KEY_SIZE_CRYPTO = 32
|
20 |
+
AES_GCM_NONCE_SIZE_CRYPTO = 12
|
21 |
+
LENGTH_HEADER_SIZE = 4
|
22 |
+
|
23 |
+
def parse_kv_string_to_dict(kv_str: str) -> dict:
|
24 |
+
"""Parses a string of key:value or key=value pairs into a dictionary."""
|
25 |
+
data_dict = {}
|
26 |
+
for line in kv_str.splitlines():
|
27 |
+
line = line.strip()
|
28 |
+
if not line or line.startswith('#'):
|
29 |
+
continue
|
30 |
+
if '=' in line:
|
31 |
+
key, value = line.split('=', 1)
|
32 |
+
elif ':' in line:
|
33 |
+
key, value = line.split(':', 1)
|
34 |
+
else:
|
35 |
+
raise ValueError(f"Invalid format on line: '{line}'. Use 'key: value' or 'key = value'.")
|
36 |
+
data_dict[key.strip()] = value.strip().strip("'\"")
|
37 |
+
return data_dict
|
38 |
+
|
39 |
+
def encrypt_data_hybrid(data: bytes, public_key_pem: str) -> bytes:
|
40 |
+
"""Encrypts data using RSA-OAEP + AES-GCM hybrid encryption."""
|
41 |
+
public_key = serialization.load_pem_public_key(public_key_pem.encode('utf-8'))
|
42 |
+
aes_key = os.urandom(AES_KEY_SIZE_CRYPTO)
|
43 |
+
nonce = os.urandom(AES_GCM_NONCE_SIZE_CRYPTO)
|
44 |
+
|
45 |
+
ciphertext_with_tag = AESGCM(aes_key).encrypt(nonce, data, None)
|
46 |
+
|
47 |
+
rsa_encrypted_aes_key = public_key.encrypt(
|
48 |
+
aes_key,
|
49 |
+
padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None)
|
50 |
+
)
|
51 |
+
|
52 |
+
encrypted_aes_key_len_bytes = struct.pack('>I', len(rsa_encrypted_aes_key))
|
53 |
+
return encrypted_aes_key_len_bytes + rsa_encrypted_aes_key + nonce + ciphertext_with_tag
|
54 |
+
|
55 |
+
def embed_data_in_image(img_obj: Image.Image, data: bytes) -> Image.Image:
|
56 |
+
"""Embeds data into the LSB of an image."""
|
57 |
+
img_rgb = img_obj.convert("RGB")
|
58 |
+
pixel_data = np.array(img_rgb).ravel()
|
59 |
+
|
60 |
+
data_length_header = struct.pack('>I', len(data))
|
61 |
+
binary_payload = ''.join(format(byte, '08b') for byte in data_length_header + data)
|
62 |
+
|
63 |
+
if len(binary_payload) > pixel_data.size:
|
64 |
+
raise ValueError(f"Data too large for image capacity. Needs {len(binary_payload)} bits, image has {pixel_data.size}.")
|
65 |
+
|
66 |
+
for i in range(len(binary_payload)):
|
67 |
+
pixel_data[i] = (pixel_data[i] & 0xFE) | int(binary_payload[i])
|
68 |
+
|
69 |
+
stego_pixels = pixel_data.reshape(img_rgb.size[1], img_rgb.size[0], 3)
|
70 |
+
return Image.fromarray(stego_pixels, 'RGB')
|
71 |
+
|
72 |
+
def create_carrier_image(width=800, height=600) -> Image.Image:
|
73 |
+
"""Generates a simple carrier image."""
|
74 |
+
img = Image.new('RGB', (width, height), color = (73, 109, 137))
|
75 |
+
d = ImageDraw.Draw(img)
|
76 |
+
try:
|
77 |
+
font = ImageFont.truetype("DejaVuSans.ttf", 40)
|
78 |
+
except IOError:
|
79 |
+
font = ImageFont.load_default()
|
80 |
+
d.text((width/2, height/2), "KeyLock Encrypted Data", fill=(255,255,0), font=font, anchor="ms")
|
81 |
+
return img
|
82 |
+
|
83 |
+
def create_encrypted_image(secret_data_str: str, public_key_pem: str) -> Image.Image:
|
84 |
+
"""
|
85 |
+
High-level orchestrator function to create the final encrypted image.
|
86 |
+
"""
|
87 |
+
logger.info("Starting image creation process...")
|
88 |
+
|
89 |
+
# 1. Parse and validate secret data
|
90 |
+
if not secret_data_str.strip():
|
91 |
+
raise ValueError("Secret data cannot be empty.")
|
92 |
+
secret_dict = parse_kv_string_to_dict(secret_data_str)
|
93 |
+
if not secret_dict:
|
94 |
+
raise ValueError("No valid key-value pairs found in secret data.")
|
95 |
+
|
96 |
+
# 2. Serialize data to JSON bytes
|
97 |
+
json_bytes = json.dumps(secret_dict).encode('utf-8')
|
98 |
+
logger.info(f"Serialized {len(json_bytes)} bytes of data.")
|
99 |
+
|
100 |
+
# 3. Encrypt the data
|
101 |
+
encrypted_payload = encrypt_data_hybrid(json_bytes, public_key_pem)
|
102 |
+
logger.info(f"Encrypted payload created with size: {len(encrypted_payload)} bytes.")
|
103 |
+
|
104 |
+
# 4. Create a carrier image and embed the data
|
105 |
+
carrier_image = create_carrier_image()
|
106 |
+
stego_image = embed_data_in_image(carrier_image, encrypted_payload)
|
107 |
+
logger.info("Data successfully embedded into carrier image.")
|
108 |
+
|
109 |
+
return stego_image
|