broadfield-dev commited on
Commit
d2a3c6e
Β·
verified Β·
1 Parent(s): cbb8164

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +34 -56
app.py CHANGED
@@ -39,11 +39,9 @@ T2I_PROMPT = "A stunning view of a distant galaxy, nebulae, and constellations,
39
  def resize_and_crop(img: Image.Image, size: int = IMAGE_SIZE) -> Image.Image:
40
  """Resizes an image to fit within a square of `size` and then center-crops it."""
41
  try:
42
- # ImageOps.fit is perfect for this: it resizes while maintaining aspect ratio and crops from the center.
43
  return ImageOps.fit(img, (size, size), Image.Resampling.LANCZOS)
44
  except Exception as e:
45
  logger.error(f"Failed to resize and crop image: {e}")
46
- # Fallback to a simple resize if 'fit' fails
47
  return img.resize((size, size), Image.Resampling.LANCZOS)
48
 
49
  def prepare_base_image(uploaded_image: Image.Image | None, progress) -> Image.Image:
@@ -54,13 +52,10 @@ def prepare_base_image(uploaded_image: Image.Image | None, progress) -> Image.Im
54
  3. AI-generated image.
55
  All images are resized and cropped to a standard size.
56
  """
57
- # 1. User-uploaded image
58
  if uploaded_image:
59
  progress(0, desc="βœ… Using uploaded image...")
60
  logger.info("Using user-uploaded image.")
61
  return resize_and_crop(uploaded_image)
62
-
63
- # 2. Default URL image
64
  try:
65
  progress(0, desc="⏳ No image uploaded. Fetching default background...")
66
  logger.info(f"Fetching default image from URL: {DEFAULT_IMAGE_URL}")
@@ -71,8 +66,6 @@ def prepare_base_image(uploaded_image: Image.Image | None, progress) -> Image.Im
71
  return resize_and_crop(img)
72
  except Exception as e:
73
  logger.warning(f"Could not fetch default image: {e}. Falling back to AI generation.")
74
-
75
- # 3. AI-generated image (last resort)
76
  try:
77
  progress(0, desc=f"⏳ Generating new image with {T2I_MODEL}...")
78
  logger.info(f"Generating a new image using model: {T2I_MODEL}")
@@ -97,13 +90,12 @@ def create_encrypted_image(
97
  secret_data_str: str,
98
  public_key_pem: str,
99
  base_image: Image.Image,
100
- show_key_overlay: bool
101
  ) -> Image.Image:
102
  if not secret_data_str.strip():
103
  raise ValueError("Secret data cannot be empty.")
104
  if not public_key_pem.strip():
105
  raise ValueError("Public Key cannot be empty.")
106
-
107
  data_dict = {}
108
  for line in secret_data_str.splitlines():
109
  line = line.strip()
@@ -111,79 +103,73 @@ def create_encrypted_image(
111
  parts = line.split(':', 1) if ':' in line else line.split('=', 1)
112
  if len(parts) == 2:
113
  data_dict[parts[0].strip()] = parts[1].strip().strip("'\"")
114
-
115
  if not data_dict:
116
  raise ValueError("No valid key-value pairs found in secret data.")
117
-
118
  json_bytes = json.dumps(data_dict).encode('utf-8')
119
  public_key = serialization.load_pem_public_key(public_key_pem.encode('utf-8'))
120
-
121
  aes_key, nonce = os.urandom(32), os.urandom(12)
122
  ciphertext = AESGCM(aes_key).encrypt(nonce, json_bytes, None)
123
-
124
  rsa_encrypted_key = public_key.encrypt(aes_key, padding.OAEP(mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=None))
125
-
126
  encrypted_payload = struct.pack('>I', len(rsa_encrypted_key)) + rsa_encrypted_key + nonce + ciphertext
127
 
128
- # Use the prepared base_image
129
  img = base_image.copy().convert("RGB")
130
  width, height = img.size
131
 
132
  # --- Add Stylish Overlays ---
133
- draw = ImageDraw.Draw(img, "RGBA") # Use RGBA to draw transparent shapes
134
  try:
135
  font_bold = ImageFont.truetype("DejaVuSans-Bold.ttf", 30)
136
  font_regular = ImageFont.truetype("DejaVuSans.ttf", 15)
137
- font_small = ImageFont.truetype("DejaVuSans.ttf", 12)
138
  except IOError:
139
  font_bold = ImageFont.load_default(size=28)
140
  font_regular = ImageFont.load_default(size=14)
141
- font_small = ImageFont.load_default(size=11)
142
 
143
- # Colors matching the Gradio theme (slate, sky)
144
- overlay_color = (15, 23, 42, 190) # very dark slate, semi-transparent
145
- title_color = (226, 232, 240) # light slate/sky
146
- key_color = (148, 163, 184) # slate-400 for keys
147
- value_color = (241, 245, 249) # slate-100 for values
148
 
149
- # Main title overlay (top)
150
  draw.rectangle([0, 20, width, 80], fill=overlay_color)
151
  draw.text((width / 2, 50), "KeyLock Secure Data", fill=title_color, font=font_bold, anchor="ms")
152
 
153
- # Key/Value data overlay (bottom-left)
154
- if show_key_overlay:
155
  box_padding = 15
156
  line_spacing = 6
157
  text_start_x = 35
158
-
159
- # Calculate box height dynamically
160
- lines = [f"{key}: {value}" for key, value in data_dict.items()]
161
- line_heights = [draw.textbbox((0,0), line, font=font_regular)[3] for line in lines]
 
 
 
162
  total_text_height = sum(line_heights) + (len(lines) - 1) * line_spacing
163
  box_height = total_text_height + (box_padding * 2)
164
  box_y0 = height - box_height - 20
165
 
166
  draw.rectangle([20, box_y0, width - 20, height - 20], fill=overlay_color)
167
-
168
  current_y = box_y0 + box_padding
169
- for i, (key, value) in enumerate(data_dict.items()):
170
- # Draw key and value separately for different colors
171
- key_text = f"{key}:"
172
- draw.text((text_start_x, current_y), key_text, fill=key_color, font=font_regular)
173
- key_bbox = draw.textbbox((text_start_x, current_y), key_text, font=font_regular)
174
- draw.text((key_bbox[2] + 8, current_y), str(value), fill=value_color, font=font_regular)
175
- current_y += line_heights[i] + line_spacing
176
-
177
- # --- Steganography: Embed encrypted data into pixels ---
 
 
 
 
 
178
  pixel_data = np.array(img.convert("RGB")).ravel()
179
  binary_payload = ''.join(format(b, '08b') for b in struct.pack('>I', len(encrypted_payload)) + encrypted_payload)
180
-
181
  if len(binary_payload) > pixel_data.size:
182
- raise ValueError(f"Data is too large for the image. Max size: {pixel_data.size // 8} bytes. Your data: ~{len(binary_payload) // 8} bytes.")
183
-
184
  pixel_data[:len(binary_payload)] = (pixel_data[:len(binary_payload)] & 0xFE) | np.array(list(binary_payload), dtype=np.uint8)
185
  stego_pixels = pixel_data.reshape((height, width, 3))
186
-
187
  return Image.fromarray(stego_pixels, 'RGB')
188
 
189
  # --- Gradio Wrapper Functions ---
@@ -206,20 +192,16 @@ def get_server_list():
206
  logger.error(status)
207
  yield gr.Dropdown(choices=[], value=None, label="Error fetching servers"), status, []
208
 
209
- def create_keylock_wrapper(service_name: str, secret_data: str, available_endpoints: list, uploaded_image: Image.Image | None, show_keys: bool, progress=gr.Progress(track_tqdm=True)):
210
  if not service_name:
211
  raise gr.Error("Please select a target server.")
212
  public_key = next((e['public_key'] for e in available_endpoints if e['name'] == service_name), None)
213
  if not public_key:
214
  raise gr.Error(f"Could not find public key for '{service_name}'. Please refresh the server list.")
215
  try:
216
- # Step 1: Prepare the base image with status updates via the progress object
217
  base_image = prepare_base_image(uploaded_image, progress)
218
  progress(0.5, desc="Encrypting and embedding data...")
219
-
220
- # Step 2: Create the final encrypted image with overlays
221
- created_image = create_encrypted_image(secret_data, public_key, base_image, show_keys)
222
-
223
  return created_image, f"βœ… Success! Image created for '{service_name}'."
224
  except Exception as e:
225
  logger.error(f"Error creating image: {e}", exc_info=True)
@@ -230,21 +212,17 @@ def send_keylock_wrapper(service_name: str, image: Image.Image, available_endpoi
230
  if image is None: raise gr.Error("Please create or upload an image to send.")
231
  endpoint_details = next((e for e in available_endpoints if e['name'] == service_name), None)
232
  if not endpoint_details or not endpoint_details.get('link'): raise gr.Error(f"Config Error for '{service_name}'.")
233
-
234
  server_url = endpoint_details['link']
235
  status = f"Connecting to remote server: {server_url}"
236
  yield None, status
237
-
238
  try:
239
  with io.BytesIO() as buffer:
240
  image.save(buffer, format="PNG")
241
  b64_string = base64.b64encode(buffer.getvalue()).decode("utf-8")
242
-
243
  client = Client(server_url)
244
  result = client.predict(image_base64_string=b64_string, api_name="/keylock-auth-decoder")
245
  decrypted_data = json.loads(result) if isinstance(result, str) else result
246
  yield decrypted_data, "βœ… Success! Data decrypted by remote server."
247
-
248
  except Exception as e:
249
  logger.error(f"Error calling server with gradio_client: {e}", exc_info=True)
250
  yield None, f"❌ Error calling server API: {e}"
@@ -285,7 +263,7 @@ with gr.Blocks(theme=theme, title="KeyLock Operations Dashboard") as demo:
285
 
286
  gr.Markdown("### Image Options")
287
  creator_base_image_input = gr.Image(label="Optional Base Image (600x600 recommended)", type="pil", sources=["upload"], show_download_button=False)
288
- creator_show_keys_checkbox = gr.Checkbox(label="Show key/value data on final image", value=True)
289
 
290
  creator_button = gr.Button("✨ Create Auth Image", variant="primary", scale=2)
291
 
@@ -333,7 +311,7 @@ with gr.Blocks(theme=theme, title="KeyLock Operations Dashboard") as demo:
333
 
334
  creator_button.click(
335
  fn=create_keylock_wrapper,
336
- inputs=[creator_service_dropdown, creator_secret_input, endpoints_state, creator_base_image_input, creator_show_keys_checkbox],
337
  outputs=[creator_image_output, creator_status]
338
  )
339
 
 
39
  def resize_and_crop(img: Image.Image, size: int = IMAGE_SIZE) -> Image.Image:
40
  """Resizes an image to fit within a square of `size` and then center-crops it."""
41
  try:
 
42
  return ImageOps.fit(img, (size, size), Image.Resampling.LANCZOS)
43
  except Exception as e:
44
  logger.error(f"Failed to resize and crop image: {e}")
 
45
  return img.resize((size, size), Image.Resampling.LANCZOS)
46
 
47
  def prepare_base_image(uploaded_image: Image.Image | None, progress) -> Image.Image:
 
52
  3. AI-generated image.
53
  All images are resized and cropped to a standard size.
54
  """
 
55
  if uploaded_image:
56
  progress(0, desc="βœ… Using uploaded image...")
57
  logger.info("Using user-uploaded image.")
58
  return resize_and_crop(uploaded_image)
 
 
59
  try:
60
  progress(0, desc="⏳ No image uploaded. Fetching default background...")
61
  logger.info(f"Fetching default image from URL: {DEFAULT_IMAGE_URL}")
 
66
  return resize_and_crop(img)
67
  except Exception as e:
68
  logger.warning(f"Could not fetch default image: {e}. Falling back to AI generation.")
 
 
69
  try:
70
  progress(0, desc=f"⏳ Generating new image with {T2I_MODEL}...")
71
  logger.info(f"Generating a new image using model: {T2I_MODEL}")
 
90
  secret_data_str: str,
91
  public_key_pem: str,
92
  base_image: Image.Image,
93
+ overlay_option: str
94
  ) -> Image.Image:
95
  if not secret_data_str.strip():
96
  raise ValueError("Secret data cannot be empty.")
97
  if not public_key_pem.strip():
98
  raise ValueError("Public Key cannot be empty.")
 
99
  data_dict = {}
100
  for line in secret_data_str.splitlines():
101
  line = line.strip()
 
103
  parts = line.split(':', 1) if ':' in line else line.split('=', 1)
104
  if len(parts) == 2:
105
  data_dict[parts[0].strip()] = parts[1].strip().strip("'\"")
 
106
  if not data_dict:
107
  raise ValueError("No valid key-value pairs found in secret data.")
 
108
  json_bytes = json.dumps(data_dict).encode('utf-8')
109
  public_key = serialization.load_pem_public_key(public_key_pem.encode('utf-8'))
 
110
  aes_key, nonce = os.urandom(32), os.urandom(12)
111
  ciphertext = AESGCM(aes_key).encrypt(nonce, json_bytes, None)
 
112
  rsa_encrypted_key = public_key.encrypt(aes_key, padding.OAEP(mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=None))
 
113
  encrypted_payload = struct.pack('>I', len(rsa_encrypted_key)) + rsa_encrypted_key + nonce + ciphertext
114
 
 
115
  img = base_image.copy().convert("RGB")
116
  width, height = img.size
117
 
118
  # --- Add Stylish Overlays ---
119
+ draw = ImageDraw.Draw(img, "RGBA")
120
  try:
121
  font_bold = ImageFont.truetype("DejaVuSans-Bold.ttf", 30)
122
  font_regular = ImageFont.truetype("DejaVuSans.ttf", 15)
 
123
  except IOError:
124
  font_bold = ImageFont.load_default(size=28)
125
  font_regular = ImageFont.load_default(size=14)
 
126
 
127
+ overlay_color = (15, 23, 42, 190)
128
+ title_color = (226, 232, 240)
129
+ key_color = (148, 163, 184)
130
+ value_color = (241, 245, 249)
 
131
 
 
132
  draw.rectangle([0, 20, width, 80], fill=overlay_color)
133
  draw.text((width / 2, 50), "KeyLock Secure Data", fill=title_color, font=font_bold, anchor="ms")
134
 
135
+ # Data overlay logic based on user's choice
136
+ if overlay_option != "None":
137
  box_padding = 15
138
  line_spacing = 6
139
  text_start_x = 35
140
+
141
+ if overlay_option == "Keys Only":
142
+ lines = list(data_dict.keys())
143
+ else: # "Keys and Values"
144
+ lines = [f"{key}: {value}" for key, value in data_dict.items()]
145
+
146
+ line_heights = [draw.textbbox((0, 0), line, font=font_regular)[3] for line in lines]
147
  total_text_height = sum(line_heights) + (len(lines) - 1) * line_spacing
148
  box_height = total_text_height + (box_padding * 2)
149
  box_y0 = height - box_height - 20
150
 
151
  draw.rectangle([20, box_y0, width - 20, height - 20], fill=overlay_color)
 
152
  current_y = box_y0 + box_padding
153
+
154
+ if overlay_option == "Keys Only":
155
+ for i, key in enumerate(data_dict.keys()):
156
+ draw.text((text_start_x, current_y), key, fill=key_color, font=font_regular)
157
+ current_y += line_heights[i] + line_spacing
158
+ elif overlay_option == "Keys and Values":
159
+ for i, (key, value) in enumerate(data_dict.items()):
160
+ key_text = f"{key}:"
161
+ draw.text((text_start_x, current_y), key_text, fill=key_color, font=font_regular)
162
+ key_bbox = draw.textbbox((text_start_x, current_y), key_text, font=font_regular)
163
+ draw.text((key_bbox[2] + 8, current_y), str(value), fill=value_color, font=font_regular)
164
+ current_y += line_heights[i] + line_spacing
165
+
166
+ # --- Steganography ---
167
  pixel_data = np.array(img.convert("RGB")).ravel()
168
  binary_payload = ''.join(format(b, '08b') for b in struct.pack('>I', len(encrypted_payload)) + encrypted_payload)
 
169
  if len(binary_payload) > pixel_data.size:
170
+ raise ValueError(f"Data is too large for the image. Max size: {pixel_data.size // 8} bytes.")
 
171
  pixel_data[:len(binary_payload)] = (pixel_data[:len(binary_payload)] & 0xFE) | np.array(list(binary_payload), dtype=np.uint8)
172
  stego_pixels = pixel_data.reshape((height, width, 3))
 
173
  return Image.fromarray(stego_pixels, 'RGB')
174
 
175
  # --- Gradio Wrapper Functions ---
 
192
  logger.error(status)
193
  yield gr.Dropdown(choices=[], value=None, label="Error fetching servers"), status, []
194
 
195
+ def create_keylock_wrapper(service_name: str, secret_data: str, available_endpoints: list, uploaded_image: Image.Image | None, overlay_option: str, progress=gr.Progress(track_tqdm=True)):
196
  if not service_name:
197
  raise gr.Error("Please select a target server.")
198
  public_key = next((e['public_key'] for e in available_endpoints if e['name'] == service_name), None)
199
  if not public_key:
200
  raise gr.Error(f"Could not find public key for '{service_name}'. Please refresh the server list.")
201
  try:
 
202
  base_image = prepare_base_image(uploaded_image, progress)
203
  progress(0.5, desc="Encrypting and embedding data...")
204
+ created_image = create_encrypted_image(secret_data, public_key, base_image, overlay_option)
 
 
 
205
  return created_image, f"βœ… Success! Image created for '{service_name}'."
206
  except Exception as e:
207
  logger.error(f"Error creating image: {e}", exc_info=True)
 
212
  if image is None: raise gr.Error("Please create or upload an image to send.")
213
  endpoint_details = next((e for e in available_endpoints if e['name'] == service_name), None)
214
  if not endpoint_details or not endpoint_details.get('link'): raise gr.Error(f"Config Error for '{service_name}'.")
 
215
  server_url = endpoint_details['link']
216
  status = f"Connecting to remote server: {server_url}"
217
  yield None, status
 
218
  try:
219
  with io.BytesIO() as buffer:
220
  image.save(buffer, format="PNG")
221
  b64_string = base64.b64encode(buffer.getvalue()).decode("utf-8")
 
222
  client = Client(server_url)
223
  result = client.predict(image_base64_string=b64_string, api_name="/keylock-auth-decoder")
224
  decrypted_data = json.loads(result) if isinstance(result, str) else result
225
  yield decrypted_data, "βœ… Success! Data decrypted by remote server."
 
226
  except Exception as e:
227
  logger.error(f"Error calling server with gradio_client: {e}", exc_info=True)
228
  yield None, f"❌ Error calling server API: {e}"
 
263
 
264
  gr.Markdown("### Image Options")
265
  creator_base_image_input = gr.Image(label="Optional Base Image (600x600 recommended)", type="pil", sources=["upload"], show_download_button=False)
266
+ creator_overlay_option = gr.Radio(label="Overlay Content", choices=["Keys and Values", "Keys Only", "None"], value="Keys and Values", info="Choose what data to display on the final image.")
267
 
268
  creator_button = gr.Button("✨ Create Auth Image", variant="primary", scale=2)
269
 
 
311
 
312
  creator_button.click(
313
  fn=create_keylock_wrapper,
314
+ inputs=[creator_service_dropdown, creator_secret_input, endpoints_state, creator_base_image_input, creator_overlay_option],
315
  outputs=[creator_image_output, creator_status]
316
  )
317