Guaranteed compile-time string literal obfuscation header-only library for C++14.
- Copy
obfuscate.hinto your project - Wrap strings with
AY_OBFUSCATE("My String")
Now your project will not expose those strings in plain text in the binary image.
Note that these strings will still be accessible to determined hackers. Using obfuscation to hide private passwords or any other security sensitive strings is not recommended by the author.
When plain text string literals are used in C++ programs, they will be compiled as-is into the resultant binary. This causes them to be incredibly easy to find. One can simply open up the binary file in a text editor to see all of the embedded string literals in plain view. A special utility called strings exists which can be used to search binary files for plain text strings.
This header-only library seeks to make it difficult (but not impossible) for embedded string literals in binary files to be found by encrypting them with an XOR cipher at compile-time using a constant expression, forcing the compiler to work with the encrypted string instead of the plain text literal. Usage of AY_OBFUSCATE additionally removes the need for a const pointer to the string, which more often than not (for small strings) convinces the compiler to inline the encrypted string, building it up at runtime in a series of assembly operations, protecting the binary image against simple XOR decryption attacks. Encrypted strings will then be decrypted at runtime to be utilised within the program.
- Guaranteed compile-time obfuscation - the string is compiled with a constexpr expression.
- Global lifetime - stored in a unique lambda with
thread_localstorage by default, orstaticstorage in freestanding mode. - Implicitly convertible to a char* - easy to integrate into existing codebases.
- Deterministic 64-bit key by default - generated from the call site line number.
By simply wrapping your string literal "My String" with AY_OBFUSCATE("My String") it will be encrypted at compile time with a 64 bit key derived from the call site and stored in an ay::obfuscated_data object which you can manipulate at runtime. For convenience it is also implicitly convertable to a char*.
If you use AY_OBFUSCATE_KEY, the supplied key must have at least one set bit in each byte. This avoids leaving parts of the string unobfuscated.
For CRT-free or freestanding builds, define AY_OBFUSCATE_FREESTANDING before including the header. This disables thread_local storage and destructor-based zeroing so MSVC does not emit TLS teardown dependencies for this library. On MSVC, compile the translation unit with /Zc:threadSafeInit- as well to avoid local static initialization helpers.
#define AY_OBFUSCATE_FREESTANDING 1
#include "obfuscate.h"The repository includes a self-checking demo program in demo.cpp. It exercises:
- default
AY_OBFUSCATEusage and runtime state transitions AY_OBFUSCATE_KEY- direct
ay::make_obfuscator/ay::obfuscated_datausage data()andsize()for non-null-terminated buffers- global, local, and temporary contexts
- wide-character literals
- threaded local-copy usage and locked shared-reference usage
Build the normal demo target:
cmake -S . -B build -DGENERATE_DEMO=ON
cmake --build build --config Release --target demo
./build/Release/demoBuild the freestanding demo target:
cmake -S . -B build -DGENERATE_DEMO=ON
cmake --build build --config Release --target demo_freestanding
./build/Release/demo_freestandingOn Windows, CMake produces demo.exe and demo_freestanding.exe. On MSVC, the freestanding demo target automatically adds /Zc:threadSafeInit-.
For example, the following program will not store the string "Hello World" in plain text anywhere in the compiled executable.
#include "obfuscate.h"
int main()
{
std::cout << AY_OBFUSCATE("Hello World") << std::endl;
return 0;
}Because the obfuscated string that is generated by AY_OBFUSCATE has global lifetime per-thread, it is completely fine to also use it in both a local and a temporary context.
char* var = AY_OBFUSCATE("string");
const char* var = AY_OBFUSCATE("string");
static const char* var = AY_OBFUSCATE("string");
std::string var(AY_OBFUSCATE("string"));
function_that_takes_char_pointer(AY_OBFUSCATE("string"));For non-null-terminated buffers or fixed-width character arrays, use data() and size() instead of C string APIs:
constexpr auto obfuscator = ay::obfuscator<4, AY_OBFUSCATE_DEFAULT_KEY>("ABCD");
auto buffer = ay::obfuscated_data<obfuscator.size(), obfuscator.key()>(obfuscator);
auto* data = buffer.data();
std::string value(data, data + buffer.size());By default, this library can be used in a multi-threaded environment only if AY_OBFUSCATE is used in a local context per thread. This is because the obfuscated string is internally stored with thread_local storage. The following usage is supported:
void fun()
{
auto var = AY_OBFUSCATE("Thread Safe");
var.decrypt();
std::cout << var << std::endl;
var.encrypt();
}
int main()
{
std::thread thread1(fun);
std::thread thread2(fun);
thread1.join();
thread2.join();
return 0;
}Conversely, sharing an obfuscated string returned from AY_OBFUSCATE between multiple threads is not supported. In this case you must put locks in appropriate places in your code to ensure that only one thread accesses it at a time. The following usage is not supported:
int main()
{
for (size_t i = 0; i < 1000; i++)
{
auto var = AY_OBFUSCATE("NOT Thread Safe");
var.decrypt();
std::thread thread([&var]() {
std::cout << var << std::endl;
});
// We are encrypting the string here, but outputting it in
// another thread at the same time. This will not work.
var.encrypt();
thread.join();
}
return 0;
}When AY_OBFUSCATE_USE_THREAD_LOCAL is 0 (including AY_OBFUSCATE_FREESTANDING=1), each call site uses shared static storage instead. In that mode, all threads see the same backing buffer for a given macro expansion, so external synchronization is required around every concurrent use.
Freestanding mode also disables destructor-based zeroing to avoid CRT teardown hooks. If you rely on the destructor to scrub process-exit memory, do not use freestanding mode.
This does come at a small cost. In a very naive login program, which obfuscates two strings (username and password) the following binary file bloat exists.
| Config | Plain string literals | Obfuscated strings | Bloat |
|---|---|---|---|
| Release | 18944 | 21504 | 2560 (13.5%) |
| Debug | 95232 | 101888 | 6656 (7.0%) |
This output is generated by running test_bloat.py