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}