Compile-time RNG tricks

(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 <uint64_t S>
  constexpr guid_t guidgen()
    return guid_t {
        cx::pcg32<S>() >> 16,
        0x4000 | cx::pcg32<S>() >> 20,
        (uint64_t{8 + (cx::pcg32<S>() >> 30)} << 60)
        | uint64_t{cx::pcg32<S>() & 0x0fffffff} << 32
        | uint64_t{cx::pcg32<S>()} };
#define cx_guid \
  cx::guidgen<cx::fnv1(__FILE__ __DATE__ __TIME__) + __LINE__>

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 <uint64_t S>
constexpr char encrypt_at(const char* s, size_t idx)
  return s[idx] ^
      pcg32_advance(S, idx+1)) >> 24);
template <size_t N>
struct char_array
  char data[N];
template <uint64_t S, size_t ...Is>
constexpr char_array<sizeof...(Is)> encrypt(
    const char *s, std::index_sequence<Is...>)
  return {{ encrypt_at<S>(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;
  for (size_t i = 0; i < n; ++i)
    S = pcg32_advance(S);
    ret.push_back(s[i] ^
      static_cast<char>(pcg32_output(S) >> 24));
  return ret;
template <uint64_t S, size_t N>
class encrypted_string
  constexpr encrypted_string(const char(&a)[N])
    : m_enc(encrypt<S>(a, std::make_index_sequence<N-1>()))
  constexpr size_t size() const { return N-1; }
  operator std::string() const
    return decrypt(S,, N-1);
  const char_array<N-1> m_enc;
template <uint64_t S, size_t N>
constexpr encrypted_string<S, N> make_encrypted_string(
    const char(&s)[N])
  return encrypted_string<S, N>(s);
  uint64_t{cx::fnv1(__FILE__ __DATE__ __TIME__) + __LINE__}
#define 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<Q.....y..bas
 404720 69635f73 7472696e 6700               ic_string.

Not bad. Per-compile obfuscation on string literals, with zero memory overhead and only a modest cost at the point of use.

Leave a Reply