ghostbit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ghostbit/__init__.py ADDED
@@ -0,0 +1,26 @@
1
+ """
2
+ Ghostbit - A Python library for image steganography using LSB encoding.
3
+
4
+ This library provides functions for encoding and decoding secret messages
5
+ in images using Least Significant Bit (LSB) steganography.
6
+ """
7
+
8
+ from .steganography import (
9
+ encode_lsb,
10
+ decode_lsb,
11
+ encode_dct,
12
+ decode_dct,
13
+ calculate_psnr,
14
+ detect_hidden_data,
15
+ )
16
+
17
+ __version__ = "0.1.0"
18
+ __all__ = [
19
+ # Steganography functions
20
+ "encode_lsb",
21
+ "decode_lsb",
22
+ "encode_dct",
23
+ "decode_dct",
24
+ "calculate_psnr",
25
+ "detect_hidden_data",
26
+ ]
@@ -0,0 +1,371 @@
1
+ """
2
+ Steganography implementation with LSB and DCT methods.
3
+ Supports embedding and extracting secret messages in images.
4
+ """
5
+
6
+ from PIL import Image
7
+ import numpy as np
8
+ from scipy.fft import dct, idct
9
+
10
+
11
+ def encode_lsb(img_path, message, output_path):
12
+ """
13
+ Encode a message into an image using LSB (Least Significant Bit) steganography.
14
+
15
+ Args:
16
+ img_path: Path to the cover image
17
+ message: Secret message to embed
18
+ output_path: Path to save the stego-image
19
+
20
+ Raises:
21
+ ValueError: If the image is too small to hold the message
22
+ """
23
+ img = Image.open(img_path)
24
+
25
+ # Convert to RGB if necessary
26
+ if img.mode != 'RGB':
27
+ img = img.convert('RGB')
28
+
29
+ width, height = img.size
30
+ pixels = np.array(img)
31
+
32
+ # Convert message to binary string
33
+ message += chr(0) # Null delimiter
34
+ binary_message = ''.join(format(ord(c), '08b') for c in message)
35
+ message_length = len(binary_message)
36
+
37
+ # Check if image has enough capacity (3 bits per pixel)
38
+ max_capacity = width * height * 3
39
+ if message_length > max_capacity:
40
+ raise ValueError(f"Message too long. Max capacity: {max_capacity} bits, "
41
+ f"message requires: {message_length} bits")
42
+
43
+ # Embed message bits into LSBs
44
+ bit_index = 0
45
+ for y in range(height):
46
+ for x in range(width):
47
+ for channel in range(3): # RGB channels
48
+ if bit_index < message_length:
49
+ # Clear LSB and set it to message bit
50
+ # Use 0xFE (254) instead of ~1 to avoid signed integer issues with uint8
51
+ pixels[y, x, channel] = (pixels[y, x, channel] & 0xFE) | int(binary_message[bit_index])
52
+ bit_index += 1
53
+ else:
54
+ break
55
+ if bit_index >= message_length:
56
+ break
57
+ if bit_index >= message_length:
58
+ break
59
+
60
+ # Save stego-image
61
+ stego_img = Image.fromarray(pixels)
62
+ stego_img.save(output_path)
63
+ print(f"Message encoded successfully. Saved to {output_path}")
64
+
65
+
66
+ def decode_lsb(img_path):
67
+ """
68
+ Decode a message from an image using LSB steganography.
69
+
70
+ Args:
71
+ img_path: Path to the stego-image
72
+
73
+ Returns:
74
+ Extracted secret message
75
+ """
76
+ img = Image.open(img_path)
77
+
78
+ # Convert to RGB if necessary
79
+ if img.mode != 'RGB':
80
+ img = img.convert('RGB')
81
+
82
+ width, height = img.size
83
+ pixels = np.array(img)
84
+
85
+ # Extract LSBs
86
+ binary_message = []
87
+ for y in range(height):
88
+ for x in range(width):
89
+ for channel in range(3): # RGB channels
90
+ # Extract LSB
91
+ binary_message.append(str(pixels[y, x, channel] & 1))
92
+
93
+ # Convert binary string to message
94
+ message = []
95
+ for i in range(0, len(binary_message), 8):
96
+ byte = ''.join(binary_message[i:i+8])
97
+ if len(byte) == 8:
98
+ char = chr(int(byte, 2))
99
+ if char == chr(0): # Null delimiter found
100
+ return ''.join(message)
101
+ message.append(char)
102
+
103
+ return ''.join(message)
104
+
105
+
106
+ def encode_dct(img_path, message, output_path, block_size=8):
107
+ """
108
+ Encode a message into an image using DCT (Discrete Cosine Transform) steganography.
109
+
110
+ Args:
111
+ img_path: Path to the cover image
112
+ message: Secret message to embed
113
+ output_path: Path to save the stego-image
114
+ block_size: Size of DCT blocks (default: 8x8)
115
+
116
+ Raises:
117
+ ValueError: If the image is too small to hold the message
118
+ """
119
+ img = Image.open(img_path)
120
+
121
+ # Convert to RGB if necessary
122
+ if img.mode != 'RGB':
123
+ img = img.convert('RGB')
124
+
125
+ width, height = img.size
126
+ pixels = np.array(img, dtype=np.float64)
127
+
128
+ # Convert message to binary string
129
+ message += chr(0) # Null delimiter
130
+ binary_message = ''.join(format(ord(c), '08b') for c in message)
131
+ message_length = len(binary_message)
132
+
133
+ # Calculate capacity (using middle frequency coefficients)
134
+ num_blocks_x = width // block_size
135
+ num_blocks_y = height // block_size
136
+ # Use 1 coefficient per block per channel (middle frequency position)
137
+ max_capacity = num_blocks_x * num_blocks_y * 3
138
+
139
+ if message_length > max_capacity:
140
+ raise ValueError(f"Message too long. Max capacity: {max_capacity} bits, "
141
+ f"message requires: {message_length} bits")
142
+
143
+ # Process each channel separately
144
+ bit_index = 0
145
+ for channel in range(3):
146
+ channel_data = pixels[:, :, channel]
147
+
148
+ for block_y in range(num_blocks_y):
149
+ for block_x in range(num_blocks_x):
150
+ if bit_index >= message_length:
151
+ break
152
+
153
+ # Extract 8x8 block
154
+ y_start = block_y * block_size
155
+ y_end = y_start + block_size
156
+ x_start = block_x * block_size
157
+ x_end = x_start + block_size
158
+
159
+ block = channel_data[y_start:y_end, x_start:x_end]
160
+
161
+ # Apply DCT
162
+ dct_block = dct(dct(block, axis=0, norm='ortho'), axis=1, norm='ortho')
163
+
164
+ # Embed bit in middle frequency coefficient (position 3,3)
165
+ embed_pos = (3, 3)
166
+ if bit_index < message_length:
167
+ bit_value = int(binary_message[bit_index])
168
+ # Quantize and embed: if bit is 1, make quantized coefficient odd; if 0, make even
169
+ coeff = dct_block[embed_pos]
170
+ quantized = int(np.round(coeff))
171
+ # Adjust to match desired bit value
172
+ if (quantized % 2) != bit_value:
173
+ quantized = quantized + 1 if quantized >= 0 else quantized - 1
174
+ dct_block[embed_pos] = quantized
175
+ bit_index += 1
176
+
177
+ # Apply inverse DCT
178
+ idct_block = idct(idct(dct_block, axis=0, norm='ortho'), axis=1, norm='ortho')
179
+
180
+ # Update channel data
181
+ channel_data[y_start:y_end, x_start:x_end] = idct_block
182
+
183
+ if bit_index >= message_length:
184
+ break
185
+
186
+ pixels[:, :, channel] = channel_data
187
+
188
+ # Clip values to valid range and convert back to uint8
189
+ pixels = np.clip(pixels, 0, 255).astype(np.uint8)
190
+
191
+ # Save stego-image
192
+ stego_img = Image.fromarray(pixels)
193
+ stego_img.save(output_path)
194
+ print(f"Message encoded successfully using DCT. Saved to {output_path}")
195
+
196
+
197
+ def decode_dct(img_path, block_size=8):
198
+ """
199
+ Decode a message from an image using DCT steganography.
200
+
201
+ Args:
202
+ img_path: Path to the stego-image
203
+ block_size: Size of DCT blocks (default: 8x8)
204
+
205
+ Returns:
206
+ Extracted secret message
207
+ """
208
+ img = Image.open(img_path)
209
+
210
+ # Convert to RGB if necessary
211
+ if img.mode != 'RGB':
212
+ img = img.convert('RGB')
213
+
214
+ width, height = img.size
215
+ pixels = np.array(img, dtype=np.float64)
216
+
217
+ # Extract bits from DCT coefficients
218
+ binary_message = []
219
+ num_blocks_x = width // block_size
220
+ num_blocks_y = height // block_size
221
+
222
+ for channel in range(3):
223
+ channel_data = pixels[:, :, channel]
224
+
225
+ for block_y in range(num_blocks_y):
226
+ for block_x in range(num_blocks_x):
227
+ # Extract 8x8 block
228
+ y_start = block_y * block_size
229
+ y_end = y_start + block_size
230
+ x_start = block_x * block_size
231
+ x_end = x_start + block_size
232
+
233
+ block = channel_data[y_start:y_end, x_start:x_end]
234
+
235
+ # Apply DCT
236
+ dct_block = dct(dct(block, axis=0, norm='ortho'), axis=1, norm='ortho')
237
+
238
+ # Extract bit from middle frequency coefficient (position 3,3)
239
+ embed_pos = (3, 3)
240
+ coeff = dct_block[embed_pos]
241
+ # Extract LSB of quantized coefficient
242
+ bit_value = int(np.round(coeff)) % 2
243
+ binary_message.append(str(bit_value))
244
+
245
+ # Convert binary string to message
246
+ message = []
247
+ for i in range(0, len(binary_message), 8):
248
+ byte = ''.join(binary_message[i:i+8])
249
+ if len(byte) == 8:
250
+ char = chr(int(byte, 2))
251
+ if char == chr(0): # Null delimiter found
252
+ return ''.join(message)
253
+ message.append(char)
254
+
255
+ return ''.join(message)
256
+
257
+
258
+ def calculate_psnr(img1_path, img2_path):
259
+ """
260
+ Calculate PSNR (Peak Signal-to-Noise Ratio) between two images.
261
+
262
+ Args:
263
+ img1_path: Path to first image
264
+ img2_path: Path to second image
265
+
266
+ Returns:
267
+ PSNR value in dB
268
+ """
269
+ img1 = np.array(Image.open(img1_path).convert('RGB'))
270
+ img2 = np.array(Image.open(img2_path).convert('RGB'))
271
+
272
+ mse = np.mean((img1 - img2) ** 2)
273
+ if mse == 0:
274
+ return float('inf')
275
+
276
+ max_pixel = 255.0
277
+ psnr = 20 * np.log10(max_pixel / np.sqrt(mse))
278
+ return psnr
279
+
280
+
281
+ def detect_hidden_data(img_path, method='decode'):
282
+ """
283
+ Detect whether an image contains hidden data using LSB steganography.
284
+
285
+ Args:
286
+ img_path: Path to the image to analyze
287
+ method: Detection method to use:
288
+ - 'decode': Try to decode and check for null-terminated message (fast, reliable)
289
+ - 'statistical': Use statistical analysis of LSB distribution
290
+ - 'both': Use both methods and return combined result
291
+
292
+ Returns:
293
+ dict with keys:
294
+ - has_hidden_data: bool indicating if hidden data was detected
295
+ - confidence: float between 0.0 and 1.0 indicating confidence level
296
+ - method_used: str indicating which method(s) were used
297
+ - decoded_message: str if decode method found a message (None otherwise)
298
+ - message_length: int length of decoded message if found (None otherwise)
299
+ """
300
+ img = Image.open(img_path)
301
+
302
+ # Convert to RGB if necessary
303
+ if img.mode != 'RGB':
304
+ img = img.convert('RGB')
305
+
306
+ width, height = img.size
307
+ pixels = np.array(img)
308
+
309
+ result = {
310
+ 'has_hidden_data': False,
311
+ 'confidence': 0.0,
312
+ 'method_used': method,
313
+ 'decoded_message': None,
314
+ 'message_length': None
315
+ }
316
+
317
+ # Method 1: Try to decode and check for null-terminated message
318
+ if method in ('decode', 'both'):
319
+ try:
320
+ decoded = decode_lsb(img_path)
321
+ if decoded: # Found a non-empty null-terminated message
322
+ result['has_hidden_data'] = True
323
+ result['confidence'] = 0.95 # High confidence if decode succeeds
324
+ result['decoded_message'] = decoded
325
+ result['message_length'] = len(decoded)
326
+ if method == 'decode':
327
+ return result
328
+ except Exception:
329
+ pass # Decode failed, continue with other methods
330
+
331
+ # Method 2: Statistical analysis of LSB distribution
332
+ if method in ('statistical', 'both'):
333
+ # Extract all LSBs
334
+ lsbs = pixels & 1
335
+ total_bits = width * height * 3
336
+
337
+ # Count 0s and 1s in LSBs
338
+ zeros = np.sum(lsbs == 0)
339
+ ones = np.sum(lsbs == 1)
340
+
341
+ # In natural images, LSBs should be roughly 50/50 (random)
342
+ # If there's hidden data, we might see patterns
343
+ # Check if distribution is significantly skewed
344
+ expected = total_bits / 2
345
+ if total_bits > 0:
346
+ deviation = abs(zeros - expected) / expected
347
+
348
+ # Also check for patterns: consecutive same bits might indicate data
349
+ # Sample a subset to check for patterns
350
+ sample_size = min(10000, total_bits)
351
+ sample_indices = np.random.choice(total_bits, sample_size, replace=False)
352
+ sample_lsbs = lsbs.flatten()[sample_indices]
353
+
354
+ # Count transitions (0->1 or 1->0)
355
+ transitions = np.sum(np.diff(sample_lsbs) != 0)
356
+ transition_ratio = transitions / (sample_size - 1) if sample_size > 1 else 0
357
+
358
+ # Natural images have high transition ratio (~0.5)
359
+ # Hidden data might have lower transition ratio if it's structured
360
+ # But this is not definitive, so we use low confidence
361
+
362
+ # If deviation is very high or transition ratio is very low, might indicate data
363
+ if deviation > 0.1 or transition_ratio < 0.3:
364
+ if method == 'statistical':
365
+ result['has_hidden_data'] = True
366
+ result['confidence'] = 0.3 # Low confidence for statistical method
367
+ elif method == 'both' and not result['has_hidden_data']:
368
+ result['has_hidden_data'] = True
369
+ result['confidence'] = 0.4 # Medium confidence when decode failed but stats suggest data
370
+
371
+ return result
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: ghostbit
3
+ Version: 0.1.0
4
+ Summary: A Python library for image steganography using LSB encoding
5
+ Home-page: https://github.com/yourusername/ghostbit-py
6
+ Author: Ghostbit
7
+ Author-email:
8
+ License: MIT
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Topic :: Security :: Cryptography
18
+ Classifier: Topic :: Multimedia :: Graphics
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: Pillow>=10.0.0
22
+ Requires-Dist: numpy>=1.24.0
23
+ Requires-Dist: scipy>=1.10.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
26
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
27
+ Requires-Dist: black>=23.0.0; extra == "dev"
28
+ Requires-Dist: flake8>=6.0.0; extra == "dev"
29
+ Dynamic: home-page
30
+ Dynamic: requires-python
31
+
32
+ # Ghostbit
33
+
34
+ A Python library for image steganography using LSB (Least Significant Bit) encoding.
35
+
36
+ ## Features
37
+
38
+ - **LSB Steganography**: Encode and decode secret messages in images using the Least Significant Bit method
39
+ - **DCT Steganography**: Alternative encoding method using Discrete Cosine Transform
40
+ - **Image Analysis**: Detect hidden data and calculate image quality metrics
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install ghostbit
46
+ ```
47
+
48
+ Or install from source:
49
+
50
+ ```bash
51
+ cd ghostbit-py
52
+ pip install -e .
53
+ ```
54
+
55
+ ## Quick Start
56
+
57
+ ```python
58
+ from ghostbit import encode_lsb, decode_lsb
59
+
60
+ # Encode a message into an image
61
+ encode_lsb("cover_image.png", "secret message", "stego_image.png")
62
+
63
+ # Decode a message from an image
64
+ message = decode_lsb("stego_image.png")
65
+ print(message) # Output: secret message
66
+ ```
67
+
68
+ ## Usage Examples
69
+
70
+ ### Basic Encoding/Decoding
71
+
72
+ ```python
73
+ from ghostbit import encode_lsb, decode_lsb
74
+
75
+ # Encode a secret message
76
+ encode_lsb("input.png", "My secret password", "output.png")
77
+
78
+ # Decode the message
79
+ secret = decode_lsb("output.png")
80
+ ```
81
+
82
+
83
+ ## API Reference
84
+
85
+ ### Steganography Functions
86
+
87
+ - `encode_lsb(img_path, message, output_path)` - Encode message using LSB
88
+ - `decode_lsb(img_path)` - Decode message using LSB
89
+ - `encode_dct(img_path, message, output_path, block_size=8)` - Encode using DCT
90
+ - `decode_dct(img_path, block_size=8)` - Decode using DCT
91
+ - `detect_hidden_data(img_path, method='decode')` - Detect hidden data
92
+ - `calculate_psnr(img1_path, img2_path)` - Calculate PSNR between images
93
+
94
+ ## Requirements
95
+
96
+ - Python >= 3.8
97
+ - Pillow >= 10.0.0
98
+ - numpy >= 1.24.0
99
+ - scipy >= 1.10.0
100
+
101
+ ## License
102
+
103
+ MIT License
104
+
105
+ ## Contributing
106
+
107
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -0,0 +1,6 @@
1
+ ghostbit/__init__.py,sha256=lTZbSa16_zSwWs6FdWAX9DxliYbzkiJkvcO5Cq9mJeI,541
2
+ ghostbit/steganography.py,sha256=AGEKNruPQcBPTiKS6N1iTBVvq-9j6pBW_J9QddoOW_k,13105
3
+ ghostbit-0.1.0.dist-info/METADATA,sha256=HuReFT9M7-uHvzRJYaYHprtOhb3mxGClkYwV0Lg7U2Q,2802
4
+ ghostbit-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
5
+ ghostbit-0.1.0.dist-info/top_level.txt,sha256=fBF0BQJwbVqWrpkBZ2Em9HFHEQsAljKO5-PjcEVZ98o,9
6
+ ghostbit-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ ghostbit