litcrypt/
litcrypt.rs

1//! # LitCrypt
2//! The name is an abbreviation of ‘Literal Encryption’ – a Rust compiler plugin to encrypt
3//! text literals using the [XOR cipher](https://en.wikipedia.org/wiki/XOR_cipher).
4//!
5//! LitCrypt let’s you hide your static string literal in the binary from naughty eyes and protect
6//! your app from illegal cracking activity.
7//!
8//! LitCrypt works by encrypting string literals during compile time. An encrypted string remains
9//! encrypted both on disk and in memory during runtime. It is decypted only when used.
10//!
11//! ## Usage
12//! In `Cargo.toml`, add:
13//!
14//! ```toml
15//! [dependencies]
16//! litcrypt = "0.2"
17//! ```
18//!
19//! # Example
20//!
21//! ```rust
22//! #[macro_use]
23//! extern crate litcrypt;
24//!
25//! use_litcrypt!("MY-SECRET-SPELL");
26//!
27//! fn main(){
28//!     println!("his name is: {}", lc!("Voldemort"));
29//! }
30//! ```
31//!
32//! The [`use_litcrypt!`] macro must be called first, for initialization. Its parameter is the
33//! secret key that is used to encrypt all [`lc!`]-wrapped string literal(s).
34//! This key is also encrypted and will not visible in a static analyzer.
35//!
36//! Only after that can you use the [`lc!`] macro.
37//!
38//! You can also override the key using an environment variable `LITCRYPT_ENCRYPT_KEY` e.g:
39//! ```bash
40//! ❯ export LITCRYPT_ENCRYPT_KEY="myverysuperdupermegaultrasecretkey"
41//! ```
42//!
43//! LitCrypt will statically encrypt every string encapsulated in an `lc!` macro.
44//!
45//! Check the output binary using the `strings` command, e.g:
46//!
47//! ```bash
48//! ❯ strings target/debug/my_valuable_app | grep Voldemort
49//! ```
50//!
51//! If the output is blank then the resp. strings in your app are safe from a static analyzer tool
52//! like a hex editor.
53//!
54//! For an example see the `./examples` directory:
55//!
56//! ```bash
57//! ❯ cargo run --example simple
58//! ```
59extern crate proc_macro;
60extern crate proc_macro2;
61extern crate quote;
62extern crate rand;
63
64#[cfg(test)]
65#[macro_use(expect)]
66extern crate expectest;
67
68use proc_macro::{TokenStream, TokenTree};
69use proc_macro2::Literal;
70use quote::quote;
71use rand::{rngs::OsRng, RngCore};
72use std::env;
73
74mod xor;
75
76lazy_static::lazy_static! {
77    static ref RAND_SPELL: [u8; 64] = {
78        let mut key = [0u8; 64];
79        OsRng.fill_bytes(&mut key);
80        key
81    };
82}
83
84#[inline(always)]
85fn get_magic_spell() -> Vec<u8> {
86    match env::var("LITCRYPT_ENCRYPT_KEY") {
87        Ok(key) => key.as_bytes().to_vec(),
88        Err(_) => {
89            // `lc!` will call this function multi times
90            // we must provide exact same result for each invocation
91            // so use static lazy field for cache
92            RAND_SPELL.to_vec()
93        }
94    }
95}
96
97/// Sets the encryption key used for encrypting subsequence strings wrapped in a [`lc!`] macro.
98///
99/// This key is also encrypted an  will not visible in a static analyzer.
100#[proc_macro]
101pub fn use_litcrypt(_tokens: TokenStream) -> TokenStream {
102    let magic_spell = get_magic_spell();
103
104    let encdec_func = quote! {
105        pub mod litcrypt_internal {
106            // This XOR code taken from https://github.com/zummenix/xor-rs
107            /// Returns result of a XOR operation applied to a `source` byte sequence.
108            ///
109            /// `key` will be an infinitely repeating byte sequence.
110            pub fn xor(source: &[u8], key: &[u8]) -> Vec<u8> {
111                match key.len() {
112                    0 => source.into(),
113                    1 => xor_with_byte(source, key[0]),
114                    _ => {
115                        let key_iter = InfiniteByteIterator::new(key);
116                        source.iter().zip(key_iter).map(|(&a, b)| a ^ b).collect()
117                    }
118                }
119            }
120
121            /// Returns result of a XOR operation applied to a `source` byte sequence.
122            ///
123            /// `byte` will be an infinitely repeating byte sequence.
124            pub fn xor_with_byte(source: &[u8], byte: u8) -> Vec<u8> {
125                source.iter().map(|&a| a ^ byte).collect()
126            }
127
128            struct InfiniteByteIterator<'a> {
129                bytes: &'a [u8],
130                index: usize,
131            }
132
133            impl<'a> InfiniteByteIterator<'a> {
134                pub fn new(bytes: &'a [u8]) -> InfiniteByteIterator<'a> {
135                    InfiniteByteIterator {
136                        bytes: bytes,
137                        index: 0,
138                    }
139                }
140            }
141
142            impl<'a> Iterator for InfiniteByteIterator<'a> {
143                type Item = u8;
144                fn next(&mut self) -> Option<u8> {
145                    let byte = self.bytes[self.index];
146                    self.index = next_index(self.index, self.bytes.len());
147                    Some(byte)
148                }
149            }
150
151            fn next_index(index: usize, count: usize) -> usize {
152                if index + 1 < count {
153                    index + 1
154                } else {
155                    0
156                }
157            }
158
159            pub fn decrypt_bytes(encrypted: &[u8], encrypt_key: &[u8]) -> String {
160                let decrypted = xor(&encrypted[..], &encrypt_key);
161                String::from_utf8(decrypted).unwrap()
162            }
163        }
164    };
165    let result = {
166        let ekey = xor::xor(&magic_spell, b"l33t");
167        let ekey = Literal::byte_string(&ekey);
168        quote! {
169            static LITCRYPT_ENCRYPT_KEY: &'static [u8] = #ekey;
170            #encdec_func
171        }
172    };
173    result.into()
174}
175
176/// Encrypts the resp. string with the key set before, via calling [`use_litcrypt!`].
177#[proc_macro]
178pub fn lc(tokens: TokenStream) -> TokenStream {
179    let mut something = String::from("");
180    for tok in tokens {
181        something = match tok {
182            TokenTree::Literal(lit) => lit.to_string(),
183            _ => "<unknown>".to_owned(),
184        }
185    }
186    something = String::from(&something[1..something.len() - 1]);
187
188    encrypt_string(something)
189}
190
191/// Encrypts an environment variable at compile time with the key set before, via calling [`use_litcrypt!`].
192#[proc_macro]
193pub fn lc_env(tokens: TokenStream) -> TokenStream {
194    let mut var_name = String::from("");
195
196    for tok in tokens {
197        var_name = match tok {
198            TokenTree::Literal(lit) => lit.to_string(),
199            _ => "<unknown>".to_owned(),
200        }
201    }
202
203    var_name = String::from(&var_name[1..var_name.len() - 1]);
204
205    encrypt_string(env::var(var_name).unwrap_or(String::from("unknown")))
206}
207
208fn encrypt_string(something: String) -> TokenStream {
209    let magic_spell = get_magic_spell();
210    let encrypt_key = xor::xor(&magic_spell, b"l33t");
211    let encrypted = xor::xor(something.as_bytes(), &encrypt_key);
212    let encrypted = Literal::byte_string(&encrypted);
213
214    let result = quote! {
215        crate::litcrypt_internal::decrypt_bytes(#encrypted, crate::LITCRYPT_ENCRYPT_KEY)
216    };
217
218    result.into()
219}