stegcloak/
lib.rs

1//! # StegCloak
2//!
3//! Hides secrets inside text by compressing and encrypting the secret before cloaking it
4//! with special unicode invisible characters. It can be used to safely watermark strings,
5//! invisible scripts on webpages, texts on social media, or for any other covert communication.
6//! Completely invisible!
7//!
8//! Inspired by the original javascript [stegcloak](https://github.com/KuroLabs/stegcloak).
9//!
10//! This is incompatible with the original js stegcloak. But it can compile to wasm with the
11//! `wasm` feature.
12//!
13//! # Features
14//!
15//! - Allows you to invisibly hide your secret inside regular text
16//! - Protect your secret with password and HMAC integrity
17//! - Encrypts your secret with AES-256-CTR
18//! - Uses 6 invisible unicode characters that work in many places such as Gmail, WhatsApp, Telegram, Facebook, and more!
19//! - Uses compression to minimize size of payload.
20//! - Wasm compatible
21//! - Fast🦀!
22//!   - Can hide the entire wikipedia source in ~201468 characters taking ~3.5ms in plaintext mode and ~7ms in encrypt mode
23//!   - Can reveal the entire wikipedia source in ~1.3ms in plaintext mode and ~5ms in encrypted mode
24//!
25//! # Cargo Features
26//! `wasm` - If you need wasm support, this feature's for you!
27//!
28//! # Warning
29//!
30//! This is currently under dev. Algorithm may be changed at any time, and previously encoded
31//! messages may no longer be compatible with the new version.
32//!
33//! Every effort has been made to be cryptographically secure, however, this _should not_ be
34//! relied on for any sensitive or secure communications! Author absolves self from all possible
35//! issues that could arise from usage of this software.
36//!
37//! StegCloak doesn't solve the Alice-Bob-Warden problem, it's powerful only when people are not
38//! looking for it and it helps you achieve that really well, given its invisible properties around
39//! the web! It could be safely used for watermarking in forums, invisible tweets, social media etc.
40//! Please don't use it when you know there's someone who is actively sniffing your data - looking at
41//! the unicode characters through a data analysis tool. In that case, even though the secret encoded
42//! cannot be deciphered, the fact lies that the Warden (middle-man) knows some secret communication
43//! took place, because he would have noticed an unusual amount of special invisible characters.
44//!
45//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
46//! LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
47//! IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
48//! WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
49//! OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
50
51pub mod codec;
52pub mod compact;
53pub mod crypto;
54
55use codec::CodecError;
56use compact::DeCompressError;
57use crypto::DeEncryptError;
58
59pub mod encrypt {
60    use super::StegError;
61
62    /// Hide an encrypted secret inside a message
63    ///
64    /// # Arguments
65    ///
66    /// * `secret` - The secret you want to hide
67    /// * `password` - The password to encrypt the secret with
68    /// * `integrity` - Create message that protects against tampering
69    /// * `message` - The visible text everybody else will see
70    ///
71    /// # Examples
72    ///
73    /// ```rust
74    ///     stegcloak::encrypt::hide("mysecret", "mypassword", false, "cover text"); // -> "cover text"
75    /// ```
76    ///
77    pub fn hide(
78        secret: impl AsRef<str>,
79        password: impl AsRef<str>,
80        integrity: bool,
81        message: impl AsRef<str>,
82    ) -> Result<String, StegError> {
83        let secret = secret.as_ref();
84        let password = password.as_ref();
85        let message = message.as_ref();
86
87        super::_hide(true, integrity, secret, password, message)
88    }
89
90    /// Reveal an encrypted secret inside a message
91    ///
92    /// # Arguments
93    ///
94    /// * `password` - The password to decrypt the secret with
95    /// * `message` - The visible text everybody else sees
96    ///
97    /// # Examples
98    /// ```rust
99    ///     stegcloak::encrypt::reveal("mypassword", "cover text"); // -> "mysecret"
100    /// ```
101    ///
102    pub fn reveal(
103        password: impl AsRef<str>,
104        message: impl AsRef<str>,
105    ) -> Result<String, StegError> {
106        let password = password.as_ref();
107        let message = message.as_ref();
108
109        super::_reveal(true, password, message)
110    }
111}
112
113pub mod plaintext {
114    use super::StegError;
115
116    /// Hide a plaintext secret inside a message
117    ///
118    /// Warn: The secret will be in plaintext! Anyone can freely decode it!
119    ///
120    /// # Arguments
121    ///
122    /// * `secret` - The secret you want to hide
123    /// * `message` - The visible text everybody else will see
124    ///
125    /// # Examples
126    ///
127    /// ```rust
128    ///     stegcloak::plaintext::hide("mysecret", "cover text"); // -> "cover text"
129    /// ```
130    ///
131    pub fn hide(secret: impl AsRef<str>, message: impl AsRef<str>) -> Result<String, StegError> {
132        let secret = secret.as_ref();
133        let message = message.as_ref();
134
135        super::_hide(false, false, secret, "", message)
136    }
137
138    /// Reveal a plaintext secret inside a message
139    ///
140    /// # Arguments
141    ///
142    /// * `message` - The visible text everybody else sees
143    ///
144    /// # Examples
145    ///
146    /// ```rust
147    ///     stegcloak::plaintext::reveal("cover text"); // -> "mysecret"
148    /// ```
149    ///
150    pub fn reveal(message: impl AsRef<str>) -> Result<String, StegError> {
151        let message = message.as_ref();
152
153        super::_reveal(false, "", message)
154    }
155}
156
157fn _hide(
158    encrypt: bool,
159    integrity: bool,
160    secret: &str,
161    password: &str,
162    message: &str,
163) -> Result<String, StegError> {
164    // minimum 1 space required
165    let Some(space_pos) = message.find(' ') else {
166        return Err(StegError::SpaceRequired);
167    };
168
169    let secret = compact::compress(secret)?;
170    let data = if encrypt {
171        crypto::encrypt(password, &secret, integrity)?
172    } else {
173        secret
174    };
175
176    let encoded = codec::encode(&data);
177
178    let mut message = message.to_owned();
179    message.insert_str(space_pos + 1, &encoded);
180
181    Ok(message)
182}
183
184fn _reveal(encrypt: bool, password: &str, message: &str) -> Result<String, StegError> {
185    if !message.contains(' ') {
186        return Err(StegError::SpaceRequired);
187    }
188
189    let decoded = codec::decode(message)?;
190    let data = if encrypt {
191        crypto::decrypt(password, &decoded)?
192    } else {
193        decoded
194    };
195
196    Ok(compact::decompress(&data)?)
197}
198
199#[derive(Debug, thiserror::Error)]
200pub enum StegError {
201    #[error("Text does not contain a space")]
202    SpaceRequired,
203    #[error("Failed Compression/Decompression: {0}")]
204    DeCompressError(#[from] DeCompressError),
205    #[error("Failed Compression/Decompression: {0}")]
206    DeEncryptError(#[from] DeEncryptError),
207    #[error("Codec failed: {0}")]
208    CodecError(#[from] CodecError),
209}