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}