Change Directory
This commit is contained in:
495
librespot/crypto/Shannon.py
Normal file
495
librespot/crypto/Shannon.py
Normal file
@@ -0,0 +1,495 @@
|
||||
"""
|
||||
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", len(key))
|
||||
for i in range(0, len(key), 4):
|
||||
self._R[KEYP] = self._R[KEYP] ^ struct.unpack(
|
||||
"<I", key[i:i + 4])[0] # Little Endian order
|
||||
self._cycle()
|
||||
|
||||
# save a copy of the register
|
||||
self._CRC = copy.copy(self._R)
|
||||
|
||||
# now diffuse
|
||||
self._diffuse()
|
||||
|
||||
# now xor the copy back -- makes key loading irreversible */
|
||||
for i in range(N):
|
||||
self._R[i] ^= self._CRC[i]
|
||||
|
||||
""" Constructor """
|
||||
|
||||
def __init__(self, key):
|
||||
self._initstate()
|
||||
self._loadkey(key)
|
||||
self._konst = self._R[0] # in case we proceed to stream generation
|
||||
self._initR = copy.copy(self._R)
|
||||
self._nbuf = 0
|
||||
|
||||
""" Published "IV" interface """
|
||||
|
||||
def nonce(self, nonce):
|
||||
global INITKONST
|
||||
|
||||
if type(nonce) == int:
|
||||
# Accept int as well (BigEndian)
|
||||
nonce = bytes(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("<I", buf[i:i + 4])[0]
|
||||
self._macfunc(t)
|
||||
t ^= self._sbuf
|
||||
result += struct.pack("<I", t)
|
||||
buf = buf[4:]
|
||||
|
||||
# handle any trailing bytes
|
||||
if len(buf):
|
||||
self._cycle()
|
||||
self._mbuf = 0
|
||||
self._nbuf = 32
|
||||
result += self._encrypt_chunk(buf)
|
||||
|
||||
return bytes(result)
|
||||
|
||||
""" Decrypt small chunk """
|
||||
|
||||
def _decrypt_chunk(self, chunk):
|
||||
result = []
|
||||
for c in chunk:
|
||||
result.append(c ^ ((self._sbuf >> (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("<I", buf[i:i + 4])[0] ^ self._sbuf
|
||||
self._macfunc(t)
|
||||
result += struct.pack("<I", t)
|
||||
buf = buf[4:]
|
||||
|
||||
# handle any trailing bytes
|
||||
if len(buf):
|
||||
self._cycle()
|
||||
self._mbuf = 0
|
||||
self._nbuf = 32
|
||||
result += self._decrypt_chunk(buf)
|
||||
|
||||
return bytes(result)
|
||||
|
||||
""" Having accumulated a MAC, finish processing and return it.
|
||||
Note that any unprocessed bytes are treated as if
|
||||
they were encrypted zero bytes, so plaintext (zero) is accumulated. """
|
||||
|
||||
def finish(self, buf_len):
|
||||
global KEYP, \
|
||||
INITKONST
|
||||
|
||||
# handle any previously buffered bytes
|
||||
if self._nbuf != 0:
|
||||
# LFSR already cycled
|
||||
self._macfunc(self._mbuf)
|
||||
|
||||
# perturb the MAC to mark end of input.
|
||||
# Note that only the stream register is updated, not the CRC. This is an
|
||||
# action that can't be duplicated by passing in plaintext, hence
|
||||
# defeating any kind of extension attack.
|
||||
self._cycle()
|
||||
self._R[KEYP] ^= INITKONST ^ (self._nbuf << 3)
|
||||
self._nbuf = 0
|
||||
|
||||
# now add the CRC to the stream register and diffuse it
|
||||
for i in range(N):
|
||||
self._R[i] ^= self._CRC[i]
|
||||
|
||||
self._diffuse()
|
||||
|
||||
result = []
|
||||
# produce output from the stream buffer
|
||||
i = 0
|
||||
for i in range(0, buf_len, 4):
|
||||
self._cycle()
|
||||
if i + 4 <= buf_len:
|
||||
result += struct.pack("<I", self._sbuf)
|
||||
else:
|
||||
sbuf = self._sbuf
|
||||
for j in range(i, buf_len):
|
||||
result.append(sbuf & 0xFF)
|
||||
sbuf >>= 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)])
|
||||
Reference in New Issue
Block a user