ghostrate 1.0.0__tar.gz
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.
- ghostrate-1.0.0/PKG-INFO +100 -0
- ghostrate-1.0.0/README.md +75 -0
- ghostrate-1.0.0/pyproject.toml +41 -0
- ghostrate-1.0.0/setup.cfg +4 -0
- ghostrate-1.0.0/setup.py +29 -0
- ghostrate-1.0.0/src/ghostrate/__init__.py +34 -0
- ghostrate-1.0.0/src/ghostrate/cli.py +259 -0
- ghostrate-1.0.0/src/ghostrate.egg-info/PKG-INFO +100 -0
- ghostrate-1.0.0/src/ghostrate.egg-info/SOURCES.txt +10 -0
- ghostrate-1.0.0/src/ghostrate.egg-info/dependency_links.txt +1 -0
- ghostrate-1.0.0/src/ghostrate.egg-info/entry_points.txt +2 -0
- ghostrate-1.0.0/src/ghostrate.egg-info/top_level.txt +1 -0
ghostrate-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ghostrate
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Audio Steganography & Forensic Tool — Hide and reveal spoken messages in music
|
|
5
|
+
Home-page: https://github.com/jawad/ghostrate
|
|
6
|
+
Author: Jawad
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/jawad/ghostrate
|
|
9
|
+
Keywords: audio,steganography,forensics,sound,dsp,encryption,watermark
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
14
|
+
Classifier: Topic :: Security :: Cryptography
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
22
|
+
Classifier: Operating System :: OS Independent
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# GhostRate 🎙️🕵️♂️
|
|
27
|
+
|
|
28
|
+
> **Audio Steganography & Forensic Suite — Web Studio + Python CLI.**
|
|
29
|
+
|
|
30
|
+
GhostRate embeds hidden spoken voice messages inside any music track using **Phase Mid-Side Inversion** and **Phantom Center Balancing**.
|
|
31
|
+
|
|
32
|
+
* **Normal Ear:** Plays as a completely standard stereo song on headphones, speakers, or car audio.
|
|
33
|
+
* **Filter-Immune:** Survives external audio converters, resamplers, and anti-aliasing downsampling (unlike fragile high-frequency tricks).
|
|
34
|
+
* **Forensic Extraction:** Mid-side phase cancellation $(L - R)$ cancels the host music into dead silence, revealing the secret spoken voice with $+18\text{ dB}$ make-up gain.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 💻 Web Studio (In-Browser)
|
|
39
|
+
|
|
40
|
+
The Web Studio is a client-side TypeScript application featuring real-time visualizers, microphone recording, stealth gain adjustment, and a 1-click **Universal Decoder**.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Start local dev server
|
|
44
|
+
npm run dev
|
|
45
|
+
|
|
46
|
+
# Run automated test suite
|
|
47
|
+
npm test
|
|
48
|
+
|
|
49
|
+
# Build production bundle
|
|
50
|
+
npm run build
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 🐍 Python CLI (Offline Forensic Tool)
|
|
56
|
+
|
|
57
|
+
GhostRate includes a standalone Python CLI in `python/ghostrate.py` with **zero external dependencies** (uses only Python's standard library: `wave`, `struct`, `argparse`). It can be run on any air-gapped machine, Linux server, or offline terminal.
|
|
58
|
+
|
|
59
|
+
### 1. Hide Voice Inside Music (With Optional Passcode Encryption)
|
|
60
|
+
```bash
|
|
61
|
+
# Public (any L-R decoder reveals it):
|
|
62
|
+
python3 python/ghostrate.py hide music.wav voice.wav -o stego_song.wav --stealth 0.06
|
|
63
|
+
|
|
64
|
+
# Encrypted (requires passcode to descramble):
|
|
65
|
+
python3 python/ghostrate.py hide music.wav voice.wav -o stego_song.wav --stealth 0.06 --key "MySecret123"
|
|
66
|
+
```
|
|
67
|
+
* `--stealth 0.06`: Adjusts voice stealth level (`0.03` for invisible, `0.06` for balanced, `0.15` for louder).
|
|
68
|
+
* `--key "password"`: Scrambles the voice using a deterministic pseudo-random key sequence. Without this key, decoding yields only white noise!
|
|
69
|
+
|
|
70
|
+
### 2. Reveal Secret Message from Any Stego File
|
|
71
|
+
```bash
|
|
72
|
+
# Public stego file:
|
|
73
|
+
python3 python/ghostrate.py reveal stego_song.wav -o revealed_secret.wav
|
|
74
|
+
|
|
75
|
+
# Encrypted stego file:
|
|
76
|
+
python3 python/ghostrate.py reveal stego_song.wav -o revealed_secret.wav --key "MySecret123"
|
|
77
|
+
```
|
|
78
|
+
Cancels out the host music and descrambles the isolated speech.
|
|
79
|
+
|
|
80
|
+
### 3. Inspect Audio File Phase Correlation
|
|
81
|
+
```bash
|
|
82
|
+
python3 python/ghostrate.py info stego_song.wav
|
|
83
|
+
```
|
|
84
|
+
Analyzes stereo difference energy to detect whether an audio track contains a hidden payload.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 🔬 How the Math Works
|
|
89
|
+
|
|
90
|
+
1. **Phantom Center Normalization:**
|
|
91
|
+
Most commercial songs have wide stereo guitars or synths ($L \ne R$). GhostRate first sums both channels into a phantom center mono base:
|
|
92
|
+
$$M[n] = \frac{\text{Music}_L[n] + \text{Music}_R[n]}{2}$$
|
|
93
|
+
|
|
94
|
+
2. **Phase Inversion Embedding:**
|
|
95
|
+
$$\text{Left Ear} = M[n] + g \cdot \text{Voice}[n]$$
|
|
96
|
+
$$\text{Right Ear} = M[n] - g \cdot \text{Voice}[n]$$
|
|
97
|
+
|
|
98
|
+
3. **Phase Cancellation Decoding:**
|
|
99
|
+
$$\text{Decoded} = \text{Left} - \text{Right} = (M - M) + 2g \cdot \text{Voice} = \mathbf{2g \cdot \text{Voice}}$$
|
|
100
|
+
The music vanishes to $0$, and the voice doubles in volume!
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# GhostRate 🎙️🕵️♂️
|
|
2
|
+
|
|
3
|
+
> **Audio Steganography & Forensic Suite — Web Studio + Python CLI.**
|
|
4
|
+
|
|
5
|
+
GhostRate embeds hidden spoken voice messages inside any music track using **Phase Mid-Side Inversion** and **Phantom Center Balancing**.
|
|
6
|
+
|
|
7
|
+
* **Normal Ear:** Plays as a completely standard stereo song on headphones, speakers, or car audio.
|
|
8
|
+
* **Filter-Immune:** Survives external audio converters, resamplers, and anti-aliasing downsampling (unlike fragile high-frequency tricks).
|
|
9
|
+
* **Forensic Extraction:** Mid-side phase cancellation $(L - R)$ cancels the host music into dead silence, revealing the secret spoken voice with $+18\text{ dB}$ make-up gain.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 💻 Web Studio (In-Browser)
|
|
14
|
+
|
|
15
|
+
The Web Studio is a client-side TypeScript application featuring real-time visualizers, microphone recording, stealth gain adjustment, and a 1-click **Universal Decoder**.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Start local dev server
|
|
19
|
+
npm run dev
|
|
20
|
+
|
|
21
|
+
# Run automated test suite
|
|
22
|
+
npm test
|
|
23
|
+
|
|
24
|
+
# Build production bundle
|
|
25
|
+
npm run build
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 🐍 Python CLI (Offline Forensic Tool)
|
|
31
|
+
|
|
32
|
+
GhostRate includes a standalone Python CLI in `python/ghostrate.py` with **zero external dependencies** (uses only Python's standard library: `wave`, `struct`, `argparse`). It can be run on any air-gapped machine, Linux server, or offline terminal.
|
|
33
|
+
|
|
34
|
+
### 1. Hide Voice Inside Music (With Optional Passcode Encryption)
|
|
35
|
+
```bash
|
|
36
|
+
# Public (any L-R decoder reveals it):
|
|
37
|
+
python3 python/ghostrate.py hide music.wav voice.wav -o stego_song.wav --stealth 0.06
|
|
38
|
+
|
|
39
|
+
# Encrypted (requires passcode to descramble):
|
|
40
|
+
python3 python/ghostrate.py hide music.wav voice.wav -o stego_song.wav --stealth 0.06 --key "MySecret123"
|
|
41
|
+
```
|
|
42
|
+
* `--stealth 0.06`: Adjusts voice stealth level (`0.03` for invisible, `0.06` for balanced, `0.15` for louder).
|
|
43
|
+
* `--key "password"`: Scrambles the voice using a deterministic pseudo-random key sequence. Without this key, decoding yields only white noise!
|
|
44
|
+
|
|
45
|
+
### 2. Reveal Secret Message from Any Stego File
|
|
46
|
+
```bash
|
|
47
|
+
# Public stego file:
|
|
48
|
+
python3 python/ghostrate.py reveal stego_song.wav -o revealed_secret.wav
|
|
49
|
+
|
|
50
|
+
# Encrypted stego file:
|
|
51
|
+
python3 python/ghostrate.py reveal stego_song.wav -o revealed_secret.wav --key "MySecret123"
|
|
52
|
+
```
|
|
53
|
+
Cancels out the host music and descrambles the isolated speech.
|
|
54
|
+
|
|
55
|
+
### 3. Inspect Audio File Phase Correlation
|
|
56
|
+
```bash
|
|
57
|
+
python3 python/ghostrate.py info stego_song.wav
|
|
58
|
+
```
|
|
59
|
+
Analyzes stereo difference energy to detect whether an audio track contains a hidden payload.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 🔬 How the Math Works
|
|
64
|
+
|
|
65
|
+
1. **Phantom Center Normalization:**
|
|
66
|
+
Most commercial songs have wide stereo guitars or synths ($L \ne R$). GhostRate first sums both channels into a phantom center mono base:
|
|
67
|
+
$$M[n] = \frac{\text{Music}_L[n] + \text{Music}_R[n]}{2}$$
|
|
68
|
+
|
|
69
|
+
2. **Phase Inversion Embedding:**
|
|
70
|
+
$$\text{Left Ear} = M[n] + g \cdot \text{Voice}[n]$$
|
|
71
|
+
$$\text{Right Ear} = M[n] - g \cdot \text{Voice}[n]$$
|
|
72
|
+
|
|
73
|
+
3. **Phase Cancellation Decoding:**
|
|
74
|
+
$$\text{Decoded} = \text{Left} - \text{Right} = (M - M) + 2g \cdot \text{Voice} = \mathbf{2g \cdot \text{Voice}}$$
|
|
75
|
+
The music vanishes to $0$, and the voice doubles in volume!
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ghostrate"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Audio Steganography & Forensic Tool — Hide and reveal spoken messages in music"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Jawad" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["audio", "steganography", "forensics", "sound", "dsp", "encryption", "watermark"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 5 - Production/Stable",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"Topic :: Multimedia :: Sound/Audio :: Analysis",
|
|
21
|
+
"Topic :: Security :: Cryptography",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.8",
|
|
24
|
+
"Programming Language :: Python :: 3.9",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"License :: OSI Approved :: MIT License",
|
|
29
|
+
"Operating System :: OS Independent",
|
|
30
|
+
]
|
|
31
|
+
dependencies = []
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/jawad/ghostrate"
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
ghostrate = "ghostrate.cli:main"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
41
|
+
include = ["ghostrate*"]
|
ghostrate-1.0.0/setup.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="ghostrate",
|
|
5
|
+
version="1.0.0",
|
|
6
|
+
description="Audio Steganography & Forensic Tool — Hide and reveal spoken messages in music",
|
|
7
|
+
long_description=open("README.md").read(),
|
|
8
|
+
long_description_content_type="text/markdown",
|
|
9
|
+
author="Jawad",
|
|
10
|
+
url="https://github.com/jawad/ghostrate",
|
|
11
|
+
license="MIT",
|
|
12
|
+
package_dir={"": "src"},
|
|
13
|
+
packages=find_packages(where="src"),
|
|
14
|
+
entry_points={
|
|
15
|
+
"console_scripts": [
|
|
16
|
+
"ghostrate=ghostrate.cli:main",
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
classifiers=[
|
|
20
|
+
"Development Status :: 5 - Production/Stable",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Topic :: Multimedia :: Sound/Audio :: Analysis",
|
|
23
|
+
"Topic :: Security :: Cryptography",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"License :: OSI Approved :: MIT License",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
],
|
|
28
|
+
python_requires=">=3.8",
|
|
29
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""GhostRate — Audio Steganography & Forensic Tool.
|
|
2
|
+
|
|
3
|
+
Embed and reveal hidden spoken messages inside music using phase cancellation
|
|
4
|
+
and cryptographic key scrambling.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .cli import (
|
|
8
|
+
read_wav,
|
|
9
|
+
write_stereo_wav,
|
|
10
|
+
write_mono_wav,
|
|
11
|
+
generate_key_sequence,
|
|
12
|
+
apply_key_scrambler,
|
|
13
|
+
linear_resample,
|
|
14
|
+
normalize,
|
|
15
|
+
hide,
|
|
16
|
+
reveal,
|
|
17
|
+
info,
|
|
18
|
+
main,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__version__ = "1.0.0"
|
|
22
|
+
__all__ = [
|
|
23
|
+
"read_wav",
|
|
24
|
+
"write_stereo_wav",
|
|
25
|
+
"write_mono_wav",
|
|
26
|
+
"generate_key_sequence",
|
|
27
|
+
"apply_key_scrambler",
|
|
28
|
+
"linear_resample",
|
|
29
|
+
"normalize",
|
|
30
|
+
"hide",
|
|
31
|
+
"reveal",
|
|
32
|
+
"info",
|
|
33
|
+
"main",
|
|
34
|
+
]
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""GhostRate CLI & Library Core Implementation.
|
|
3
|
+
|
|
4
|
+
Zero third-party dependencies (uses only standard library wave, struct, argparse).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import struct
|
|
9
|
+
import sys
|
|
10
|
+
import wave
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_key_sequence(password: str, length: int):
|
|
14
|
+
"""Generates a deterministic bipolar pseudo-random sequence {-1.0, 1.0}
|
|
15
|
+
using a 32-bit FNV-1a hash seed and a 32-bit Xorshift PRNG.
|
|
16
|
+
"""
|
|
17
|
+
if not password or not password.strip():
|
|
18
|
+
return [1.0] * length
|
|
19
|
+
|
|
20
|
+
seed = 2166136261
|
|
21
|
+
for char in password:
|
|
22
|
+
seed = ((seed ^ ord(char)) * 16777619) & 0xFFFFFFFF
|
|
23
|
+
if seed == 0:
|
|
24
|
+
seed = 1
|
|
25
|
+
|
|
26
|
+
state = seed
|
|
27
|
+
seq = [1.0] * length
|
|
28
|
+
chip_size = 4
|
|
29
|
+
current_sign = 1.0
|
|
30
|
+
|
|
31
|
+
for i in range(length):
|
|
32
|
+
if i % chip_size == 0:
|
|
33
|
+
state = (state ^ ((state << 13) & 0xFFFFFFFF)) & 0xFFFFFFFF
|
|
34
|
+
state = (state ^ (state >> 17)) & 0xFFFFFFFF
|
|
35
|
+
state = (state ^ ((state << 5) & 0xFFFFFFFF)) & 0xFFFFFFFF
|
|
36
|
+
current_sign = 1.0 if (state >> 31) == 0 else -1.0
|
|
37
|
+
seq[i] = current_sign
|
|
38
|
+
|
|
39
|
+
return seq
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def apply_key_scrambler(samples, key_seq):
|
|
43
|
+
"""Scrambles or descrambles samples with a bipolar key sequence."""
|
|
44
|
+
n = min(len(samples), len(key_seq))
|
|
45
|
+
return [samples[i] * key_seq[i] for i in range(n)]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def read_wav(file_path):
|
|
49
|
+
"""Reads a WAV file and returns (samples_left, samples_right, sample_rate, num_channels)."""
|
|
50
|
+
with wave.open(file_path, "rb") as wf:
|
|
51
|
+
num_channels = wf.getnchannels()
|
|
52
|
+
sampwidth = wf.getsampwidth()
|
|
53
|
+
framerate = wf.getframerate()
|
|
54
|
+
nframes = wf.getnframes()
|
|
55
|
+
raw_bytes = wf.readframes(nframes)
|
|
56
|
+
|
|
57
|
+
if sampwidth != 2:
|
|
58
|
+
raise ValueError(
|
|
59
|
+
f"Unsupported sample bit-depth: {sampwidth * 8}-bit. Please provide 16-bit PCM WAV."
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
num_samples = nframes * num_channels
|
|
63
|
+
fmt = f"<{num_samples}h"
|
|
64
|
+
ints = struct.unpack(fmt, raw_bytes)
|
|
65
|
+
|
|
66
|
+
if num_channels == 1:
|
|
67
|
+
mono = [s / 32768.0 for s in ints]
|
|
68
|
+
return mono, mono, framerate, 1
|
|
69
|
+
elif num_channels == 2:
|
|
70
|
+
left = [ints[i * 2] / 32768.0 for i in range(nframes)]
|
|
71
|
+
right = [ints[i * 2 + 1] / 32768.0 for i in range(nframes)]
|
|
72
|
+
return left, right, framerate, 2
|
|
73
|
+
else:
|
|
74
|
+
raise ValueError(f"Unsupported channel count: {num_channels}")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def write_stereo_wav(file_path, left, right, sample_rate):
|
|
78
|
+
"""Writes interleaved 16-bit stereo PCM WAV file."""
|
|
79
|
+
nframes = len(left)
|
|
80
|
+
out_ints = []
|
|
81
|
+
for i in range(nframes):
|
|
82
|
+
s_l = max(-1.0, min(1.0, left[i]))
|
|
83
|
+
s_r = max(-1.0, min(1.0, right[i]))
|
|
84
|
+
out_ints.append(int(round(s_l * 32767)))
|
|
85
|
+
out_ints.append(int(round(s_r * 32767)))
|
|
86
|
+
|
|
87
|
+
fmt = f"<{len(out_ints)}h"
|
|
88
|
+
raw_bytes = struct.pack(fmt, *out_ints)
|
|
89
|
+
|
|
90
|
+
with wave.open(file_path, "wb") as wf:
|
|
91
|
+
wf.setnchannels(2)
|
|
92
|
+
wf.setsampwidth(2)
|
|
93
|
+
wf.setframerate(sample_rate)
|
|
94
|
+
wf.writeframes(raw_bytes)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def write_mono_wav(file_path, samples, sample_rate):
|
|
98
|
+
"""Writes 16-bit mono PCM WAV file."""
|
|
99
|
+
out_ints = [
|
|
100
|
+
int(round(max(-1.0, min(1.0, s)) * 32767)) for s in samples
|
|
101
|
+
]
|
|
102
|
+
fmt = f"<{len(out_ints)}h"
|
|
103
|
+
raw_bytes = struct.pack(fmt, *out_ints)
|
|
104
|
+
|
|
105
|
+
with wave.open(file_path, "wb") as wf:
|
|
106
|
+
wf.setnchannels(1)
|
|
107
|
+
wf.setsampwidth(2)
|
|
108
|
+
wf.setframerate(sample_rate)
|
|
109
|
+
wf.writeframes(raw_bytes)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def linear_resample(samples, src_rate, dst_rate):
|
|
113
|
+
"""Fast linear interpolation resampler."""
|
|
114
|
+
if src_rate == dst_rate:
|
|
115
|
+
return list(samples)
|
|
116
|
+
ratio = dst_rate / src_rate
|
|
117
|
+
dst_len = int(round(len(samples) * ratio))
|
|
118
|
+
out = [0.0] * dst_len
|
|
119
|
+
for i in range(dst_len):
|
|
120
|
+
src_pos = i / ratio
|
|
121
|
+
idx = int(src_pos)
|
|
122
|
+
frac = src_pos - idx
|
|
123
|
+
if idx + 1 < len(samples):
|
|
124
|
+
out[i] = samples[idx] * (1.0 - frac) + samples[idx + 1] * frac
|
|
125
|
+
elif idx < len(samples):
|
|
126
|
+
out[i] = samples[idx]
|
|
127
|
+
return out
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def normalize(samples, target_peak=0.95):
|
|
131
|
+
"""Normalizes peak amplitude."""
|
|
132
|
+
peak = max(abs(s) for s in samples) if samples else 0
|
|
133
|
+
if peak == 0:
|
|
134
|
+
return samples
|
|
135
|
+
scale = target_peak / peak
|
|
136
|
+
return [s * scale for s in samples]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def hide(music_path, voice_path, output_path="stego_output.wav", stealth=0.06, password=""):
|
|
140
|
+
"""Programmatic API to embed voice into music."""
|
|
141
|
+
music_l, music_r, music_rate, _ = read_wav(music_path)
|
|
142
|
+
voice_l, _, voice_rate, _ = read_wav(voice_path)
|
|
143
|
+
|
|
144
|
+
if voice_rate != music_rate:
|
|
145
|
+
voice_aligned = linear_resample(voice_l, voice_rate, music_rate)
|
|
146
|
+
else:
|
|
147
|
+
voice_aligned = voice_l
|
|
148
|
+
|
|
149
|
+
voice_aligned = normalize(voice_aligned, 0.95)
|
|
150
|
+
music_len = len(music_l)
|
|
151
|
+
|
|
152
|
+
if password:
|
|
153
|
+
key_seq = generate_key_sequence(password, music_len)
|
|
154
|
+
voice_payload = apply_key_scrambler(voice_aligned, key_seq)
|
|
155
|
+
else:
|
|
156
|
+
voice_payload = voice_aligned
|
|
157
|
+
|
|
158
|
+
stego_left = [0.0] * music_len
|
|
159
|
+
stego_right = [0.0] * music_len
|
|
160
|
+
|
|
161
|
+
for i in range(music_len):
|
|
162
|
+
m = (music_l[i] + music_r[i]) * 0.5
|
|
163
|
+
v = (voice_payload[i] if i < len(voice_payload) else 0.0) * stealth
|
|
164
|
+
stego_left[i] = max(-1.0, min(1.0, m + v))
|
|
165
|
+
stego_right[i] = max(-1.0, min(1.0, m - v))
|
|
166
|
+
|
|
167
|
+
write_stereo_wav(output_path, stego_left, stego_right, music_rate)
|
|
168
|
+
return output_path
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def reveal(stego_path, output_path="revealed_voice.wav", password=""):
|
|
172
|
+
"""Programmatic API to extract hidden voice from stego WAV."""
|
|
173
|
+
stego_l, stego_r, sample_rate, num_channels = read_wav(stego_path)
|
|
174
|
+
|
|
175
|
+
if num_channels < 2:
|
|
176
|
+
raise ValueError("Input file is Mono. Phase steganography requires a stereo file.")
|
|
177
|
+
|
|
178
|
+
nframes = len(stego_l)
|
|
179
|
+
diff = [stego_l[i] - stego_r[i] for i in range(nframes)]
|
|
180
|
+
|
|
181
|
+
if password:
|
|
182
|
+
key_seq = generate_key_sequence(password, nframes)
|
|
183
|
+
revealed_raw = apply_key_scrambler(diff, key_seq)
|
|
184
|
+
else:
|
|
185
|
+
revealed_raw = diff
|
|
186
|
+
|
|
187
|
+
revealed_voice = normalize(revealed_raw, 0.95)
|
|
188
|
+
write_mono_wav(output_path, revealed_voice, sample_rate)
|
|
189
|
+
return output_path
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def info(file_path):
|
|
193
|
+
"""Analyzes audio file phase difference."""
|
|
194
|
+
l, r, rate, channels = read_wav(file_path)
|
|
195
|
+
dur = len(l) / rate
|
|
196
|
+
diff_energy = sum((l[i] - r[i]) ** 2 for i in range(len(l))) if channels == 2 else 0
|
|
197
|
+
total_energy = sum((l[i] ** 2 + r[i] ** 2) for i in range(len(l))) + 1e-9 if channels == 2 else 1
|
|
198
|
+
ratio = (diff_energy / total_energy) * 100 if channels == 2 else 0
|
|
199
|
+
return {
|
|
200
|
+
"file": file_path,
|
|
201
|
+
"channels": channels,
|
|
202
|
+
"sample_rate": rate,
|
|
203
|
+
"duration": dur,
|
|
204
|
+
"phase_difference_energy_pct": ratio,
|
|
205
|
+
"has_payload": ratio > 0.05,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def main():
|
|
210
|
+
parser = argparse.ArgumentParser(
|
|
211
|
+
prog="ghostrate",
|
|
212
|
+
description="GhostRate CLI — Audio Steganography & Forensic Tool"
|
|
213
|
+
)
|
|
214
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
215
|
+
|
|
216
|
+
# hide
|
|
217
|
+
p_hide = subparsers.add_parser("hide", help="Embed secret voice into music")
|
|
218
|
+
p_hide.add_argument("music", help="Path to host music WAV file")
|
|
219
|
+
p_hide.add_argument("voice", help="Path to secret voice WAV file")
|
|
220
|
+
p_hide.add_argument("-o", "--output", default="stego_output.wav", help="Output stego WAV path")
|
|
221
|
+
p_hide.add_argument("-s", "--stealth", type=float, default=0.06, help="Stealth gain (default: 0.06)")
|
|
222
|
+
p_hide.add_argument("-k", "--key", "--password", dest="key", default="", help="Cryptographic password")
|
|
223
|
+
|
|
224
|
+
# reveal
|
|
225
|
+
p_reveal = subparsers.add_parser("reveal", help="Extract hidden voice from stego WAV")
|
|
226
|
+
p_reveal.add_argument("input", help="Path to stego WAV file")
|
|
227
|
+
p_reveal.add_argument("-o", "--output", default="revealed_voice.wav", help="Output revealed WAV path")
|
|
228
|
+
p_reveal.add_argument("-k", "--key", "--password", dest="key", default="", help="Decryption password")
|
|
229
|
+
|
|
230
|
+
# info
|
|
231
|
+
p_info = subparsers.add_parser("info", help="Inspect audio file metadata")
|
|
232
|
+
p_info.add_argument("input", help="Path to WAV file")
|
|
233
|
+
|
|
234
|
+
args = parser.parse_args()
|
|
235
|
+
|
|
236
|
+
if args.command == "hide":
|
|
237
|
+
print(f"[*] Embedding secret voice from '{args.voice}' into '{args.music}'...")
|
|
238
|
+
out = hide(args.music, args.voice, args.output, args.stealth, args.key)
|
|
239
|
+
print(f"[SUCCESS] Stego audio saved to: {out}")
|
|
240
|
+
elif args.command == "reveal":
|
|
241
|
+
print(f"[*] Extracting secret voice from '{args.input}'...")
|
|
242
|
+
out = reveal(args.input, args.output, args.key)
|
|
243
|
+
print(f"[SUCCESS] Revealed voice saved to: {out}")
|
|
244
|
+
elif args.command == "info":
|
|
245
|
+
res = info(args.input)
|
|
246
|
+
print("--- Audio File Analysis ---")
|
|
247
|
+
print(f"File: {res['file']}")
|
|
248
|
+
print(f"Channels: {res['channels']}")
|
|
249
|
+
print(f"Sample Rate: {res['sample_rate']} Hz")
|
|
250
|
+
print(f"Duration: {res['duration']:.2f} seconds")
|
|
251
|
+
print(f"Phase Difference Energy: {res['phase_difference_energy_pct']:.3f}%")
|
|
252
|
+
if res['has_payload']:
|
|
253
|
+
print("[NOTE] Significant stereo phase difference detected (likely contains a payload)!")
|
|
254
|
+
else:
|
|
255
|
+
print("[NOTE] Audio channels are mono/centered.")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
if __name__ == "__main__":
|
|
259
|
+
main()
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ghostrate
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Audio Steganography & Forensic Tool — Hide and reveal spoken messages in music
|
|
5
|
+
Home-page: https://github.com/jawad/ghostrate
|
|
6
|
+
Author: Jawad
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/jawad/ghostrate
|
|
9
|
+
Keywords: audio,steganography,forensics,sound,dsp,encryption,watermark
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
14
|
+
Classifier: Topic :: Security :: Cryptography
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
22
|
+
Classifier: Operating System :: OS Independent
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# GhostRate 🎙️🕵️♂️
|
|
27
|
+
|
|
28
|
+
> **Audio Steganography & Forensic Suite — Web Studio + Python CLI.**
|
|
29
|
+
|
|
30
|
+
GhostRate embeds hidden spoken voice messages inside any music track using **Phase Mid-Side Inversion** and **Phantom Center Balancing**.
|
|
31
|
+
|
|
32
|
+
* **Normal Ear:** Plays as a completely standard stereo song on headphones, speakers, or car audio.
|
|
33
|
+
* **Filter-Immune:** Survives external audio converters, resamplers, and anti-aliasing downsampling (unlike fragile high-frequency tricks).
|
|
34
|
+
* **Forensic Extraction:** Mid-side phase cancellation $(L - R)$ cancels the host music into dead silence, revealing the secret spoken voice with $+18\text{ dB}$ make-up gain.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 💻 Web Studio (In-Browser)
|
|
39
|
+
|
|
40
|
+
The Web Studio is a client-side TypeScript application featuring real-time visualizers, microphone recording, stealth gain adjustment, and a 1-click **Universal Decoder**.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Start local dev server
|
|
44
|
+
npm run dev
|
|
45
|
+
|
|
46
|
+
# Run automated test suite
|
|
47
|
+
npm test
|
|
48
|
+
|
|
49
|
+
# Build production bundle
|
|
50
|
+
npm run build
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 🐍 Python CLI (Offline Forensic Tool)
|
|
56
|
+
|
|
57
|
+
GhostRate includes a standalone Python CLI in `python/ghostrate.py` with **zero external dependencies** (uses only Python's standard library: `wave`, `struct`, `argparse`). It can be run on any air-gapped machine, Linux server, or offline terminal.
|
|
58
|
+
|
|
59
|
+
### 1. Hide Voice Inside Music (With Optional Passcode Encryption)
|
|
60
|
+
```bash
|
|
61
|
+
# Public (any L-R decoder reveals it):
|
|
62
|
+
python3 python/ghostrate.py hide music.wav voice.wav -o stego_song.wav --stealth 0.06
|
|
63
|
+
|
|
64
|
+
# Encrypted (requires passcode to descramble):
|
|
65
|
+
python3 python/ghostrate.py hide music.wav voice.wav -o stego_song.wav --stealth 0.06 --key "MySecret123"
|
|
66
|
+
```
|
|
67
|
+
* `--stealth 0.06`: Adjusts voice stealth level (`0.03` for invisible, `0.06` for balanced, `0.15` for louder).
|
|
68
|
+
* `--key "password"`: Scrambles the voice using a deterministic pseudo-random key sequence. Without this key, decoding yields only white noise!
|
|
69
|
+
|
|
70
|
+
### 2. Reveal Secret Message from Any Stego File
|
|
71
|
+
```bash
|
|
72
|
+
# Public stego file:
|
|
73
|
+
python3 python/ghostrate.py reveal stego_song.wav -o revealed_secret.wav
|
|
74
|
+
|
|
75
|
+
# Encrypted stego file:
|
|
76
|
+
python3 python/ghostrate.py reveal stego_song.wav -o revealed_secret.wav --key "MySecret123"
|
|
77
|
+
```
|
|
78
|
+
Cancels out the host music and descrambles the isolated speech.
|
|
79
|
+
|
|
80
|
+
### 3. Inspect Audio File Phase Correlation
|
|
81
|
+
```bash
|
|
82
|
+
python3 python/ghostrate.py info stego_song.wav
|
|
83
|
+
```
|
|
84
|
+
Analyzes stereo difference energy to detect whether an audio track contains a hidden payload.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 🔬 How the Math Works
|
|
89
|
+
|
|
90
|
+
1. **Phantom Center Normalization:**
|
|
91
|
+
Most commercial songs have wide stereo guitars or synths ($L \ne R$). GhostRate first sums both channels into a phantom center mono base:
|
|
92
|
+
$$M[n] = \frac{\text{Music}_L[n] + \text{Music}_R[n]}{2}$$
|
|
93
|
+
|
|
94
|
+
2. **Phase Inversion Embedding:**
|
|
95
|
+
$$\text{Left Ear} = M[n] + g \cdot \text{Voice}[n]$$
|
|
96
|
+
$$\text{Right Ear} = M[n] - g \cdot \text{Voice}[n]$$
|
|
97
|
+
|
|
98
|
+
3. **Phase Cancellation Decoding:**
|
|
99
|
+
$$\text{Decoded} = \text{Left} - \text{Right} = (M - M) + 2g \cdot \text{Voice} = \mathbf{2g \cdot \text{Voice}}$$
|
|
100
|
+
The music vanishes to $0$, and the voice doubles in volume!
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
src/ghostrate/__init__.py
|
|
5
|
+
src/ghostrate/cli.py
|
|
6
|
+
src/ghostrate.egg-info/PKG-INFO
|
|
7
|
+
src/ghostrate.egg-info/SOURCES.txt
|
|
8
|
+
src/ghostrate.egg-info/dependency_links.txt
|
|
9
|
+
src/ghostrate.egg-info/entry_points.txt
|
|
10
|
+
src/ghostrate.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ghostrate
|