2
$\begingroup$

I have the code for a decryption algorithm but I'm struggling to figure out how to reverse it into the original encryption code.

function write(x) {
    return x >= "0" && "9" >= x ? (x.charCodeAt(0) - 48) : (x >= "a") && "f" >= x ? (x.charCodeAt(0) - 87) : 0;
}

function ondata(input) {
    var DATA = "";
    var i = 0;
    for (; i < input.length; i += 2) {
        var diff = write(input[i]);
        var firstNum = write(input[i + 1]);
        val = 16 * diff + firstNum ^ [31, 126, 188, 168, 210, 3, 208, 219, 229, 149, 187, 222, 17, 31, 59, 100, 131, 62, 163, 81, 115, 154, 2, 220, 166, 30, 112, 192, 48, 172, 174, 119, 249, 5, 251, 41, 46, 148, 43, 210, 35, 42, 231, 145, 16, 6, 10, 28, 228, 49, 103, 248, 151, 109, 19, 20, 151, 243, 12, 193, 152, 126, 83, 235, 27, 109, 66, 96, 179, 88, 11, 189, 30, 187, 23, 172, 93, 109, 239, 247, 123, 105, 55, 98, 117, 175, 114, 212, 146, 136, 38, 115, 124, 39][i / 2];
        DATA += String.fromCharCode(val);
    }
    return DATA;
}

ondata("6c0adddaa66abebc"); // 'starting' (javascript)

As shown, the string 6c0adddaa66abebc produces the output starting. My initial thought was to take the character code of each input character, but I wasn't sure what do to then. I'm sure it's just simple algebra but an explanation or a point in the right direction would be great, I'd like to learn from this.

UPDATE:

Ilmari Karonen's post gave me the help I needed to understand this. The problem I had was that I didn't actually understand what the long val = 16 * ... line was doing. His refactored code and explanation helped me to understand the process. Here's how I came up with the solution if anybody's interested in the thought process:

I knew I had to reverse the function, like in algebra. So logically, I started with finding the character code of each letter in the starting string:

var x = value.charCodeAt(i);

Now, I had to "undo" the xor applied, which obviously (not to me) was just the xor function with the same key:

x ^= key[i];

Then, I had to undo the conversion of the hex string to a decimal number, which was just converting the decimal to a hex number:

var hex = x.toString(16);

I also had to add padded zero's to the hex number, because the decoder worked in pairs of two.

if (hex.length < 2) hex = '0' + hex;

Final solution:

function parse(value)
{
    var result = "";

    for (var i = 0; i < value.length; i++)
    {       
        var x = value.charCodeAt(i);        
        x ^= key[i];

        var hex = x.toString(16);
        if(hex.length < 2)
          hex = '0' + hex;
        result += hex;
    }
    return result;
}
$\endgroup$
  • 1
    $\begingroup$ This is a decent question, and I've answered it below (although only partially so far, just in case it's an exercise you're supposed to complete on your own). However, I'm not 100% sure it really belongs on this site; it might be better suited for Stack Overflow, or perhaps for Reverse Engineering. If others feel the same way, we could always ask for a mod to migrate it. $\endgroup$ – Ilmari Karonen Mar 14 '16 at 3:23
  • $\begingroup$ @IlmariKaronen I agree, I wasn't quite sure which site to put in on. I feel like on stackoverflow it would have been closed for "asking for code", but if anyone thinks it should be moved go ahead. $\endgroup$ – CC Inc Mar 14 '16 at 3:58
  • $\begingroup$ The trick with SO is that "asking for code" is really more about how you ask your question than about what you ask. You want to stand out from the "gimme teh codez 4 my homework now" crowd, so you need to phrase your question as "please teach me how to do this myself" instead of "please give me code I can copy-paste". That's really all there is to it. $\endgroup$ – Ilmari Karonen Mar 14 '16 at 10:20
1
$\begingroup$

First of all, the write() function does nothing but decode a single hexadecimal digit into a number from 0 to 15. You could replace it with:

function write(x) {
    return parseInt(x, 16);
}

Thus, the code:

var diff = write(input[i]);
var firstNum = write(input[i + 1]);

takes two consecutive hex digits from the input string and decodes them. The expression 16 * diff + firstNum then just multiplies the first digit by 16 and adds it to the second one, giving you an 8-bit number from 0 to 255. (In fact, since parseInt() can handle multi-digit numbers, you could replace all that code with just parseInt(input.substr(i, 2)).)

Next, the 8-bit number we just decoded is XORed with the value of this long expression:

[31, 126, 188, 168, 210, 3, 208, 219, 229, 149, 187, 222, 17, 31, 59, 100, 131, 62, 163, 81, 115, 154, 2, 220, 166, 30, 112, 192, 48, 172, 174, 119, 249, 5, 251, 41, 46, 148, 43, 210, 35, 42, 231, 145, 16, 6, 10, 28, 228, 49, 103, 248, 151, 109, 19, 20, 151, 243, 12, 193, 152, 126, 83, 235, 27, 109, 66, 96, 179, 88, 11, 189, 30, 187, 23, 172, 93, 109, 239, 247, 123, 105, 55, 98, 117, 175, 114, 212, 146, 136, 38, 115, 124, 39][i / 2]

Scrolling to the end, you can see that it's just a long array literal indexed by i / 2. Since i counts hex digits from the start of the input and is incremented by 2 per iteration, i / 2 is simply counting the number of bytes output so far. Thus, each byte of the decoded string is XORed with a different number from this fixed array.

(If the index i / 2 points past the end of the key array, the resulting key byte will be undefined, which gets converted to zero when it's XORed with the decoded byte. Thus, in effect, for long inputs this code behaves as if the key array was padded with zeros.)

Finally, the resulting 8-bit number is just passed to String.fromCharCode(), which converts it into an ASCII (or ISO Latin 1) character, which then gets appended to the output string.

Thus, this code just decodes the input from hexadecimal into a byte string, and then XORs the bytes with a fixed key. Now can you reverse it?


Ps. Here's how I'd rewrite the decoder:

var key = [31, 126, 188, 168, 210, 3, 208, 219, 229, 149, 187, 222, 17, 31, 59, 100, 131, 62, 163, 81, 115, 154, 2, 220, 166, 30, 112, 192, 48, 172, 174, 119, 249, 5, 251, 41, 46, 148, 43, 210, 35, 42, 231, 145, 16, 6, 10, 28, 228, 49, 103, 248, 151, 109, 19, 20, 151, 243, 12, 193, 152, 126, 83, 235, 27, 109, 66, 96, 179, 88, 11, 189, 30, 187, 23, 172, 93, 109, 239, 247, 123, 105, 55, 98, 117, 175, 114, 212, 146, 136, 38, 115, 124, 39];

function decode(hex) {
    var len = hex.length / 2, bytes = [];

    // decode input hex string into array of bytes:
    for (var i = 0; i < len; i++) {
        bytes[i] = parseInt(hex.substr(2*i, 2), 16);
    }

    // XOR each decoded byte with the corresponding key byte:
    for (var i = 0; i < len; i++) {
        bytes[i] ^= key[i];
    }

    // convert the byte array into a string and return it:
    return String.fromCharCode.apply(null, bytes);
}

You can check that this gives the same results as your original code, but it's hopefully more readable, and also possibly a bit faster (not that speed really matters for such short strings).

$\endgroup$
  • $\begingroup$ Great, thank you for the explanation and especially for not just giving me the solution outright. What definitely confused me the most was the purpose of the val = 16 * ... line in the code. Once I had your rewritten converter code, the reversal was a piece of cake. Thanks for the help! $\endgroup$ – CC Inc Mar 14 '16 at 3:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.