1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
use std::path::PathBuf;
use std::str::FromStr;
use structopt::StructOpt;

#[derive(StructOpt)]
#[structopt(name = "🦕 stegosaurust", about = "Hide text in images, using rust.")]
pub struct Opt {
    #[structopt(subcommand)]
    pub cmd: Command,
}

#[derive(StructOpt)]
pub enum Command {
    #[structopt(
        name = "encode",
        visible_alias = "enc",
        about = "encode files using steganography"
    )]
    Encode(Encode),
    #[structopt(
        name = "disguise",
        visible_alias = "dsg",
        about = "mask all files in a directory using steganography"
    )]
    Disguise(Disguise),
}

#[derive(StructOpt)]
pub struct Encode {
    #[structopt(flatten)]
    pub opts: EncodeOpts,

    /// Check max message size that can be encoded with options given. Does not perform the encoding, acts like a dry-run
    #[structopt(short = "C", long)]
    pub check_max_length: bool,

    /// Output file, stdout if not present
    #[structopt(short, long, parse(from_os_str))]
    pub output: Option<PathBuf>,

    /// Input file to encode, stdin if not present
    #[structopt(short, long, parse(from_os_str), conflicts_with = "decode")]
    pub input: Option<PathBuf>,

    /// Input image
    #[structopt(parse(from_os_str))]
    pub image: PathBuf,
}

#[derive(StructOpt)]
pub struct Disguise {
    #[structopt(flatten)]
    pub opts: EncodeOpts,

    /// Directory containing files to disguise
    #[structopt(parse(from_os_str))]
    pub dir: PathBuf,
}

#[derive(StructOpt, Clone)]
pub struct EncodeOpts {
    /// Decode a message from the image
    #[structopt(short, long)]
    pub decode: bool,

    /// Encode/decode with base64
    #[structopt(short, long)]
    pub base64: bool,

    /// Compress/decompress data
    #[structopt(short, long)]
    pub compress: bool,

    /// Encrypt the text before encoding it with AES-256-CBC
    #[structopt(short, long)]
    pub key: Option<String>,

    /// Method to use for encoding [default=lsb]
    #[structopt(short, long, possible_values=&StegMethod::variants())]
    pub method: Option<StegMethod>,

    /// Method for bit distribution [default=sequential] [possible values: sequential, linear (linear-N when decoding)]
    #[structopt(long)]
    pub distribution: Option<BitDistribution>,

    /// Seed for random significant bit encoding
    #[structopt(short, long, required_if("method", "rsb"))]
    pub seed: Option<String>,

    /// Maximum bit to possible modify
    #[structopt(short = "N", long, required_if("method", "rsb"), possible_values=&["1","2","3","4"])]
    pub max_bit: Option<u8>,
}

/// Supported steganography encoding algorithms
#[derive(StructOpt, Debug, Clone, Copy)]
pub enum StegMethod {
    /// Least significant bit encoding
    ///
    /// With a binary message, each bit of the message is encoded
    /// into the least significant bit of each RGB byte of each pixel.
    LeastSignificantBit,
    /// Random significant bit encoding
    ///
    /// With a binary message, each bit of the message is encoded
    /// randomly into one of the `n` least significant bits of each RGB byte of each pixel.
    RandomSignificantBit,
}

impl FromStr for StegMethod {
    type Err = String;
    fn from_str(method: &str) -> Result<Self, Self::Err> {
        match method {
            "lsb" => Ok(Self::LeastSignificantBit),
            "rsb" => Ok(Self::RandomSignificantBit),
            other => Err(format!("unknown encoding method: {}", other)),
        }
    }
}

impl Default for StegMethod {
    fn default() -> Self {
        StegMethod::LeastSignificantBit
    }
}

impl StegMethod {
    fn variants() -> [&'static str; 2] {
        ["lsb", "rsb"]
    }
}

/// Supported bit encoding bit distribution methods
#[derive(StructOpt, Debug, Clone)]
pub enum BitDistribution {
    /// Encode bits sequentially into the image starting from top-left
    Sequential,
    /// Evenly space out the bits in the image so not all packed into top-left
    Linear { length: usize },
}

impl FromStr for BitDistribution {
    type Err = String;
    fn from_str(method: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = method.split('-').collect();
        let method = if parts.len() <= 1 { method } else { parts[0] };
        match method {
            "sequential" => Ok(Self::Sequential),
            "linear" => {
                let length = *(parts.get(1).unwrap_or(&"0"));
                let length = length.parse::<usize>().unwrap_or_else(|err| {
                    eprintln!(
                        "error parsing message length in linear bit distribution: {}",
                        err
                    );
                    std::process::exit(1);
                });
                Ok(Self::Linear { length })
            }
            other => Err(format!("unknown bit distribution {}", other)),
        }
    }
}

impl Default for BitDistribution {
    fn default() -> Self {
        BitDistribution::Sequential
    }
}