Update server/app.py
Browse files- server/app.py +35 -20
server/app.py
CHANGED
@@ -12,19 +12,15 @@ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
12 |
from cryptography.hazmat.primitives import hashes
|
13 |
from cryptography.hazmat.primitives import serialization
|
14 |
from cryptography.hazmat.primitives.asymmetric import padding
|
15 |
-
# Import 'rsa' for the key generation utility
|
16 |
from cryptography.hazmat.primitives.asymmetric import rsa
|
17 |
from cryptography.exceptions import InvalidTag
|
18 |
|
19 |
-
# --- Constants ---
|
20 |
HEADER_BITS = 32
|
21 |
AES_GCM_NONCE_SIZE = 12
|
22 |
|
23 |
-
# --- Configure Logging ---
|
24 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
25 |
logger = logging.getLogger(__name__)
|
26 |
|
27 |
-
# --- Load Keys and Config ---
|
28 |
KEYLOCK_PRIV_KEY = os.environ.get('KEYLOCK_PRIV_KEY')
|
29 |
KEYLOCK_STATUS_MESSAGE = ""
|
30 |
|
@@ -53,9 +49,7 @@ except Exception as e:
|
|
53 |
logger.error(f"Error loading public key: {e}")
|
54 |
PUBLIC_KEY_PEM_STRING = f"Error loading public key: {e}"
|
55 |
|
56 |
-
# --- Key Generation Utility ---
|
57 |
def generate_rsa_keys():
|
58 |
-
"""Generates a new 2048-bit RSA private and public key pair."""
|
59 |
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
60 |
private_pem = private_key.private_bytes(
|
61 |
encoding=serialization.Encoding.PEM,
|
@@ -68,9 +62,7 @@ def generate_rsa_keys():
|
|
68 |
).decode('utf-8')
|
69 |
return private_pem, public_pem
|
70 |
|
71 |
-
# --- Core Decryption Function ---
|
72 |
def decode_data(image_base64_string: str) -> dict:
|
73 |
-
"""Decrypts a payload hidden in a PNG image via LSB steganography and hybrid encryption."""
|
74 |
if not KEYLOCK_PRIV_KEY:
|
75 |
error_msg = "Server Error: The API is not configured with a private key."
|
76 |
raise gr.Error(error_msg)
|
@@ -122,27 +114,51 @@ def decode_data(image_base64_string: str) -> dict:
|
|
122 |
except Exception as e:
|
123 |
raise gr.Error(f"An unexpected server error occurred. Details: {e}")
|
124 |
|
125 |
-
# ==============================================================================
|
126 |
-
# GRADIO INTERFACE
|
127 |
-
# ==============================================================================
|
128 |
with gr.Blocks(title="Secure Decoder API", theme=gr.themes.Soft()) as demo:
|
129 |
gr.Markdown("# π Secure KeyLock Decoder API")
|
130 |
gr.Markdown("This application provides a secure API endpoint to decrypt and extract JSON data hidden within PNG images.")
|
131 |
|
132 |
with gr.Tabs():
|
133 |
with gr.TabItem("π Quick Start & Documentation"):
|
134 |
-
# This tab remains unchanged, showing clients how to use the API
|
135 |
gr.Markdown("## How to Use This API")
|
136 |
gr.Markdown("### Step 1: Get the Server's Public Key")
|
137 |
-
gr.Code(value=PUBLIC_KEY_PEM_STRING, language="
|
138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
139 |
|
140 |
with gr.TabItem("βοΈ Server Status & Error Guide"):
|
141 |
gr.Markdown("## Server Status")
|
142 |
gr.Textbox(label="Private Key Status", value=KEYLOCK_STATUS_MESSAGE, interactive=False, lines=3)
|
143 |
gr.Markdown("---")
|
144 |
|
145 |
-
# --- NEW: Key Generation Utility ---
|
146 |
with gr.Accordion("π Admin: Key Pair Generator", open=False):
|
147 |
gr.Markdown(
|
148 |
"**For Administrators Only.** Use this tool to generate a new RSA key pair for the server. "
|
@@ -177,14 +193,13 @@ with gr.Blocks(title="Secure Decoder API", theme=gr.themes.Soft()) as demo:
|
|
177 |
gr.Markdown("## Error Handling Guide")
|
178 |
gr.Markdown(
|
179 |
"""
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
"""
|
185 |
)
|
186 |
|
187 |
-
# --- Hidden component for the API endpoint ---
|
188 |
with gr.Row(visible=False):
|
189 |
api_input = gr.Textbox()
|
190 |
api_output = gr.JSON()
|
|
|
12 |
from cryptography.hazmat.primitives import hashes
|
13 |
from cryptography.hazmat.primitives import serialization
|
14 |
from cryptography.hazmat.primitives.asymmetric import padding
|
|
|
15 |
from cryptography.hazmat.primitives.asymmetric import rsa
|
16 |
from cryptography.exceptions import InvalidTag
|
17 |
|
|
|
18 |
HEADER_BITS = 32
|
19 |
AES_GCM_NONCE_SIZE = 12
|
20 |
|
|
|
21 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
22 |
logger = logging.getLogger(__name__)
|
23 |
|
|
|
24 |
KEYLOCK_PRIV_KEY = os.environ.get('KEYLOCK_PRIV_KEY')
|
25 |
KEYLOCK_STATUS_MESSAGE = ""
|
26 |
|
|
|
49 |
logger.error(f"Error loading public key: {e}")
|
50 |
PUBLIC_KEY_PEM_STRING = f"Error loading public key: {e}"
|
51 |
|
|
|
52 |
def generate_rsa_keys():
|
|
|
53 |
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
54 |
private_pem = private_key.private_bytes(
|
55 |
encoding=serialization.Encoding.PEM,
|
|
|
62 |
).decode('utf-8')
|
63 |
return private_pem, public_pem
|
64 |
|
|
|
65 |
def decode_data(image_base64_string: str) -> dict:
|
|
|
66 |
if not KEYLOCK_PRIV_KEY:
|
67 |
error_msg = "Server Error: The API is not configured with a private key."
|
68 |
raise gr.Error(error_msg)
|
|
|
114 |
except Exception as e:
|
115 |
raise gr.Error(f"An unexpected server error occurred. Details: {e}")
|
116 |
|
|
|
|
|
|
|
117 |
with gr.Blocks(title="Secure Decoder API", theme=gr.themes.Soft()) as demo:
|
118 |
gr.Markdown("# π Secure KeyLock Decoder API")
|
119 |
gr.Markdown("This application provides a secure API endpoint to decrypt and extract JSON data hidden within PNG images.")
|
120 |
|
121 |
with gr.Tabs():
|
122 |
with gr.TabItem("π Quick Start & Documentation"):
|
|
|
123 |
gr.Markdown("## How to Use This API")
|
124 |
gr.Markdown("### Step 1: Get the Server's Public Key")
|
125 |
+
gr.Code(value=PUBLIC_KEY_PEM_STRING, language="pem", label="Server Public Key")
|
126 |
+
with gr.Accordion("Step 2: Create the Encrypted Image (Client-Side Python Example)", open=False):
|
127 |
+
gr.Markdown("Use the following Python code in your client application to create a KeyLock image.")
|
128 |
+
gr.Code(
|
129 |
+
language="python",
|
130 |
+
label="Client-Side Image Creation Script",
|
131 |
+
value="""
|
132 |
+
import json; import os; import struct; from PIL import Image; import numpy as np
|
133 |
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
134 |
+
from cryptography.hazmat.primitives import hashes, serialization
|
135 |
+
from cryptography.hazmat.primitives.asymmetric import padding
|
136 |
+
def create_keylock_image(public_key_pem: str, json_data: dict, template_image_path: str, output_path: str):
|
137 |
+
public_key = serialization.load_pem_public_key(public_key_pem.encode())
|
138 |
+
data_to_encrypt = json.dumps(json_data).encode('utf-8')
|
139 |
+
aes_key = AESGCM.generate_key(bit_length=256)
|
140 |
+
nonce = os.urandom(12)
|
141 |
+
ciphertext_with_tag = AESGCM(aes_key).encrypt(nonce, data_to_encrypt, None)
|
142 |
+
encrypted_aes_key = public_key.encrypt(aes_key, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None))
|
143 |
+
crypto_payload = struct.pack('>I', len(encrypted_aes_key)) + encrypted_aes_key + nonce + ciphertext_with_tag
|
144 |
+
header = struct.pack('>I', len(crypto_payload))
|
145 |
+
full_binary_string = ''.join(f'{byte:08b}' for byte in header + crypto_payload)
|
146 |
+
img = Image.open(template_image_path).convert("RGB")
|
147 |
+
pixel_data = np.array(img)
|
148 |
+
if len(full_binary_string) > pixel_data.size:
|
149 |
+
raise ValueError("Data is too large for the image.")
|
150 |
+
flat_pixels = pixel_data.ravel()
|
151 |
+
for i in range(len(full_binary_string)):
|
152 |
+
flat_pixels[i] = (flat_pixels[i] & 0b11111110) | int(full_binary_string[i])
|
153 |
+
Image.fromarray(pixel_data).save(output_path, "PNG")
|
154 |
+
"""
|
155 |
+
)
|
156 |
|
157 |
with gr.TabItem("βοΈ Server Status & Error Guide"):
|
158 |
gr.Markdown("## Server Status")
|
159 |
gr.Textbox(label="Private Key Status", value=KEYLOCK_STATUS_MESSAGE, interactive=False, lines=3)
|
160 |
gr.Markdown("---")
|
161 |
|
|
|
162 |
with gr.Accordion("π Admin: Key Pair Generator", open=False):
|
163 |
gr.Markdown(
|
164 |
"**For Administrators Only.** Use this tool to generate a new RSA key pair for the server. "
|
|
|
193 |
gr.Markdown("## Error Handling Guide")
|
194 |
gr.Markdown(
|
195 |
"""
|
196 |
+
- **`"Decryption failed. ... InvalidTag"`**: The most common error. The image was encrypted with a **different public key** or the image file was corrupted.
|
197 |
+
- **`"Decryption failed. ... Key mismatch"`**: The RSA key size used to encrypt the data does not match the server's key size.
|
198 |
+
- **`"Image data corrupt or truncated..."`**: The image file is incomplete or damaged.
|
199 |
+
- **`"Server Error: ... not configured"`**: The administrator has not set the `KEYLOCK_PRIV_KEY` secret correctly.
|
200 |
"""
|
201 |
)
|
202 |
|
|
|
203 |
with gr.Row(visible=False):
|
204 |
api_input = gr.Textbox()
|
205 |
api_output = gr.JSON()
|