shadowbits 1.0.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.
shadowbits/cli.py ADDED
@@ -0,0 +1,74 @@
1
+ import argparse
2
+ from emb_img import embed_file
3
+ from ext_img import extract_file
4
+ from emb_aud import embed_audio
5
+ from ext_aud import extract_audio
6
+
7
+
8
+ def main():
9
+ parser = argparse.ArgumentParser(description='Steganography tool for hiding files in images')
10
+ subparsers = parser.add_subparsers(dest='command', help='Available commands')
11
+
12
+
13
+ # Embed command for image
14
+ img_parser = subparsers.add_parser('img', help='Image operations')
15
+ img_subparser = img_parser.add_subparsers(dest='action' , help='Image actions')
16
+ embed_cmd = img_subparser.add_parser('embed', help='Hide a file in an image')
17
+ embed_cmd.add_argument('--in', dest='input', required=True, help='File to hide')
18
+ embed_cmd.add_argument('--cover', dest='image', required=True, help='Cover image')
19
+ embed_cmd.add_argument('--key', required=True, help='Secret key for randomization')
20
+
21
+ # Extract command for image
22
+ extract_cmd = img_subparser.add_parser('extract', help='Extract hidden file from image')
23
+ extract_cmd.add_argument('--stego', required=True, help='Image with hidden file')
24
+ extract_cmd.add_argument('--key', required=True, help='Secret key used for hiding')
25
+
26
+ # Embed command for audio
27
+ aud_parser = subparsers.add_parser('aud', help='Audio operation')
28
+ aud_subparser = aud_parser.add_subparsers(dest='action', help='Audio actions')
29
+ embed_audio_cmd = aud_subparser.add_parser('embed', help='Hide a file in an audio file')
30
+ embed_audio_cmd.add_argument('--in', dest='input', required=True, help='File to hide')
31
+ embed_audio_cmd.add_argument('--cover', dest='song', required=True, help='Cover audio')
32
+ embed_audio_cmd.add_argument('--key', required=True, help='Secret key to randomize bits')
33
+
34
+ # Extract command for audio
35
+ extract_audio_cmd = aud_subparser.add_parser('extract', help='Extract hidden file from audio')
36
+ extract_audio_cmd.add_argument('--stego', required=True, help='Audio with hidden file')
37
+ extract_audio_cmd.add_argument('--key', required=True, help='Secret key used for hiding')
38
+
39
+
40
+ args = parser.parse_args()
41
+
42
+ if not args.command:
43
+ parser.print_help()
44
+ exit(1)
45
+
46
+ try:
47
+ if args.command == 'img':
48
+ if args.action == 'embed':
49
+ embed_file(args.image, args.input, args.key)
50
+ print(f"Successfully embedded {args.input} in {args.image}.")
51
+ elif args.action == 'extract':
52
+ extract_file(args.stego, args.key)
53
+ print(f"Successfully extracted hidden file from {args.stego}.")
54
+ else:
55
+ img_parser.print_help()
56
+ raise SystemExit(1)
57
+
58
+ elif args.command == 'aud':
59
+ if args.action == 'embed':
60
+ embed_audio(args.song, args.input, args.key)
61
+ print(f"Successfully embedded {args.input} in {args.song}.")
62
+ elif args.action == 'extract':
63
+ extract_audio(args.stego, args.key)
64
+ print(f"Successfully extracted hidden file from {args.stego}.")
65
+ else:
66
+ aud_parser.print_help()
67
+ raise SystemExit(1)
68
+
69
+ except Exception as e:
70
+ print(f"Error: {e}")
71
+ exit(1)
72
+
73
+ if __name__ == '__main__':
74
+ main()
@@ -0,0 +1,28 @@
1
+ import hashlib
2
+ from Crypto.Cipher import AES
3
+
4
+ def derive_key(password):
5
+ return hashlib.sha256(password.encode()).digest()
6
+
7
+ def to_seed(password: str) -> int:
8
+ digest = hashlib.sha256(password.encode()).digest()
9
+ return int.from_bytes(digest, 'big')
10
+
11
+ def encryption(payload, key):
12
+ cipher = AES.new(derive_key(key), AES.MODE_EAX)
13
+ ciphertext, tag = cipher.encrypt_and_digest(payload)
14
+ payload = cipher.nonce + tag + ciphertext
15
+ return payload
16
+
17
+ def decryption(payload, key):
18
+ if len(payload) < 32:
19
+ raise ValueError("Encrypted payload too short")
20
+ nonce = payload[:16]
21
+ tag = payload[16:32]
22
+ ciphertext = payload[32:]
23
+ cipher = AES.new(derive_key(key), AES.MODE_EAX, nonce=nonce)
24
+ try:
25
+ payload = cipher.decrypt_and_verify(ciphertext, tag)
26
+ return payload
27
+ except ValueError as e:
28
+ raise ValueError("Decryption failed - wrong key or corrupted data")
shadowbits/emb_aud.py ADDED
@@ -0,0 +1,99 @@
1
+ import wave
2
+ import random
3
+ from crypto.aes import *
4
+ import os
5
+ import time
6
+
7
+ def valid_wav(cover_path):
8
+ try:
9
+ with wave.open(cover_path, 'rb') as wave_file:
10
+ channels = wave_file.getnchannels()
11
+ sample_width = wave_file.getsampwidth()
12
+ framerate = wave_file.getframerate()
13
+ frames = wave_file.getnframes()
14
+
15
+ if not (1 <= channels <= 8):
16
+ return False
17
+ if sample_width not in [1, 2, 3, 4]:
18
+ return False
19
+ if not (8000 <= framerate <= 192000):
20
+ return False
21
+ if frames <= 0:
22
+ return False
23
+
24
+ wave_file.readframes(min(1024, frames))
25
+
26
+ return True
27
+
28
+ except (wave.Error, EOFError, FileNotFoundError, PermissionError):
29
+ return False
30
+ except Exception:
31
+ return False
32
+
33
+
34
+ def embed_audio(cover_path, payload_path, key):
35
+ start = time.time()
36
+ try:
37
+ if not os.path.exists(cover_path):
38
+ raise FileNotFoundError(f"Cover file path {cover_path} not found.")
39
+ if not os.path.exists(payload_path):
40
+ raise FileNotFoundError(f"Secret file path {payload_path} not found.")
41
+ if not valid_wav(cover_path):
42
+ raise ValueError(f"{cover_path} is not a valid WAV file")
43
+
44
+ print("Loading cover file...")
45
+ with wave.open(cover_path, mode='rb') as song:
46
+ frame_bytes = bytearray(song.readframes(song.getnframes()))
47
+ params = song.getparams()
48
+
49
+ print("Loading payload file...")
50
+ with open(payload_path, 'rb') as f:
51
+ payload = f.read()
52
+
53
+ print("Encrypting the payload's byte...")
54
+ payload = encryption(payload, key)
55
+
56
+ print("Attaching the markers on payload's bytes...")
57
+ starting = b'###START###'
58
+ ending = b'###END###'
59
+ full_payload = starting + payload + ending
60
+
61
+ print("Converting the payload's byte into bits...")
62
+ payload_bits = []
63
+ for byte in full_payload:
64
+ bits = format(byte, '08b')
65
+ payload_bits.extend([int(b) for b in bits])
66
+
67
+ print("Checking cover file capacity...")
68
+ max_payload_bits = len(frame_bytes)
69
+ if len(payload_bits) > max_payload_bits:
70
+ raise ValueError(f"Payload too large! Need {len(payload_bits)} bits but only have {max_payload_bits} available")
71
+
72
+ seed = to_seed(key)
73
+ shifu = random.Random(seed)
74
+ indexes = list(range(len(frame_bytes)))
75
+ shifu.shuffle(indexes)
76
+
77
+ print("Embedding the payload bits into cover file bytes...")
78
+ for bit_idx, bit in enumerate(payload_bits):
79
+ i = indexes[bit_idx]
80
+ frame_bytes[i] = (frame_bytes[i] & 0xFE) | bit
81
+
82
+ output_path = "encoded.wav"
83
+ if os.path.exists(output_path):
84
+ counter = 1
85
+ while os.path.exists(f"encoded({counter}).wav"):
86
+ counter += 1
87
+ output_path = f"encoded({counter}).wav"
88
+
89
+ print("Saving stego file...")
90
+ with wave.open(output_path, 'wb') as fd:
91
+ fd.setparams(params)
92
+ fd.writeframes(bytes(frame_bytes))
93
+ end = time.time() - start
94
+ print(f"Time taken: {int(end)} seconds.")
95
+ return output_path
96
+
97
+ except Exception as e:
98
+ print(f"Error in embed_audio: {e}")
99
+ raise
shadowbits/emb_img.py ADDED
@@ -0,0 +1,103 @@
1
+ from PIL import Image
2
+ import random
3
+ from crypto.aes import *
4
+ import os
5
+ import time
6
+
7
+ ### To varify if the image is valid
8
+ def valid_img(cover_path):
9
+ try:
10
+ with Image.open(cover_path) as img:
11
+ # Check format first
12
+ if img.format and img.format.upper() == "PNG":
13
+ img.verify() # Only verify if it claims to be PNG
14
+ return True
15
+ return False
16
+ except Exception:
17
+ return False
18
+
19
+
20
+ def embed_file(cover_path, payload_path, key):
21
+ start = time.time()
22
+ try:
23
+ if not os.path.exists(cover_path):
24
+ raise FileNotFoundError(f"Cover image {cover_path} not found.")
25
+ if not os.path.exists(payload_path):
26
+ raise FileNotFoundError(f"Payload file {payload_path} not found.")
27
+ if not valid_img(cover_path):
28
+ raise ValueError(f"{cover_path} is not a valid PNG image file.")
29
+
30
+ print("Loading cover file...")
31
+ img = Image.open(cover_path)
32
+ mode = img.mode
33
+
34
+ if mode != 'RGB':
35
+ print(f"Converting the {mode} mode to RGB mode...")
36
+ img = img.convert('RGB')
37
+
38
+ pixels = list(img.getdata())
39
+
40
+ print("Loading payload file in bytes...")
41
+ with open(payload_path, 'rb') as f:
42
+ payload = f.read()
43
+
44
+ print("Encrypting the payload bytes...")
45
+ payload = encryption(payload, key)
46
+
47
+ print("Attaching the markers on payload bytes...")
48
+ starting = b'###START###'
49
+ ending = b'###END###'
50
+
51
+ length = len(payload).to_bytes(4, 'big')
52
+ data = starting + length + payload + ending
53
+
54
+ print("Convertng payload bytes into bits...")
55
+ bits = ''.join(f'{byte:08b}' for byte in data)
56
+
57
+ max_bits = len(pixels) * 3 ### 3 color channels per pixel
58
+ print("Checking cover file capacity...")
59
+ if len(bits) > max_bits:
60
+ raise ValueError('Payload is too large to embed in cover file.')
61
+
62
+ seed = to_seed(key)
63
+ prng = random.Random(seed)
64
+ indexes = list(range(len(pixels)))
65
+ prng.shuffle(indexes)
66
+
67
+ print("Embedding data into image...")
68
+ new_pixels = [None] * len(pixels)
69
+ bit_idx = 0
70
+
71
+ for i in range(len(indexes)):
72
+ pixel_idx = indexes[i]
73
+ r, g, b = pixels[pixel_idx]
74
+
75
+ if bit_idx < len(bits):
76
+ r = (r & ~1) | int(bits[bit_idx])
77
+ bit_idx += 1
78
+ if bit_idx < len(bits):
79
+ g = (g & ~1) | int(bits[bit_idx])
80
+ bit_idx += 1
81
+ if bit_idx < len(bits):
82
+ b = (b & ~1) | int(bits[bit_idx])
83
+ bit_idx += 1
84
+
85
+ new_pixels[pixel_idx] = (r, g, b)
86
+
87
+ img.putdata(new_pixels)
88
+
89
+ output_path = "stego_file.png"
90
+ if os.path.exists(output_path):
91
+ counter = 1
92
+ while os.path.exists(f"stego_file({counter}).png"):
93
+ counter += 1
94
+ output_path = f"stego_file({counter}).png"
95
+
96
+ print("Saving the stego file...")
97
+ file = img.save(output_path)
98
+ end = time.time() - start
99
+ print(f"Time taken: {int(end)} seconds.")
100
+ return file
101
+ except Exception as e:
102
+ print(f"Error in embedding : {e}")
103
+ raise
shadowbits/ext_aud.py ADDED
@@ -0,0 +1,74 @@
1
+ import wave
2
+ from emb_aud import valid_wav
3
+ from crypto.aes import *
4
+ import os
5
+ import random
6
+ from validator import detect_file_type
7
+ import time
8
+
9
+ def extract_audio(stego_path, key):
10
+ start = time.time()
11
+ try:
12
+ if not valid_wav(stego_path):
13
+ raise ValueError(f"{stego_path} is not a valid wav file")
14
+
15
+ print("Loading stego file...")
16
+ with wave.open(stego_path, 'rb') as song:
17
+ frame_bytes = song.readframes(song.getnframes())
18
+
19
+ seed = to_seed(key)
20
+ shifu = random.Random(seed)
21
+ indexes = list(range(len(frame_bytes)))
22
+ shifu.shuffle(indexes)
23
+
24
+ print("Extracting the bits from stego file...")
25
+ extracted_bits = []
26
+ for i in indexes:
27
+ extracted_bits.append(frame_bytes[i] & 1)
28
+
29
+ print("Converting extracted bits into bytes...")
30
+ extracted_bytes = bytearray()
31
+ for i in range(0, len(extracted_bits), 8):
32
+ if i + 7 < len(extracted_bits):
33
+ byte_bits = extracted_bits[i:i+8]
34
+ byte_value = 0
35
+ for j, bit in enumerate(byte_bits):
36
+ byte_value |= bit << (7-j)
37
+ extracted_bytes.append(byte_value)
38
+
39
+ starting = b'###START###'
40
+ ending = b'###END###'
41
+ print("Locating markers in bytes...")
42
+ try:
43
+ start_byte = extracted_bytes.find(starting)
44
+ if start_byte == -1:
45
+ raise ValueError("Starting point not found - file may not contain embedded data or key is incorrect")
46
+
47
+ search = start_byte + len(starting)
48
+ end_byte = extracted_bytes.find(ending, search)
49
+ if end_byte == -1:
50
+ raise ValueError("Ending point not found - embedded data maybe corrupted")
51
+
52
+ print("Extracting payload's bytes from bytes....")
53
+ payload = bytes(extracted_bytes[search:end_byte])
54
+
55
+ except Exception as e:
56
+ print(f"Error finding payload data : {e}")
57
+ return None
58
+
59
+ print("Decrypting the payload's bytes...")
60
+ payload = decryption(payload, key)
61
+
62
+ extension, mime_type = detect_file_type(payload)
63
+ output_path = f"hidden_file.{extension}"
64
+
65
+ print("Saving the extracted file...")
66
+ with open(output_path, 'wb') as fd:
67
+ fd.write(payload)
68
+ end = time.time() - start
69
+ print(f"Time taken: {int(end)} seconds.")
70
+ return payload
71
+
72
+ except Exception as e:
73
+ print(f"Error in extracting data from file: {e}")
74
+ return None
shadowbits/ext_img.py ADDED
@@ -0,0 +1,91 @@
1
+ from PIL import Image
2
+ import random
3
+ from crypto.aes import *
4
+ import os
5
+ from emb_img import valid_img
6
+ from validator import detect_file_type
7
+ import time
8
+
9
+ def extract_file(stego_path, key):
10
+ start = time.time()
11
+ try:
12
+ if not os.path.exists(stego_path):
13
+ raise FileNotFoundError(f"stego image {stego_path} not found.")
14
+ if not valid_img(stego_path):
15
+ raise ValueError(f"Image is not a valid PNG file.")
16
+
17
+ print("Loading stego file...")
18
+ img = Image.open(stego_path)
19
+ pixels = list(img.getdata())
20
+
21
+ seed = to_seed(key)
22
+ prng = random.Random(seed)
23
+ indexes = list(range(len(pixels)))
24
+ prng.shuffle(indexes)
25
+
26
+ print("Extracting the bits from stego file...")
27
+ bits = ''
28
+ for i in range(len(pixels)):
29
+ pixel_idx = indexes[i]
30
+ r, g, b = pixels[pixel_idx]
31
+ bits += str(r & 1)
32
+ bits += str(g & 1)
33
+ bits += str(b & 1)
34
+
35
+
36
+ if len(bits) % 8 != 0:
37
+ bits = bits[:-(len(bits) % 8)] # Remove incomplete byte
38
+
39
+ print("Converting bits into bytes...")
40
+ bytes_list = bytes([int(bits[i:i+8], 2) for i in range(0, len(bits), 8)])
41
+
42
+ starting = b'###START###'
43
+ ending = b'###END###'
44
+
45
+ print("Locating markers in bytes...")
46
+ start_point = bytes_list.find(starting)
47
+ if start_point == -1:
48
+ raise ValueError("Starting marker of the payload not found in image or key is incorrect")
49
+
50
+ data_start = start_point + len(starting)
51
+
52
+ if len(bytes_list) < data_start + 4:
53
+ raise ValueError("Not enough data to extract length")
54
+
55
+ length = int.from_bytes(bytes_list[data_start:data_start + 4], 'big')
56
+
57
+ payload_start = data_start + 4
58
+ payload_end = payload_start + length
59
+
60
+ if len(bytes_list) < payload_end:
61
+ raise ValueError(f"Not enough data to extract payload of length {length}")
62
+
63
+ print("Extracting payload's bytes from bytes...")
64
+ payload = bytes_list[payload_start:payload_end]
65
+
66
+ print("Verifying the end marker in bytes...")
67
+ end_start = payload_end
68
+ end_end = end_start + len(ending)
69
+
70
+ if len(bytes_list) < end_end:
71
+ raise ValueError(f"Not enough data to verify the end marker.")
72
+
73
+ extract_end = bytes_list[end_start:end_end]
74
+ if extract_end != ending:
75
+ raise ValueError(f"End marker not found or corrupted")
76
+
77
+ print("Decrypting the payload bytes...")
78
+ payload = decryption(payload, key)
79
+
80
+ extension, mime_type = detect_file_type(payload)
81
+ out_path = f"extracted_file.{extension}"
82
+
83
+ print("Saving the extracted file...")
84
+ with open(out_path, 'wb') as f:
85
+ f.write(payload)
86
+ end = time.time() - start
87
+ print(f"Time taken: {int(end)} seconds.")
88
+ return out_path
89
+ except Exception as e:
90
+ print(f"Error in extracion : {e}")
91
+ raise
@@ -0,0 +1,87 @@
1
+ def detect_file_type(data):
2
+ """
3
+ Detect file type based on magic bytes (file signatures)
4
+ Returns tuple: (extension, mime_type)
5
+ """
6
+ if not data or len(data) < 4:
7
+ return 'bin', 'application/octet-stream'
8
+
9
+ # Common file signatures
10
+ signatures = {
11
+ # Images
12
+ b'\xFF\xD8\xFF': ('jpg', 'image/jpeg'),
13
+ b'\x89PNG\r\n\x1a\n': ('png', 'image/png'),
14
+ b'GIF87a': ('gif', 'image/gif'),
15
+ b'GIF89a': ('gif', 'image/gif'),
16
+ b'BM': ('bmp', 'image/bmp'),
17
+ b'RIFF': ('webp', 'image/webp'),
18
+ b'\x00\x00\x01\x00': ('ico', 'image/x-icon'),
19
+
20
+ # Documents
21
+ b'%PDF': ('pdf', 'application/pdf'),
22
+ b'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1': ('doc', 'application/msword'),
23
+ b'PK\x03\x04': ('zip', 'application/zip'), # Also docx, xlsx, etc.
24
+
25
+ # Audio
26
+ b'ID3': ('mp3', 'audio/mpeg'),
27
+ b'\xFF\xFB': ('mp3', 'audio/mpeg'),
28
+ b'\xFF\xF3': ('mp3', 'audio/mpeg'),
29
+ b'\xFF\xF2': ('mp3', 'audio/mpeg'),
30
+ b'OggS': ('ogg', 'audio/ogg'),
31
+ b'fLaC': ('flac', 'audio/flac'),
32
+
33
+ # Video
34
+ b'\x00\x00\x00\x18ftypmp4': ('mp4', 'video/mp4'),
35
+ b'\x00\x00\x00\x20ftypM4V': ('m4v', 'video/x-m4v'),
36
+ b'RIFF....AVI ': ('avi', 'video/x-msvideo'),
37
+
38
+ # Archives
39
+ b'\x50\x4B\x05\x06': ('zip', 'application/zip'),
40
+ b'\x50\x4B\x07\x08': ('zip', 'application/zip'),
41
+ b'\x1F\x8B\x08': ('gz', 'application/gzip'),
42
+ b'Rar!\x1A\x07\x00': ('rar', 'application/x-rar-compressed'),
43
+ b'7z\xBC\xAF\x27\x1C': ('7z', 'application/x-7z-compressed'),
44
+
45
+ # Text/Code
46
+ b'#!/bin/bash': ('sh', 'text/x-shellscript'),
47
+ b'#!/bin/sh': ('sh', 'text/x-shellscript'),
48
+ b'<?xml': ('xml', 'text/xml'),
49
+ b'<html': ('html', 'text/html'),
50
+ b'<!DOCTYPE html': ('html', 'text/html'),
51
+ }
52
+
53
+ for sig, (ext, mime) in signatures.items():
54
+ if data.startswith(sig):
55
+ # Special handling for RIFF files
56
+ if sig == b'RIFF' and len(data) >= 12:
57
+ if data[8:12] == b'WAVE':
58
+ return 'wav', 'audio/wav'
59
+ elif data[8:12] == b'WEBP':
60
+ return 'webp', 'image/webp'
61
+ elif data[8:12] == b'AVI ':
62
+ return 'avi', 'video/x-msvideo'
63
+ return ext, mime
64
+
65
+ # Check for text files
66
+ try:
67
+ text = data[:min(512, len(data))].decode('utf-8', errors='strict')
68
+ if all(ord(c) < 127 and (c.isprintable() or c.isspace()) for c in text):
69
+ # Further classify text files
70
+ text_lower = text.lower()
71
+ if any(keyword in text_lower for keyword in ['<html', '<!doctype', '<head', '<body']):
72
+ return 'html', 'text/html'
73
+ elif text.strip().startswith('<?xml'):
74
+ return 'xml', 'text/xml'
75
+ elif any(keyword in text_lower for keyword in ['import ', 'def ', 'class ', 'if __name__']):
76
+ return 'py', 'text/x-python'
77
+ elif any(keyword in text_lower for keyword in ['#include', 'int main', 'void ']):
78
+ return 'c', 'text/x-c'
79
+ elif any(keyword in text_lower for keyword in ['function', 'var ', 'let ', 'const ']):
80
+ return 'js', 'text/javascript'
81
+ else:
82
+ return 'txt', 'text/plain'
83
+ except UnicodeDecodeError:
84
+ pass
85
+
86
+ # If no signature matches, return binary
87
+ return 'bin', 'application/octet-stream'
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.4
2
+ Name: shadowbits
3
+ Version: 1.0.0
4
+ Summary: Powerful steganography tool for hiding files in images and audio using LSB techniques
5
+ Author-email: kaizoku73 <ultimate0134@gmail.com>
6
+ Maintainer-email: kaizoku73 <ultimate0134@gmail.com>
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/kaizoku73/ShadowBits
9
+ Project-URL: Repository, https://github.com/kaizoku73/ShadowBits
10
+ Project-URL: Bug Reports, https://github.com/kaizoku73/ShadowBits/issues
11
+ Project-URL: Documentation, https://kaizoku.gitbook.io/steganography
12
+ Project-URL: LSB Image Docs, https://kaizoku.gitbook.io/steganography/lsb-for-image
13
+ Project-URL: LSB Audio Docs, https://kaizoku.gitbook.io/steganography/lsb-for-audio
14
+ Keywords: steganography,lsb,least-significant-bit,image-steganography,audio-steganography,cryptography,security,png,wav,file-hiding,aes-encryption,bits-manipulation
15
+ Classifier: Development Status :: 5 - Production/Stable
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: End Users/Desktop
18
+ Classifier: Topic :: Security :: Cryptography
19
+ Classifier: Topic :: Multimedia :: Graphics
20
+ Classifier: Topic :: Multimedia :: Sound/Audio
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: License :: OSI Approved :: MIT License
23
+ Classifier: Programming Language :: Python :: 3
24
+ Classifier: Programming Language :: Python :: 3.7
25
+ Classifier: Programming Language :: Python :: 3.8
26
+ Classifier: Programming Language :: Python :: 3.9
27
+ Classifier: Programming Language :: Python :: 3.10
28
+ Classifier: Programming Language :: Python :: 3.11
29
+ Classifier: Programming Language :: Python :: 3.12
30
+ Classifier: Operating System :: OS Independent
31
+ Requires-Python: >=3.7
32
+ Description-Content-Type: text/markdown
33
+ License-File: LICENSE
34
+ Requires-Dist: future>=0.18.0
35
+ Requires-Dist: Pillow>=8.0.0
36
+ Requires-Dist: pycryptodome>=3.15.0
37
+ Dynamic: license-file
38
+
39
+ # ShadowBits 🔒
40
+
41
+ A powerful steganography tool that allows you to hide files within images and audio files using LSB (Least Significant Bit) techniques. ShadowBits supports both embedding and extraction operations with optional AES encryption for enhanced security.
42
+
43
+ ## Features
44
+
45
+ - **Image Steganography**: Hide files within PNG images using LSB manipulation
46
+ - **Audio Steganography**: Embed files in WAV audio files
47
+ - **AES Encryption**: Optional encryption layer for embedded data
48
+ - **Key-based Randomization**: Uses secret keys to randomize bit placement for enhanced security
49
+ - **Automatic File Type Detection**: Detects and preserves original file types during extraction
50
+ - **Format Validation**: Validates PNG images and WAV audio files before processing
51
+ - **Collision Prevention**: Automatically handles filename conflicts during output
52
+ - **Comprehensive Error Handling**: Robust error handling for various failure scenarios
53
+
54
+ ## Quick Start
55
+
56
+ ### Prerequisites
57
+
58
+ ```bash
59
+ pip install -r requirements.txt
60
+ ```
61
+ ### Installation
62
+
63
+ ```bash
64
+ pip install shadowbits
65
+ ```
66
+
67
+ ### One-Line Installation
68
+
69
+ ```bash
70
+ # System-wide (with sudo)
71
+ curl -sSL https://raw.githubusercontent.com/kaizoku73/ShadowBits/main/install.sh | sudo bash
72
+
73
+ # User installation (no sudo)
74
+ curl -sSL https://raw.githubusercontent.com/kaizoku73/ShadowBits/main/install.sh | bash
75
+ ```
76
+
77
+ ### Uninstallation
78
+
79
+ ```bash
80
+ # To uninstall Resono (no sudo):
81
+ curl -sSL https://raw.githubusercontent.com/kaizoku73/ShadowBits/main/uninstall.sh | bash
82
+
83
+ # To uninstall Resono (With sudo):
84
+ curl -sSL https://raw.githubusercontent.com/kaizoku73/ShadowBits/main/uninstall.sh | sudo bash
85
+
86
+ ```
87
+
88
+ ## Usage
89
+
90
+ ShadowBits provides a command-line interface with four main operations:
91
+
92
+ ### Image Operations
93
+
94
+ #### Embed a file in an image
95
+ ```bash
96
+ shadowbits img embed --in secret.txt --cover image.png --key mysecretkey
97
+ ```
98
+
99
+ #### Extract from image
100
+ ```bash
101
+ shadowbits img extract --stego stego_image.png --key mysecretkey
102
+ ```
103
+
104
+
105
+ ### Audio Operations
106
+
107
+ #### Embed a file in audio
108
+ ```bash
109
+ shadowbits aud embed --in secret.pdf --cover music.wav --key myaudiokey
110
+ ```
111
+
112
+ #### Extract from audio
113
+ ```bash
114
+ shadowbits aud extract --stego stego_audio.wav --key myaudiokey
115
+ ```
116
+
117
+ ## How It Works
118
+
119
+ ### LSB Steganography
120
+ ShadowBits uses the Least Significant Bit (LSB) method to hide data:
121
+
122
+ - **Images**: Modifies the least significant bit of RGB color channels in a randomized order
123
+ - **Audio**: Modifies the least significant bit of audio sample data in a randomized pattern
124
+
125
+ ## What is LSB and how does it work?
126
+ For a detailed explanation on LSB steganography and how it works, check out this article: https://kaizoku.gitbook.io/steganography
127
+
128
+ ### Security Features
129
+
130
+ 1. **Key-based Randomization**: Uses PRNG seeded with your secret key to randomize bit placement
131
+ 2. **Automatic AES Encryption**: All data is encrypted using AES-EAX mode with SHA-256 key derivation
132
+ 3. **Data Integrity Markers**: Uses start/end markers to ensure data completeness
133
+ 4. **Format Validation**: Verifies PNG/WAV file formats before processing
134
+ 5. **File Type Detection**: Automatically detects original file type using magic bytes for proper restoration
135
+
136
+ ## Hidden Files
137
+ The tool can hide any file type and will automatically detect and restore the original format using magic byte signatures, including:
138
+ - Images: JPG, PNG, GIF, BMP, WebP, ICO
139
+ - Documents: PDF, DOC, ZIP archives
140
+ - Audio: MP3, OGG, FLAC, WAV
141
+ - Video: MP4, M4V, AVI
142
+ - Archives: ZIP, GZ, RAR, 7Z
143
+ - Text/Code: HTML, XML, Python, C, JavaScript, plain text
144
+ - Binary files: Any other format as .bin
145
+
146
+
147
+ ## Limitations
148
+
149
+ - **Image capacity**: Limited by image size (3 bits per pixel for RGB images)
150
+ - **Audio capacity**: Limited by audio file length (1 bit per sample)
151
+ - **File size**: To hide larger files, you need larger cover media file
152
+
153
+ ## Examples
154
+
155
+ ### Hide a document in a photo
156
+ ```bash
157
+ shadowbits img embed --in document.pdf --cover vacation.jpg --key family2023
158
+ ```
159
+
160
+ ### Extract the hidden document
161
+ ```bash
162
+ shadowbits img extract --stego stego_file.png --key family2023
163
+ ```
164
+
165
+ ### Hide source code in music
166
+ ```bash
167
+ shadowbits aud embed --in source_code.zip --cover favorite_song.mp3 --key coding123
168
+ ```
169
+
170
+ ## Security Considerations
171
+
172
+ - **Key Management**: Use strong, unique keys for each operation
173
+ - **Key Security**: The same key is used for both encryption and randomization
174
+ - **Cover Selection**: Choose cover files with sufficient capacity for your payload
175
+ - **File Format**: Ensure cover images are PNG and audio files are WAV
176
+ - **Key Reuse**: Avoid reusing keys across different files
177
+
178
+ ## Error Handling
179
+
180
+ ShadowBits includes comprehensive error handling for:
181
+ - Invalid file formats
182
+ - Insufficient cover media capacity
183
+ - Corrupted embedded data or invalid markers
184
+ - AES Decryption failures (wrong key or corrupted data)
185
+ - Missing files or permissions
186
+
187
+ ## Contributing
188
+
189
+ Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests.
190
+
191
+ ## License
192
+
193
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
194
+
195
+ ## Disclaimer
196
+
197
+ This tool is for educational and legitimate purposes only. Users are responsible for ensuring compliance with applicable laws and regulations when using steganography techniques.
198
+
199
+ ## Support
200
+
201
+ If you encounter any issues or have questions, please open an issue on GitHub.
202
+
203
+ ---
204
+
205
+ **ShadowBits** - Where your secrets hide in plain sight 👁️‍🗨️
206
+
207
+ **Made by kaizoku**
@@ -0,0 +1,13 @@
1
+ shadowbits/cli.py,sha256=I9yQkazd9epOsbbPNjG8RWCVYzZCtWTErRX2PPP0xiQ,3315
2
+ shadowbits/emb_aud.py,sha256=5kGFYC-r59pWMrgnW3ktDc2h6-Ix_vfyrqGMC7-Dju0,3403
3
+ shadowbits/emb_img.py,sha256=XGZG9GA4za5Upu0oPgRpfh4k4cPBdgRjs6AvlDg4x0k,3382
4
+ shadowbits/ext_aud.py,sha256=SzXs97EedBu3v6iO25ZCLBRAV7tkYRa4XSP3shCx5eE,2592
5
+ shadowbits/ext_img.py,sha256=tgNOlnMVYUEOk3rTXFbekrGdk_emIuDw5ysD82eOlHw,3161
6
+ shadowbits/validator.py,sha256=uauRuxAY4KMfQJpxew-VKuMEx6DYu3wXfsHr0C5TlsY,3544
7
+ shadowbits/crypto/aes.py,sha256=J55J5PN89zqakhOxmAk-Zc6uQwCFifEXTAexxLAF5Q8,945
8
+ shadowbits-1.0.0.dist-info/licenses/LICENSE,sha256=iEyXbm1ZX-a9ro7ubAKtd4A7JsYWHqC6WTC5rWukeNs,1065
9
+ shadowbits-1.0.0.dist-info/METADATA,sha256=m0o93hk-rcKVw76nYlg3Aszvsbwi22PadODkOP89Q08,7306
10
+ shadowbits-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ shadowbits-1.0.0.dist-info/entry_points.txt,sha256=oXTWczfc9Y5-Fa7svu9DdEDj4qzEPNhmymENMRS1C5w,51
12
+ shadowbits-1.0.0.dist-info/top_level.txt,sha256=Di2Wya3HFPV4vkFBc8SAkbeWVPvRglnG_yE7ZaQOHzM,11
13
+ shadowbits-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ shadowbits = shadowbits.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 kaizoku73
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ shadowbits