111 lines
3.5 KiB
Python
111 lines
3.5 KiB
Python
import base64
|
|
import json
|
|
import os
|
|
from Cryptodome.Cipher import AES, PKCS1_OAEP
|
|
from Cryptodome.Hash import HMAC, SHA256
|
|
from Cryptodome.Random import get_random_bytes
|
|
from Cryptodome.Util import Padding
|
|
|
|
from vinetrimmer.utils.MSL.schemes.KeyExchangeRequest import KeyExchangeRequest
|
|
|
|
class PlayReady(KeyExchangeRequest):
|
|
"""
|
|
Implementation of the PlayReady Key Exchange Scheme for MSL.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.encryption_key = None
|
|
self.sign_key = None
|
|
self.sender = None
|
|
|
|
def perform_key_exchange(self, session, endpoint, sender, cdm):
|
|
"""
|
|
Performs a key exchange using PlayReady.
|
|
|
|
Parameters:
|
|
session: HTTP session with necessary cookies
|
|
endpoint: Endpoint for key exchange
|
|
sender: ESN of the device
|
|
cdm: CDM instance
|
|
"""
|
|
self.sender = sender
|
|
|
|
# Generate random keys for encryption and signing
|
|
self.encryption_key = get_random_bytes(16) # AES-128
|
|
self.sign_key = get_random_bytes(32) # HMAC-SHA256
|
|
|
|
# Return keys in the format required by MSL
|
|
return {
|
|
"encryptionkey": base64.b64encode(self.encryption_key).decode("utf-8"),
|
|
"hmackey": base64.b64encode(self.sign_key).decode("utf-8")
|
|
}
|
|
|
|
def encrypt(self, data, encryption_envelope=None):
|
|
"""
|
|
Encrypts data using the encryption key.
|
|
|
|
Parameters:
|
|
data: Data to encrypt
|
|
encryption_envelope: Not used in PlayReady
|
|
"""
|
|
if not self.encryption_key:
|
|
raise ValueError("No encryption key available")
|
|
|
|
# Generate a random IV
|
|
iv = get_random_bytes(16)
|
|
|
|
# Encrypt the data
|
|
cipher = AES.new(self.encryption_key, AES.MODE_CBC, iv)
|
|
ciphertext = cipher.encrypt(Padding.pad(data.encode("utf-8"), 16))
|
|
|
|
# Return encrypted data in the format required by MSL
|
|
return {
|
|
"keyid": self.sender,
|
|
"iv": base64.b64encode(iv).decode("utf-8"),
|
|
"ciphertext": base64.b64encode(ciphertext).decode("utf-8")
|
|
}
|
|
|
|
def decrypt(self, data):
|
|
"""
|
|
Decrypts data using the encryption key.
|
|
|
|
Parameters:
|
|
data: Encrypted data (with iv and ciphertext)
|
|
"""
|
|
if not self.encryption_key:
|
|
raise ValueError("No encryption key available")
|
|
|
|
# Decode IV and ciphertext
|
|
iv = base64.b64decode(data["iv"])
|
|
ciphertext = base64.b64decode(data["ciphertext"])
|
|
|
|
# Decrypt the data
|
|
cipher = AES.new(self.encryption_key, AES.MODE_CBC, iv)
|
|
plaintext = Padding.unpad(cipher.decrypt(ciphertext), 16)
|
|
|
|
return plaintext
|
|
|
|
def sign(self, data):
|
|
"""
|
|
Signs data using the sign key.
|
|
|
|
Parameters:
|
|
data: Data to sign
|
|
"""
|
|
if not self.sign_key:
|
|
raise ValueError("No sign key available")
|
|
|
|
# Sign the data with HMAC-SHA256
|
|
signature = HMAC.new(self.sign_key, data.encode("utf-8"), SHA256).digest()
|
|
return base64.b64encode(signature)
|
|
|
|
def verify(self, data, signature):
|
|
"""
|
|
Verifies a signature.
|
|
|
|
Parameters:
|
|
data: Data that was signed
|
|
signature: Signature to verify
|
|
"""
|
|
expected_signature = self.sign(data)
|
|
return signature == expected_signature.decode("utf-8") |