(Start at the beginning of the series – and all the source can be found in my github repo)
Compile-time random number generation is quite useful. For instance, we could generate GUIDs (version 4 UUIDs):
namespace cx
{
struct guid_t
{
uint32_t data1;
uint16_t data2;
uint16_t data3;
uint64_t data4;
};
template
constexpr guid_t guidgen()
{
return guid_t {
cx::pcg32(),
cx::pcg32() >> 16,
0x4000 | cx::pcg32() >> 20,
(uint64_t{8 + (cx::pcg32() >> 30)} << 60)
| uint64_t{cx::pcg32() & 0x0fffffff} << 32
| uint64_t{cx::pcg32()} };
}
}
#define cx_guid \
cx::guidgen
There are situations right now where one might have scripts (probably Python or some such language) in one’s build chain that do this type of generation of C++ code, for example to introduce randomness to each build in the name of security, or simply to mark the build uniquely. It would be nice not to have to maintain extra non-C++ code and extra steps in the build chain.
Is PCG32 secure? Maybe secure enough…
Security you say? We have a source of random numbers that changes every build… we could transparently “encrypt” string literals and decode them at point of use. Might be useful to someone. To wrangle with a string in a constexpr
manner, I need to treat it as an array, and then encrypt (I’ll just xor) each character with my random byte stream. I can figure out how long a string literal is at compile time, store the random seed as a template parameter, and use an index_sequence
expansion to do the encryption.
template
constexpr char encrypt_at(const char* s, size_t idx)
{
return s[idx] ^
static_cast(pcg32_output(
pcg32_advance(S, idx+1)) >> 24);
}
template
struct char_array
{
char data[N];
};
template
constexpr char_array encrypt(
const char *s, std::index_sequence)
{
return {{ encrypt_at(s, Is)... }};
}
That takes care of constructing an encrypted array from a string literal. The rest is just wrapping it up in an encrypted_string
class and providing a sane runtime decryption function (we could use the existing C++11 constexpr
functions, but they are strangely formulated for runtime use – maybe a more natural formulation would be easier to optimize). And we can give the class a conversion to string
.
inline std::string decrypt(uint64_t S, const char* s, size_t n)
{
std::string ret;
ret.reserve(n);
for (size_t i = 0; i < n; ++i)
{
S = pcg32_advance(S);
ret.push_back(s[i] ^
static_cast(pcg32_output(S) >> 24));
}
return ret;
}
template
class encrypted_string
{
public:
constexpr encrypted_string(const char(&a)[N])
: m_enc(encrypt(a, std::make_index_sequence()))
{}
constexpr size_t size() const { return N-1; }
operator std::string() const
{
return decrypt(S, m_enc.data, N-1);
}
private:
const char_array m_enc;
};
template
constexpr encrypted_string make_encrypted_string(
const char(&s)[N])
{
return encrypted_string(s);
}
#define CX_ENCSTR_RNGSEED \
uint64_t{cx::fnv1(__FILE__ __DATE__ __TIME__) + __LINE__}
#define cx_make_encrypted_string \
cx::make_encrypted_string
The more observant and pedantic among you may have noticed that strictly, I’ve strayed into C++14 territory here, using std::index_sequence
. But I think I’m still in the spirit of C++11 constexpr
. And more importantly, VS2015 supports std::integer_sequence
.
Anyway, let’s exercise this code and check it does the right thing:
int main(int, char* [])
{
constexpr auto enc =
cx_make_encrypted_string("I accidentally the string");
cout << string(enc) << endl;
return 0;
}
Here's the result:
$ ./test_constexpr
I accidentally the string
$
And the plaintext string doesn't appear in the binary:
$ objdump -s -j .rodata ./test_constexpr
./test_constexpr: file format elf64-x86-64
Contents of section .rodata:
404700 01000200 d31e337d 58244d8a 48e52178 ......3}X$M.H.!x
404710 139d483c 5113d3f1 0aec79a3 80626173 ..H
Not bad. Per-compile obfuscation on string literals, with zero memory overhead and only a modest cost at the point of use.