Shifty XORyption

15

Write a program or function (or set of programs/functions) to encrypt and decrypt data given the following specification:

Encryption

  1. Calculate an XOR hash of the input by XOR-ing every byte with each-other.

  2. XOR every byte of the input by this hash.

  3. Shift the result four bits left.

  4. Pad the left side with the first four bits of the XOR hash.

  5. Pad the right side with the last four bits of the XOR hash.

Example

  • Given input: "G0lf" (0x47306C66)

  • Calculate XOR hash: 0x47 ^ 0x30 ^ 0x6C ^ 0x66 = 0x7D

  • XOR every byte by hash: 0x3A4D111B

  • Expected result (after shift and pad): "s¤Ñ\x11½" (0x73A4D111BD)

Rules

  • Your program/function can input/output of whatever type makes sense in your golfing language of choice (String, Byte Array, etc) as long as the input/output are the actual bytes. For example, you may not output a hexidecimal string.

  • Encryption and decryption can be separated into separate programs (score will be the their combined size) or a single one. Single methods can take an argument for whether it should encrypt or decrypt.

  • Input for encryption can be expected to be at least 1 byte in size.

  • Input for decryption can be expected to be at least 2 bytes.

  • Non-printable bytes do not need to be escaped in the output.

nderscore

Posted 2015-04-25T16:55:24.837

Reputation: 4 912

1Could an decimal array be used as an output form? – ɐɔıʇǝɥʇuʎs – 2015-04-25T17:56:58.573

@ɐɔıʇɥʇuʎs It would be acceptable to take input and output as an array of integers to represent bytes. – nderscore – 2015-04-25T19:01:10.270

Is there a maximum input length (for example, 14 bytes (56 bits), so that the final result fits in a 64-bit integer)? – Doorknob – 2015-04-25T20:00:04.520

1Just a note: From a cryptography point of view this is not an encryption, as it has no key (or a 0-bit key). – Paŭlo Ebermann – 2015-04-25T21:21:20.203

@Doorknob No restrictions on input size. It can be expected to fit in memory though. – nderscore – 2015-04-25T21:33:41.377

@PaŭloEbermann What would the correct terminology be for this? I don't want to say it's an encoding because the result is definitely obfuscated. Would it qualify as a cipher? – nderscore – 2015-04-25T22:00:00.657

1I'm just waiting for someone to post something about never rolling your own encryption, ignoring the site this is on... – user253751 – 2015-04-25T22:02:13.417

Funny random comment, I pronounce XOR "zorr", so I expect to see "a XOR hash" not "an XOR hash" – Claudiu – 2015-04-26T03:08:24.713

What is the character encoding you use for the encrypted result? – FUZxxl – 2015-04-26T11:02:34.613

Is it allowed to receive input as an 8 × n array where each row of the array contains one byte represented as a vector of eight bits? – FUZxxl – 2015-04-26T11:06:23.093

@FUZxxl no encoding is necessary. A two dimensional array of bits is okay. – nderscore – 2015-04-26T15:31:01.430

Answers

9

CJam, 28 + 27 = 55 bytes

For each part I'm presenting one program which expects input/output to be in the form of an integer array and one which uses a string. The above byte count is for the integer array version, but the linked script and the explanation are for the string-based version (which can be used to test the example given in the question).

Encryption

q~_:^_GbYUe[\@f^Gfbe_*2/Gfbp
q:i_:^_GbYUe[\@f^Gfbe_*2/Gfb:c

Decryption

q~{_G/\G%}%)\(G*@+\2/Gfbf^p
q:i{_G/\G%}%)\(G*@+\2/Gfbf^:c

Here is a test script which does a full round trip and prints the encrypted code before doing the decryption again.

Explanation

q:i_:^_GbYUe[\@f^Gfbe_*2/Gfb:c
q:i                            e# Read the input and convert characters to byte values.
   _:^                         e# Duplicate and fold XOR onto the characters to get 
                               e# the hash.
      _Gb                      e# Duplicate and convert to base 16 to get nibbles.
         YUe[                  e# Pad to width 2 with zeroes.
             \@                e# Pull up other copy and integer array.
               f^              e# XOR each integer with the hash.
                 Gfbe_         e# Convert each result to base 16 and flatten that.
                      *        e# Join the hash nibbles with this array.
                       2/      e# Split into pairs.
                         Gfb   e# Interpret each pair as base 16.
                            :c e# Convert each integer to a character.

q:i{_G/\G%}%)\(G*@+\2/Gfbf^:c
q:i                            e# Read the input and convert characters to byte values.
   {      }%                   e# Map this block onto each byte.
    _G/\G%                     e# Get the two base 16 digits individually.
            )\(                e# Slice off the last and first nibble.
               G*@+\           e# Combine into byte (the hash) and swap with array.
                    2/Gfb      e# Split array into pairs and interpret each as base 16.
                         f^    e# XOR each with the hash.
                           :c  e# Convert each integer to a character.

Martin Ender

Posted 2015-04-25T16:55:24.837

Reputation: 184 808

6

CJam, 36 + 34 = 70 bytes

A bit different approach using binary forms

Encrypter:

q_:^:Hf^H+{i2b8Ue[}%)4/~@\]e_8/2fb:c

How it works:

q_:^                                  e# Read input as string, copy and XOR all the chars
    :Hf^                              e# Store the XOR in H and XOR each char with H
        H+                            e# Append H to the char array
          {       }%                  e# On each of the elements in the array
           i2b                        e# Convert the ASCII value to binary
              8Ue[                    e# Pad with 0 so that the length is 8
                    )                 e# Pop out the last array element, which is H
                     4/~@\            e# Put first 4 bits of H before the input array
                                      e# And rest 4 after it
                          ]e_8/       e# Flatten everything into a single array and group
                                      e# into pieces of 8 bits
                               2fb:c  e# Convert each 8 bit part to integer and then to
                                      e# its character form

Decrypter:

q{i2b8Ue[4/~}%)\(@+2b\:+8/2fb\f^:c

How it works:

q{          }%                      e# For each character of the input string
  i2b                               e# Convert to ASCII code and then to its binary form
     8Ue[                           e# Pad with enough 0 so that length is 8 bit
         4/~                        e# Split into parts of 4 and unwrap
              )\(@+                 e# Take out the first and last 4 bit group and join
                                    e# them together to get the XOR Hash H
                   2b\              e# Convert H to decimal form and swap to put the
                                    e# remaining converted input array on top
                      :+8/          e# Join all bits together and split into groups of 8
                          2fb       e# Convert each 8 but group to decimal form
                             \f^    e# Swap to put H on top and XOR each number with H
                                :c  e# Get character from each of the ASCII value

Try the encrypter and decrypter online

Optimizer

Posted 2015-04-25T16:55:24.837

Reputation: 25 836

6

Pyth, 69 bytes

Ksm>+0jCd16_2zJ?,hKeKQmxFdCcK2=KsmmxFkC,dJc?tPKQK2smCid16c?KQ++hJKeJ2

This combines encryption and decryption, simply add a 0 as argument for encryption or a 1 for decryption. The reason for this is simple. Converting strings into bits (or 4 bit integer) or the reverse is really really long in Pyth. By combining the both functions into one program, I can save a lot of bytes.

Online demonstrations: Encryption and Decryption.

Explanation:

The first part converts the input into a list of 4-bit integer (each char get converted into 2 4-bit integer) and stores it in K.

  m          z   map each character d of input (=z) to:
       Cd            the ascii-value of d
      j  16          convert the result into base 16
   >+0     _2        insert a zero to the front and take the last 2 values
                     (so that each char gets mapped to exactly 2 numbers)
Ks               chain all these tuples and assign them to K

The second part determines the hash values and stores them in J. If Q==0 it computes them by xor, otherwise it takes the first and last value of K.

 ?     Q           ... if Q (=second input) else ...
  ,hKeK            [K[0], K[-1]]
        m   CcK2   map each d of zipped(K chopped into pairs) to:
                   [zipped(...) gives me 2 lists, one with the values of the even indices, and one with the odd indices]
         xFd           fold the list d by xor
J                  store the result in J (this is the hash value)

The next part does the xor using the hash values. When Q == 0 it is performed on the complete list K, otherwise only on the list K without the first and last value.

=KsmmxFkC,dJc?tPKQK2
             ?tPKQK    K[1:-1] if Q else K 
   m        c      2   map each d of [... chopped into pairs] to:
    m   C,dJ              map each pair k of zip(d,J) to:
     xFk                     apply xor to the 2 values in k
=Ks                    chain all these tuples and assign them to K

And the last part converts K back to chars:

smCid16c?KQ++hJKeJ2
        ?KQ++hJKeJ    K if Q else J[0] + K + J[1]
 m     c          2   map each pair of [... chopped into pairs] to:
   id16                  convert d into a single integer
  C                      convert to char
s                     join all chars and print

Jakube

Posted 2015-04-25T16:55:24.837

Reputation: 21 462

0

Javascript (ES6) 83 + 73 = 156

Both functions take input as and ouput an array of numbers to represent bytes.

Encrypt 85 84 83

E=s=>s.concat((h=s.reduce((x,y)=>x^y))<<4&240^h).map(x=>a<<4&240|(a=x^h)>>4,a=h>>4)

Decrypt 75 73

D=s=>s.map(x=>(a<<4&240|(a=x)>>4)^h,h=(a=s.shift())&240|s[~-s.length]&15)

Demonstration (Firefox only)

E=s=>s.concat((h=s.reduce((x,y)=>x^y))<<4&240^h).map(x=>a<<4&240|(a=x^h)>>4,a=h>>4)
D=s=>s.map(x=>(a<<4&240|(a=x)>>4)^h,h=(a=s.shift())&240|s[~-s.length]&15)

toHexString = x=>'0x'+x.map(y=>y.toString(16)).join('')

input = [...'G0lf'].map(x=>x.charCodeAt());
document.write('Input: ' + toHexString(input) + '<br />');

encrypted = E(input);
document.write('Encrypted: ' + toHexString(encrypted) + '<br />');

decrypted = D(encrypted);
document.write('Decrypted: ' + toHexString(decrypted) + '<br />');


Using Strings 131 + 129 = 260

And just for fun... here's some versions which use strings for input/output instead.

E=(s,h=0)=>[for(x of s)(h^=y=x.charCodeAt(),y)].concat(h<<4&240^h).map(x=>String.fromCharCode(a<<4&240|(a=x^h)>>4),a=h>>4).join('')

D=s=>(s=[s.charCodeAt(j=i)for(i in s)]).map(x=>String.fromCharCode((a<<4&240|(a=x)>>4)^h),h=(a=s.shift())&240|s[~-j]&15).join('')

E('G0lf') // 's¤Ñ\x11½'
D('s¤Ñ\x11½') // 'G0lf'

nderscore

Posted 2015-04-25T16:55:24.837

Reputation: 4 912