1

I was working on an encryption algorithm and I wonder how I can change the following code into something simpler and how to reverse this code.

typedef struct 
{
    unsigned low : 4;
    unsigned high : 4;
} nibles;

static void crypt_enc(char *data, int size)
{
    char last = 0;

    //...

    // Pass 2
    for (i = 0; i < size; i++)
    {
        nibles *n = (nibles *)&data[i];

        n->low = last;
        last = n->high;
        n->high = n->low;
    }
    ((nibles *)&data[0])->low = last;
}

data is the input and the output for this code.

Jonathan Lima
  • 21
  • 1
  • 4

5 Answers5

3

You are setting both nibbles of every byte to the same thing, because you set the high nibble to the same as the low nibble in the end. I'll assume this is a bug and that your intention was to shift all the nibbles in the data, carrying from one byte to the other, and rolling around. Id est, ABCDEF (nibbles order from low to high) would become FABCDE. Please correct me if I got that wrong.

The code should be something like:

static void crypt_enc(char *data, int size)
{
    char last = 0;

    //...

    // Pass 2
    for (i = 0; i < size; i++)
    {
        nibles *n = (nibles *)&data[i];

        unsigned char old_low = n->low;
        n->low = last;
        last = n->high;
        n->high = old_low;
    }
    ((nibles *)&data[0])->low = last;
}

Is everything okay now? No. The cast to nibbles* is only well-defined if the alignment of nibbles is not stricter than the alignment of char. And that is not guaranteed (however, with a small change, GCC generates a type with the same alignment).

Personally, I'd avoid this issue altogether. Here's how I'd do it:

void set_low_nibble(char& c, unsigned char nibble) {
    // assumes nibble has no bits set in the four higher bits)
    unsigned char& b = reinterpret_cast<unsigned char&>(c);
    b = (b & 0xF0) | nibble;
}

void set_high_nibble(char& c, unsigned char nibble) {
    unsigned char& b = reinterpret_cast<unsigned char&>(c);
    b = (b & 0x0F) | (nibble << 4);
}

unsigned char get_low_nibble(unsigned char c) {
    return c & 0x0F;
}

unsigned char get_high_nibble(unsigned char c) {
    return (c & 0xF0) >> 4;
}

static void crypt_enc(char *data, int size)
{
    char last;

    //...

    // Pass 2
    for (i = 0; i < size; ++i)
    {
        unsigned char old_low = get_low_nibble(data[i]);
        set_low_nibble(data[i], last);
        last = get_high_nibble(data[i]);
        set_high_nibble(data[i], old_low);
    }
    set_low_nibble(data[0], last);
}

Doing the reverse amounts to changing "low" to "high" and vice-versa; rolling to the last nibble, not the first; and going through the data in the opposite direction:

for (i = size-1; i >= 0; --i)
{
    unsigned char old_high = get_high_nibble(data[i]);
    set_high_nibble(data[i], last);
    last = get_low_nibble(data[i]);
    set_low_nibble(data[i], old_high);
}
set_high_nibble(data[size-1], last);

If you want you can get rid of all the transfers to the temporary last. You just need to save the last nibble of all, and then shift the nibbles directly without the use of another variable:

last = get_high_nibble(data[size-1]);
for (i = size-1; i > 0; --i) // the last one needs special care
{
    set_high_nibble(data[i], get_low_nibble(data[i]));
    set_low_nibble(data[i], get_high_nibble(data[i-1]));
}
set_high_nibble(data[0], get_low_nibble(data[0]));
set_low_nibble(data[0], last);
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • `n->high = n->low;` -- I think you mean `n->high = old_low;` – Robᵩ Sep 22 '11 at 19:30
  • Dang, I missed the bit about casting to nibbles. This is why I like SO. – Philip Sep 22 '11 at 20:20
  • +1 for easy to read code. I think, you'd be better off shifting from array end to beginning to reduce temporary storage usage and transfers. Save last nibble to `last`, and keep transferring high nibble of prev. byte to low nibble of current. – vhallac Sep 22 '11 at 21:59
  • @vhallac that's a neat suggestion! Though I don't think it will be much of a performance improvement. I'd expect any decent compiler to registerize last. But I'll consider it anyway. – R. Martinho Fernandes Sep 22 '11 at 22:05
1

It looks like you're just shifting each nibble one place and then taking the low nibble of the last byte and moving it to the beginning. Just do the reverse to decrypt (start at the end of data, move to the beginning)

KevinDTimm
  • 14,226
  • 3
  • 42
  • 60
0

As you are using bit fields, it is very unlikely that there will be a shift style method to move nibbles around. If this shifting is important to you, then I recommend you consider storing them in an unsigned integer of some sort. In that form, bit operations can be performed effectively.

EvilTeach
  • 28,120
  • 21
  • 85
  • 141
0

Kevin's answer is right in what you are attempting to do. However, you've made an elementary mistake. The end result is that your whole array is filled with zeros instead of rotating nibbles.

To see why that is the case, I'd suggest you first implement a byte rotation ({a, b, c} -> {c, a, b}) the same way - which is by using a loop counter increasing from 0 to array size. See if you can do better by reducing transfers into the variable last.

Once you see how you can do that, you can simply apply the same logic to nibbles ({al:ah, bl:bh, cl:ch} -> {ch:al, ah:bl, bh:cl}). My representation here is incorrect if you think in terms of hex values. The hex value 0xXY is Y:X in my notation. If you think about how you've done the byte rotation, you can figure out how to save only one nibble, and simply transfer nibbles without actually moving them into last.

Community
  • 1
  • 1
vhallac
  • 13,301
  • 3
  • 25
  • 36
0

Reversing the code is impossible as the algorithm nukes the first byte entirely and discards the lower half of the rest.

On the first iteration of the for loop, the lower part of the first byte is set to zero.

n->low = last;

It's never saved off anywhere. It's simply gone.

// I think this is what you were trying for
last = ((nibbles *)&data[0])->low;
for (i = 0; i < size-1; i++)
{
    nibbles *n = (nibbles *)&data[i];
    nibbles *next = (nibbles *)&data[i+1];
    n->low = n->high;
    n->high = next->low;
}
((nibbles *)&data[size-1])->high = last;

To reverse it:

last = ((nibbles *)&data[size-1])->high;
for (i = size-1; i > 0; i--)
{
    nibbles *n = (nibbles *)&data[i];
    nibbles *prev = (nibbles *)&data[i-1];
    n->high = n->low;
    n->low = prev->high;
}
((nibbles *)&data[0])->low = last;

... unless I got high and low backwards.

But anyway, this is NOWHERE near the field of encryption. This is obfuscation at best. Security through obscurity is a terrible terrible practice and home-brew encryption get's people in trouble. If you're playing around, all the more power to you. But if you actually want something to be secure, please for the love of all your bytes use a well known and secure encryption scheme.

Philip
  • 1,539
  • 14
  • 23
  • There's an unspecified pass 1... That could be *real* encryption :) Or not. – R. Martinho Fernandes Sep 22 '11 at 19:59
  • Hmmm. Could be. Heh, I've seen confused people think that adding a second layer of "their encryption" adds security. It doesn't. ...and it's hard to explain why in a couple of sentences. So uh, you can trust me, I'm from the internet! – Philip Sep 22 '11 at 20:13
  • Even with a first pass, you can't encrypt without a key. And that's not in parameter list. :) – vhallac Sep 22 '11 at 22:01