Change Directory
This commit is contained in:
62
librespot/crypto/CipherPair.py
Normal file
62
librespot/crypto/CipherPair.py
Normal file
@@ -0,0 +1,62 @@
|
||||
from librespot.crypto.Packet import Packet
|
||||
from librespot.crypto.Shannon import Shannon
|
||||
import struct
|
||||
|
||||
|
||||
class CipherPair:
|
||||
send_cipher: Shannon
|
||||
receive_cipher: Shannon
|
||||
send_nonce = 0
|
||||
receive_nonce = 0
|
||||
|
||||
def __init__(self, send_key: bytes, receive_key: bytes):
|
||||
# self.send_cipher = Shannon()
|
||||
# self.send_cipher.key(send_key)
|
||||
self.send_cipher = Shannon(send_key)
|
||||
self.send_nonce = 0
|
||||
|
||||
# self.receive_cipher = Shannon()
|
||||
# self.receive_cipher.key(receive_key)
|
||||
self.receive_cipher = Shannon(receive_key)
|
||||
self.receive_nonce = 0
|
||||
|
||||
def send_encoded(self, conn, cmd: bytes, payload: bytes):
|
||||
self.send_cipher.nonce(self.send_nonce)
|
||||
self.send_nonce += 1
|
||||
|
||||
buffer = b""
|
||||
buffer += cmd
|
||||
buffer += struct.pack(">H", len(payload))
|
||||
buffer += payload
|
||||
|
||||
buffer = self.send_cipher.encrypt(buffer)
|
||||
|
||||
# mac = self.send_cipher.finish(bytes(4))
|
||||
mac = self.send_cipher.finish(4)
|
||||
|
||||
conn.write(buffer)
|
||||
conn.write(mac)
|
||||
conn.flush()
|
||||
|
||||
def receive_encoded(self, conn) -> Packet:
|
||||
try:
|
||||
self.receive_cipher.nonce(self.receive_nonce)
|
||||
self.receive_nonce += 1
|
||||
|
||||
header_bytes = self.receive_cipher.decrypt(conn.read(3))
|
||||
|
||||
cmd = struct.pack(">s", bytes([header_bytes[0]]))
|
||||
payload_length = (header_bytes[1] << 8) | (header_bytes[2] & 0xff)
|
||||
|
||||
payload_bytes = self.receive_cipher.decrypt(
|
||||
conn.read(payload_length))
|
||||
|
||||
mac = conn.read(4)
|
||||
|
||||
expected_mac = self.receive_cipher.finish(4)
|
||||
if mac != expected_mac:
|
||||
raise RuntimeError()
|
||||
|
||||
return Packet(cmd, payload_bytes)
|
||||
except IndexError:
|
||||
raise RuntimeError()
|
||||
38
librespot/crypto/DiffieHellman.py
Normal file
38
librespot/crypto/DiffieHellman.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from binascii import unhexlify
|
||||
from librespot.common.Utils import Utils
|
||||
import os
|
||||
import struct
|
||||
|
||||
|
||||
class DiffieHellman:
|
||||
prime_bytes: bytearray = bytes([
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2,
|
||||
0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1,
|
||||
0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6,
|
||||
0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd,
|
||||
0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d,
|
||||
0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45,
|
||||
0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4, 0x4c, 0x42, 0xe9,
|
||||
0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
])
|
||||
prime: int = int.from_bytes(prime_bytes, "big")
|
||||
private_key: int
|
||||
public_key: int
|
||||
|
||||
def __init__(self):
|
||||
key_data = os.urandom(95)
|
||||
self.private_key = int.from_bytes(key_data, "big")
|
||||
self.public_key = pow(2, self.private_key, self.prime)
|
||||
|
||||
def compute_shared_key(self, remote_key_bytes: bytes):
|
||||
remote_key = int.from_bytes(remote_key_bytes, "big")
|
||||
return pow(remote_key, self.private_key, self.prime)
|
||||
|
||||
def private_key(self):
|
||||
return self.private_key
|
||||
|
||||
def public_key(self):
|
||||
return self.public_key
|
||||
|
||||
def public_key_array(self):
|
||||
return Utils.to_byte_array(self.public_key)
|
||||
66
librespot/crypto/Packet.py
Normal file
66
librespot/crypto/Packet.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import re
|
||||
|
||||
|
||||
class Packet:
|
||||
cmd: bytes
|
||||
payload: bytes
|
||||
|
||||
def __init__(self, cmd: bytes, payload: bytes):
|
||||
self.cmd = cmd
|
||||
self.payload = payload
|
||||
|
||||
def is_cmd(self, cmd: bytes):
|
||||
return cmd == self.cmd
|
||||
|
||||
class Type:
|
||||
secret_block = b"\x02"
|
||||
ping = b"\x04"
|
||||
stream_chunk = b"\x08"
|
||||
stream_chunk_res = b"\x09"
|
||||
channel_error = b"\x0a"
|
||||
channel_abort = b"\x0b"
|
||||
request_key = b"\x0c"
|
||||
aes_key = b"\x0d"
|
||||
aes_key_error = b"\x0e"
|
||||
image = b"\x19"
|
||||
country_code = b"\x1b"
|
||||
pong = b"\x49"
|
||||
pong_ack = b"\x4a"
|
||||
pause = b"\x4b"
|
||||
product_info = b"\x50"
|
||||
legacy_welcome = b"\x69"
|
||||
license_version = b"\x76"
|
||||
login = b"\xab"
|
||||
ap_welcome = b"\xac"
|
||||
auth_failure = b"\xad"
|
||||
mercury_req = b"\xb2"
|
||||
mercury_sub = b"\xb3"
|
||||
mercury_unsub = b"\xb4"
|
||||
mercury_event = b"\xb5"
|
||||
track_ended_time = b"\x82"
|
||||
unknown_data_all_zeros = b"\x1f"
|
||||
preferred_locale = b"\x74"
|
||||
unknown_0x4f = b"\x4f"
|
||||
unknown_0x0f = b"\x0f"
|
||||
unknown_0x10 = b"\x10"
|
||||
|
||||
@staticmethod
|
||||
def parse(val: bytes):
|
||||
for cmd in [
|
||||
Packet.Type.__dict__[attr]
|
||||
for attr in Packet.Type.__dict__.keys()
|
||||
if re.search("__.+?__", attr) is None
|
||||
and type(Packet.Type.__dict__[attr]) is bytes
|
||||
]:
|
||||
if cmd == val:
|
||||
return cmd
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def for_method(method: str):
|
||||
if method == "SUB":
|
||||
return Packet.Type.mercury_sub
|
||||
if method == "UNSUB":
|
||||
return Packet.Type.mercury_unsub
|
||||
return Packet.Type.mercury_req
|
||||
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)])
|
||||
311
librespot/crypto/Shannon_DEV.py
Normal file
311
librespot/crypto/Shannon_DEV.py
Normal file
@@ -0,0 +1,311 @@
|
||||
import struct
|
||||
|
||||
|
||||
class Shannon:
|
||||
n = 16
|
||||
fold = n
|
||||
initkonst = 0x6996c53a
|
||||
keyp = 13
|
||||
|
||||
r: list
|
||||
crc: list
|
||||
initr: list
|
||||
konst: int
|
||||
sbuf: int
|
||||
mbuf: int
|
||||
nbuf: int
|
||||
|
||||
def __init__(self):
|
||||
self.r = [0 for _ in range(self.n)]
|
||||
self.crc = [0 for _ in range(self.n)]
|
||||
self.initr = [0 for _ in range(self.n)]
|
||||
|
||||
def rotl(self, i: int, distance: int):
|
||||
return ((i << distance) | (i >> (32 - distance))) & 0xffffffff
|
||||
|
||||
def sbox(self, i: int):
|
||||
i ^= self.rotl(i, 5) | self.rotl(i, 7)
|
||||
i ^= self.rotl(i, 19) | self.rotl(i, 22)
|
||||
|
||||
return i
|
||||
|
||||
def sbox2(self, i: int):
|
||||
i ^= self.rotl(i, 7) | self.rotl(i, 22)
|
||||
i ^= self.rotl(i, 5) | self.rotl(i, 19)
|
||||
|
||||
return i
|
||||
|
||||
def cycle(self):
|
||||
t: int
|
||||
|
||||
t = self.r[12] ^ self.r[13] ^ self.konst
|
||||
t = self.sbox(t) ^ self.rotl(self.r[0], 1)
|
||||
|
||||
for i in range(1, self.n):
|
||||
self.r[i - 1] = self.r[i]
|
||||
|
||||
self.r[self.n - 1] = t
|
||||
|
||||
t = self.sbox2(self.r[2] ^ self.r[15])
|
||||
self.r[0] ^= t
|
||||
self.sbuf = t ^ self.r[8] ^ self.r[12]
|
||||
|
||||
def crc_func(self, i: int):
|
||||
t: int
|
||||
|
||||
t = self.crc[0] ^ self.crc[2] ^ self.crc[15] ^ i
|
||||
|
||||
for j in range(1, self.n):
|
||||
self.crc[j - 1] = self.crc[j]
|
||||
|
||||
self.crc[self.n - 1] = t
|
||||
|
||||
def mac_func(self, i: int):
|
||||
self.crc_func(i)
|
||||
|
||||
self.r[self.keyp] ^= i
|
||||
|
||||
def init_state(self):
|
||||
self.r[0] = 1
|
||||
self.r[1] = 1
|
||||
|
||||
for i in range(2, self.n):
|
||||
self.r[i] = self.r[i - 1] + self.r[i - 2]
|
||||
|
||||
self.konst = self.initkonst
|
||||
|
||||
def save_state(self):
|
||||
for i in range(self.n):
|
||||
self.initr[i] = self.r[i]
|
||||
|
||||
def reload_state(self):
|
||||
for i in range(self.n):
|
||||
self.r[i] = self.initr[i]
|
||||
|
||||
def gen_konst(self):
|
||||
self.konst = self.r[0]
|
||||
|
||||
def add_key(self, k: int):
|
||||
self.r[self.keyp] ^= k
|
||||
|
||||
def diffuse(self):
|
||||
for i in range(self.fold):
|
||||
self.cycle()
|
||||
|
||||
def load_key(self, key: bytes):
|
||||
extra = bytearray(4)
|
||||
i: int
|
||||
j: int
|
||||
t: int
|
||||
|
||||
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[self.keyp] = \
|
||||
self.r[self.keyp] ^ \
|
||||
struct.unpack("<I", key[i: i + 4])[0]
|
||||
|
||||
self.cycle()
|
||||
|
||||
for i in range(self.n):
|
||||
self.crc[i] = self.r[i]
|
||||
|
||||
self.diffuse()
|
||||
|
||||
for i in range(self.n):
|
||||
self.r[i] ^= self.crc[i]
|
||||
|
||||
def key(self, key: bytes):
|
||||
self.init_state()
|
||||
|
||||
self.load_key(key)
|
||||
|
||||
self.gen_konst()
|
||||
|
||||
self.save_state()
|
||||
|
||||
self.nbuf = 0
|
||||
|
||||
def nonce(self, nonce: bytes):
|
||||
self.reload_state()
|
||||
|
||||
self.konst = self.initkonst
|
||||
|
||||
self.load_key(nonce)
|
||||
|
||||
self.gen_konst()
|
||||
|
||||
self.nbuf = 0
|
||||
|
||||
def encrypt(self, buffer: bytes, n: int = None):
|
||||
if n is None:
|
||||
return self.encrypt(buffer, len(buffer))
|
||||
|
||||
buffer = bytearray(buffer)
|
||||
|
||||
i = 0
|
||||
j: int
|
||||
t: int
|
||||
|
||||
if self.nbuf != 0:
|
||||
while self.nbuf != 0 and n != 0:
|
||||
self.mbuf ^= (buffer[i] & 0xff) << (32 - self.nbuf)
|
||||
buffer[i] ^= (self.sbuf >> (32 - self.nbuf)) & 0xff
|
||||
|
||||
i += 1
|
||||
|
||||
self.nbuf -= 8
|
||||
|
||||
n -= 1
|
||||
|
||||
if self.nbuf != 0:
|
||||
return
|
||||
|
||||
self.mac_func(self.mbuf)
|
||||
|
||||
j = n & ~0x03
|
||||
|
||||
while i < j:
|
||||
self.cycle()
|
||||
|
||||
t = ((buffer[i + 3] & 0xFF) << 24) | \
|
||||
((buffer[i + 2] & 0xFF) << 16) | \
|
||||
((buffer[i + 1] & 0xFF) << 8) | \
|
||||
(buffer[i] & 0xFF)
|
||||
|
||||
self.mac_func(t)
|
||||
|
||||
t ^= self.sbuf
|
||||
|
||||
buffer[i + 3] = (t >> 24) & 0xFF
|
||||
buffer[i + 2] = (t >> 16) & 0xFF
|
||||
buffer[i + 3] = (t >> 8) & 0xFF
|
||||
buffer[i] = t & 0xFF
|
||||
|
||||
i += 4
|
||||
|
||||
n &= 0x03
|
||||
|
||||
if n != 0:
|
||||
self.cycle()
|
||||
|
||||
self.mbuf = 0
|
||||
self.nbuf = 32
|
||||
|
||||
while self.nbuf != 0 and n != 0:
|
||||
self.mbuf ^= (buffer[i] & 0xff) << (32 - self.nbuf)
|
||||
buffer[i] ^= (self.sbuf >> (32 - self.nbuf)) & 0xff
|
||||
|
||||
i += 1
|
||||
|
||||
self.nbuf -= 8
|
||||
|
||||
n -= 1
|
||||
return bytes(buffer)
|
||||
|
||||
def decrypt(self, buffer: bytes, n: int = None):
|
||||
if n is None:
|
||||
return self.decrypt(buffer, len(buffer))
|
||||
|
||||
buffer = bytearray(buffer)
|
||||
|
||||
i = 0
|
||||
j: int
|
||||
t: int
|
||||
|
||||
if self.nbuf != 0:
|
||||
while self.nbuf != 0 and n != 0:
|
||||
buffer[i] ^= (self.sbuf >> (32 - self.nbuf)) & 0xff
|
||||
self.mbuf ^= (buffer[i] & 0xff) << (32 - self.nbuf)
|
||||
|
||||
i += 1
|
||||
|
||||
self.nbuf -= 8
|
||||
|
||||
n -= 1
|
||||
|
||||
if self.nbuf != 0:
|
||||
return
|
||||
|
||||
self.mac_func(self.mbuf)
|
||||
|
||||
j = n & ~0x03
|
||||
|
||||
while i < j:
|
||||
self.cycle()
|
||||
|
||||
t = ((buffer[i + 3] & 0xFF) << 24) | \
|
||||
((buffer[i + 2] & 0xFF) << 16) | \
|
||||
((buffer[i + 1] & 0xFF) << 8) | \
|
||||
(buffer[i] & 0xFF)
|
||||
|
||||
t ^= self.sbuf
|
||||
|
||||
self.mac_func(t)
|
||||
|
||||
buffer[i + 3] = (t >> 24) & 0xFF
|
||||
buffer[i + 2] = (t >> 16) & 0xFF
|
||||
buffer[i + 1] = (t >> 8) & 0xFF
|
||||
buffer[i] = t & 0xFF
|
||||
|
||||
i += 4
|
||||
|
||||
n &= 0x03
|
||||
|
||||
if n != 0:
|
||||
self.cycle()
|
||||
|
||||
self.mbuf = 0
|
||||
self.nbuf = 32
|
||||
|
||||
while self.nbuf != 0 and n != 0:
|
||||
buffer[i] ^= (self.sbuf >> (32 - self.nbuf)) & 0xff
|
||||
self.mbuf ^= (buffer[i] & 0xff) << (32 - self.nbuf)
|
||||
|
||||
i += 1
|
||||
|
||||
self.nbuf -= 8
|
||||
|
||||
n -= 1
|
||||
|
||||
return bytes(buffer)
|
||||
|
||||
def finish(self, buffer: bytes, n: int = None):
|
||||
if n is None:
|
||||
return self.finish(buffer, len(buffer))
|
||||
|
||||
buffer = bytearray(buffer)
|
||||
|
||||
i = 0
|
||||
j: int
|
||||
|
||||
if self.nbuf != 0:
|
||||
self.mac_func(self.mbuf)
|
||||
|
||||
self.cycle()
|
||||
self.add_key(self.initkonst ^ (self.nbuf << 3))
|
||||
|
||||
self.nbuf = 0
|
||||
|
||||
for j in range(self.n):
|
||||
self.r[j] ^= self.crc[j]
|
||||
|
||||
self.diffuse()
|
||||
|
||||
while n > 0:
|
||||
self.cycle()
|
||||
|
||||
if n >= 4:
|
||||
buffer[i + 3] = (self.sbuf >> 24) & 0xff
|
||||
buffer[i + 2] = (self.sbuf >> 16) & 0xff
|
||||
buffer[i + 1] = (self.sbuf >> 8) & 0xff
|
||||
buffer[i] = self.sbuf & 0xff
|
||||
|
||||
n -= 4
|
||||
i += 4
|
||||
else:
|
||||
for j in range(n):
|
||||
buffer[i + j] = (self.sbuf >> (i * 8)) & 0xff
|
||||
break
|
||||
return bytes(buffer)
|
||||
4
librespot/crypto/__init__.py
Normal file
4
librespot/crypto/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from librespot.crypto.CipherPair import CipherPair
|
||||
from librespot.crypto.DiffieHellman import DiffieHellman
|
||||
from librespot.crypto.Packet import Packet
|
||||
from librespot.crypto.Shannon import Shannon
|
||||
Reference in New Issue
Block a user