Initial commit
This commit is contained in:
parent
c7fa107fe7
commit
244b0f9b62
852
tddp-client/pyDes.py
Normal file
852
tddp-client/pyDes.py
Normal file
@ -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 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.
|
||||||
|
"""
|
||||||
|
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)
|
207
tddp-client/tddp-client.py
Normal file
207
tddp-client/tddp-client.py
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# TP-Link Device Debug Protocol (TDDP) v2 Client
|
||||||
|
# Based on https://www.google.com/patents/CN102096654A?cl=en
|
||||||
|
#
|
||||||
|
# HIGHLY EXPERIMENTAL and untested!
|
||||||
|
# The protocol is available on all kinds of TP-Link devices such as routers, cameras, smart plugs etc.
|
||||||
|
#
|
||||||
|
# by Lubomir Stroetmann
|
||||||
|
# Copyright 2016 softScheck GmbH
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
from pyDes import *
|
||||||
|
import hashlib
|
||||||
|
import argparse
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import binascii
|
||||||
|
|
||||||
|
version = 0.1
|
||||||
|
|
||||||
|
# Default username and password
|
||||||
|
username = "admin"
|
||||||
|
password = "admin"
|
||||||
|
|
||||||
|
# Check if IP is valid
|
||||||
|
def validIP(ip):
|
||||||
|
try:
|
||||||
|
socket.inet_pton(socket.AF_INET, ip)
|
||||||
|
except socket.error:
|
||||||
|
parser.error("Invalid IP Address.")
|
||||||
|
return ip
|
||||||
|
|
||||||
|
# Parse commandline arguments
|
||||||
|
parser = argparse.ArgumentParser(description="Experimental TP-Link TDDPv2 Client v" + str(version))
|
||||||
|
parser.add_argument("-v", "--verbose", help="Verbose mode", action="store_true")
|
||||||
|
parser.add_argument("-t", "--target", metavar="<ip>", required=True, help="Target IP Address", type=validIP)
|
||||||
|
parser.add_argument("-u", "--username", metavar="<username>", help="Username (default: admin)")
|
||||||
|
parser.add_argument("-p", "--password", metavar="<password>", help="Password (default: admin)")
|
||||||
|
|
||||||
|
|
||||||
|
# We only allow three different CMD_SPE_OPR commands that read out values from the target device
|
||||||
|
# Other values can potentially be write commands and modify the device
|
||||||
|
commands = {'test1':"0A", 'test2':"12", 'test3':"14"}
|
||||||
|
parser.add_argument("-c", "--command", metavar="<command>", help="Preset command to send. Choices are: "+", ".join(commands), choices=commands)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Set Target IP, username and password to calculate DES decryption key for data and command to execute
|
||||||
|
ip = args.target
|
||||||
|
if args.username:
|
||||||
|
username = args.username
|
||||||
|
if args.password:
|
||||||
|
password = args.password
|
||||||
|
if args.command is None:
|
||||||
|
cmd = "12"
|
||||||
|
else:
|
||||||
|
cmd = commands[args.command]
|
||||||
|
|
||||||
|
# TDDP runs on UDP Port 1040
|
||||||
|
# Response is sent to UDP Port 61000
|
||||||
|
port_send = 1040
|
||||||
|
port_receive = 61000
|
||||||
|
|
||||||
|
|
||||||
|
# TDDP DES Key = MD5 of username and password concatenated
|
||||||
|
# Key is first 8 bytes only
|
||||||
|
tddp_key = hashlib.md5(username + password).hexdigest()[:16]
|
||||||
|
if args.verbose:
|
||||||
|
print "TDDP Key:\t", tddp_key, "(" + username + password + ")"
|
||||||
|
|
||||||
|
## TDDP Header
|
||||||
|
# 0 1 2 3
|
||||||
|
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
# | Ver | Type | Code | ReplyInfo |
|
||||||
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
# | PktLength |
|
||||||
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
# | PktID | SubType | Reserve |
|
||||||
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
# | MD5 Digest[0-3] |
|
||||||
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
# | MD5 Digest[4-7] |
|
||||||
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
# | MD5 Digest[8-11] |
|
||||||
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
# | MD5 Digest[12-15] |
|
||||||
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
## TDDP Protocol Version
|
||||||
|
tddp_ver = "02"
|
||||||
|
|
||||||
|
## Packet Type
|
||||||
|
# 0x01 SET_USR_CFG - set configuration information
|
||||||
|
# 0x02 GET_SYS_INF - get configuration information
|
||||||
|
# 0x03 CMD_SPE_OPR - special configuration commands
|
||||||
|
# 0x04 HEART_BEAT - the heartbeat package
|
||||||
|
tddp_type = "03"
|
||||||
|
|
||||||
|
## Code Request Type
|
||||||
|
# 0x01 TDDP_REQUEST
|
||||||
|
# 0x02 TDDP_REPLY
|
||||||
|
tddp_code = "01"
|
||||||
|
|
||||||
|
## Reply Info Status
|
||||||
|
# 0x00 REPLY_OK
|
||||||
|
# 0x02 ?
|
||||||
|
# 0x03 ?
|
||||||
|
# 0x09 REPLY_ERROR
|
||||||
|
# 0xFF ?
|
||||||
|
tddp_reply = "00"
|
||||||
|
|
||||||
|
## Packet Length (not including header)
|
||||||
|
# 4 bytes
|
||||||
|
tddp_length = "00000000"
|
||||||
|
|
||||||
|
## Packet ID
|
||||||
|
# 2 bytes
|
||||||
|
# supposed to be incremented +1 for each packet
|
||||||
|
tddp_id = "0001"
|
||||||
|
|
||||||
|
# Subtype for CMD_SPE_OPR (Special Operations Command)
|
||||||
|
# Set to 0x00 for SET_USR_CFG and GET_SYS_INF
|
||||||
|
#
|
||||||
|
# Subtypes described in patent application, hex value unknown:
|
||||||
|
# CMD_SYS_OPR Router system operation, including: init, save, reboot, reset, clr dos
|
||||||
|
# CMD_AUTO_TEST MAC for writing operation, the user replies CMD_SYS_INIT broadcast packet
|
||||||
|
# CMD_CONFIG_MAC Factory settings MAC operations
|
||||||
|
# CMD_CANCEL_TEST Cancel automatic test, stop receiving broadcast packets
|
||||||
|
# CMD_GET_PROD_ID Get product ID
|
||||||
|
# CMD_SYS_INIT Initialize a router
|
||||||
|
# CMD_CONFIG_PIN Router PIN code
|
||||||
|
#
|
||||||
|
# Subtypes that seem to work for a HS-110 Smart Plug:
|
||||||
|
# 0x0A returns "ABCD0110"
|
||||||
|
# 0x12 returns the deviceID
|
||||||
|
# 0x14 returns the hwID
|
||||||
|
# 0x06 changes MAC
|
||||||
|
# 0x13 changes deviceID
|
||||||
|
# 0x15 changes deviceID
|
||||||
|
tddp_subtype = cmd
|
||||||
|
|
||||||
|
# Reserved
|
||||||
|
tddp_reserved = "00"
|
||||||
|
|
||||||
|
# Digest 0-15 (32char/128bit/16byte)
|
||||||
|
# MD5 digest of entire packet
|
||||||
|
# Set to 0 initially for building the digest, then overwrite with result
|
||||||
|
tddp_digest = "%0.32X" % 00
|
||||||
|
|
||||||
|
# TDDP Data
|
||||||
|
# Always pad with 0x00 to a length divisible by 8
|
||||||
|
# We're not sending any data since we're only sending read commands
|
||||||
|
tddp_data = ""
|
||||||
|
|
||||||
|
# Recalculate length if sending data
|
||||||
|
tddp_length = len(tddp_data)/2
|
||||||
|
tddp_length = "%0.8X" % tddp_length
|
||||||
|
|
||||||
|
## Encrypt data with key
|
||||||
|
key = des(binascii.unhexlify(tddp_key), ECB)
|
||||||
|
data = key.encrypt(binascii.unhexlify(tddp_data))
|
||||||
|
|
||||||
|
## Assemble packet
|
||||||
|
tddp_packet = "".join([tddp_ver, tddp_type, tddp_code, tddp_reply, tddp_length, tddp_id, tddp_subtype, tddp_reserved, tddp_digest, data.encode('hex')])
|
||||||
|
|
||||||
|
# Calculate MD5
|
||||||
|
tddp_digest = hashlib.md5(binascii.unhexlify(tddp_packet)).hexdigest()
|
||||||
|
tddp_packet = tddp_packet[:24] + tddp_digest + tddp_packet[56:]
|
||||||
|
|
||||||
|
# Send a request
|
||||||
|
sock_send = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock_send.sendto(binascii.unhexlify(tddp_packet), (ip, port_send))
|
||||||
|
if args.verbose:
|
||||||
|
print "Raw Request:\t", tddp_packet
|
||||||
|
t = tddp_packet
|
||||||
|
print "Request Data:\tVersion", t[0:2], "Type", t[2:4], "Status", t[6:8], "Length", t[8:16], "ID", t[16:20], "Subtype", t[20:22]
|
||||||
|
sock_send.close()
|
||||||
|
|
||||||
|
# Receive the reply
|
||||||
|
sock_receive = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock_receive.bind(('', port_receive))
|
||||||
|
response, addr = sock_receive.recvfrom(1024)
|
||||||
|
r = response.encode('hex')
|
||||||
|
if args.verbose:
|
||||||
|
print "Raw Reply:\t", r
|
||||||
|
sock_receive.close()
|
||||||
|
print "Reply Data:\tVersion", r[0:2], "Type", r[2:4], "Status", r[6:8], "Length", r[8:16], "ID", r[16:20], "Subtype", r[20:22]
|
||||||
|
|
||||||
|
# Take payload and decrypt using key
|
||||||
|
recv_data = r[56:]
|
||||||
|
if recv_data:
|
||||||
|
print "Decrypted:\t" + key.decrypt(binascii.unhexlify(recv_data))
|
||||||
|
|
186
tplink-smarthome-commands.txt
Normal file
186
tplink-smarthome-commands.txt
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
TP-Link Smart Home Protocol Command List
|
||||||
|
========================================
|
||||||
|
(for TP-Link HS100 and HS110)
|
||||||
|
|
||||||
|
System Commands
|
||||||
|
========================================
|
||||||
|
Get System Info (Software & Hardware Versions, MAC, deviceID, hwID etc.)
|
||||||
|
{"system":{"get_sysinfo":null}}
|
||||||
|
|
||||||
|
Reboot
|
||||||
|
{"system":{"reboot":{"delay":1}}}
|
||||||
|
|
||||||
|
Reset (To Factory Settings)
|
||||||
|
{"system":{"reset":{"delay":1}}}
|
||||||
|
|
||||||
|
Turn On
|
||||||
|
{"system":{"set_relay_state":{"state":1}}}
|
||||||
|
|
||||||
|
Turn Off
|
||||||
|
{"system":{"set_relay_state":{"state":0}}}
|
||||||
|
|
||||||
|
Turn Off Device LED (Night mode)
|
||||||
|
{"system":{"set_led_off":{"off":1}}}
|
||||||
|
|
||||||
|
Set Device Alias
|
||||||
|
{"system":{"set_dev_alias":{"alias":"supercool plug"}}}
|
||||||
|
|
||||||
|
Set MAC Address
|
||||||
|
{"system":{"set_mac_addr":{"mac":"50-C7-BF-01-02-03"}}}
|
||||||
|
|
||||||
|
Set Device ID
|
||||||
|
{"system":{"set_device_id":{"deviceId":"0123456789ABCDEF0123456789ABCDEF01234567"}}}
|
||||||
|
|
||||||
|
Set Hardware ID
|
||||||
|
{"system":{"set_hw_id":{"hwId":"0123456789ABCDEF0123456789ABCDEF"}}}
|
||||||
|
|
||||||
|
Set Location
|
||||||
|
{"system":{"set_dev_location":{"longitude":6.9582814,"latitude":50.9412784}}}
|
||||||
|
|
||||||
|
Perform uBoot Bootloader Check
|
||||||
|
{"system":{"test_check_uboot":null}}
|
||||||
|
|
||||||
|
Get Device Icon
|
||||||
|
{"system":{"get_dev_icon":null}}
|
||||||
|
|
||||||
|
Set Device Icon
|
||||||
|
{"system":{"set_dev_icon":{"icon":"xxxx","hash":"ABCD"}}}
|
||||||
|
|
||||||
|
Set Test Mode (command only accepted coming from IP 192.168.1.100)
|
||||||
|
{"system":{"set_test_mode":{"enable":1}}}
|
||||||
|
|
||||||
|
Download Firmware from URL
|
||||||
|
{"system":{"download_firmware":{"url":"http://...."}}}
|
||||||
|
|
||||||
|
Get Download State
|
||||||
|
{"system":{"get_download_state":{}}}
|
||||||
|
|
||||||
|
Flash Downloaded Firmware
|
||||||
|
{"system":{"flash_firmware":{}}}
|
||||||
|
|
||||||
|
Check Config
|
||||||
|
{"system":{"check_new_config":null}}
|
||||||
|
|
||||||
|
|
||||||
|
WLAN Commands
|
||||||
|
========================================
|
||||||
|
Scan for list of available APs
|
||||||
|
{"netif":{"get_scaninfo":{"refresh":1}}}
|
||||||
|
|
||||||
|
Connect to AP with given SSID and Password
|
||||||
|
{"netif":{"set_stainfo":{"ssid":"WiFi","password":"secret","key_type":3}}}
|
||||||
|
|
||||||
|
|
||||||
|
Cloud Commands
|
||||||
|
========================================
|
||||||
|
Get Cloud Info (Server, Username, Connection Status)
|
||||||
|
{"cnCloud":{"get_info":null}}
|
||||||
|
|
||||||
|
Get Firmware List from Cloud Server
|
||||||
|
{"cnCloud":{"get_intl_fw_list":{}}}
|
||||||
|
|
||||||
|
Set Server URL
|
||||||
|
{"cnCloud":{"set_server_url":{"server":"devs.tplinkcloud.com"}}}
|
||||||
|
|
||||||
|
Connect with Cloud username & Password
|
||||||
|
{"cnCloud":{"bind":{"username":"your@email.com", "password":"secret"}}}
|
||||||
|
|
||||||
|
Unregister Device from Cloud Account
|
||||||
|
{"cnCloud":{"unbind":null}}
|
||||||
|
|
||||||
|
|
||||||
|
Time Commands
|
||||||
|
========================================
|
||||||
|
Get Time
|
||||||
|
{"time":{"get_time":null}}
|
||||||
|
|
||||||
|
Get Timezone
|
||||||
|
{"time":{"get_timezone":null}}
|
||||||
|
|
||||||
|
Set Timezone
|
||||||
|
{"time":{"set_timezone":{"year":2016,"month":1,"mday":1,"hour":10,"min":10,"sec":10,"index":42}}}
|
||||||
|
|
||||||
|
|
||||||
|
EMeter Energy Usage Statistics Commands
|
||||||
|
(for TP-Link HS110)
|
||||||
|
========================================
|
||||||
|
Get Realtime Current and Voltage Reading
|
||||||
|
{"emeter":{"get_realtime":{}}}
|
||||||
|
|
||||||
|
Get EMeter VGain and IGain Settings
|
||||||
|
{"emeter":{"get_vgain_igain":{}}}
|
||||||
|
|
||||||
|
Set EMeter VGain and Igain
|
||||||
|
{"emeter":{"set_vgain_igain":{"vgain":13462,"igain":16835}}}
|
||||||
|
|
||||||
|
Start EMeter Calibration
|
||||||
|
{"emeter":{"start_calibration":{"vtarget":13462,"itarget":16835}}}
|
||||||
|
|
||||||
|
Get Daily Statistic for given Month
|
||||||
|
{"emeter":{"get_daystat":{"month":1,"year":2016}}}
|
||||||
|
|
||||||
|
Get Montly Statistic for given Year
|
||||||
|
{"emeter":{""get_monthstat":{"year":2016}}}
|
||||||
|
|
||||||
|
Erase All EMeter Statistics
|
||||||
|
{"emeter":{"erase_emeter_stat":null}}
|
||||||
|
|
||||||
|
|
||||||
|
Schedule Commands
|
||||||
|
(action to perform regularly on given weekdays)
|
||||||
|
========================================
|
||||||
|
Get Next Scheduled Action
|
||||||
|
{"schedule":{"get_next_action":null}}
|
||||||
|
|
||||||
|
Get Schedule Rules List
|
||||||
|
{"schedule":{"get_rules":null}}
|
||||||
|
|
||||||
|
Add New Schedule Rule
|
||||||
|
{"schedule":{"add_rule":{"stime_opt":0,"wday":[1,0,0,1,1,0,0],"smin":1014,"enable":1,"repeat":1,"etime_opt":-1,"name":"lights on","eact":-1,"month":0,"sact":1,"year":0,"longitude":0,"day":0,"force":0,"latitude":0,"emin":0},"set_overall_enable":{"enable":1}}}
|
||||||
|
|
||||||
|
Edit Schedule Rule with given ID
|
||||||
|
{"schedule":{"edit_rule":{"stime_opt":0,"wday":[1,0,0,1,1,0,0],"smin":1014,"enable":1,"repeat":1,"etime_opt":-1,"id":"4B44932DFC09780B554A740BC1798CBC","name":"lights on","eact":-1,"month":0,"sact":1,"year":0,"longitude":0,"day":0,"force":0,"latitude":0,"emin":0}}}
|
||||||
|
|
||||||
|
Delete Schedule Rule with given ID
|
||||||
|
{"schedule":{"delete_rule":{"id":"4B44932DFC09780B554A740BC1798CBC"}}}
|
||||||
|
|
||||||
|
Delete All Schedule Rules and Erase Statistics
|
||||||
|
{"schedule":{"delete_all_rules":null,"erase_runtime_stat":null}}
|
||||||
|
|
||||||
|
|
||||||
|
Countdown Rule Commands
|
||||||
|
(action to perform after number of seconds)
|
||||||
|
========================================
|
||||||
|
Get Rule (only one allowed)
|
||||||
|
{"count_down":{"get_rules":null}}
|
||||||
|
|
||||||
|
Add New Countdown Rule
|
||||||
|
{"count_down":{"add_rule":{"enable":1,"delay":1800,"act":1,"name":"turn on"}}}
|
||||||
|
|
||||||
|
Edit Countdown Rule with given ID
|
||||||
|
{"count_down":{"edit_rule":{"enable":1,"id":"7C90311A1CD3227F25C6001D88F7FC13","delay":1800,"act":1,"name":"turn on"}}}
|
||||||
|
|
||||||
|
Delete Countdown Rule with given ID
|
||||||
|
{"count_down":{"delete_rule":{"id":"7C90311A1CD3227F25C6001D88F7FC13"}}}
|
||||||
|
|
||||||
|
Delete All Coundown Rules
|
||||||
|
{"count_down":{"delete_all_rules":null}}
|
||||||
|
|
||||||
|
|
||||||
|
Anti-Theft Rule Commands (aka Away Mode)
|
||||||
|
(period of time during which device will be randomly turned on and off to deter thieves)
|
||||||
|
========================================
|
||||||
|
Get Anti-Theft Rules List
|
||||||
|
{"anti_theft":{"get_rules":null}}
|
||||||
|
|
||||||
|
Add New Anti-Theft Rule
|
||||||
|
{"anti_theft":{"add_rule":{"stime_opt":0,"wday":[0,0,0,1,0,1,0],"smin":987,"enable":1,"frequency":5,"repeat":1,"etime_opt":0,"duration":2,"name":"test","lastfor":1,"month":0,"year":0,"longitude":0,"day":0,"latitude":0,"force":0,"emin":1047},"set_overall_enable":1}}
|
||||||
|
|
||||||
|
Edit Anti-Theft Rule with given ID
|
||||||
|
{"anti_theft":{"edit_rule":{"stime_opt":0,"wday":[0,0,0,1,0,1,0],"smin":987,"enable":1,"frequency":5,"repeat":1,"etime_opt":0,"id":"E36B1F4466B135C1FD481F0B4BFC9C30","duration":2,"name":"test","lastfor":1,"month":0,"year":0,"longitude":0,"day":0,"latitude":0,"force":0,"emin":1047},"set_overall_enable":1}}
|
||||||
|
|
||||||
|
Delete Anti-Theft Rule with given ID
|
||||||
|
{"anti_theft":{"delete_rule":{"id":"E36B1F4466B135C1FD481F0B4BFC9C30"}}}
|
||||||
|
|
||||||
|
Delete All Anti-Theft Rules
|
||||||
|
"anti_theft":{"delete_all_rules":null}}
|
88
tplink-smarthome.lua
Normal file
88
tplink-smarthome.lua
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
-- TP-Link Smart Home Protocol (Port 9999) Wireshark Dissector
|
||||||
|
-- For decrypting local network traffic between TP-Link
|
||||||
|
-- Smart Home Devices and the Kasa Smart Home App
|
||||||
|
--
|
||||||
|
-- Install under:
|
||||||
|
-- (Windows) %APPDATA%\Wireshark\plugins\
|
||||||
|
-- (Linux, Mac) $HOME/.wireshark/plugins
|
||||||
|
--
|
||||||
|
-- by Lubomir Stroetmann
|
||||||
|
-- Copyright 2016 softScheck GmbH
|
||||||
|
--
|
||||||
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
-- you may not use this file except in compliance with the License.
|
||||||
|
-- You may obtain a copy of the License at
|
||||||
|
--
|
||||||
|
-- http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
--
|
||||||
|
-- Unless required by applicable law or agreed to in writing, software
|
||||||
|
-- distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
-- See the License for the specific language governing permissions and
|
||||||
|
-- limitations under the License.
|
||||||
|
--
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Create TP-Link Smart Home protocol and its fields
|
||||||
|
p_tplink = Proto ("TPLink-SmartHome","TP-Link Smart Home Protocol")
|
||||||
|
|
||||||
|
-- Dissector function
|
||||||
|
function p_tplink.dissector (buf, pkt, root)
|
||||||
|
-- Validate packet length
|
||||||
|
if buf:len() == 0 then return end
|
||||||
|
pkt.cols.protocol = p_tplink.name
|
||||||
|
|
||||||
|
-- Decode data
|
||||||
|
local ascii = ""
|
||||||
|
local hex = ""
|
||||||
|
|
||||||
|
-- Skip first 4 bytes (header)
|
||||||
|
start = 4
|
||||||
|
endPosition = buf:len() - 1
|
||||||
|
|
||||||
|
-- Decryption key is -85 (256-85=171)
|
||||||
|
local key = 171
|
||||||
|
|
||||||
|
-- Decrypt Autokey XOR
|
||||||
|
-- Save results as ascii and hex
|
||||||
|
for index = start, endPosition do
|
||||||
|
local c = buf(index,1):uint()
|
||||||
|
-- XOR first byte with key
|
||||||
|
d = bit32.bxor(c,key)
|
||||||
|
-- Use byte as next key
|
||||||
|
key = c
|
||||||
|
|
||||||
|
hex = hex .. string.format("%x", d)
|
||||||
|
-- Convert to printable characters
|
||||||
|
if d >= 0x20 and d <= 0x7E then
|
||||||
|
ascii = ascii .. string.format("%c", d)
|
||||||
|
else
|
||||||
|
-- Use dot for non-printable bytes
|
||||||
|
ascii = ascii .. "."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Create subtree
|
||||||
|
subtree = root:add(p_tplink, buf(0))
|
||||||
|
|
||||||
|
-- Add data to subtree
|
||||||
|
subtree:add(ascii)
|
||||||
|
-- Description of payload
|
||||||
|
subtree:append_text(" (decrypted)")
|
||||||
|
|
||||||
|
-- Call JSON Dissector with decrypted data
|
||||||
|
local b = ByteArray.new(hex)
|
||||||
|
local tvb = ByteArray.tvb(b, "JSON TVB")
|
||||||
|
Dissector.get("json"):call(tvb, pkt, root)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Initialization routine
|
||||||
|
function p_tplink.init()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Register a chained dissector for port 9999
|
||||||
|
local tcp_dissector_table = DissectorTable.get("tcp.port")
|
||||||
|
dissector = tcp_dissector_table:get_dissector(9999)
|
||||||
|
tcp_dissector_table:add(9999, p_tplink)
|
99
tplink-smartplug.py
Normal file
99
tplink-smartplug.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# TP-Link Wi-Fi Smart Plug Protocol Client
|
||||||
|
# For use with TP-Link HS-100 or HS-110
|
||||||
|
#
|
||||||
|
# by Lubomir Stroetmann
|
||||||
|
# Copyright 2016 softScheck GmbH
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
import socket
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
version = 0.1
|
||||||
|
|
||||||
|
# Check if IP is valid
|
||||||
|
def validIP(ip):
|
||||||
|
try:
|
||||||
|
socket.inet_pton(socket.AF_INET, ip)
|
||||||
|
except socket.error:
|
||||||
|
parser.error("Invalid IP Address.")
|
||||||
|
return ip
|
||||||
|
|
||||||
|
# Predefined Smart Plug Commands
|
||||||
|
# For a full list of commands, consult tplink_commands.txt
|
||||||
|
commands = {'info' : '{"system":{"get_sysinfo":{}}}',
|
||||||
|
'on' : '{"system":{"set_relay_state":{"state":1}}}',
|
||||||
|
'off' : '{"system":{"set_relay_state":{"state":0}}}',
|
||||||
|
'cloudinfo': '{"cnCloud":{"get_info":{}}}',
|
||||||
|
'wlanscan' : '{"netif":{"get_scaninfo":{"refresh":0}}}',
|
||||||
|
'time' : '{"time":{"get_time":{}}}',
|
||||||
|
'schedule' : '{"schedule":{"get_rules":{}}}',
|
||||||
|
'countdown': '{"count_down":{"get_rules":{}}}',
|
||||||
|
'antitheft': '{"anti_theft":{"get_rules":{}}}',
|
||||||
|
'reboot' : '{"system":{"reboot":{"delay":1}}}',
|
||||||
|
'reset' : '{"system":{"reset":{"delay":1}}}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Encryption and Decryption of TP-Link Smart Home Protocol
|
||||||
|
# XOR Autokey Cipher with starting key = 171
|
||||||
|
def encrypt(string):
|
||||||
|
key = 171
|
||||||
|
result = "\0\0\0\0"
|
||||||
|
for i in string:
|
||||||
|
a = key ^ ord(i)
|
||||||
|
key = a
|
||||||
|
result += chr(a)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def decrypt(string):
|
||||||
|
key = 171
|
||||||
|
result = ""
|
||||||
|
for i in string:
|
||||||
|
a = key ^ ord(i)
|
||||||
|
key = ord(i)
|
||||||
|
result += chr(a)
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Parse commandline arguments
|
||||||
|
parser = argparse.ArgumentParser(description="TP-Link Wi-Fi Smart Plug Client v" + str(version))
|
||||||
|
parser.add_argument("-t", "--target", metavar="<ip>", required=True, help="Target IP Address", type=validIP)
|
||||||
|
group = parser.add_mutually_exclusive_group(required=True)
|
||||||
|
group.add_argument("-c", "--command", metavar="<command>", help="Preset command to send. Choices are: "+", ".join(commands), choices=commands)
|
||||||
|
group.add_argument("-j", "--json", metavar="<JSON string>", help="Full JSON string of command to send")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Set target IP, port and command to send
|
||||||
|
ip = args.target
|
||||||
|
port = 9999
|
||||||
|
if args.command is None:
|
||||||
|
cmd = args.json
|
||||||
|
else:
|
||||||
|
cmd = commands[args.command]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Send command and receive reply
|
||||||
|
try:
|
||||||
|
sock_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock_tcp.connect((ip, port))
|
||||||
|
sock_tcp.send(encrypt(cmd))
|
||||||
|
data = sock_tcp.recv(2048)
|
||||||
|
sock_tcp.close()
|
||||||
|
|
||||||
|
print "Sent: ", cmd
|
||||||
|
print "Received: ", decrypt(data[4:])
|
||||||
|
except socket.error:
|
||||||
|
quit("Cound not connect to host " + ip + ":" + str(port))
|
Loading…
Reference in New Issue
Block a user