Compile time string encryption using constexpr
Asked Answered
A

2

17

I want to have a compile-time string encryption, such that I could write in my code:

const auto encryptedInvalidLicense = ENCRYPT("Invalid license");
std::cout << encryptedInvalidLicense.decrypt() << std::endl; // outputs "Invalid license"

and the string "Invalid license" wont appear in the binaries. Pre-builds might be the answer, but I'm looking for a pure c++ constexpr solution to this problem, and that it will be supported by VS2015.

Any suggestions?


  1. I've already looked into Compile-time string encryption, which doesn't provide a constexpr solution to the problem.

  2. I've also looked into http://www.unknowncheats.me/forum/c-and-c/113715-compile-time-string-encryption.html . Though it's a constexpr solution, VS2015 still adds the strings plain text to the binaries.

Ahearn answered 29/8, 2015 at 14:27 Comment(1)
Possible duplicate of Encrypting / obfuscating a string literal at compile-timeTrice
W
16

Here's how I would do it:

1.) Use the str_const template for constexpr string manipulation described here: Conveniently Declaring Compile-Time Strings in C++

Code:

class str_const {
    // constexpr string
private:
    const char * const p_;
    const std::size_t sz_;

public:
    template <std::size_t N>
    constexpr str_const(const char(&a)[N])
            : p_(a)
            , sz_(N - 1)
    {}

    constexpr char operator[](std::size_t n) const { return n < sz_ ? p_[n] : throw std::out_of_range(""); }
    constexpr std::size_t size() const { return sz_; }

    constexpr const char * get() const { return p_; }
};

This lets you do things like str_const message = "Invalid license" and manipulate message in constexpr functions.

2.) Make a simple compile-time pseudorandom generator, using the macros __TIME__ and __LINE__ to generate the seed. This is described in detail here: Generate random numbers in C++ at compile time

They give some template-based code.

3.) Make a struct, with a constexpr ctor which takes either const char [] and templates itself against the size similarly to the str_const example, or which just takes a str_const, and generates two str_const which it are its member variables.

  • A str_const of length n containing pseudorandom unsigned chars, generated using the pseudorandom generator, where n is the length of the input. (the "noise string")
  • A str_const of length n containing the entry-wise sum (as unsigned chars) of the input characters with the noise characters. (the "cipher text")

Then it has a member function decrypt which need not be constexpr, and can return a std::string, which simply subtracts each character of the noise string from the corresponding character of the cipher text and returns the resulting string.

If your compiler is still storing the original string literal in the binary, it means that either it's storing the input string literal (the constructor argument) which I don't think it should be doing since its a temporary, or its basically inlining the decrypt function, and you should be able to prevent that by obfuscating it with function pointers, or marking it volatile or similar.

Edit: I'm not sure if the standard requires that temporary constexpr objects should not appear in the binary. Actually I'm curious about that now. My expectation is that at least in a release build, a good compiler should remove them when they are no longer needed.

Edit: So, you already accepted my answer. But anyways for completeness, here's some source code that implements the above ideas, using only C++11 standard. It works on gcc-4.9 and clang-3.6, even when optimizations are disabled, as nearly as I can tell.

#include <array>
#include <iostream>
#include <string>

typedef uint32_t u32;
typedef uint64_t u64;
typedef unsigned char uchar;

template<u32 S, u32 A = 16807UL, u32 C = 0UL, u32 M = (1UL<<31)-1>
struct LinearGenerator {
    static const u32 state = ((u64)S * A + C) % M;
    static const u32 value = state;
    typedef LinearGenerator<state> next;
    struct Split { // Leapfrog
        typedef LinearGenerator< state, A*A, 0, M> Gen1;
        typedef LinearGenerator<next::state, A*A, 0, M> Gen2;
    };
};

// Metafunction to get a particular index from generator
template<u32 S, std::size_t index>
struct Generate {
    static const uchar value = Generate<LinearGenerator<S>::state, index - 1>::value;
};

template<u32 S>
struct Generate<S, 0> {
    static const uchar value = static_cast<uchar> (LinearGenerator<S>::value);
};

// List of indices
template<std::size_t...>
struct StList {};

// Concatenate
template<typename TL, typename TR>
struct Concat;

template<std::size_t... SL, std::size_t... SR>
struct Concat<StList<SL...>, StList<SR...>> {
    typedef StList<SL..., SR...> type;
};

template<typename TL, typename TR>
using Concat_t = typename Concat<TL, TR>::type;

// Count from zero to n-1
template<size_t s>
struct Count {
    typedef Concat_t<typename Count<s-1>::type, StList<s-1>> type;
};

template<>
struct Count<0> {
    typedef StList<> type;
};

template<size_t s>
using Count_t = typename Count<s>::type;

// Get a scrambled character of a string
template<u32 seed, std::size_t index, std::size_t N>
constexpr uchar get_scrambled_char(const char(&a)[N]) {
    return static_cast<uchar>(a[index]) + Generate<seed, index>::value;
}

// Get a ciphertext from a plaintext string
template<u32 seed, typename T>
struct cipher_helper;

template<u32 seed, std::size_t... SL>
struct cipher_helper<seed, StList<SL...>> {
    static constexpr std::array<uchar, sizeof...(SL)> get_array(const char (&a)[sizeof...(SL)]) {
        return {{ get_scrambled_char<seed, SL>(a)... }};
    }
};

template<u32 seed, std::size_t N>
constexpr std::array<uchar, N> get_cipher_text (const char (&a)[N]) {
    return cipher_helper<seed, Count_t<N>>::get_array(a);
}

// Get a noise sequence from a seed and string length
template<u32 seed, typename T>
struct noise_helper;

template<u32 seed, std::size_t... SL>
struct noise_helper<seed, StList<SL...>> {
    static constexpr std::array<uchar, sizeof...(SL)> get_array() {
        return {{ Generate<seed, SL>::value ... }};
    }
};

template<u32 seed, std::size_t N>
constexpr std::array<uchar, N> get_key() {
    return noise_helper<seed, Count_t<N>>::get_array();
}


/*
// Get an unscrambled character of a string
template<u32 seed, std::size_t index, std::size_t N>
char get_unscrambled_char(const std::array<uchar, N> & a) {
    return static_cast<char> (a[index] - Generate<seed, index>::value);
}
*/

// Metafunction to get the size of an array
template<typename T>
struct array_info;

template <typename T, size_t N>
struct array_info<T[N]>
{
    typedef T type;
    enum { size = N };
};

template <typename T, size_t N>
struct array_info<const T(&)[N]> : array_info<T[N]> {};

// Scramble a string
template<u32 seed, std::size_t N>
class obfuscated_string {
private:
    std::array<uchar, N> cipher_text_;
    std::array<uchar, N> key_;
public:
    explicit constexpr obfuscated_string(const char(&a)[N])
        : cipher_text_(get_cipher_text<seed, N>(a))
        , key_(get_key<seed,N>())
    {}

    operator std::string() const {
        char plain_text[N];
        for (volatile std::size_t i = 0; i < N; ++i) {
            volatile char temp = static_cast<char>( cipher_text_[i] - key_[i] );
            plain_text[i] = temp;
        }
        return std::string{plain_text, plain_text + (N - 1)};///We do not copy the termination character
    }
};

template<u32 seed, std::size_t N>
std::ostream & operator<< (std::ostream & s, const obfuscated_string<seed, N> & str) {
    s << static_cast<std::string>(str);
    return s;
}

#define RNG_SEED ((__TIME__[7] - '0') * 1  + (__TIME__[6] - '0') * 10  + \
              (__TIME__[4] - '0') * 60   + (__TIME__[3] - '0') * 600 + \
              (__TIME__[1] - '0') * 3600 + (__TIME__[0] - '0') * 36000) + \
              (__LINE__ * 100000)


#define LIT(STR) \
    obfuscated_string<RNG_SEED, array_info<decltype(STR)>::size>{STR}

auto S2 = LIT(("Hewwo, I'm hunting wabbits"));

int main() {
    constexpr auto S1 = LIT(("What's up doc"));
    std::cout << S1 << std::endl;
    std::cout << S2 << std::endl;
}
Wien answered 29/8, 2015 at 15:43 Comment(4)
Seems to work only with Whole Level Optimization and "Maximize Speed (/O2)" or "Minimize Size (/O1)".Ahearn
#include <string>, also probably remove boostGoff
I was getting crazy to understand some problems showing up when concatenating to static_cast<std::string>(LIT("Test")) another string and calling c_str(). I think it should be return std::string{plain_text, plain_text + N - 1};. See test here: coliru.stacked-crooked.com/a/afb58889a8360538Tolidine
Also return std::string(plain_text); and return std::string(plain_text, N - 1) would be good.Tolidine
Z
-2

I'd have code like this:

#if GENERATE_CODE
.... code to generate the C++ code describing the encrypted literal
.... generating exactly the code in the #else part
#else
static char encryptedLicense [32] = "..."; or whatever
#endif

Not trying to be clever. Copy the #if code in a separate file, compile and run, paste the result back.

Zanazander answered 29/8, 2015 at 15:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.