Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd9028354b |
@@ -1,56 +1,3 @@
|
||||
2.6.5.3268
|
||||
subscene, addic7ed
|
||||
- either of those providers might impose a reCAPTCHA verification. In order to use those providers, please create an account at an AntiCaptcha service ([anti-captcha.com](http://getcaptchasolution.com/kkvviom7nh) or [deathbycaptcha.com](http://deathbycaptcha.com)), add funds, then supply your credentials/apikey in the configuration
|
||||
|
||||
Changelog
|
||||
- clarify README
|
||||
- core: fix custom folder handling; #761
|
||||
- core: providers: screwzira: move to ktuvit and wizdom
|
||||
- core: add option to not download subtitles for certain audio languages existing; and/or no audio stream; fix #756
|
||||
- core: delay item refreshes after refresh call (default: 5 seconds; exposed in advanced settings)
|
||||
- menu: allow extraction of embedded subtitles for whole tv shows
|
||||
|
||||
|
||||
2.6.5.3247
|
||||
|
||||
subscene, addic7ed
|
||||
|
||||
either of those providers might impose a reCAPTCHA verification. In order to use those providers, please create an account at an AntiCaptcha service (anti-captcha.com or deathbycaptcha.com), add funds, then supply your credentials/apikey in the configuration
|
||||
Changelog
|
||||
core: fix for tv.plex.agents.movie not populating its media types
|
||||
core: tasks: findBetterSubtitles: increase minimum score for better subtitles for movies with extracted embedded subs from 82 to 112
|
||||
|
||||
|
||||
2.6.5.3241
|
||||
|
||||
subscene, addic7ed
|
||||
|
||||
either of those providers might impose a reCAPTCHA verification. In order to use those providers, please create an account at an AntiCaptcha service (anti-captcha.com or deathbycaptcha.com), add funds, then supply your credentials/apikey in the configuration
|
||||
Changelog
|
||||
|
||||
core: add support for new Plex Movie agent
|
||||
core: remove py3 compat breaking unnecessary change
|
||||
core: skip drawing tags for SRT
|
||||
core: advanced_settings: refiners: drone: add custom pem_file support; fixes #735
|
||||
providers: core: set DownloadLimitPerDayExceeded timeout to 4 hours (was one day);
|
||||
providers: addic7ed: limit downloads per day; add vip setting
|
||||
providers: addic7ed: properly compare last_dl, add last_reset tracking info to log #723
|
||||
providers: addic7ed: properly implement limits
|
||||
submod: HI: remove more music tags
|
||||
submod: common CM_punctuation_space2: detect AND don't try changing domain/url/host when fixing punctuation
|
||||
|
||||
|
||||
2.6.5.3223
|
||||
|
||||
subscene, addic7ed
|
||||
|
||||
either of those providers might impose a reCAPTCHA verification. In order to use those providers, please create an account at an AntiCaptcha service (anti-captcha.com or deathbycaptcha.com), add funds, then supply your credentials/apikey in the configuration
|
||||
Changelog
|
||||
|
||||
core: scoring: reorder subtitles based on second non-hash-score if main hash score is the same; morpheus65535/bazarr#821
|
||||
providers: bsplayer: verify hash; clean up
|
||||
|
||||
|
||||
2.6.5.3217
|
||||
|
||||
subscene, addic7ed
|
||||
@@ -70,7 +17,6 @@ Changelog
|
||||
|
||||
2.6.5.3183
|
||||
|
||||
|
||||
subscene, addic7ed
|
||||
- either of those providers might impose a reCAPTCHA verification. In order to use those providers, please create an account at an AntiCaptcha service ([anti-captcha.com](http://getcaptchasolution.com/kkvviom7nh) or [deathbycaptcha.com](http://deathbycaptcha.com)), add funds, then supply your credentials/apikey in the configuration
|
||||
|
||||
|
||||
@@ -108,51 +108,28 @@ def MetadataMenu(rating_key, title=None, base_title=None, display_items=False, p
|
||||
add_incl_excl_options(oc, current_kind, title=sub_title, rating_key=rating_key, callback_menu=InclExclMenu)
|
||||
|
||||
# mass-extract embedded
|
||||
if config.plex_transcoder:
|
||||
if current_kind == "season":
|
||||
for lang in config.lang_list:
|
||||
oc.add(DirectoryObject(
|
||||
key=Callback(SeasonExtractEmbedded, rating_key=rating_key, language=lang,
|
||||
base_title=show.section.title, display_items=display_items, item_title=item_title,
|
||||
title=title,
|
||||
previous_item_type=previous_item_type, with_mods=True,
|
||||
previous_rating_key=previous_rating_key, randomize=timestamp()),
|
||||
title=_(u"Extract missing %(language)s embedded subtitles", language=display_language(lang)),
|
||||
summary=_("Extracts the not yet extracted embedded subtitles of all episodes for the current "
|
||||
"season with all configured default modifications")
|
||||
))
|
||||
oc.add(DirectoryObject(
|
||||
key=Callback(SeasonExtractEmbedded, rating_key=rating_key, language=lang,
|
||||
base_title=show.section.title, display_items=display_items, item_title=item_title,
|
||||
title=title, force=True,
|
||||
previous_item_type=previous_item_type, with_mods=True,
|
||||
previous_rating_key=previous_rating_key, randomize=timestamp()),
|
||||
title=_(u"Extract and activate %(language)s embedded subtitles", language=display_language(lang)),
|
||||
summary=_("Extracts embedded subtitles of all episodes for the current season "
|
||||
"with all configured default modifications")
|
||||
))
|
||||
elif current_kind == "series":
|
||||
for lang in config.lang_list:
|
||||
oc.add(DirectoryObject(
|
||||
key=Callback(SeasonExtractEmbedded, rating_key=rating_key, language=lang,
|
||||
base_title=title, display_items=display_items, item_title=item_title,
|
||||
title=title, mode="series",
|
||||
previous_item_type=previous_item_type, with_mods=True,
|
||||
previous_rating_key=previous_rating_key, randomize=timestamp()),
|
||||
title=_(u"Extract missing %(language)s embedded subtitles", language=display_language(lang)),
|
||||
summary=_("Extracts the not yet extracted embedded subtitles of all episodes for the current "
|
||||
"series with all configured default modifications")
|
||||
))
|
||||
oc.add(DirectoryObject(
|
||||
key=Callback(SeasonExtractEmbedded, rating_key=rating_key, language=lang,
|
||||
base_title=title, display_items=display_items, item_title=item_title,
|
||||
title=title, force=True, mode="series",
|
||||
previous_item_type=previous_item_type, with_mods=True,
|
||||
previous_rating_key=previous_rating_key, randomize=timestamp()),
|
||||
title=_(u"Extract and activate %(language)s embedded subtitles", language=display_language(lang)),
|
||||
summary=_("Extracts embedded subtitles of all episodes for the current series "
|
||||
"with all configured default modifications")
|
||||
))
|
||||
if current_kind == "season" and config.plex_transcoder:
|
||||
for lang in config.lang_list:
|
||||
oc.add(DirectoryObject(
|
||||
key=Callback(SeasonExtractEmbedded, rating_key=rating_key, language=lang,
|
||||
base_title=show.section.title, display_items=display_items, item_title=item_title,
|
||||
title=title,
|
||||
previous_item_type=previous_item_type, with_mods=True,
|
||||
previous_rating_key=previous_rating_key, randomize=timestamp()),
|
||||
title=_(u"Extract missing %(language)s embedded subtitles", language=display_language(lang)),
|
||||
summary=_("Extracts the not yet extracted embedded subtitles of all episodes for the current "
|
||||
"season with all configured default modifications")
|
||||
))
|
||||
oc.add(DirectoryObject(
|
||||
key=Callback(SeasonExtractEmbedded, rating_key=rating_key, language=lang,
|
||||
base_title=show.section.title, display_items=display_items, item_title=item_title,
|
||||
title=title, force=True,
|
||||
previous_item_type=previous_item_type, with_mods=True,
|
||||
previous_rating_key=previous_rating_key, randomize=timestamp()),
|
||||
title=_(u"Extract and activate %(language)s embedded subtitles", language=display_language(lang)),
|
||||
summary=_("Extracts embedded subtitles of all episodes for the current season "
|
||||
"with all configured default modifications")
|
||||
))
|
||||
|
||||
# add refresh
|
||||
oc.add(DirectoryObject(
|
||||
@@ -183,10 +160,9 @@ def SeasonExtractEmbedded(**kwargs):
|
||||
item_title = kwargs.pop("item_title")
|
||||
title = kwargs.pop("title")
|
||||
force = kwargs.pop("force", False)
|
||||
mode = kwargs.pop("mode", "season")
|
||||
|
||||
Thread.Create(season_extract_embedded, **{"rating_key": rating_key, "requested_language": requested_language,
|
||||
"with_mods": with_mods, "force": force, "mode": mode})
|
||||
"with_mods": with_mods, "force": force})
|
||||
|
||||
kwargs["header"] = _("Success")
|
||||
kwargs["message"] = _(u"Extracting of embedded subtitles for %s triggered", title)
|
||||
|
||||
@@ -160,8 +160,6 @@ class Config(object):
|
||||
exact_filenames = False
|
||||
only_one = False
|
||||
any_language_is_enough = False
|
||||
ignore_for_audio = False
|
||||
ignore_subs_for_empty_audio = False
|
||||
embedded_auto_extract = False
|
||||
ietf_as_alpha3 = False
|
||||
unrar = None
|
||||
@@ -254,7 +252,6 @@ class Config(object):
|
||||
self.plex_transcoder = self.get_plex_transcoder()
|
||||
self.only_one = cast_bool(Prefs['subtitles.only_one'])
|
||||
self.any_language_is_enough = Prefs['subtitles.any_language_is_enough']
|
||||
self.ignore_for_audio = self.ignore_subs_for_audio()
|
||||
self.embedded_auto_extract = cast_bool(Prefs["subtitles.embedded.autoextract"])
|
||||
self.ietf_as_alpha3 = cast_bool(Prefs["subtitles.language.ietf_normalize"])
|
||||
self.use_custom_dns = self.parse_custom_dns()
|
||||
@@ -671,23 +668,12 @@ class Config(object):
|
||||
enabled_for_primary_agents = {"movie": [], "show": []}
|
||||
enabled_sections = {}
|
||||
|
||||
legacy_agents = {
|
||||
"com.plexapp.agents.thetvdb": [SHOW],
|
||||
"com.plexapp.agents.thetvdbdvdorder": [SHOW],
|
||||
"com.plexapp.agents.hama": [SHOW, MOVIE],
|
||||
"com.plexapp.agents.themoviedb": [SHOW, MOVIE],
|
||||
"com.plexapp.agents.imdb": [SHOW, MOVIE],
|
||||
}
|
||||
|
||||
# find which agents we're enabled for
|
||||
for agent in Plex.agents():
|
||||
#if not agent.primary:
|
||||
# continue
|
||||
if agent.identifier not in legacy_agents:
|
||||
if not agent.primary:
|
||||
continue
|
||||
|
||||
#media_types = [t.media_type for t in list(agent.media_types)]
|
||||
media_types = legacy_agents[agent.identifier] + []
|
||||
media_types = [t.media_type for t in list(agent.media_types)]
|
||||
|
||||
# the new movie agent doesn't populate its media types, workaround
|
||||
if not media_types and agent.identifier == "tv.plex.agents.movie":
|
||||
@@ -708,22 +694,6 @@ class Config(object):
|
||||
Log.Debug(u"I'm enabled for: %s" % [lib.title for key, lib in enabled_sections.iteritems()])
|
||||
return enabled_sections
|
||||
|
||||
def lang_str_to_list(self, s, l):
|
||||
if len(s) and s != "None":
|
||||
for lang in s.split(u","):
|
||||
lang = lang.strip()
|
||||
if lang == "NULL":
|
||||
l.append(lang)
|
||||
continue
|
||||
try:
|
||||
real_lang = Language.fromietf(lang)
|
||||
except:
|
||||
try:
|
||||
real_lang = Language.fromname(lang)
|
||||
except:
|
||||
continue
|
||||
l.append(real_lang)
|
||||
|
||||
# Prepare a list of languages we want subs for
|
||||
def get_lang_list(self, provider=None, ordered=False):
|
||||
# advanced settings
|
||||
@@ -764,9 +734,17 @@ class Config(object):
|
||||
except:
|
||||
pass
|
||||
|
||||
self.lang_str_to_list(lang_custom, l)
|
||||
if "NULL" in l:
|
||||
l.remove("NULL")
|
||||
if len(lang_custom) and lang_custom != "None":
|
||||
for lang in lang_custom.split(u","):
|
||||
lang = lang.strip()
|
||||
try:
|
||||
real_lang = Language.fromietf(lang)
|
||||
except:
|
||||
try:
|
||||
real_lang = Language.fromname(lang)
|
||||
except:
|
||||
continue
|
||||
l.append(real_lang)
|
||||
|
||||
if self.forced_also:
|
||||
if Prefs["subtitles.when_forced"] == "Always":
|
||||
@@ -797,22 +775,12 @@ class Config(object):
|
||||
|
||||
lang_list = property(get_lang_list)
|
||||
|
||||
def ignore_subs_for_audio(self):
|
||||
c = Prefs['subtitles.ignore_for_audio'].strip()
|
||||
l = []
|
||||
|
||||
self.lang_str_to_list(c, l)
|
||||
if "NULL" in l:
|
||||
l.remove("NULL")
|
||||
self.ignore_subs_for_empty_audio = True
|
||||
return l
|
||||
|
||||
def get_subtitle_destination_folder(self):
|
||||
if not Prefs["subtitles.save.filesystem"]:
|
||||
return
|
||||
|
||||
fld_custom = Prefs["subtitles.save.subFolder.Custom"].strip() if \
|
||||
Prefs["subtitles.save.subFolder.Custom"] else None
|
||||
fld_custom = Prefs["subtitles.save.subFolder.Custom"].strip() if cast_bool(
|
||||
Prefs["subtitles.save.subFolder.Custom"]) else None
|
||||
return fld_custom or (
|
||||
Prefs["subtitles.save.subFolder"] if Prefs["subtitles.save.subFolder"] != "current folder" else None)
|
||||
|
||||
@@ -844,8 +812,7 @@ class Config(object):
|
||||
'subscenter': False,
|
||||
'assrt': cast_bool(Prefs['provider.assrt.enabled']),
|
||||
'bsplayer': cast_bool(Prefs['provider.bsplayer.enabled']),
|
||||
'ktuvit': cast_bool(Prefs['provider.ktuvit.enabled']),
|
||||
'wizdom': cast_bool(Prefs['provider.wizdom.enabled']),
|
||||
'screwzira': cast_bool(Prefs['provider.screwzira.enabled']),
|
||||
}
|
||||
|
||||
@property
|
||||
@@ -876,8 +843,7 @@ class Config(object):
|
||||
providers["subscene"] = False
|
||||
providers["napisy24"] = False
|
||||
providers["bsplayer"] = False
|
||||
providers["ktuvit"] = False
|
||||
providers["wizdom"] = False
|
||||
providers["screwzira"] = False
|
||||
providers_forced_off = dict(providers)
|
||||
|
||||
if not self.unrar and providers["legendastv"]:
|
||||
@@ -955,11 +921,7 @@ class Config(object):
|
||||
'legendastv': {'username': Prefs['provider.legendastv.username'],
|
||||
'password': Prefs['provider.legendastv.password'],
|
||||
},
|
||||
'assrt': {'token': Prefs['provider.assrt.token'], },
|
||||
'ktuvit': {
|
||||
'username': Prefs['provider.ktuvit.username'],
|
||||
'password': Prefs['provider.ktuvit.password'],
|
||||
},
|
||||
'assrt': {'token': Prefs['provider.assrt.token'], }
|
||||
}
|
||||
|
||||
return provider_settings
|
||||
|
||||
@@ -16,7 +16,6 @@ from support.storage import get_pack_data, store_pack_data
|
||||
def get_missing_languages(video, part):
|
||||
languages_list = config.get_lang_list(ordered=True)
|
||||
languages = set(languages_list)
|
||||
ignore_langs = set(config.ignore_for_audio)
|
||||
valid_langs_in_media = set()
|
||||
|
||||
if Prefs["subtitles.when"] != "Always":
|
||||
@@ -30,15 +29,6 @@ def get_missing_languages(video, part):
|
||||
video)
|
||||
return set()
|
||||
|
||||
if config.ignore_subs_for_empty_audio and not video.audio_languages:
|
||||
Log.Debug("Skipping subtitle search for %s, ignoring as no audio stream exists", video)
|
||||
return set()
|
||||
|
||||
inter = ignore_langs.intersection(video.audio_languages)
|
||||
if ignore_langs and inter:
|
||||
Log.Debug("Skipping subtitle search for %s, ignoring due to existing audio streams: %s", video, inter)
|
||||
return set()
|
||||
|
||||
# should we treat IETF as alpha3? (ditch the country part)
|
||||
alpha3_map = {}
|
||||
if config.ietf_as_alpha3:
|
||||
|
||||
@@ -94,36 +94,30 @@ def multi_extract_embedded(stream_list, refresh=False, with_mods=False, single_t
|
||||
execute()
|
||||
|
||||
|
||||
def season_extract_embedded(rating_key, requested_language, with_mods=False, force=False, mode="season"):
|
||||
def season_extract_embedded(rating_key, requested_language, with_mods=False, force=False):
|
||||
# get stored subtitle info for item id
|
||||
subtitle_storage = get_subtitle_storage()
|
||||
|
||||
try:
|
||||
rating_keys = [rating_key]
|
||||
if mode == "series":
|
||||
seasons = get_all_items(key="children", value=rating_key, base="library/metadata")
|
||||
rating_keys = [season[MI_KEY] for season in seasons]
|
||||
for data in get_all_items(key="children", value=rating_key, base="library/metadata"):
|
||||
item = get_item(data[MI_KEY])
|
||||
if item:
|
||||
stored_subs = subtitle_storage.load_or_new(item)
|
||||
for part in get_all_parts(item):
|
||||
embedded_subs = stored_subs.get_by_provider(part.id, requested_language, "embedded")
|
||||
current = stored_subs.get_any(part.id, requested_language)
|
||||
if not embedded_subs or force:
|
||||
stream_data = get_embedded_subtitle_streams(part, requested_language=requested_language)
|
||||
if stream_data:
|
||||
stream = stream_data[0]["stream"]
|
||||
|
||||
for rating_key in rating_keys:
|
||||
for data in get_all_items(key="children", value=rating_key, base="library/metadata"):
|
||||
item = get_item(data[MI_KEY])
|
||||
if item:
|
||||
stored_subs = subtitle_storage.load_or_new(item)
|
||||
for part in get_all_parts(item):
|
||||
embedded_subs = stored_subs.get_by_provider(part.id, requested_language, "embedded")
|
||||
current = stored_subs.get_any(part.id, requested_language)
|
||||
if not embedded_subs or force:
|
||||
stream_data = get_embedded_subtitle_streams(part, requested_language=requested_language)
|
||||
if stream_data:
|
||||
stream = stream_data[0]["stream"]
|
||||
set_current = not current or force
|
||||
refresh = not current
|
||||
|
||||
set_current = not current or force
|
||||
refresh = not current
|
||||
|
||||
extract_embedded_sub(rating_key=item.rating_key, part_id=part.id,
|
||||
stream_index=str(stream.index), set_current=set_current,
|
||||
refresh=refresh, language=requested_language, with_mods=with_mods,
|
||||
extract_mode="m")
|
||||
extract_embedded_sub(rating_key=item.rating_key, part_id=part.id,
|
||||
stream_index=str(stream.index), set_current=set_current,
|
||||
refresh=refresh, language=requested_language, with_mods=with_mods,
|
||||
extract_mode="m")
|
||||
finally:
|
||||
subtitle_storage.destroy()
|
||||
|
||||
|
||||
@@ -349,13 +349,11 @@ def refresh_item(rating_key, force=False, timeout=8000, refresh_kind=None, paren
|
||||
refresh = [item.rating_key for item in list(Plex["library/metadata"].children(int(rating_key)))]
|
||||
|
||||
multiple = len(refresh) > 1
|
||||
wait = config.advanced.get("refresh_after_called", 5)
|
||||
Thread.Sleep(wait)
|
||||
for key in refresh:
|
||||
Log.Info("%s item %s", "Refreshing" if not force else "Forced-refreshing", key)
|
||||
Plex["library/metadata"].refresh(key)
|
||||
if multiple:
|
||||
Thread.Sleep(wait)
|
||||
Thread.Sleep(10.0)
|
||||
|
||||
|
||||
def get_current_sub(rating_key, part_id, language, plex_item=None):
|
||||
|
||||
@@ -192,12 +192,6 @@
|
||||
],
|
||||
"default": "Always"
|
||||
},
|
||||
{
|
||||
"id": "subtitles.ignore_for_audio",
|
||||
"label": "Don't download subtitles for Audio languages (use ISO-639-1 codes; comma-separated; NULL=no audio)",
|
||||
"type": "text",
|
||||
"default": "None"
|
||||
},
|
||||
{
|
||||
"id": "subtitles.when_forced",
|
||||
"label": "Download foreign/forced subtitles",
|
||||
@@ -528,30 +522,10 @@
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"id": "provider.wizdom.enabled",
|
||||
"label": "Provider: Enable WizdomSubs (Hebrew)",
|
||||
"id": "provider.screwzira.enabled",
|
||||
"label": "Provider: Enable ScrewZira (Hebrew)",
|
||||
"type": "bool",
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"id": "provider.ktuvit.enabled",
|
||||
"label": "Provider: Enable Ktuvit (Hebrew)",
|
||||
"type": "bool",
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"id": "provider.ktuvit.username",
|
||||
"label": "Ktuvit Username",
|
||||
"type": "text",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"id": "provider.ktuvit.password",
|
||||
"label": "Ktuvit Password",
|
||||
"type": "text",
|
||||
"option": "hidden",
|
||||
"secure": "true",
|
||||
"default": ""
|
||||
"default": "false"
|
||||
},
|
||||
{
|
||||
"id": "providers.multithreading",
|
||||
|
||||
+3
-3
@@ -13,7 +13,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2.6.5.3277</string>
|
||||
<string>2.6.5.3247</string>
|
||||
<key>PlexFrameworkVersion</key>
|
||||
<string>2</string>
|
||||
<key>PlexPluginClass</key>
|
||||
@@ -23,7 +23,7 @@
|
||||
<key>PlexPluginConsoleLogging</key>
|
||||
<string>0</string>
|
||||
<key>PlexPluginDevMode</key>
|
||||
<string>0</string>
|
||||
<string>1</string>
|
||||
<key>PlexPluginCodePolicy</key>
|
||||
<!-- this allows channels to access some python methods which are otherwise blocked, as well as import external code libraries, and interact with the PMS HTTP API -->
|
||||
<string>Elevated</string>
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<h1>Sub-Zero for Plex</h1><i>Subtitles done right</i>
|
||||
|
||||
Version 2.6.5.3277
|
||||
Version 2.6.5.3247 DEV
|
||||
|
||||
Originally based on @bramwalet's awesome <a href="https://github.com/bramwalet/Subliminal.bundle">Subliminal.bundle</a>
|
||||
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: ascii -*-
|
||||
###########################################################################
|
||||
# pbkdf2 - PKCS#5 v2.0 Password-Based Key Derivation
|
||||
#
|
||||
# Copyright (C) 2007-2011 Dwayne C. Litzenberger <dlitz@dlitz.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Country of origin: Canada
|
||||
#
|
||||
###########################################################################
|
||||
# Sample PBKDF2 usage:
|
||||
# from Crypto.Cipher import AES
|
||||
# from pbkdf2 import PBKDF2
|
||||
# import os
|
||||
#
|
||||
# salt = os.urandom(8) # 64-bit salt
|
||||
# key = PBKDF2("This passphrase is a secret.", salt).read(32) # 256-bit key
|
||||
# iv = os.urandom(16) # 128-bit IV
|
||||
# cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
# ...
|
||||
#
|
||||
# Sample crypt() usage:
|
||||
# from pbkdf2 import crypt
|
||||
# pwhash = crypt("secret")
|
||||
# alleged_pw = raw_input("Enter password: ")
|
||||
# if pwhash == crypt(alleged_pw, pwhash):
|
||||
# print "Password good"
|
||||
# else:
|
||||
# print "Invalid password"
|
||||
#
|
||||
###########################################################################
|
||||
|
||||
__version__ = "1.3"
|
||||
__all__ = ['PBKDF2', 'crypt']
|
||||
|
||||
from struct import pack
|
||||
from random import randint
|
||||
import string
|
||||
import sys
|
||||
|
||||
try:
|
||||
# Use PyCrypto (if available).
|
||||
from Crypto.Hash import HMAC, SHA as SHA1
|
||||
except ImportError:
|
||||
# PyCrypto not available. Use the Python standard library.
|
||||
import hmac as HMAC
|
||||
try:
|
||||
from hashlib import sha1 as SHA1
|
||||
except ImportError:
|
||||
# hashlib not available. Use the old sha module.
|
||||
import sha as SHA1
|
||||
|
||||
#
|
||||
# Python 2.1 thru 3.2 compatibility
|
||||
#
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
_0xffffffffL = long(1) << 32
|
||||
def isunicode(s):
|
||||
return isinstance(s, unicode)
|
||||
def isbytes(s):
|
||||
return isinstance(s, str)
|
||||
def isinteger(n):
|
||||
return isinstance(n, (int, long))
|
||||
def b(s):
|
||||
return s
|
||||
def binxor(a, b):
|
||||
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])
|
||||
def b64encode(data, chars="+/"):
|
||||
tt = string.maketrans("+/", chars)
|
||||
return data.encode('base64').replace("\n", "").translate(tt)
|
||||
from binascii import b2a_hex
|
||||
else:
|
||||
_0xffffffffL = 0xffffffff
|
||||
def isunicode(s):
|
||||
return isinstance(s, str)
|
||||
def isbytes(s):
|
||||
return isinstance(s, bytes)
|
||||
def isinteger(n):
|
||||
return isinstance(n, int)
|
||||
def callable(obj):
|
||||
return hasattr(obj, '__call__')
|
||||
def b(s):
|
||||
return s.encode("latin-1")
|
||||
def binxor(a, b):
|
||||
return bytes([x ^ y for (x, y) in zip(a, b)])
|
||||
from base64 import b64encode as _b64encode
|
||||
def b64encode(data, chars="+/"):
|
||||
if isunicode(chars):
|
||||
return _b64encode(data, chars.encode('utf-8')).decode('utf-8')
|
||||
else:
|
||||
return _b64encode(data, chars)
|
||||
from binascii import b2a_hex as _b2a_hex
|
||||
def b2a_hex(s):
|
||||
return _b2a_hex(s).decode('us-ascii')
|
||||
xrange = range
|
||||
|
||||
class PBKDF2(object):
|
||||
"""PBKDF2.py : PKCS#5 v2.0 Password-Based Key Derivation
|
||||
|
||||
This implementation takes a passphrase and a salt (and optionally an
|
||||
iteration count, a digest module, and a MAC module) and provides a
|
||||
file-like object from which an arbitrarily-sized key can be read.
|
||||
|
||||
If the passphrase and/or salt are unicode objects, they are encoded as
|
||||
UTF-8 before they are processed.
|
||||
|
||||
The idea behind PBKDF2 is to derive a cryptographic key from a
|
||||
passphrase and a salt.
|
||||
|
||||
PBKDF2 may also be used as a strong salted password hash. The
|
||||
'crypt' function is provided for that purpose.
|
||||
|
||||
Remember: Keys generated using PBKDF2 are only as strong as the
|
||||
passphrases they are derived from.
|
||||
"""
|
||||
|
||||
def __init__(self, passphrase, salt, iterations=1000,
|
||||
digestmodule=SHA1, macmodule=HMAC):
|
||||
self.__macmodule = macmodule
|
||||
self.__digestmodule = digestmodule
|
||||
self._setup(passphrase, salt, iterations, self._pseudorandom)
|
||||
|
||||
def _pseudorandom(self, key, msg):
|
||||
"""Pseudorandom function. e.g. HMAC-SHA1"""
|
||||
return self.__macmodule.new(key=key, msg=msg,
|
||||
digestmod=self.__digestmodule).digest()
|
||||
|
||||
def read(self, bytes):
|
||||
"""Read the specified number of key bytes."""
|
||||
if self.closed:
|
||||
raise ValueError("file-like object is closed")
|
||||
|
||||
size = len(self.__buf)
|
||||
blocks = [self.__buf]
|
||||
i = self.__blockNum
|
||||
while size < bytes:
|
||||
i += 1
|
||||
if i > _0xffffffffL or i < 1:
|
||||
# We could return "" here, but
|
||||
raise OverflowError("derived key too long")
|
||||
block = self.__f(i)
|
||||
blocks.append(block)
|
||||
size += len(block)
|
||||
buf = b("").join(blocks)
|
||||
retval = buf[:bytes]
|
||||
self.__buf = buf[bytes:]
|
||||
self.__blockNum = i
|
||||
return retval
|
||||
|
||||
def __f(self, i):
|
||||
# i must fit within 32 bits
|
||||
assert 1 <= i <= _0xffffffffL
|
||||
U = self.__prf(self.__passphrase, self.__salt + pack("!L", i))
|
||||
result = U
|
||||
for j in xrange(2, 1+self.__iterations):
|
||||
U = self.__prf(self.__passphrase, U)
|
||||
result = binxor(result, U)
|
||||
return result
|
||||
|
||||
def hexread(self, octets):
|
||||
"""Read the specified number of octets. Return them as hexadecimal.
|
||||
|
||||
Note that len(obj.hexread(n)) == 2*n.
|
||||
"""
|
||||
return b2a_hex(self.read(octets))
|
||||
|
||||
def _setup(self, passphrase, salt, iterations, prf):
|
||||
# Sanity checks:
|
||||
|
||||
# passphrase and salt must be str or unicode (in the latter
|
||||
# case, we convert to UTF-8)
|
||||
if isunicode(passphrase):
|
||||
passphrase = passphrase.encode("UTF-8")
|
||||
elif not isbytes(passphrase):
|
||||
raise TypeError("passphrase must be str or unicode")
|
||||
if isunicode(salt):
|
||||
salt = salt.encode("UTF-8")
|
||||
elif not isbytes(salt):
|
||||
raise TypeError("salt must be str or unicode")
|
||||
|
||||
# iterations must be an integer >= 1
|
||||
if not isinteger(iterations):
|
||||
raise TypeError("iterations must be an integer")
|
||||
if iterations < 1:
|
||||
raise ValueError("iterations must be at least 1")
|
||||
|
||||
# prf must be callable
|
||||
if not callable(prf):
|
||||
raise TypeError("prf must be callable")
|
||||
|
||||
self.__passphrase = passphrase
|
||||
self.__salt = salt
|
||||
self.__iterations = iterations
|
||||
self.__prf = prf
|
||||
self.__blockNum = 0
|
||||
self.__buf = b("")
|
||||
self.closed = False
|
||||
|
||||
def close(self):
|
||||
"""Close the stream."""
|
||||
if not self.closed:
|
||||
del self.__passphrase
|
||||
del self.__salt
|
||||
del self.__iterations
|
||||
del self.__prf
|
||||
del self.__blockNum
|
||||
del self.__buf
|
||||
self.closed = True
|
||||
|
||||
def crypt(word, salt=None, iterations=None):
|
||||
"""PBKDF2-based unix crypt(3) replacement.
|
||||
|
||||
The number of iterations specified in the salt overrides the 'iterations'
|
||||
parameter.
|
||||
|
||||
The effective hash length is 192 bits.
|
||||
"""
|
||||
|
||||
# Generate a (pseudo-)random salt if the user hasn't provided one.
|
||||
if salt is None:
|
||||
salt = _makesalt()
|
||||
|
||||
# salt must be a string or the us-ascii subset of unicode
|
||||
if isunicode(salt):
|
||||
salt = salt.encode('us-ascii').decode('us-ascii')
|
||||
elif isbytes(salt):
|
||||
salt = salt.decode('us-ascii')
|
||||
else:
|
||||
raise TypeError("salt must be a string")
|
||||
|
||||
# word must be a string or unicode (in the latter case, we convert to UTF-8)
|
||||
if isunicode(word):
|
||||
word = word.encode("UTF-8")
|
||||
elif not isbytes(word):
|
||||
raise TypeError("word must be a string or unicode")
|
||||
|
||||
# Try to extract the real salt and iteration count from the salt
|
||||
if salt.startswith("$p5k2$"):
|
||||
(iterations, salt, dummy) = salt.split("$")[2:5]
|
||||
if iterations == "":
|
||||
iterations = 400
|
||||
else:
|
||||
converted = int(iterations, 16)
|
||||
if iterations != "%x" % converted: # lowercase hex, minimum digits
|
||||
raise ValueError("Invalid salt")
|
||||
iterations = converted
|
||||
if not (iterations >= 1):
|
||||
raise ValueError("Invalid salt")
|
||||
|
||||
# Make sure the salt matches the allowed character set
|
||||
allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
|
||||
for ch in salt:
|
||||
if ch not in allowed:
|
||||
raise ValueError("Illegal character %r in salt" % (ch,))
|
||||
|
||||
if iterations is None or iterations == 400:
|
||||
iterations = 400
|
||||
salt = "$p5k2$$" + salt
|
||||
else:
|
||||
salt = "$p5k2$%x$%s" % (iterations, salt)
|
||||
rawhash = PBKDF2(word, salt, iterations).read(24)
|
||||
return salt + "$" + b64encode(rawhash, "./")
|
||||
|
||||
# Add crypt as a static method of the PBKDF2 class
|
||||
# This makes it easier to do "from PBKDF2 import PBKDF2" and still use
|
||||
# crypt.
|
||||
PBKDF2.crypt = staticmethod(crypt)
|
||||
|
||||
def _makesalt():
|
||||
"""Return a 48-bit pseudorandom salt for crypt().
|
||||
|
||||
This function is not suitable for generating cryptographic secrets.
|
||||
"""
|
||||
binarysalt = b("").join([pack("@H", randint(0, 0xffff)) for i in range(3)])
|
||||
return b64encode(binarysalt, "./")
|
||||
|
||||
# vim:set ts=4 sw=4 sts=4 expandtab:
|
||||
@@ -1,53 +0,0 @@
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014 Richard Moore
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
# This is a pure-Python implementation of the AES algorithm and AES common
|
||||
# modes of operation.
|
||||
|
||||
# See: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
|
||||
# See: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
|
||||
|
||||
|
||||
# Supported key sizes:
|
||||
# 128-bit
|
||||
# 192-bit
|
||||
# 256-bit
|
||||
|
||||
|
||||
# Supported modes of operation:
|
||||
# ECB - Electronic Codebook
|
||||
# CBC - Cipher-Block Chaining
|
||||
# CFB - Cipher Feedback
|
||||
# OFB - Output Feedback
|
||||
# CTR - Counter
|
||||
|
||||
# See the README.md for API details and general information.
|
||||
|
||||
# Also useful, PyCrypto, a crypto library implemented in C with Python bindings:
|
||||
# https://www.dlitz.net/software/pycrypto/
|
||||
|
||||
|
||||
VERSION = [1, 3, 0]
|
||||
|
||||
from .aes import AES, AESModeOfOperationCTR, AESModeOfOperationCBC, AESModeOfOperationCFB, AESModeOfOperationECB, AESModeOfOperationOFB, AESModesOfOperation, Counter
|
||||
from .blockfeeder import decrypt_stream, Decrypter, encrypt_stream, Encrypter
|
||||
from .blockfeeder import PADDING_NONE, PADDING_DEFAULT
|
||||
@@ -1,589 +0,0 @@
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014 Richard Moore
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
# This is a pure-Python implementation of the AES algorithm and AES common
|
||||
# modes of operation.
|
||||
|
||||
# See: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
|
||||
|
||||
# Honestly, the best description of the modes of operations are the wonderful
|
||||
# diagrams on Wikipedia. They explain in moments what my words could never
|
||||
# achieve. Hence the inline documentation here is sparer than I'd prefer.
|
||||
# See: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
|
||||
|
||||
# Also useful, PyCrypto, a crypto library implemented in C with Python bindings:
|
||||
# https://www.dlitz.net/software/pycrypto/
|
||||
|
||||
|
||||
# Supported key sizes:
|
||||
# 128-bit
|
||||
# 192-bit
|
||||
# 256-bit
|
||||
|
||||
|
||||
# Supported modes of operation:
|
||||
# ECB - Electronic Codebook
|
||||
# CBC - Cipher-Block Chaining
|
||||
# CFB - Cipher Feedback
|
||||
# OFB - Output Feedback
|
||||
# CTR - Counter
|
||||
|
||||
|
||||
# See the README.md for API details and general information.
|
||||
|
||||
|
||||
import copy
|
||||
import struct
|
||||
|
||||
__all__ = ["AES", "AESModeOfOperationCTR", "AESModeOfOperationCBC", "AESModeOfOperationCFB",
|
||||
"AESModeOfOperationECB", "AESModeOfOperationOFB", "AESModesOfOperation", "Counter"]
|
||||
|
||||
|
||||
def _compact_word(word):
|
||||
return (word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3]
|
||||
|
||||
def _string_to_bytes(text):
|
||||
return list(ord(c) for c in text)
|
||||
|
||||
def _bytes_to_string(binary):
|
||||
return "".join(chr(b) for b in binary)
|
||||
|
||||
def _concat_list(a, b):
|
||||
return a + b
|
||||
|
||||
|
||||
# Python 3 compatibility
|
||||
try:
|
||||
xrange
|
||||
except Exception:
|
||||
xrange = range
|
||||
|
||||
# Python 3 supports bytes, which is already an array of integers
|
||||
def _string_to_bytes(text):
|
||||
if isinstance(text, bytes):
|
||||
return text
|
||||
return [ord(c) for c in text]
|
||||
|
||||
# In Python 3, we return bytes
|
||||
def _bytes_to_string(binary):
|
||||
return bytes(binary)
|
||||
|
||||
# Python 3 cannot concatenate a list onto a bytes, so we bytes-ify it first
|
||||
def _concat_list(a, b):
|
||||
return a + bytes(b)
|
||||
|
||||
|
||||
# Based *largely* on the Rijndael implementation
|
||||
# See: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf
|
||||
class AES(object):
|
||||
'''Encapsulates the AES block cipher.
|
||||
|
||||
You generally should not need this. Use the AESModeOfOperation classes
|
||||
below instead.'''
|
||||
|
||||
# Number of rounds by keysize
|
||||
number_of_rounds = {16: 10, 24: 12, 32: 14}
|
||||
|
||||
# Round constant words
|
||||
rcon = [ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 ]
|
||||
|
||||
# S-box and Inverse S-box (S is for Substitution)
|
||||
S = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 ]
|
||||
Si =[ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d ]
|
||||
|
||||
# Transformations for encryption
|
||||
T1 = [ 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a ]
|
||||
T2 = [ 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616 ]
|
||||
T3 = [ 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16 ]
|
||||
T4 = [ 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c ]
|
||||
|
||||
# Transformations for decryption
|
||||
T5 = [ 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742 ]
|
||||
T6 = [ 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857 ]
|
||||
T7 = [ 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8 ]
|
||||
T8 = [ 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0 ]
|
||||
|
||||
# Transformations for decryption key expansion
|
||||
U1 = [ 0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3 ]
|
||||
U2 = [ 0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697 ]
|
||||
U3 = [ 0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46 ]
|
||||
U4 = [ 0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d ]
|
||||
|
||||
def __init__(self, key):
|
||||
|
||||
if len(key) not in (16, 24, 32):
|
||||
raise ValueError('Invalid key size')
|
||||
|
||||
rounds = self.number_of_rounds[len(key)]
|
||||
|
||||
# Encryption round keys
|
||||
self._Ke = [[0] * 4 for i in xrange(rounds + 1)]
|
||||
|
||||
# Decryption round keys
|
||||
self._Kd = [[0] * 4 for i in xrange(rounds + 1)]
|
||||
|
||||
round_key_count = (rounds + 1) * 4
|
||||
KC = len(key) // 4
|
||||
|
||||
# Convert the key into ints
|
||||
tk = [ struct.unpack('>i', key[i:i + 4])[0] for i in xrange(0, len(key), 4) ]
|
||||
|
||||
# Copy values into round key arrays
|
||||
for i in xrange(0, KC):
|
||||
self._Ke[i // 4][i % 4] = tk[i]
|
||||
self._Kd[rounds - (i // 4)][i % 4] = tk[i]
|
||||
|
||||
# Key expansion (fips-197 section 5.2)
|
||||
rconpointer = 0
|
||||
t = KC
|
||||
while t < round_key_count:
|
||||
|
||||
tt = tk[KC - 1]
|
||||
tk[0] ^= ((self.S[(tt >> 16) & 0xFF] << 24) ^
|
||||
(self.S[(tt >> 8) & 0xFF] << 16) ^
|
||||
(self.S[ tt & 0xFF] << 8) ^
|
||||
self.S[(tt >> 24) & 0xFF] ^
|
||||
(self.rcon[rconpointer] << 24))
|
||||
rconpointer += 1
|
||||
|
||||
if KC != 8:
|
||||
for i in xrange(1, KC):
|
||||
tk[i] ^= tk[i - 1]
|
||||
|
||||
# Key expansion for 256-bit keys is "slightly different" (fips-197)
|
||||
else:
|
||||
for i in xrange(1, KC // 2):
|
||||
tk[i] ^= tk[i - 1]
|
||||
tt = tk[KC // 2 - 1]
|
||||
|
||||
tk[KC // 2] ^= (self.S[ tt & 0xFF] ^
|
||||
(self.S[(tt >> 8) & 0xFF] << 8) ^
|
||||
(self.S[(tt >> 16) & 0xFF] << 16) ^
|
||||
(self.S[(tt >> 24) & 0xFF] << 24))
|
||||
|
||||
for i in xrange(KC // 2 + 1, KC):
|
||||
tk[i] ^= tk[i - 1]
|
||||
|
||||
# Copy values into round key arrays
|
||||
j = 0
|
||||
while j < KC and t < round_key_count:
|
||||
self._Ke[t // 4][t % 4] = tk[j]
|
||||
self._Kd[rounds - (t // 4)][t % 4] = tk[j]
|
||||
j += 1
|
||||
t += 1
|
||||
|
||||
# Inverse-Cipher-ify the decryption round key (fips-197 section 5.3)
|
||||
for r in xrange(1, rounds):
|
||||
for j in xrange(0, 4):
|
||||
tt = self._Kd[r][j]
|
||||
self._Kd[r][j] = (self.U1[(tt >> 24) & 0xFF] ^
|
||||
self.U2[(tt >> 16) & 0xFF] ^
|
||||
self.U3[(tt >> 8) & 0xFF] ^
|
||||
self.U4[ tt & 0xFF])
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
'Encrypt a block of plain text using the AES block cipher.'
|
||||
|
||||
if len(plaintext) != 16:
|
||||
raise ValueError('wrong block length')
|
||||
|
||||
rounds = len(self._Ke) - 1
|
||||
(s1, s2, s3) = [1, 2, 3]
|
||||
a = [0, 0, 0, 0]
|
||||
|
||||
# Convert plaintext to (ints ^ key)
|
||||
t = [(_compact_word(plaintext[4 * i:4 * i + 4]) ^ self._Ke[0][i]) for i in xrange(0, 4)]
|
||||
|
||||
# Apply round transforms
|
||||
for r in xrange(1, rounds):
|
||||
for i in xrange(0, 4):
|
||||
a[i] = (self.T1[(t[ i ] >> 24) & 0xFF] ^
|
||||
self.T2[(t[(i + s1) % 4] >> 16) & 0xFF] ^
|
||||
self.T3[(t[(i + s2) % 4] >> 8) & 0xFF] ^
|
||||
self.T4[ t[(i + s3) % 4] & 0xFF] ^
|
||||
self._Ke[r][i])
|
||||
t = copy.copy(a)
|
||||
|
||||
# The last round is special
|
||||
result = [ ]
|
||||
for i in xrange(0, 4):
|
||||
tt = self._Ke[rounds][i]
|
||||
result.append((self.S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
|
||||
result.append((self.S[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
|
||||
result.append((self.S[(t[(i + s2) % 4] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF)
|
||||
result.append((self.S[ t[(i + s3) % 4] & 0xFF] ^ tt ) & 0xFF)
|
||||
|
||||
return result
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
'Decrypt a block of cipher text using the AES block cipher.'
|
||||
|
||||
if len(ciphertext) != 16:
|
||||
raise ValueError('wrong block length')
|
||||
|
||||
rounds = len(self._Kd) - 1
|
||||
(s1, s2, s3) = [3, 2, 1]
|
||||
a = [0, 0, 0, 0]
|
||||
|
||||
# Convert ciphertext to (ints ^ key)
|
||||
t = [(_compact_word(ciphertext[4 * i:4 * i + 4]) ^ self._Kd[0][i]) for i in xrange(0, 4)]
|
||||
|
||||
# Apply round transforms
|
||||
for r in xrange(1, rounds):
|
||||
for i in xrange(0, 4):
|
||||
a[i] = (self.T5[(t[ i ] >> 24) & 0xFF] ^
|
||||
self.T6[(t[(i + s1) % 4] >> 16) & 0xFF] ^
|
||||
self.T7[(t[(i + s2) % 4] >> 8) & 0xFF] ^
|
||||
self.T8[ t[(i + s3) % 4] & 0xFF] ^
|
||||
self._Kd[r][i])
|
||||
t = copy.copy(a)
|
||||
|
||||
# The last round is special
|
||||
result = [ ]
|
||||
for i in xrange(0, 4):
|
||||
tt = self._Kd[rounds][i]
|
||||
result.append((self.Si[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
|
||||
result.append((self.Si[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
|
||||
result.append((self.Si[(t[(i + s2) % 4] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF)
|
||||
result.append((self.Si[ t[(i + s3) % 4] & 0xFF] ^ tt ) & 0xFF)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class Counter(object):
|
||||
'''A counter object for the Counter (CTR) mode of operation.
|
||||
|
||||
To create a custom counter, you can usually just override the
|
||||
increment method.'''
|
||||
|
||||
def __init__(self, initial_value = 1):
|
||||
|
||||
# Convert the value into an array of bytes long
|
||||
self._counter = [ ((initial_value >> i) % 256) for i in xrange(128 - 8, -1, -8) ]
|
||||
|
||||
value = property(lambda s: s._counter)
|
||||
|
||||
def increment(self):
|
||||
'''Increment the counter (overflow rolls back to 0).'''
|
||||
|
||||
for i in xrange(len(self._counter) - 1, -1, -1):
|
||||
self._counter[i] += 1
|
||||
|
||||
if self._counter[i] < 256: break
|
||||
|
||||
# Carry the one
|
||||
self._counter[i] = 0
|
||||
|
||||
# Overflow
|
||||
else:
|
||||
self._counter = [ 0 ] * len(self._counter)
|
||||
|
||||
|
||||
class AESBlockModeOfOperation(object):
|
||||
'''Super-class for AES modes of operation that require blocks.'''
|
||||
def __init__(self, key):
|
||||
self._aes = AES(key)
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
raise Exception('not implemented')
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
raise Exception('not implemented')
|
||||
|
||||
|
||||
class AESStreamModeOfOperation(AESBlockModeOfOperation):
|
||||
'''Super-class for AES modes of operation that are stream-ciphers.'''
|
||||
|
||||
class AESSegmentModeOfOperation(AESStreamModeOfOperation):
|
||||
'''Super-class for AES modes of operation that segment data.'''
|
||||
|
||||
segment_bytes = 16
|
||||
|
||||
|
||||
|
||||
class AESModeOfOperationECB(AESBlockModeOfOperation):
|
||||
'''AES Electronic Codebook Mode of Operation.
|
||||
|
||||
o Block-cipher, so data must be padded to 16 byte boundaries
|
||||
|
||||
Security Notes:
|
||||
o This mode is not recommended
|
||||
o Any two identical blocks produce identical encrypted values,
|
||||
exposing data patterns. (See the image of Tux on wikipedia)
|
||||
|
||||
Also see:
|
||||
o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_codebook_.28ECB.29
|
||||
o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.1'''
|
||||
|
||||
|
||||
name = "Electronic Codebook (ECB)"
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
if len(plaintext) != 16:
|
||||
raise ValueError('plaintext block must be 16 bytes')
|
||||
|
||||
plaintext = _string_to_bytes(plaintext)
|
||||
return _bytes_to_string(self._aes.encrypt(plaintext))
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
if len(ciphertext) != 16:
|
||||
raise ValueError('ciphertext block must be 16 bytes')
|
||||
|
||||
ciphertext = _string_to_bytes(ciphertext)
|
||||
return _bytes_to_string(self._aes.decrypt(ciphertext))
|
||||
|
||||
|
||||
|
||||
class AESModeOfOperationCBC(AESBlockModeOfOperation):
|
||||
'''AES Cipher-Block Chaining Mode of Operation.
|
||||
|
||||
o The Initialization Vector (IV)
|
||||
o Block-cipher, so data must be padded to 16 byte boundaries
|
||||
o An incorrect initialization vector will only cause the first
|
||||
block to be corrupt; all other blocks will be intact
|
||||
o A corrupt bit in the cipher text will cause a block to be
|
||||
corrupted, and the next block to be inverted, but all other
|
||||
blocks will be intact.
|
||||
|
||||
Security Notes:
|
||||
o This method (and CTR) ARE recommended.
|
||||
|
||||
Also see:
|
||||
o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_.28CBC.29
|
||||
o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.2'''
|
||||
|
||||
|
||||
name = "Cipher-Block Chaining (CBC)"
|
||||
|
||||
def __init__(self, key, iv = None):
|
||||
if iv is None:
|
||||
self._last_cipherblock = [ 0 ] * 16
|
||||
elif len(iv) != 16:
|
||||
raise ValueError('initialization vector must be 16 bytes')
|
||||
else:
|
||||
self._last_cipherblock = _string_to_bytes(iv)
|
||||
|
||||
AESBlockModeOfOperation.__init__(self, key)
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
if len(plaintext) != 16:
|
||||
raise ValueError('plaintext block must be 16 bytes')
|
||||
|
||||
plaintext = _string_to_bytes(plaintext)
|
||||
precipherblock = [ (p ^ l) for (p, l) in zip(plaintext, self._last_cipherblock) ]
|
||||
self._last_cipherblock = self._aes.encrypt(precipherblock)
|
||||
|
||||
return _bytes_to_string(self._last_cipherblock)
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
if len(ciphertext) != 16:
|
||||
raise ValueError('ciphertext block must be 16 bytes')
|
||||
|
||||
cipherblock = _string_to_bytes(ciphertext)
|
||||
plaintext = [ (p ^ l) for (p, l) in zip(self._aes.decrypt(cipherblock), self._last_cipherblock) ]
|
||||
self._last_cipherblock = cipherblock
|
||||
|
||||
return _bytes_to_string(plaintext)
|
||||
|
||||
|
||||
|
||||
class AESModeOfOperationCFB(AESSegmentModeOfOperation):
|
||||
'''AES Cipher Feedback Mode of Operation.
|
||||
|
||||
o A stream-cipher, so input does not need to be padded to blocks,
|
||||
but does need to be padded to segment_size
|
||||
|
||||
Also see:
|
||||
o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_.28CFB.29
|
||||
o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.3'''
|
||||
|
||||
|
||||
name = "Cipher Feedback (CFB)"
|
||||
|
||||
def __init__(self, key, iv, segment_size = 1):
|
||||
if segment_size == 0: segment_size = 1
|
||||
|
||||
if iv is None:
|
||||
self._shift_register = [ 0 ] * 16
|
||||
elif len(iv) != 16:
|
||||
raise ValueError('initialization vector must be 16 bytes')
|
||||
else:
|
||||
self._shift_register = _string_to_bytes(iv)
|
||||
|
||||
self._segment_bytes = segment_size
|
||||
|
||||
AESBlockModeOfOperation.__init__(self, key)
|
||||
|
||||
segment_bytes = property(lambda s: s._segment_bytes)
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
if len(plaintext) % self._segment_bytes != 0:
|
||||
raise ValueError('plaintext block must be a multiple of segment_size')
|
||||
|
||||
plaintext = _string_to_bytes(plaintext)
|
||||
|
||||
# Break block into segments
|
||||
encrypted = [ ]
|
||||
for i in xrange(0, len(plaintext), self._segment_bytes):
|
||||
plaintext_segment = plaintext[i: i + self._segment_bytes]
|
||||
xor_segment = self._aes.encrypt(self._shift_register)[:len(plaintext_segment)]
|
||||
cipher_segment = [ (p ^ x) for (p, x) in zip(plaintext_segment, xor_segment) ]
|
||||
|
||||
# Shift the top bits out and the ciphertext in
|
||||
self._shift_register = _concat_list(self._shift_register[len(cipher_segment):], cipher_segment)
|
||||
|
||||
encrypted.extend(cipher_segment)
|
||||
|
||||
return _bytes_to_string(encrypted)
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
if len(ciphertext) % self._segment_bytes != 0:
|
||||
raise ValueError('ciphertext block must be a multiple of segment_size')
|
||||
|
||||
ciphertext = _string_to_bytes(ciphertext)
|
||||
|
||||
# Break block into segments
|
||||
decrypted = [ ]
|
||||
for i in xrange(0, len(ciphertext), self._segment_bytes):
|
||||
cipher_segment = ciphertext[i: i + self._segment_bytes]
|
||||
xor_segment = self._aes.encrypt(self._shift_register)[:len(cipher_segment)]
|
||||
plaintext_segment = [ (p ^ x) for (p, x) in zip(cipher_segment, xor_segment) ]
|
||||
|
||||
# Shift the top bits out and the ciphertext in
|
||||
self._shift_register = _concat_list(self._shift_register[len(cipher_segment):], cipher_segment)
|
||||
|
||||
decrypted.extend(plaintext_segment)
|
||||
|
||||
return _bytes_to_string(decrypted)
|
||||
|
||||
|
||||
|
||||
class AESModeOfOperationOFB(AESStreamModeOfOperation):
|
||||
'''AES Output Feedback Mode of Operation.
|
||||
|
||||
o A stream-cipher, so input does not need to be padded to blocks,
|
||||
allowing arbitrary length data.
|
||||
o A bit twiddled in the cipher text, twiddles the same bit in the
|
||||
same bit in the plain text, which can be useful for error
|
||||
correction techniques.
|
||||
|
||||
Also see:
|
||||
o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_.28OFB.29
|
||||
o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.4'''
|
||||
|
||||
|
||||
name = "Output Feedback (OFB)"
|
||||
|
||||
def __init__(self, key, iv = None):
|
||||
if iv is None:
|
||||
self._last_precipherblock = [ 0 ] * 16
|
||||
elif len(iv) != 16:
|
||||
raise ValueError('initialization vector must be 16 bytes')
|
||||
else:
|
||||
self._last_precipherblock = _string_to_bytes(iv)
|
||||
|
||||
self._remaining_block = [ ]
|
||||
|
||||
AESBlockModeOfOperation.__init__(self, key)
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
encrypted = [ ]
|
||||
for p in _string_to_bytes(plaintext):
|
||||
if len(self._remaining_block) == 0:
|
||||
self._remaining_block = self._aes.encrypt(self._last_precipherblock)
|
||||
self._last_precipherblock = [ ]
|
||||
precipherbyte = self._remaining_block.pop(0)
|
||||
self._last_precipherblock.append(precipherbyte)
|
||||
cipherbyte = p ^ precipherbyte
|
||||
encrypted.append(cipherbyte)
|
||||
|
||||
return _bytes_to_string(encrypted)
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
# AES-OFB is symetric
|
||||
return self.encrypt(ciphertext)
|
||||
|
||||
|
||||
|
||||
class AESModeOfOperationCTR(AESStreamModeOfOperation):
|
||||
'''AES Counter Mode of Operation.
|
||||
|
||||
o A stream-cipher, so input does not need to be padded to blocks,
|
||||
allowing arbitrary length data.
|
||||
o The counter must be the same size as the key size (ie. len(key))
|
||||
o Each block independant of the other, so a corrupt byte will not
|
||||
damage future blocks.
|
||||
o Each block has a uniue counter value associated with it, which
|
||||
contributes to the encrypted value, so no data patterns are
|
||||
leaked.
|
||||
o Also known as: Counter Mode (CM), Integer Counter Mode (ICM) and
|
||||
Segmented Integer Counter (SIC
|
||||
|
||||
Security Notes:
|
||||
o This method (and CBC) ARE recommended.
|
||||
o Each message block is associated with a counter value which must be
|
||||
unique for ALL messages with the same key. Otherwise security may be
|
||||
compromised.
|
||||
|
||||
Also see:
|
||||
|
||||
o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29
|
||||
o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.5
|
||||
and Appendix B for managing the initial counter'''
|
||||
|
||||
|
||||
name = "Counter (CTR)"
|
||||
|
||||
def __init__(self, key, counter = None):
|
||||
AESBlockModeOfOperation.__init__(self, key)
|
||||
|
||||
if counter is None:
|
||||
counter = Counter()
|
||||
|
||||
self._counter = counter
|
||||
self._remaining_counter = [ ]
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
while len(self._remaining_counter) < len(plaintext):
|
||||
self._remaining_counter += self._aes.encrypt(self._counter.value)
|
||||
self._counter.increment()
|
||||
|
||||
plaintext = _string_to_bytes(plaintext)
|
||||
|
||||
encrypted = [ (p ^ c) for (p, c) in zip(plaintext, self._remaining_counter) ]
|
||||
self._remaining_counter = self._remaining_counter[len(encrypted):]
|
||||
|
||||
return _bytes_to_string(encrypted)
|
||||
|
||||
def decrypt(self, crypttext):
|
||||
# AES-CTR is symetric
|
||||
return self.encrypt(crypttext)
|
||||
|
||||
|
||||
# Simple lookup table for each mode
|
||||
AESModesOfOperation = dict(
|
||||
ctr = AESModeOfOperationCTR,
|
||||
cbc = AESModeOfOperationCBC,
|
||||
cfb = AESModeOfOperationCFB,
|
||||
ecb = AESModeOfOperationECB,
|
||||
ofb = AESModeOfOperationOFB,
|
||||
)
|
||||
@@ -1,227 +0,0 @@
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014 Richard Moore
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
|
||||
from .aes import AESBlockModeOfOperation, AESSegmentModeOfOperation, AESStreamModeOfOperation
|
||||
from .util import append_PKCS7_padding, strip_PKCS7_padding, to_bufferable
|
||||
|
||||
|
||||
# First we inject three functions to each of the modes of operations
|
||||
#
|
||||
# _can_consume(size)
|
||||
# - Given a size, determine how many bytes could be consumed in
|
||||
# a single call to either the decrypt or encrypt method
|
||||
#
|
||||
# _final_encrypt(data, padding = PADDING_DEFAULT)
|
||||
# - call and return encrypt on this (last) chunk of data,
|
||||
# padding as necessary; this will always be at least 16
|
||||
# bytes unless the total incoming input was less than 16
|
||||
# bytes
|
||||
#
|
||||
# _final_decrypt(data, padding = PADDING_DEFAULT)
|
||||
# - same as _final_encrypt except for decrypt, for
|
||||
# stripping off padding
|
||||
#
|
||||
|
||||
PADDING_NONE = 'none'
|
||||
PADDING_DEFAULT = 'default'
|
||||
|
||||
# @TODO: Ciphertext stealing and explicit PKCS#7
|
||||
# PADDING_CIPHERTEXT_STEALING
|
||||
# PADDING_PKCS7
|
||||
|
||||
# ECB and CBC are block-only ciphers
|
||||
|
||||
def _block_can_consume(self, size):
|
||||
if size >= 16: return 16
|
||||
return 0
|
||||
|
||||
# After padding, we may have more than one block
|
||||
def _block_final_encrypt(self, data, padding = PADDING_DEFAULT):
|
||||
if padding == PADDING_DEFAULT:
|
||||
data = append_PKCS7_padding(data)
|
||||
|
||||
elif padding == PADDING_NONE:
|
||||
if len(data) != 16:
|
||||
raise Exception('invalid data length for final block')
|
||||
else:
|
||||
raise Exception('invalid padding option')
|
||||
|
||||
if len(data) == 32:
|
||||
return self.encrypt(data[:16]) + self.encrypt(data[16:])
|
||||
|
||||
return self.encrypt(data)
|
||||
|
||||
|
||||
def _block_final_decrypt(self, data, padding = PADDING_DEFAULT):
|
||||
if padding == PADDING_DEFAULT:
|
||||
return strip_PKCS7_padding(self.decrypt(data))
|
||||
|
||||
if padding == PADDING_NONE:
|
||||
if len(data) != 16:
|
||||
raise Exception('invalid data length for final block')
|
||||
return self.decrypt(data)
|
||||
|
||||
raise Exception('invalid padding option')
|
||||
|
||||
AESBlockModeOfOperation._can_consume = _block_can_consume
|
||||
AESBlockModeOfOperation._final_encrypt = _block_final_encrypt
|
||||
AESBlockModeOfOperation._final_decrypt = _block_final_decrypt
|
||||
|
||||
|
||||
|
||||
# CFB is a segment cipher
|
||||
|
||||
def _segment_can_consume(self, size):
|
||||
return self.segment_bytes * int(size // self.segment_bytes)
|
||||
|
||||
# CFB can handle a non-segment-sized block at the end using the remaining cipherblock
|
||||
def _segment_final_encrypt(self, data, padding = PADDING_DEFAULT):
|
||||
if padding != PADDING_DEFAULT:
|
||||
raise Exception('invalid padding option')
|
||||
|
||||
faux_padding = (chr(0) * (self.segment_bytes - (len(data) % self.segment_bytes)))
|
||||
padded = data + to_bufferable(faux_padding)
|
||||
return self.encrypt(padded)[:len(data)]
|
||||
|
||||
# CFB can handle a non-segment-sized block at the end using the remaining cipherblock
|
||||
def _segment_final_decrypt(self, data, padding = PADDING_DEFAULT):
|
||||
if padding != PADDING_DEFAULT:
|
||||
raise Exception('invalid padding option')
|
||||
|
||||
faux_padding = (chr(0) * (self.segment_bytes - (len(data) % self.segment_bytes)))
|
||||
padded = data + to_bufferable(faux_padding)
|
||||
return self.decrypt(padded)[:len(data)]
|
||||
|
||||
AESSegmentModeOfOperation._can_consume = _segment_can_consume
|
||||
AESSegmentModeOfOperation._final_encrypt = _segment_final_encrypt
|
||||
AESSegmentModeOfOperation._final_decrypt = _segment_final_decrypt
|
||||
|
||||
|
||||
|
||||
# OFB and CTR are stream ciphers
|
||||
|
||||
def _stream_can_consume(self, size):
|
||||
return size
|
||||
|
||||
def _stream_final_encrypt(self, data, padding = PADDING_DEFAULT):
|
||||
if padding not in [PADDING_NONE, PADDING_DEFAULT]:
|
||||
raise Exception('invalid padding option')
|
||||
|
||||
return self.encrypt(data)
|
||||
|
||||
def _stream_final_decrypt(self, data, padding = PADDING_DEFAULT):
|
||||
if padding not in [PADDING_NONE, PADDING_DEFAULT]:
|
||||
raise Exception('invalid padding option')
|
||||
|
||||
return self.decrypt(data)
|
||||
|
||||
AESStreamModeOfOperation._can_consume = _stream_can_consume
|
||||
AESStreamModeOfOperation._final_encrypt = _stream_final_encrypt
|
||||
AESStreamModeOfOperation._final_decrypt = _stream_final_decrypt
|
||||
|
||||
|
||||
|
||||
class BlockFeeder(object):
|
||||
'''The super-class for objects to handle chunking a stream of bytes
|
||||
into the appropriate block size for the underlying mode of operation
|
||||
and applying (or stripping) padding, as necessary.'''
|
||||
|
||||
def __init__(self, mode, feed, final, padding = PADDING_DEFAULT):
|
||||
self._mode = mode
|
||||
self._feed = feed
|
||||
self._final = final
|
||||
self._buffer = to_bufferable("")
|
||||
self._padding = padding
|
||||
|
||||
def feed(self, data = None):
|
||||
'''Provide bytes to encrypt (or decrypt), returning any bytes
|
||||
possible from this or any previous calls to feed.
|
||||
|
||||
Call with None or an empty string to flush the mode of
|
||||
operation and return any final bytes; no further calls to
|
||||
feed may be made.'''
|
||||
|
||||
if self._buffer is None:
|
||||
raise ValueError('already finished feeder')
|
||||
|
||||
# Finalize; process the spare bytes we were keeping
|
||||
if data is None:
|
||||
result = self._final(self._buffer, self._padding)
|
||||
self._buffer = None
|
||||
return result
|
||||
|
||||
self._buffer += to_bufferable(data)
|
||||
|
||||
# We keep 16 bytes around so we can determine padding
|
||||
result = to_bufferable('')
|
||||
while len(self._buffer) > 16:
|
||||
can_consume = self._mode._can_consume(len(self._buffer) - 16)
|
||||
if can_consume == 0: break
|
||||
result += self._feed(self._buffer[:can_consume])
|
||||
self._buffer = self._buffer[can_consume:]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class Encrypter(BlockFeeder):
|
||||
'Accepts bytes of plaintext and returns encrypted ciphertext.'
|
||||
|
||||
def __init__(self, mode, padding = PADDING_DEFAULT):
|
||||
BlockFeeder.__init__(self, mode, mode.encrypt, mode._final_encrypt, padding)
|
||||
|
||||
|
||||
class Decrypter(BlockFeeder):
|
||||
'Accepts bytes of ciphertext and returns decrypted plaintext.'
|
||||
|
||||
def __init__(self, mode, padding = PADDING_DEFAULT):
|
||||
BlockFeeder.__init__(self, mode, mode.decrypt, mode._final_decrypt, padding)
|
||||
|
||||
|
||||
# 8kb blocks
|
||||
BLOCK_SIZE = (1 << 13)
|
||||
|
||||
def _feed_stream(feeder, in_stream, out_stream, block_size = BLOCK_SIZE):
|
||||
'Uses feeder to read and convert from in_stream and write to out_stream.'
|
||||
|
||||
while True:
|
||||
chunk = in_stream.read(block_size)
|
||||
if not chunk:
|
||||
break
|
||||
converted = feeder.feed(chunk)
|
||||
out_stream.write(converted)
|
||||
converted = feeder.feed()
|
||||
out_stream.write(converted)
|
||||
|
||||
|
||||
def encrypt_stream(mode, in_stream, out_stream, block_size = BLOCK_SIZE, padding = PADDING_DEFAULT):
|
||||
'Encrypts a stream of bytes from in_stream to out_stream using mode.'
|
||||
|
||||
encrypter = Encrypter(mode, padding = padding)
|
||||
_feed_stream(encrypter, in_stream, out_stream, block_size)
|
||||
|
||||
|
||||
def decrypt_stream(mode, in_stream, out_stream, block_size = BLOCK_SIZE, padding = PADDING_DEFAULT):
|
||||
'Decrypts a stream of bytes from in_stream to out_stream using mode.'
|
||||
|
||||
decrypter = Decrypter(mode, padding = padding)
|
||||
_feed_stream(decrypter, in_stream, out_stream, block_size)
|
||||
@@ -1,60 +0,0 @@
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2014 Richard Moore
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
# Why to_bufferable?
|
||||
# Python 3 is very different from Python 2.x when it comes to strings of text
|
||||
# and strings of bytes; in Python 3, strings of bytes do not exist, instead to
|
||||
# represent arbitrary binary data, we must use the "bytes" object. This method
|
||||
# ensures the object behaves as we need it to.
|
||||
|
||||
def to_bufferable(binary):
|
||||
return binary
|
||||
|
||||
def _get_byte(c):
|
||||
return ord(c)
|
||||
|
||||
try:
|
||||
xrange
|
||||
except:
|
||||
|
||||
def to_bufferable(binary):
|
||||
if isinstance(binary, bytes):
|
||||
return binary
|
||||
return bytes(ord(b) for b in binary)
|
||||
|
||||
def _get_byte(c):
|
||||
return c
|
||||
|
||||
def append_PKCS7_padding(data):
|
||||
pad = 16 - (len(data) % 16)
|
||||
return data + to_bufferable(chr(pad) * pad)
|
||||
|
||||
def strip_PKCS7_padding(data):
|
||||
if len(data) % 16 != 0:
|
||||
raise ValueError("invalid length")
|
||||
|
||||
pad = _get_byte(data[-1])
|
||||
|
||||
if pad > 16:
|
||||
raise ValueError("invalid padding byte")
|
||||
|
||||
return data[:-pad]
|
||||
@@ -95,8 +95,7 @@ provider_manager = RegistrableExtensionManager('subliminal.providers', [
|
||||
'shooter = subliminal.providers.shooter:ShooterProvider',
|
||||
'thesubdb = subliminal.providers.thesubdb:TheSubDBProvider',
|
||||
'tvsubtitles = subliminal.providers.tvsubtitles:TVsubtitlesProvider',
|
||||
'ktuvit = subliminal.providers.ktuvit:KtuvitProvider'
|
||||
'wizdom = subliminal.providers.wizdom:WizdomProvider'
|
||||
'screwzira = subliminal.providers.screwzira:ScrewZiraProvider'
|
||||
])
|
||||
|
||||
#: Refiner manager
|
||||
|
||||
@@ -529,7 +529,7 @@ def scan_video(path, dont_use_actual_file=False, hints=None, providers=None, ski
|
||||
video.hints = hints
|
||||
|
||||
# get possibly alternative title from the filename itself
|
||||
alt_guess = guessit(filename, options={k: v for k, v in hints.items() if k not in ('expected_title', 'title')})
|
||||
alt_guess = guessit(filename, options=hints)
|
||||
if "title" in alt_guess and alt_guess["title"] != guessed_result["title"]:
|
||||
if video_type == "episode":
|
||||
video.alternative_series.append(alt_guess["title"])
|
||||
|
||||
+18
-194
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
from requests import Session
|
||||
from requests.cookies import RequestsCookieJar
|
||||
import json
|
||||
import logging
|
||||
from subzero.language import Language
|
||||
@@ -13,25 +11,17 @@ from subliminal.providers import Episode, Movie
|
||||
from subliminal_patch.utils import sanitize
|
||||
from subliminal_patch.subtitle import Subtitle, guess_matches
|
||||
from subliminal.subtitle import fix_line_ending
|
||||
from subliminal.exceptions import ConfigurationError, AuthenticationError
|
||||
|
||||
from string import hexdigits
|
||||
from collections import defaultdict
|
||||
import pbkdf2
|
||||
from base64 import b64decode, b64encode
|
||||
from hashlib import sha256
|
||||
import pyaes
|
||||
|
||||
__author__ = "Dor Nizar"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class KtuvitSubtitle(Subtitle):
|
||||
provider_name = 'ktuvit'
|
||||
class ScrewZiraSubtitle(Subtitle):
|
||||
provider_name = 'screwzira'
|
||||
|
||||
def __init__(self, language, title_id, subtitle_id, series, season, episode, release, year):
|
||||
super(KtuvitSubtitle, self).__init__(language, subtitle_id)
|
||||
super(ScrewZiraSubtitle, self).__init__(language, subtitle_id)
|
||||
self.title_id = title_id
|
||||
self.subtitle_id = subtitle_id
|
||||
self.series = series
|
||||
@@ -42,7 +32,7 @@ class KtuvitSubtitle(Subtitle):
|
||||
|
||||
def get_matches(self, video):
|
||||
matches = set()
|
||||
logger.debug("[{}]\n{}".format(self.__class__.__name__, self.__dict__))
|
||||
logger.debug("--ScrewZiraSubtitle--\n{}".format(self.__dict__))
|
||||
|
||||
# episode
|
||||
if isinstance(video, Episode):
|
||||
@@ -69,7 +59,7 @@ class KtuvitSubtitle(Subtitle):
|
||||
# guess
|
||||
matches |= guess_matches(video, guessit(self.release, {'type': 'movie'}))
|
||||
|
||||
logger.debug("Ktuvit subtitle criteria match:\n{}".format(matches))
|
||||
logger.debug("ScrewZira subtitle criteria match:\n{}".format(matches))
|
||||
return matches
|
||||
|
||||
@property
|
||||
@@ -77,109 +67,31 @@ class KtuvitSubtitle(Subtitle):
|
||||
return self.subtitle_id
|
||||
|
||||
|
||||
class KtuvitProvider(Provider):
|
||||
subtitle_class = KtuvitSubtitle
|
||||
class ScrewZiraProvider(Provider):
|
||||
subtitle_class = ScrewZiraSubtitle
|
||||
languages = {Language.fromalpha2(l) for l in ['he']}
|
||||
URL_SERVER = 'https://www.ktuvit.me/'
|
||||
URL_SERVER = 'https://www.screwzira.com/'
|
||||
|
||||
URI_LOGIN = 'Login.aspx'
|
||||
URI_LOGIN_POST = 'Services/MembershipService.svc/Login'
|
||||
URI_SEARCH_TITLE = 'Services/ContentProvider.svc/GetSearchForecast'
|
||||
URI_SEARCH_SERIES_SUBTITLE = 'Services/GetModuleAjax.ashx'
|
||||
URI_SEARCH_MOVIE_SUBTITLE = "MovieInfo.aspx"
|
||||
URI_REQ_SUBTITLE_ID = "Services/ContentProvider.svc/RequestSubtitleDownload"
|
||||
URI_DOWNLOAD_SUBTITLE = "Services/DownloadFile.ashx"
|
||||
|
||||
def __init__(self, username=None, password=None):
|
||||
if not all((username, password)):
|
||||
raise ConfigurationError('Username and password must be specified')
|
||||
|
||||
self.session = None
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.encrypted_password = None
|
||||
self.salt = None
|
||||
|
||||
def encrypt_password(self):
|
||||
logger.debug("password: {}".format(self.password))
|
||||
encrypted_password = KtuvitEncryptor(self.username, self.password, self.salt).encrypt()
|
||||
if not encrypted_password:
|
||||
logger.error("Could not encrypt password")
|
||||
return False
|
||||
self.encrypted_password = encrypted_password
|
||||
return True
|
||||
|
||||
def get_encryption_salt(self):
|
||||
p = re.compile(r"var encryptionSalt = '(.*)';")
|
||||
r = self.session.get(self.URL_SERVER + self.URI_LOGIN)
|
||||
r.raise_for_status()
|
||||
|
||||
logger.debug("Searching for encryptionSalt...")
|
||||
script_list = [i for i in BeautifulSoup(r.content, 'html.parser').select('div#navbar script') if i.contents]
|
||||
for item in script_list:
|
||||
search_salt = p.search(item.contents[0])
|
||||
if search_salt:
|
||||
self.salt = search_salt.group(1)
|
||||
return True
|
||||
logger.error("Could not get encryptionSalt")
|
||||
return False
|
||||
|
||||
def login(self):
|
||||
if not self.get_encryption_salt():
|
||||
return False
|
||||
if not self.encrypt_password():
|
||||
return False
|
||||
data = {
|
||||
"request": {
|
||||
"Email": self.username,
|
||||
"Password": self.encrypted_password
|
||||
}
|
||||
}
|
||||
logger.debug("Trying to log in using: {}".format(json.dumps(data)))
|
||||
r = self.session.post(self.URL_SERVER + self.URI_LOGIN_POST, json=data)
|
||||
r_result = r.json()
|
||||
login_results = ""
|
||||
if 'd' in r_result:
|
||||
try:
|
||||
login_results = json.loads(r_result['d'])
|
||||
except ValueError:
|
||||
logger.error("Could not process JSON from login response")
|
||||
return False
|
||||
|
||||
if 'IsSuccess' not in login_results:
|
||||
logger.error("Login response is different than expected")
|
||||
return False
|
||||
|
||||
if not login_results['IsSuccess']:
|
||||
logger.error("Wrong username or password!")
|
||||
raise AuthenticationError('Wrong username or password!')
|
||||
return False
|
||||
|
||||
if not r.cookies or type(r.cookies) is not RequestsCookieJar:
|
||||
logger.error("Could not get the cookie response of the login")
|
||||
return False
|
||||
|
||||
if 'Login' not in r.cookies.keys():
|
||||
logger.error("Could not found login cookie!")
|
||||
return False
|
||||
|
||||
logger.info("Connected successfully to Ktuvit!")
|
||||
return True
|
||||
|
||||
def initialize(self):
|
||||
logger.debug("Ktuvit initialize")
|
||||
logger.debug("ScrewZira initialize")
|
||||
self.session = Session()
|
||||
self.session.headers[
|
||||
'User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; ' \
|
||||
'Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
|
||||
|
||||
if not self.login():
|
||||
return False
|
||||
|
||||
def terminate(self):
|
||||
logger.debug("Ktuvit terminate")
|
||||
logger.debug("ScrewZira terminate")
|
||||
self.session.close()
|
||||
|
||||
def __init__(self):
|
||||
self.session = None
|
||||
|
||||
def _search_series(self, title):
|
||||
logger.debug("Searching '{}'".format(title))
|
||||
title_request = {
|
||||
@@ -313,105 +225,17 @@ class KtuvitProvider(Provider):
|
||||
return [s for s in self.query(title, season, episode, year) if s.language in languages]
|
||||
|
||||
def download_subtitle(self, subtitle):
|
||||
# type: (KtuvitSubtitle) -> None
|
||||
# type: (ScrewZiraSubtitle) -> None
|
||||
|
||||
logger.info('Downloading subtitle from Ktuvit: %r', subtitle)
|
||||
download_id = self._req_download_identifier(subtitle.title_id, subtitle.subtitle_id)
|
||||
if not download_id:
|
||||
logger.info('Downloading subtitle from ScrewZira: %r', subtitle)
|
||||
downloadID = self._req_download_identifier(subtitle.title_id, subtitle.subtitle_id)
|
||||
if not downloadID:
|
||||
logger.debug('Unable to retrieve download identifier')
|
||||
return None
|
||||
|
||||
content = self._download_subtitles(download_id)
|
||||
content = self._download_subtitles(downloadID)
|
||||
if not content:
|
||||
logger.debug('Unable to download subtitle')
|
||||
return None
|
||||
|
||||
subtitle.content = fix_line_ending(content)
|
||||
|
||||
|
||||
class KtuvitEncryptor:
|
||||
def __init__(self, username, password, salt):
|
||||
if not all((username, password, salt)):
|
||||
raise Exception("Encryptor did not get all required arguments")
|
||||
|
||||
self.encrypted_password = None
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.salt = salt
|
||||
|
||||
@staticmethod
|
||||
def rshift(val, n):
|
||||
return (val % 0x100000000) >> n
|
||||
|
||||
@staticmethod
|
||||
def js_parseint(s, rad=10):
|
||||
digits = ''
|
||||
for c in str(s).strip():
|
||||
if c not in hexdigits:
|
||||
break
|
||||
digits += c
|
||||
|
||||
return int(digits, rad) if digits else 0
|
||||
|
||||
@staticmethod
|
||||
def to_signed32(n):
|
||||
n = n & 0xffffffff
|
||||
return n | (-(n & 0x80000000))
|
||||
|
||||
def stringify(self, words, length):
|
||||
sigbytes = int(length / 2) + int((length % 2) > 0)
|
||||
hex_chars = list()
|
||||
|
||||
for i in xrange(0, sigbytes):
|
||||
bite = self.rshift(words[self.rshift(i, 2)], (24 - (i % 4) * 8)) & 0xff
|
||||
hex_chars.append(format(self.rshift(bite, 4), 'x'))
|
||||
hex_chars.append(format(bite & 0x0f, 'x'))
|
||||
return ''.join(hex_chars)
|
||||
|
||||
def cryptojs_hexparse(self, s):
|
||||
words = defaultdict(int)
|
||||
for i in range(0, len(s), 2):
|
||||
tmp1 = (self.js_parseint(s[i:i + 2], 16))
|
||||
tmp2 = (24 - (i % 8) * 4)
|
||||
tmp3 = self.to_signed32(tmp1 << tmp2)
|
||||
words[self.rshift(i, 3)] |= tmp3
|
||||
return self.stringify(words, len(s))
|
||||
|
||||
def cryptojs_pad_iv(self, iv):
|
||||
return str.ljust(self.cryptojs_hexparse(iv), 32, '0').decode('hex')
|
||||
|
||||
@staticmethod
|
||||
def pbkdf2_encrypt(key, salt):
|
||||
a = pbkdf2.PBKDF2(salt, key, 3000)
|
||||
return a.read(16)
|
||||
|
||||
@staticmethod
|
||||
def pad(m):
|
||||
return m + chr(16 - len(m) % 16) * (16 - len(m) % 16)
|
||||
|
||||
def aes_encrypt(self, msg, key, iv):
|
||||
if len(iv) != 16:
|
||||
logger.error("iv (Len: {}) - {} is not 16 length".format(len(iv), iv))
|
||||
return False
|
||||
msg = self.pad(msg)
|
||||
|
||||
aes = pyaes.AESModeOfOperationCBC(key, iv=iv[:16])
|
||||
return b64encode(aes.encrypt(msg))
|
||||
|
||||
def encrypt(self):
|
||||
if not self.salt:
|
||||
logger.error("No salt was instantiated!")
|
||||
return False
|
||||
msg = self.password.encode('utf-8')
|
||||
iv = self.cryptojs_pad_iv(self.username)
|
||||
key = self.pbkdf2_encrypt(self.username, self.salt.encode('utf-8'))
|
||||
|
||||
cipher = self.aes_encrypt(msg, key, iv)
|
||||
if not cipher:
|
||||
return False
|
||||
|
||||
hash_sha256 = sha256(b64decode(cipher))
|
||||
self.encrypted_password = b64encode(hash_sha256.digest())
|
||||
logger.debug("Encrypted password: {}".format(self.encrypted_password))
|
||||
logger.debug("Original password: {}".format(self.password))
|
||||
return self.encrypted_password
|
||||
@@ -143,7 +143,7 @@ class SuperSubtitlesProvider(Provider, ProviderSubtitleArchiveMixin):
|
||||
]}
|
||||
video_types = (Episode, Movie)
|
||||
# https://www.feliratok.info/?search=&soriSorszam=&nyelv=&sorozatnev=The+Flash+%282014%29&sid=3212&complexsearch=true&knyelv=0&evad=4&epizod1=1&cimke=0&minoseg=0&rlsr=0&tab=all
|
||||
server_url = 'https://www.feliratok.eu/'
|
||||
server_url = 'https://www.feliratok.info/'
|
||||
subtitle_class = SuperSubtitlesSubtitle
|
||||
hearing_impaired_verifiable = False
|
||||
multi_result_throttle = 2 # seconds
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from requests import Session
|
||||
import logging
|
||||
from subzero.language import Language
|
||||
from guessit import guessit
|
||||
|
||||
from subliminal_patch.providers import Provider
|
||||
from subliminal.providers import Episode, Movie
|
||||
from subliminal_patch.utils import sanitize
|
||||
from subliminal_patch.subtitle import Subtitle, guess_matches
|
||||
from subliminal.subtitle import fix_line_ending
|
||||
|
||||
from io import BytesIO
|
||||
from zipfile import ZipFile
|
||||
|
||||
__author__ = "Dor Nizar"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WizdomSubtitle(Subtitle):
|
||||
provider_name = 'wizdom'
|
||||
|
||||
def __init__(self, language, title_id, subtitle_id, series, season, episode, release, year, page_link):
|
||||
super(WizdomSubtitle, self).__init__(language, subtitle_id)
|
||||
self.title_id = title_id
|
||||
self.subtitle_id = subtitle_id
|
||||
self.series = series
|
||||
self.season = season
|
||||
self.episode = episode
|
||||
self.release = release
|
||||
self.year = year
|
||||
self.page_link = page_link
|
||||
|
||||
def get_matches(self, video):
|
||||
matches = set()
|
||||
logger.debug("[{}]\n{}".format(self.__class__.__name__, self.__dict__))
|
||||
|
||||
# episode
|
||||
if isinstance(video, Episode):
|
||||
# series
|
||||
if video.series and sanitize(self.series) == sanitize(video.series):
|
||||
matches.add('series')
|
||||
# season
|
||||
if video.season and self.season == video.season:
|
||||
matches.add('season')
|
||||
# episode
|
||||
if video.episode and self.episode == video.episode:
|
||||
matches.add('episode')
|
||||
# guess
|
||||
matches |= guess_matches(video, guessit(self.release, {'type': 'episode'}))
|
||||
# movie
|
||||
elif isinstance(video, Movie):
|
||||
# title
|
||||
if video.title and (sanitize(self.series) in (
|
||||
sanitize(name) for name in [video.title] + video.alternative_titles)):
|
||||
matches.add('title')
|
||||
# year
|
||||
if video.year and self.year == video.year:
|
||||
matches.add('year')
|
||||
# guess
|
||||
matches |= guess_matches(video, guessit(self.release, {'type': 'movie'}))
|
||||
|
||||
logger.debug("Wizdom subtitle criteria match:\n{}".format(matches))
|
||||
return matches
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.subtitle_id
|
||||
|
||||
|
||||
class WizdomProvider(Provider):
|
||||
subtitle_class = WizdomSubtitle
|
||||
languages = {Language.fromalpha2(lng) for lng in ['he']}
|
||||
URL_JSON_SERVER = 'https://json.wizdom.xyz/'
|
||||
URL_DOWNLOAD_SERVER = 'https://zip.wizdom.xyz/'
|
||||
URL_WIZDOM_PAGELINK = 'https://wizdom.xyz/{}/{}'
|
||||
|
||||
URL_SEARCH = URL_JSON_SERVER + "search.php?search={}"
|
||||
URL_INFO = URL_JSON_SERVER + "{}.json"
|
||||
URL_DOWNLOAD_SUBTITLE = URL_DOWNLOAD_SERVER + "{}.zip"
|
||||
|
||||
def __init__(self):
|
||||
self.session = None
|
||||
|
||||
def initialize(self):
|
||||
logger.info("Wizdom initialize")
|
||||
self.session = Session()
|
||||
self.session.headers[
|
||||
'User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; ' \
|
||||
'Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
|
||||
return True
|
||||
|
||||
def terminate(self):
|
||||
logger.info("Wizdom terminate")
|
||||
self.session.close()
|
||||
|
||||
def _search_series(self, title, movie=False):
|
||||
logger.debug("Searching '{}'".format(title))
|
||||
r = self.session.get(self.URL_SEARCH.format(title))
|
||||
if not r.ok:
|
||||
logger.error("Bad response from server while searching: '{}']".format(title))
|
||||
return []
|
||||
try:
|
||||
found = r.json()
|
||||
except ValueError:
|
||||
logger.error("Could not extract JSON from response")
|
||||
return []
|
||||
|
||||
if movie:
|
||||
series_list = [x for x in found if 'type' in x and x['type'] == 'movie']
|
||||
else:
|
||||
series_list = [x for x in found if 'type' in x and x['type'] == 'tv']
|
||||
|
||||
logger.debug("Found the following titles: {}".format([x['title_en'] for x in series_list if 'title_en' in x]))
|
||||
return series_list
|
||||
|
||||
def _search_subtitles(self, title_id, season=None, episode=None):
|
||||
r = self.session.get(self.URL_INFO.format(title_id))
|
||||
|
||||
if not r.ok:
|
||||
logger.error("Bad response from server while searching subtitles [title_id={}]".format(title_id))
|
||||
return []
|
||||
if not r.content:
|
||||
return []
|
||||
try:
|
||||
results = r.json()
|
||||
except ValueError:
|
||||
return []
|
||||
|
||||
if 'subs' not in results:
|
||||
return []
|
||||
|
||||
if season and episode:
|
||||
s_season = str(season)
|
||||
s_episode = str(episode)
|
||||
if s_season in results['subs'] and s_episode in results['subs'][s_season]:
|
||||
return results['subs'][s_season][s_episode]
|
||||
else:
|
||||
return results['subs']
|
||||
return []
|
||||
|
||||
def query(self, title, season=None, episode=None, year=None):
|
||||
subtitles = []
|
||||
if season and episode:
|
||||
logger.debug("Searching for:\nTitle: {}\nSeason: {}\nEpisode: {}\nYear: {}".format(title, season,
|
||||
episode, year))
|
||||
titles = self._search_series(title)
|
||||
else:
|
||||
logger.debug("Searching for:\nTitle: {}\nYear: {}\n".format(title, year))
|
||||
titles = self._search_series(title, movie=True)
|
||||
|
||||
for title in titles:
|
||||
title_name = title['title_en']
|
||||
title_id = title['imdb']
|
||||
if season and episode:
|
||||
results = self._search_subtitles(title_id, season, episode)
|
||||
page_link = self.URL_WIZDOM_PAGELINK.format("tv", title_id)
|
||||
else:
|
||||
results = self._search_subtitles(title_id)
|
||||
page_link = self.URL_WIZDOM_PAGELINK.format("movie", title_id)
|
||||
|
||||
if not results:
|
||||
logger.info("No subtitles found for: {}".format(title_name))
|
||||
continue
|
||||
|
||||
for result in results:
|
||||
subtitle_id, release = result['id'], result['version']
|
||||
subtitles.append(self.subtitle_class(next(iter(self.languages)), title_id, subtitle_id,
|
||||
title_name, season, episode, release, year, page_link))
|
||||
|
||||
if subtitles:
|
||||
logger.debug("Found Subtitle Candidates: {}".format([x.release for x in subtitles]))
|
||||
return subtitles
|
||||
|
||||
def list_subtitles(self, video, languages):
|
||||
season = episode = year = title = None
|
||||
|
||||
if isinstance(video, Episode):
|
||||
logger.info("list_subtitles Series: {}, season: {}, episode: {}".format(video.series,
|
||||
video.season,
|
||||
video.episode))
|
||||
title = video.series
|
||||
season = video.season
|
||||
if video.episode == 0:
|
||||
episode = 1
|
||||
else:
|
||||
episode = video.episode
|
||||
elif isinstance(video, Movie):
|
||||
logger.info("list_subtitles Movie: {}, year: {}".format(video.title, video.year))
|
||||
title = video.title
|
||||
year = video.year
|
||||
|
||||
return [s for s in self.query(title, season, episode, year) if s.language in languages]
|
||||
|
||||
def _download_subtitles(self, subtitle_id):
|
||||
logger.debug("Downloading subtitle id - {}".format(subtitle_id))
|
||||
r = self.session.get(self.URL_DOWNLOAD_SUBTITLE.format(subtitle_id))
|
||||
|
||||
if not r.ok:
|
||||
logger.error("Bad response from server while downloding zip [id={}]".format(subtitle_id))
|
||||
return None
|
||||
if not r.content:
|
||||
logger.error("Unable to download zip [id={}]".format(subtitle_id))
|
||||
return None
|
||||
|
||||
return r.content
|
||||
|
||||
def download_subtitle(self, subtitle):
|
||||
# type: (WizdomSubtitle) -> None
|
||||
|
||||
logger.info('Downloading subtitle from WizdomSubs: %r', subtitle)
|
||||
|
||||
content = None
|
||||
zip_content = self._download_subtitles(subtitle.subtitle_id)
|
||||
if not zip_content:
|
||||
return None
|
||||
if not zip_content[:2] == "PK":
|
||||
logger.warning("Response did not contain zip file")
|
||||
return None
|
||||
|
||||
zfile = ZipFile(BytesIO(zip_content))
|
||||
if not len(zfile.namelist()):
|
||||
logger.warning("Response did not contain files inside the zip archive")
|
||||
return None
|
||||
|
||||
sub_files = [x for x in zfile.namelist() if x.endswith(('.srt', '.idx', '.sub'))]
|
||||
if sub_files:
|
||||
content = zfile.open(sub_files[0]).read()
|
||||
|
||||
if not content:
|
||||
logger.warning("File inside zip is empty")
|
||||
return None
|
||||
subtitle.content = fix_line_ending(content)
|
||||
logger.info("Downloaded {} successfuly!".format(subtitle))
|
||||
@@ -21,7 +21,7 @@ if debug:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
#sub = Subtitle(Language.fromietf("eng:forced"), mods=["common", "remove_HI", "OCR_fixes", "fix_uppercase", "shift_offset(ms=-500)", "shift_offset(ms=500)", "shift_offset(s=2,ms=800)"])
|
||||
sub = Subtitle(Language.fromietf("eng"), mods=["common", "remove_HI", "OCR_fixes", "fix_uppercase", "shift_offset(ms=0,s=1)"])
|
||||
sub = Subtitle(Language.fromietf("eng"), mods=["common", "remove_HI", "OCR_fixes", "fix_uppercase", "shift_offset(ms=0,s=1)", "fix_incremental", "fix_short"])
|
||||
sub.content = open(fn).read()
|
||||
sub.normalize()
|
||||
sub.is_valid()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# coding=utf-8
|
||||
|
||||
import re
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
from subzero.language import Language
|
||||
from subzero.modification.mods import SubtitleTextModification, empty_line_post_processors, SubtitleModification
|
||||
@@ -9,7 +11,7 @@ from subzero.modification.processors.re_processor import NReProcessor
|
||||
from subzero.modification import registry
|
||||
from tld import get_tld
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
ENGLISH = Language("eng")
|
||||
|
||||
|
||||
@@ -181,15 +183,104 @@ class FixUppercase(SubtitleModification):
|
||||
entry.plaintext = self.capitalize(entry.plaintext)
|
||||
|
||||
|
||||
"""
|
||||
subsync
|
||||
class FixIncremental(SubtitleModification):
|
||||
identifier = "fix_incremental"
|
||||
description = "Fixes inremental-repeating subtitles"
|
||||
modifies_whole_file = True
|
||||
exclusive = True
|
||||
|
||||
long_description = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
|
||||
def modify(self, content, debug=False, parent=None, **kwargs):
|
||||
prev_entry = None
|
||||
for entry in parent.f:
|
||||
subs = []
|
||||
for sub in entry.text.split("\N"):
|
||||
if prev_entry and prev_entry.text and prev_entry.text.lower().endswith(sub.lower()):
|
||||
if debug:
|
||||
logger.debug(u"Skipping incremental/dup: %s" % sub)
|
||||
continue
|
||||
subs.append(sub)
|
||||
|
||||
if subs:
|
||||
entry.text = "\N".join(subs)
|
||||
|
||||
prev_entry = entry
|
||||
|
||||
|
||||
|
||||
class FixShort(SubtitleModification):
|
||||
identifier = "fix_short"
|
||||
description = "ASDasdasdasdasdd"
|
||||
modifies_whole_file = True
|
||||
exclusive = True
|
||||
|
||||
long_description = "adsadsdasdsadsa"
|
||||
|
||||
def modify(self, content, debug=False, parent=None, **kwargs):
|
||||
prev_entry = None
|
||||
prev_entry_dur = None
|
||||
max_duration = 500
|
||||
max_line_len = 200
|
||||
max_lines = 3
|
||||
entries = []
|
||||
last_lines = []
|
||||
for index, entry in enumerate(parent.f):
|
||||
current_new_lines = []
|
||||
if not last_lines and parent.f[index-1]:
|
||||
print "YOO"
|
||||
# find last lines
|
||||
last_lines = parent.f[index-1].text.split("\N")
|
||||
has_space = len(last_lines) < max_lines
|
||||
last_line = ""
|
||||
# go through each line and pack them
|
||||
for line in entry.text.split("\N"):
|
||||
new_line = ""
|
||||
if line:
|
||||
if last_line != line and last_line and len(last_line + line) <= max_line_len:
|
||||
# new line plus line fits
|
||||
if re.match(".+\W$", line):
|
||||
if last_line.endswith(" "):
|
||||
new_line = last_line + line
|
||||
else:
|
||||
new_line = last_line + " " + line
|
||||
logger.debug("MERGING '%s' with '%s' to '%s'", last_line, line, new_line)
|
||||
else:
|
||||
new_line = line
|
||||
last_line = new_line
|
||||
current_new_lines.append(new_line)
|
||||
|
||||
# merge entries
|
||||
if prev_entry:
|
||||
#print prev_entry.duration, max_duration, len(new_lines), max_lines
|
||||
if prev_entry.duration < max_duration and len(current_new_lines) < max_lines:
|
||||
#len(prev_entry.text) < max_len
|
||||
print "HIT", prev_entry.text, " + ", entry.text
|
||||
entry_text = prev_entry.text + "\N" + "\N".join(current_new_lines)
|
||||
|
||||
else:
|
||||
entry_text = "\N".join(current_new_lines)
|
||||
else:
|
||||
entry_text = "\N".join(current_new_lines)
|
||||
|
||||
#prev_entry = entry.copy()
|
||||
if not prev_entry:
|
||||
prev_entry = entry.copy()
|
||||
continue
|
||||
new_entry = prev_entry.copy()
|
||||
new_entry.text = entry_text
|
||||
prev_entry = new_entry.copy()
|
||||
entries.append(new_entry)
|
||||
#new_entries.append(entry.copy())
|
||||
|
||||
parent.f.entries = entries
|
||||
|
||||
subsync --cli --offline --overwrite --window-size=600 --max-point-dist=2.0 --min-points-no=20 --min-word-prob=0.3 --min-word-len=5 --min-correlation=0.9999 --min-words-sim=0.6 --out-time-offset=-0.08 sync --out SUBTITLE -s SUBTITLE --sub-lang=eng --sub-enc=utf-8 -r REF_FILE --ref-stream-by-type=audio --ref-stream-by-lang=eng
|
||||
|
||||
"""
|
||||
|
||||
|
||||
registry.register(CommonFixes)
|
||||
registry.register(RemoveTags)
|
||||
registry.register(ReverseRTL)
|
||||
registry.register(FixUppercase)
|
||||
registry.register(FixIncremental)
|
||||
registry.register(FixShort)
|
||||
|
||||
@@ -33,9 +33,6 @@ Don't expect support if you mess this up.
|
||||
|
||||
"debug_i18n": false,
|
||||
|
||||
// refresh item after plugin refresh has been called (default: 5)
|
||||
"refresh_after_called": 5,
|
||||
|
||||
// per-provider-config
|
||||
"providers": {
|
||||
// enabled_for: specifies which media type to enable the provider for. does not override global/throttled
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Sub-Zero for Plex
|
||||
[](https://github.com/pannal/Sub-Zero.bundle/releases/latest)
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
[](https://szslack.fragstore.net)
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fpannal%2FSub-Zero.bundle?ref=badge_shield)
|
||||
|
||||
@@ -12,9 +12,6 @@ Check out **[the Sub-Zero Wiki](https://github.com/pannal/Sub-Zero.bundle/wiki)*
|
||||
|
||||
<br />
|
||||
|
||||
## Legacy maintenance mode
|
||||
This addon will not be developed any further. It still works and arguably is still the best for managing subtitles when using Plex. As long as Plex Inc. supports agents, Sub-Zero will be maintained to work with the latest PMS version.
|
||||
|
||||
---
|
||||
|
||||
**[Kitana is now required to have a UI](https://github.com/pannal/Kitana)**
|
||||
@@ -27,7 +24,7 @@ This addon will not be developed any further. It still works and arguably is sti
|
||||
|
||||
## Helping development
|
||||
|
||||
If you like this, buy me a beer: <br>[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G9VKR2B8PMNKG) <br>or become a Patreon starting at **1 $ / month** <br><a href="https://www.patreon.com/subzero_plex" target="_blank"><img src="https://images.squarespace-cdn.com/content/v1/56c7831bf8baf3ae17ce9259/1561826418792-IJOMFASTOR6CW80N5W0Z/ke17ZwdGBToddI8pDm48kEycOuEejcFJqqLot0yQ4VVZw-zPPgdn4jUwVcJE1ZvWEtT5uBSRWt4vQZAgTJucoTqqXjS3CfNDSuuf31e0tVFrfGYmOrPFZFUXr1UxW4wA0PgPOjs31URy2JeWL9DdYhur-lC0WofN0YB1wFg-ZW0/footer-patreon.png?format=500w" height="54" /></a>
|
||||
If you like this, buy me a beer: <br>[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G9VKR2B8PMNKG) <br>or become a Patreon starting at **1 $ / month** <br><a href="https://www.patreon.com/subzero_plex" target="_blank"><img src="https://i0.wp.com/tablecakes.com/wp-content/uploads/2018/08/become-a-patron-button.png" height="54" /></a> <br>or use the OpenSubtitles Sub-Zero affiliate link to become VIP <br>**10€/year, ad-free subs, 1000 subs/day, no-cache *VIP* server**<br><a href="http://v.ht/osvip" target="_blank"><img src="https://static.opensubtitles.org/gfx/logo.gif" height="50" /></a>
|
||||
|
||||
If you register with an anti-captcha service and you decide to use [Anti-Captcha.com](http://getcaptchasolution.com/kkvviom7nh), you can use [this affiliate link](http://getcaptchasolution.com/kkvviom7nh) to help development.
|
||||
|
||||
@@ -89,30 +86,51 @@ In addition to that Sub-Zero also fixes problems introduced by the subtitle crea
|
||||
Ever had broken music icons in a subtitle? Nordic characters like `Å` which turned into `Ã¥`? Not anymore.
|
||||
|
||||
## Installation
|
||||
[Please go to the wiki](https://github.com/pannal/Sub-Zero.bundle/wiki/Installation).
|
||||
Simply go to the Plex Plugins in your Plex Media Server, search for Sub-Zero and install it.
|
||||
For further help or manual installation, [please go to the wiki](https://github.com/pannal/Sub-Zero.bundle/wiki/Installation).
|
||||
|
||||
## Big thanks to the beta/i18n testing team (in no particular order)!
|
||||
the.vbm, mmgoodnow, Vertig0ne, thliu78, tattoomees, ostman, count_confucius, eherberg, tywilliams_88, Swanny, Jippo, Joost1991 / joost, Marik, Jon, AmbyDK, Clay, Abenlog, michael, smikwily, shoghicp, Zuikkis, Isilorn, Jacob K, Ninjouz, chopeta, fvb, Uthman, Claus Møller, Semi Doludizgin, Rafael, sugarman402, Morpheus1333, Yamil.llanos, Notorius28
|
||||
|
||||
## Changelog
|
||||
|
||||
2.6.5.3277
|
||||
- core: fix enabled library/agents detection (Plex removed certain features)
|
||||
fix Plex agent integration; Plex Inc removed certain attributes; SZ is now limited to thetvdb, thetvdbdvdorder, hama, themoviedb, imdb)
|
||||
2.6.5.3247
|
||||
|
||||
|
||||
|
||||
2.6.5.3268
|
||||
subscene, addic7ed
|
||||
- either of those providers might impose a reCAPTCHA verification. In order to use those providers, please create an account at an AntiCaptcha service ([anti-captcha.com](http://getcaptchasolution.com/kkvviom7nh) or [deathbycaptcha.com](http://deathbycaptcha.com)), add funds, then supply your credentials/apikey in the configuration
|
||||
|
||||
Changelog
|
||||
- clarify README
|
||||
- core: fix custom folder handling; #761
|
||||
- core: providers: screwzira: move to ktuvit and wizdom
|
||||
- core: add option to not download subtitles for certain audio languages existing; and/or no audio stream; fix #756
|
||||
- core: delay item refreshes after refresh call (default: 5 seconds; exposed in advanced settings)
|
||||
- menu: allow extraction of embedded subtitles for whole tv shows
|
||||
core: fix for tv.plex.agents.movie not populating its media types
|
||||
core: tasks: findBetterSubtitles: increase minimum score for better subtitles for movies with extracted embedded subs from 82 to 112
|
||||
|
||||
|
||||
2.6.5.3241
|
||||
|
||||
subscene, addic7ed
|
||||
- either of those providers might impose a reCAPTCHA verification. In order to use those providers, please create an account at an AntiCaptcha service ([anti-captcha.com](http://getcaptchasolution.com/kkvviom7nh) or [deathbycaptcha.com](http://deathbycaptcha.com)), add funds, then supply your credentials/apikey in the configuration
|
||||
|
||||
Changelog
|
||||
- core: add support for new Plex Movie agent
|
||||
- core: remove py3 compat breaking unnecessary change
|
||||
- core: skip drawing tags for SRT
|
||||
- core: advanced_settings: refiners: drone: add custom pem_file support; fixes #735
|
||||
- providers: core: set DownloadLimitPerDayExceeded timeout to 4 hours (was one day);
|
||||
- providers: addic7ed: limit downloads per day; add vip setting
|
||||
- providers: addic7ed: properly compare last_dl, add last_reset tracking info to log #723
|
||||
- providers: addic7ed: properly implement limits
|
||||
- submod: HI: remove more music tags
|
||||
- submod: common CM_punctuation_space2: detect AND don't try changing domain/url/host when fixing punctuation
|
||||
|
||||
|
||||
2.6.5.3223
|
||||
|
||||
subscene, addic7ed
|
||||
- either of those providers might impose a reCAPTCHA verification. In order to use those providers, please create an account at an AntiCaptcha service ([anti-captcha.com](http://getcaptchasolution.com/kkvviom7nh) or [deathbycaptcha.com](http://deathbycaptcha.com)), add funds, then supply your credentials/apikey in the configuration
|
||||
|
||||
Changelog
|
||||
- core: scoring: reorder subtitles based on second non-hash-score if main hash score is the same; morpheus65535/bazarr#821
|
||||
- providers: bsplayer: verify hash; clean up
|
||||
|
||||
|
||||
[older changes](CHANGELOG.md)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user