The obfuscate code is actually obfuscate1; obfuscate2;
, and the restore code is actually restore2; restore1;
. It's possible to nest these steps arbitrarily.
Let's look at just the first step.
// Obfuscate
unsigned t = (x ^ (x >> d1)) & mask1;
unsigned u = x ^ t ^ (t << d1);
// Restore
t = (u ^ (u >> d1)) & mask1;
z = u ^ t ^ (t << d1);
It's actually the same step, first applied to x
, then applied to u
. The key is that t
is the same both times, since the identity a ^ b ^ b == a
together with the associativity and commutativity of ^
implies the following. (I'm going to assume sane operator precedence of ==
; if you translate these equations into C, you're going to need more parentheses.)
z == u ^ t ^ (t << d1)
== x ^ t ^ (t << d1) ^ t ^ (t << d1)
== x ^ t ^ t ^ (t << d1) ^ (t << d1)
== x ^ (t << d1) ^ (t << d1)
== x
I claim that it's sufficient if (mask1 << d1) >> d1 == mask1
(i.e., the high d1
bits of mask1
are zero) and (mask1 & (mask1 << d1)) == 0
. Here's some algebra where I push the ^
operations to the root of the expression tree for u
at the cost of blowing up the expressions.
t == (x ^ (x >> d1)) & mask1
== (x & mask1) ^ ((x >> d1) & mask1)
t << d1 == ((x & mask1) ^ ((x >> d1) & mask1)) << d1
== ((x & mask1) << d1) ^ (((x >> d1) & mask1) << d1)
u == x ^
(x & mask1) ^
((x >> d1) & mask1) ^
((x & mask1) << d1) ^
(((x >> d1) & mask1) << d1)
Let's do the same (but sparser intermediate steps) for the second expression for t
, which I'll call t'
because it hasn't been proved equal to t
yet.
t' == (u ^ (u >> d1)) & mask1
== (x & mask1) ^ // 0.
((x & mask1) & mask1) ^ // 1.
(((x >> d1) & mask1) & mask1) ^ // 2.
(((x & mask1) << d1) & mask1) ^ // 3.
((((x >> d1) & mask1) << d1) & mask1) ^ // 4.
(((x >> d1) & mask1) & mask1) ^ // 5.
(((x & mask1) >> d1) & mask1) ^ // 6.
((((x >> d1) & mask1) >> d1) & mask1) ^ // 7.
((((x & mask1) << d1) >> d1) & mask1) ^ // 8.
(((((x >> d1) & mask1) << d1) >> d1) & mask1) // 9.
Masking a second time has no effect.
t' == (x & mask1) ^ // 0.
(x & mask1) ^ // 1a.
((x >> d1) & mask1) ^ // 2a.
(((x & mask1) << d1) & mask1) ^ // 3.
((((x >> d1) & mask1) << d1) & mask1) ^ // 4.
((x >> d1) & mask1) ^ // 5a.
(((x & mask1) >> d1) & mask1) ^ // 6.
((((x >> d1) & mask1) >> d1) & mask1) ^ // 7.
((((x & mask1) << d1) >> d1) & mask1) ^ // 8.
(((((x >> d1) & mask1) << d1) >> d1) & mask1) // 9.
Eliminate 0
and 1a
, and 2a
and 5a
, which cancel each other.
t' == (((x & mask1) << d1) & mask1) ^ // 3.
((((x >> d1) & mask1) << d1) & mask1) ^ // 4.
(((x & mask1) >> d1) & mask1) ^ // 6.
((((x >> d1) & mask1) >> d1) & mask1) ^ // 7.
((((x & mask1) << d1) >> d1) & mask1) ^ // 8.
(((((x >> d1) & mask1) << d1) >> d1) & mask1) // 9.
Since (mask1 << d1) >> d1 == mask1
, shifting left and right after masking is redundant.
t' == (((x & mask1) << d1) & mask1) ^ // 3.
((((x >> d1) & mask1) << d1) & mask1) ^ // 4.
(((x & mask1) >> d1) & mask1) ^ // 6.
((((x >> d1) & mask1) >> d1) & mask1) ^ // 7.
(x & mask1) ^ // 8b.
((x >> d1) & mask1) // 9b.
The identity (mask1 & (mask1 << d1)) == 0
can be shifted right to show that (mask1 & (mask1 >> d1)) == 0
. This implies (tediously) that 6
, 7
, 8b
, and 9b
all are zero, leaving the following.
t' == (x & mask1) ^ // 8b.
((x >> d1) & mask1) // 9b.
== t
tl;dr
(mask1 << d1) >> d1 == mask1
(i.e., the high d1
bits of mask1
are zero) and (mask1 & (mask1 << d1)) == 0
. But seriously, just AES encrypt/decrypt instead.