""" Shannon: Shannon stream cipher and MAC -- reference implementation, ported from C code written by Greg Rose https://github.com/sashahilton00/spotify-connect-resources/blob/master/Shannon-1.0/ShannonRef.c Copyright 2017, Dmitry Borisov THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND AGAINST INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import struct, \ copy # Constants N = 16 INITKONST = 0x6996c53a KEYP = 13 # where to insert key/MAC/counter words FOLD = N # how many iterations of folding to do class Shannon: @staticmethod def ROTL(w, x): return ((w << x) | (w >> (32 - x))) & 0xFFFFFFFF @staticmethod def ROTR(w, x): return ((w >> x) | (w << (32 - x))) & 0xFFFFFFFF """ Nonlinear transform (sbox) of a word. There are two slightly different combinations. """ @staticmethod def sbox1(w): w ^= Shannon.ROTL(w, 5) | Shannon.ROTL(w, 7) w ^= Shannon.ROTL(w, 19) | Shannon.ROTL(w, 22) return w """ Nonlinear transform (sbox) of a word. There are two slightly different combinations. """ @staticmethod def sbox2(w): w ^= Shannon.ROTL(w, 7) | Shannon.ROTL(w, 22) w ^= Shannon.ROTL(w, 5) | Shannon.ROTL(w, 19) return w """ initialise to known state """ def _initstate(self): global N, \ INITKONST # Generate fibonacci numbers up to N self._R = [1, 1] for x in range(1, N - 1): self._R.append(self._R[x] + self._R[x - 1]) self._konst = INITKONST """ cycle the contents of the register and calculate output word in _sbuf. """ def _cycle(self): # nonlinear feedback function t = self._R[12] ^ self._R[13] ^ self._konst t = Shannon.sbox1(t) ^ Shannon.ROTL(self._R[0], 1) # Shift to the left self._R = self._R[1:] + [t] t = Shannon.sbox2(self._R[2] ^ self._R[15]) self._R[0] ^= t self._sbuf = t ^ self._R[8] ^ self._R[12] """ The Shannon MAC function is modelled after the concepts of Phelix and SHA. Basically, words to be accumulated in the MAC are incorporated in two different ways: 1. They are incorporated into the stream cipher register at a place where they will immediately have a nonlinear effect on the state 2. They are incorporated into bit-parallel CRC-16 registers; the contents of these registers will be used in MAC finalization. """ """ Accumulate a CRC of input words, later to be fed into MAC. This is actually 32 parallel CRC-16s, using the IBM CRC-16 polynomial x^16 + x^15 + x^2 + 1. """ def _crcfunc(self, i): t = self._CRC[0] ^ self._CRC[2] ^ self._CRC[15] ^ i # Accumulate CRC of input self._CRC = self._CRC[1:] + [t] """ Normal MAC word processing: do both stream register and CRC. """ def _macfunc(self, i): global KEYP self._crcfunc(i) self._R[KEYP] ^= i """ extra nonlinear diffusion of register for key and MAC """ def _diffuse(self): global FOLD for i in range(FOLD): self._cycle() """ Common actions for loading key material Allow non-word-multiple key and nonce material. Note also initializes the CRC register as a side effect. """ def _loadkey(self, key): global KEYP, \ N # Pad key with 00s to align on 4 bytes and add key_len padding_size = int((len(key) + 3) / 4) * 4 - len(key) key = key + (b'\x00' * padding_size) + struct.pack("I", nonce)) self._R = copy.copy(self._initR) self._konst = INITKONST self._loadkey(nonce) self._konst = self._R[0] self._nbuf = 0 self._mbuf = 0 """ Encrypt small chunk """ def _encrypt_chunk(self, chunk): result = [] for c in chunk: self._mbuf ^= c << (32 - self._nbuf) result.append(c ^ (self._sbuf >> (32 - self._nbuf)) & 0xFF) self._nbuf -= 8 return result """ Combined MAC and encryption. Note that plaintext is accumulated for MAC. """ def encrypt(self, buf): # handle any previously buffered bytes result = [] if self._nbuf != 0: head = buf[:(self._nbuf >> 3)] buf = buf[(self._nbuf >> 3):] result = self._encrypt_chunk(head) if self._nbuf != 0: return bytes(result) # LFSR already cycled self._macfunc(self._mbuf) # Handle body i = 0 while len(buf) >= 4: self._cycle() t = struct.unpack("> (32 - self._nbuf)) & 0xFF)) self._mbuf ^= result[-1] << (32 - self._nbuf) self._nbuf -= 8 return result """ Combined MAC and decryption. Note that plaintext is accumulated for MAC. """ def decrypt(self, buf): # handle any previously buffered bytes result = [] if self._nbuf != 0: head = buf[:(self._nbuf >> 3)] buf = buf[(self._nbuf >> 3):] result = self._decrypt_chunk(head) if self._nbuf != 0: return bytes(result) # LFSR already cycled self._macfunc(self._mbuf) # Handle whole words i = 0 while len(buf) >= 4: self._cycle() t = struct.unpack(">= 8 return bytes(result) if __name__ == '__main__': TESTSIZE = 23 TEST_KEY = b"test key 128bits" TEST_PHRASE = b'\x00' * 20 sh = Shannon( bytes([ 133, 199, 15, 101, 207, 100, 229, 237, 15, 249, 248, 155, 76, 170, 62, 189, 239, 251, 147, 213, 22, 186, 157, 47, 218, 198, 235, 14, 171, 50, 11, 121 ])) sh.set_nonce(0) p1 = sh.decrypt( bytes([ 235, 94, 210, 19, 246, 203, 195, 35, 22, 215, 80, 69, 158, 247, 110, 146, 241, 101, 199, 37, 67, 92, 5, 197, 112, 244, 77, 185, 197, 118, 119, 56, 164, 246, 159, 242, 56, 200, 39, 27, 141, 191, 37, 244, 244, 164, 44, 250, 59, 227, 245, 155, 239, 155, 137, 85, 244, 29, 52, 233, 180, 119, 166, 46, 252, 24, 141, 20, 135, 73, 144, 10, 176, 79, 88, 228, 140, 62, 173, 192, 117, 116, 152, 182, 246, 183, 88, 90, 73, 51, 159, 83, 227, 222, 140, 48, 157, 137, 185, 131, 201, 202, 122, 112, 207, 231, 153, 155, 9, 163, 225, 73, 41, 252, 249, 65, 33, 102, 83, 100, 36, 115, 174, 191, 43, 250, 113, 229, 146, 47, 154, 175, 55, 101, 73, 164, 49, 234, 103, 32, 53, 190, 236, 47, 210, 78, 141, 0, 176, 255, 79, 151, 159, 66, 20, ])) print([hex(x) for x in p1]) print([hex(x) for x in sh.finish(4)]) sh.set_nonce(1) print([hex(x) for x in sh.decrypt(bytes([173, 184, 50]))]) sh = Shannon(TEST_KEY) sh.set_nonce(0) encr = [sh.encrypt(bytes([x])) for x in TEST_PHRASE] print('Encrypted 1-by-1 (len %d)' % len(encr), [hex(x[0]) for x in encr]) print(' sbuf %08x' % sh._sbuf) print(' MAC', [hex(x) for x in sh.finish(4)]) sh.set_nonce(0) encr = sh.encrypt(TEST_PHRASE) print('Encrypted whole (len %d)' % len(encr), [hex(x) for x in encr]) print(' sbuf %08x' % sh._sbuf) print(' MAC', [hex(x) for x in sh.finish(4)]) sh.set_nonce(0) print('Decrypted whole', [hex(x) for x in sh.decrypt(encr)]) print(' MAC', [hex(x) for x in sh.finish(4)]) sh.set_nonce(0) decr = [sh.decrypt(bytes([x])) for x in encr] print('Decrypted 1-by-1', [hex(x[0]) for x in decr]) print(' MAC', [hex(x) for x in sh.finish(4)])