Added function to ticket.py to return the decrypted Title Key

Also added crypto.py which will manage all crypto-related functionality in the library. Currently only offers a function to decrypt a given Title Key.
ticket.py now creates a list of play limits placed on a title.
This commit is contained in:
Campbell 2024-02-29 23:02:36 -05:00
parent d86c754ebf
commit 3c5f8b6763
5 changed files with 70 additions and 28 deletions

View File

@ -1 +1,2 @@
build build
pycryptodome

View File

@ -1,21 +1,25 @@
# "commonkeys.py" from libWiiPy by NinjaCheetah & Contributors # "commonkeys.py" from libWiiPy by NinjaCheetah & Contributors
# https://github.com/NinjaCheetah/libWiiPy # https://github.com/NinjaCheetah/libWiiPy
default_key = 'ebe42a225e8593e448d9c5457381aaf7' import binascii
common_key = 'ebe42a225e8593e448d9c5457381aaf7'
korean_key = '63b82bb4f4614e2e13f2fefbba4c9b7e' korean_key = '63b82bb4f4614e2e13f2fefbba4c9b7e'
vwii_key = '30bfc76e7c19afbb23163330ced7c28d' vwii_key = '30bfc76e7c19afbb23163330ced7c28d'
def get_default_key(): def get_common_key(common_key_index):
"""Returns the regular Wii Common Key used to encrypt most content.""" """
return default_key Returns the specified Wii Common Key based on the index provided.
Possible values for common_key_index: 0: Common Key, 1: Korean Key, 2: vWii Key
"""
def get_korean_key(): match common_key_index:
"""Returns the Korean Wii Common Key used to encrypt Korean content.""" case 0:
return korean_key common_key_bin = binascii.unhexlify(common_key)
case 1:
common_key_bin = binascii.unhexlify(korean_key)
def get_vwii_key(): case 2:
"""Returns the vWii Common Key used to encrypt vWii-specific content.""" common_key_bin = binascii.unhexlify(vwii_key)
return vwii_key case _:
raise ValueError("The common key index provided, " + str(common_key_index + ", does not exist."))
return common_key_bin

View File

@ -2,3 +2,22 @@
# https://github.com/NinjaCheetah/libWiiPy # https://github.com/NinjaCheetah/libWiiPy
# #
# See https://wiibrew.org/wiki/Ticket for details about the TMD format # See https://wiibrew.org/wiki/Ticket for details about the TMD format
from .commonkeys import get_common_key
from Crypto.Cipher import AES
def decrypt_title_key(title_key_enc, common_key_index, title_id):
"""
Returns the decrypted version of the encrypted Title Key provided.
Requires the index of the common key to use, and the Title ID of the title that the Title Key is for.
"""
# Load the correct common key for the title.
common_key = get_common_key(common_key_index)
# Calculate the IV by adding 8 bytes to the end of the Title ID.
title_key_iv = title_id + (b'\x00' * 8)
# Create a new AES object with the values provided.
aes = AES.new(common_key, AES.MODE_CBC, title_key_iv)
# Decrypt the Title Key using the AES object.
title_key = aes.decrypt(title_key_enc)
return title_key

View File

@ -4,6 +4,20 @@
# See https://wiibrew.org/wiki/Ticket for details about the TMD format # See https://wiibrew.org/wiki/Ticket for details about the TMD format
import io import io
from .crypto import decrypt_title_key
from dataclasses import dataclass
from typing import List
@dataclass
class TitleLimit:
"""Creates a TitleLimit object that contains the type of restriction and the limit."""
# The type of play limit applied. The following types exist:
# 0 = None, 1 = Time Limit, 3 = None, 4 = Launch Count
limit_type: int
# The maximum value of the limit applied.
# This is either the number of minutes for a time limit, or the number of launches for a launch limit.
maximum_usage: int
class Ticket: class Ticket:
@ -28,9 +42,7 @@ class Ticket:
self.title_export_allowed: int # Whether title export is allowed with a PRNG key or not. self.title_export_allowed: int # Whether title export is allowed with a PRNG key or not.
self.common_key_index: int # Which common key should be used. 0 = Common Key, 1 = Korean Key, 2 = vWii Key self.common_key_index: int # Which common key should be used. 0 = Common Key, 1 = Korean Key, 2 = vWii Key
self.content_access_permissions: bytes # "Content access permissions (one bit for each content)" self.content_access_permissions: bytes # "Content access permissions (one bit for each content)"
#self.limit_type: int # Type of play limit applied to the title. self.title_limits_list: List[TitleLimit] = [] # List of play limits applied to the title.
# 0 = None, 1 = Time Limit, 3 = None, 4 = Launch Count
#self.maximum_launches: int # Maximum for the selected limit type, being either minutes or launches.
# v1 ticket data # v1 ticket data
# TODO: Figure out v1 ticket stuff # TODO: Figure out v1 ticket stuff
with io.BytesIO(self.ticket) as ticketdata: with io.BytesIO(self.ticket) as ticketdata:
@ -85,6 +97,12 @@ class Ticket:
# Content access permissions # Content access permissions
ticketdata.seek(0x222) ticketdata.seek(0x222)
self.content_access_permissions = ticketdata.read(64) self.content_access_permissions = ticketdata.read(64)
# Content limits
ticketdata.seek(0x264)
for limit in range(0, 8):
limit_type = int.from_bytes(ticketdata.read(4))
limit_value = int.from_bytes(ticketdata.read(4))
self.title_limits_list.append(TitleLimit(limit_type, limit_value))
def get_signature(self): def get_signature(self):
"""Returns the signature of the ticket.""" """Returns the signature of the ticket."""
@ -131,6 +149,6 @@ class Ticket:
def get_title_key(self): def get_title_key(self):
"""Returns the decrypted title key contained in the ticket.""" """Returns the decrypted title key contained in the ticket."""
# TODO title_key = decrypt_title_key(self.title_key_enc, self.common_key_index, self.title_id)
return b'\x00' return title_key

View File

@ -29,17 +29,17 @@ class TMD:
self.version: int # This seems to always be 0 no matter what? self.version: int # This seems to always be 0 no matter what?
self.ca_crl_version: int self.ca_crl_version: int
self.signer_crl_version: int self.signer_crl_version: int
self.vwii: int self.vwii: int # Whether the title is for the vWii. 0 = No, 1 = Yes
self.ios_tid: str self.ios_tid: str # The Title ID of the IOS version the associated title runs on.
self.ios_version: int self.ios_version: int # The IOS version the associated title runs on.
self.title_id: str self.title_id: str # The Title ID of the associated title.
self.content_type: str self.content_type: str # The type of content contained within the associated title.
self.group_id: int # Publisher of the title self.group_id: int # The ID of the publisher of the associated title.
self.region: int self.region: int # The ID of the region of the associated title.
self.ratings: int self.ratings: int
self.access_rights: int self.access_rights: int
self.title_version: int self.title_version: int # The version of the associated title.
self.num_contents: int self.num_contents: int # The number of contents contained in the associated title.
self.boot_index: int self.boot_index: int
self.content_record: List[ContentRecord] self.content_record: List[ContentRecord]
# Load data from TMD file # Load data from TMD file