Techniques for obscuring sensitive strings in C++
First of all, realise that there is nothing you can do that will stop a sufficiently determined hacker, and there are plenty of those around. The protection on every game and console around is cracked eventually, so this is only a temporary fix.
There are 4 things you can do that will increase you chances of staying hidden for a while.
1) Hide the elements of the string in some way -- something obvious like xoring ( the ^ operator) the string with another string will be good enough to make the string impossible to search for.
2) Split the string into pieces -- split up your string and pop bits of it into strangely named methods in strange modules. Don't make it easy to search through and find the method with the string in it. Of course some method will have to call all these bits, but it still makes it a little harder.
3) Don't ever build the string in memory -- most hackers use tools that let them see the string in memory after you have encoded it. If possible, avoid this. If for example you are sending the key off to a server, send it character by character, so the whole string is never around. Of course, if you are using it from something like RSA encoding, then this is trickier.
4) Do an ad-hoc algorithm -- on top of all this, add a unique twist or two. Maybe just add 1 to everything you produce, or do any encryption twice, or add a sugar. This just makes it a little harder for the hacker who already knows what to look for when someone is using, for example, vanilla md5 hashing or RSA encryption.
Above all, make sure it isn't too important when (and it will be when if you application becomes popular enough) your key is discovered!
There is a (very light) header-only project obfuscate made by adamyaxley that works perfectly. It is based on lambda functions and macros and it encrypts strings litteral with a XOR cipher at compile-time. If needed, we can change the seed for each string.
The following code will not store the string "hello world" in the compiled binary.
#include "obfuscate.h"
int main()
{
std::cout << AY_OBFUSCATE("Hello World") << std::endl;
return 0;
}
I have tested with c++17 and visual studio 2019, and check via IDA and I confirm the string is hidden. One precious advantage compared to ADVobfuscator is that it is convertible to a std::string (while being still hidden in the compiled binary) :
std::string var = AY_OBFUSCATE("string");
A strategy i've used in the past is to create an array of seemingly-random characters. You initially insert, and then locate your particular characters with a algebraic process where each step from 0 to N will yield a number < size of the array which contains the next char in your obfuscated string. (This answer is feeling obfuscated now!)
Example:
Given an array of chars (numbers and dashes are for reference only)
0123456789
----------
ALFHNFELKD
LKFKFLEHGT
FLKRKLFRFK
FJFJJFJ!JL
And an equation whose first six results are: 3, 6, 7, 10, 21, 47
Would yield the word "HELLO!" from the array above.
Basically, anyone with access to your program and a debugger can and will find the key in the application if they want to.
But, if you just want to make sure the key doesn't show up when running strings
on your binary, you could for instance make sure that the key is not within the printable range.
Obscuring key with XOR
For instance, you could use XOR to split the key into two byte arrays:
key = key1 XOR key2
If you create key1 with the same byte-length as key
you can use (completely) random byte values and then compute key2
:
key1[n] = crypto_grade_random_number(0..255)
key2[n] = key[n] XOR key1[n]
You can do this in your build environment, and then only store key1
and key2
in your application.
Protecting your binary
Another approach is to use a tool to protect your binary. For instance, there are several security tools that can make sure your binary is obfuscated and starts a virtual machine that it runs on. This makes it hard(er) to debug, and is also the convential way many commercial grade secure applications (also, alas, malware) is protected.
One of the premier tools is Themida, which does an awesome job of protecting your binaries. It is often used by well known programs, such as Spotify, to protect against reverse engineering. It has features to prevent debugging in programs such as OllyDbg and Ida Pro.
There is also a larger list, maybe somewhat outdated, of tools to protect your binary.
Some of them are free.
Password matching
Someone here discussed hashing password+salt.
If you need to store the key to match it against some kind of user submitted password, you should use a one-way hashing function, preferrably by combining username, password and a salt. The problem with this, though, is that your application has to know the salt to be able to do the one-way and compare the resulting hashes. So therefore you still need to store the salt somewhere in your application. But, as @Edward points out in the comments below, this will effectively protect against a dictionary attack using, e.g, rainbow tables.
Finally, you can use a combination of all the techniques above.