From 76ad9bf5756f028f2453433509f562dae05d731c Mon Sep 17 00:00:00 2001 From: speyrefitte Date: Wed, 9 Jul 2014 18:08:37 +0200 Subject: [PATCH] Add user inputs for RDP stack --- rdpy/protocol/rdp/lic.py | 75 ++-- rdpy/protocol/rdp/pdu.py | 190 +++++++-- rdpy/protocol/rdp/rdp.py | 125 +++++- rdpy/protocol/rfb/pyDes.py | 852 +++++++++++++++++++++++++++++++++++++ rdpy/protocol/rfb/rfb.py | 12 +- rdpy/ui/qt4.py | 194 +++++---- 6 files changed, 1272 insertions(+), 176 deletions(-) create mode 100644 rdpy/protocol/rfb/pyDes.py diff --git a/rdpy/protocol/rdp/lic.py b/rdpy/protocol/rdp/lic.py index ef049b9..3925320 100644 --- a/rdpy/protocol/rdp/lic.py +++ b/rdpy/protocol/rdp/lic.py @@ -1,15 +1,33 @@ -''' -@author: sylvain -''' -from rdpy.network.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof -from rdpy.network.const import ConstAttributes, TypeAttributes +# +# Copyright (c) 2014 Sylvain Peyrefitte +# +# This file is part of rdpy. +# +# rdpy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +""" +RDP extended license +@see: http://msdn.microsoft.com/en-us/library/cc241880.aspx +""" + +from rdpy.network.type import CompositeType, UInt8, UInt16Le, UInt32Le, String, sizeof -@ConstAttributes -@TypeAttributes(UInt8) class MessageType(object): - ''' + """ License packet message type - ''' + """ LICENSE_REQUEST = 0x01 PLATFORM_CHALLENGE = 0x02 NEW_LICENSE = 0x03 @@ -19,12 +37,11 @@ class MessageType(object): PLATFORM_CHALLENGE_RESPONSE = 0x15 ERROR_ALERT = 0xFF -@ConstAttributes -@TypeAttributes(UInt32Le) + class ErrorCode(object): - ''' - license error message code - ''' + """ + License error message code + """ ERR_INVALID_SERVER_CERTIFICATE = 0x00000001 ERR_NO_LICENSE = 0x00000002 ERR_INVALID_SCOPE = 0x00000004 @@ -34,22 +51,20 @@ class ErrorCode(object): ERR_INVALID_PRODUCTID = 0x0000000B ERR_INVALID_MESSAGE_LEN = 0x0000000C ERR_INVALID_MAC = 0x00000003 - -@ConstAttributes -@TypeAttributes(UInt32Le) + class StateTransition(object): - ''' - automata state transition - ''' + """ + Automata state transition + """ ST_TOTAL_ABORT = 0x00000001 ST_NO_TRANSITION = 0x00000002 ST_RESET_PHASE_TO_START = 0x00000003 ST_RESEND_LAST_MESSAGE = 0x00000004 class LicenceBinaryBlob(CompositeType): - ''' - blob use by license manager to echange security data - ''' + """ + Blob use by license manager to echange security data + """ def __init__(self): CompositeType.__init__(self) self.wBlobType = UInt16Le() @@ -57,9 +72,9 @@ class LicenceBinaryBlob(CompositeType): self.blobData = String(readLen = self.wBlobLen, conditional = lambda:self.wBlobLen.value > 0) class LicensingErrorMessage(CompositeType): - ''' - license error message - ''' + """ + License error message + """ def __init__(self, conditional = lambda:True): CompositeType.__init__(self, conditional = conditional) self.dwErrorCode = UInt32Le() @@ -67,14 +82,14 @@ class LicensingErrorMessage(CompositeType): self.blob = LicenceBinaryBlob() class LicPacket(CompositeType): - ''' - a license packet - ''' + """ + A license packet + """ def __init__(self): CompositeType.__init__(self) #preambule self.bMsgtype = UInt8() self.flag = UInt8() self.wMsgSize = UInt16Le(lambda: sizeof(self)) - self.errorMessage = LicensingErrorMessage(conditional = lambda:self.bMsgtype == MessageType.ERROR_ALERT) + self.errorMessage = LicensingErrorMessage(conditional = lambda:self.bMsgtype.value == MessageType.ERROR_ALERT) \ No newline at end of file diff --git a/rdpy/protocol/rdp/pdu.py b/rdpy/protocol/rdp/pdu.py index fd0d3bd..a93fc67 100644 --- a/rdpy/protocol/rdp/pdu.py +++ b/rdpy/protocol/rdp/pdu.py @@ -23,9 +23,9 @@ Implement the main graphic layer In this layer are managed all mains bitmap update orders end user inputs """ -from rdpy.network.layer import LayerAutomata +from rdpy.network.layer import LayerAutomata, LayerMode from rdpy.network.type import CompositeType, UniString, String, UInt8, UInt16Le, UInt32Le, sizeof, ArrayType, FactoryType -from rdpy.network.error import InvalidExpectedDataException, ErrorReportedFromPeer +from rdpy.network.error import InvalidExpectedDataException, ErrorReportedFromPeer, CallPureVirtualFuntion, InvalidType import gcc, lic, caps @@ -189,7 +189,7 @@ class UpdateType(object): class InputMessageType(object): """ - Use in slowpath input PDU + Use in slow-path input PDU @see: http://msdn.microsoft.com/en-us/library/cc240583.aspx """ INPUT_EVENT_SYNC = 0x0000 @@ -199,6 +199,30 @@ class InputMessageType(object): INPUT_EVENT_MOUSE = 0x8001 INPUT_EVENT_MOUSEX = 0x8002 +class PointerFlag(object): + """ + Use in Pointer event + @see: http://msdn.microsoft.com/en-us/library/cc240586.aspx + """ + PTRFLAGS_HWHEEL = 0x0400 + PTRFLAGS_WHEEL = 0x0200 + PTRFLAGS_WHEEL_NEGATIVE = 0x0100 + WheelRotationMask = 0x01FF + PTRFLAGS_MOVE = 0x0800 + PTRFLAGS_DOWN = 0x8000 + PTRFLAGS_BUTTON1 = 0x1000 + PTRFLAGS_BUTTON2 = 0x2000 + PTRFLAGS_BUTTON3 = 0x4000 + +class KeyboardFlag(object): + """ + Use in scancode key event + @see: http://msdn.microsoft.com/en-us/library/cc240584.aspx + """ + KBDFLAGS_EXTENDED = 0x0100 + KBDFLAGS_DOWN = 0x4000 + KBDFLAGS_RELEASE = 0x8000 + class ErrorInfo(object): """ Error code use in Error info PDU @@ -643,9 +667,9 @@ class UpdateDataPDU(CompositeType): for example @see: http://msdn.microsoft.com/en-us/library/cc240608.aspx """ - def __init__(self, updateType = UInt16Le(), updateData = None): + def __init__(self, updateType = 0, updateData = None): CompositeType.__init__(self) - self.updateType = updateType + self.updateType = UInt16Le(updateType) def UpdateDataFactory(): if self.updateType.value == UpdateType.UPDATETYPE_BITMAP: @@ -665,29 +689,8 @@ class BitmapUpdateDataPDU(CompositeType): """ def __init__(self): CompositeType.__init__(self) - self.numberRectangles = UInt16Le() + self.numberRectangles = UInt16Le(lambda:len(self.rectangles._array)) self.rectangles = ArrayType(BitmapData, readLen = self.numberRectangles) - -class ClientInputEventPDU(CompositeType): - """ - PDU use to send client inputs in slow path mode - @see: http://msdn.microsoft.com/en-us/library/cc746160.aspx - """ - def __init__(self, userId = 0, shareId = 0): - CompositeType.__init__(self) - self.shareDataHeader = ShareDataHeader(lambda:sizeof(self), PDUType2.PDUTYPE2_INPUT, userId, shareId) - self.numEvents = UInt16Le() - self.pad2Octets = UInt16Le() - -class SlowPathInputData(CompositeType): - """ - PDU use in slowpath sending client inputs - @see: http://msdn.microsoft.com/en-us/library/cc240583.aspx - """ - def __init__(self): - CompositeType.__init__(self) - self.eventTime = UInt32Le() - self.messageType = UInt16Le() class BitmapCompressedDataHeader(CompositeType): """ @@ -720,17 +723,114 @@ class BitmapData(CompositeType): self.bitmapLength = UInt16Le() self.bitmapComprHdr = BitmapCompressedDataHeader(conditional = lambda:(not (self.flags.value | BitmapFlag.NO_BITMAP_COMPRESSION_HDR))) self.bitmapDataStream = String(readLen = UInt16Le(lambda:(self.bitmapLength.value if (self.flags.value | BitmapFlag.NO_BITMAP_COMPRESSION_HDR) else self.bitmapComprHdr.cbCompMainBodySize.value))) + +class ClientInputEventPDU(CompositeType): + """ + PDU use to send client inputs in slow path mode + @see: http://msdn.microsoft.com/en-us/library/cc746160.aspx + """ + def __init__(self, userId = 0, shareId = 0): + CompositeType.__init__(self) + self.shareDataHeader = ShareDataHeader(lambda:sizeof(self), PDUType2.PDUTYPE2_INPUT, userId, shareId) + self.numEvents = UInt16Le(lambda:len(self.slowPathInputEvents._array)) + self.pad2Octets = UInt16Le() + self.slowPathInputEvents = ArrayType(SlowPathInputEvent, readLen = self.numEvents) +class SlowPathInputEvent(CompositeType): + """ + PDU use in slow-path sending client inputs + @see: http://msdn.microsoft.com/en-us/library/cc240583.aspx + """ + def __init__(self, messageData = None): + CompositeType.__init__(self) + self.eventTime = UInt32Le() + + def MessageTypeFactory(event): + """ + retrieve message type from event type + """ + if isinstance(event, PointerEvent): + return InputMessageType.INPUT_EVENT_MOUSE + elif isinstance(event, ScancodeKeyEvent): + return InputMessageType.INPUT_EVENT_SCANCODE + elif isinstance(event, UnicodeKeyEvent): + return InputMessageType.INPUT_EVENT_UNICODE + else: + return None + + self.messageType = UInt16Le(lambda:MessageTypeFactory(self.slowPathInputData._value)) + + def SlowPathInputDataFactory(): + if self.messageType.value == InputMessageType.INPUT_EVENT_MOUSE: + return PointerEvent() + + if messageData is None: + messageData = SlowPathInputDataFactory + + self.slowPathInputData = FactoryType(messageData) + +class PointerEvent(CompositeType): + """ + Event use to communicate mouse position + @see: http://msdn.microsoft.com/en-us/library/cc240586.aspx + """ + def __init__(self): + CompositeType.__init__(self) + self.pointerFlags = UInt16Le() + self.xPos = UInt16Le() + self.yPos = UInt16Le() + +class ScancodeKeyEvent(CompositeType): + """ + Event use to communicate keyboard informations + @see: http://msdn.microsoft.com/en-us/library/cc240584.aspx + """ + def __init__(self): + CompositeType.__init__(self) + self.keyboardFlags = UInt16Le() + self.keyCode = UInt16Le() + self.pad2Octets = UInt16Le() + +class UnicodeKeyEvent(CompositeType): + """ + Event use to communicate keyboard informations + @see: http://msdn.microsoft.com/en-us/library/cc240585.aspx + """ + def __init__(self): + CompositeType.__init__(self) + self.keyboardFlags = UInt16Le() + self.unicode = UInt16Le() + self.pad2Octets = UInt16Le() + + +class PDUClientListener(object): + """ + Interface for PDU client automata listener + """ + def recvBitmapUpdateDataPDU(self, rectangles): + """ + call when a bitmap data is received from update PDU + @param rectangles: [pdu.BitmapData] struct + """ + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "onUpdate", "PDUClientListener")) + class PDU(LayerAutomata): """ Global channel for mcs that handle session identification user, licensing management, and capabilities exchange """ - def __init__(self, mode, controller): + def __init__(self, listener): """ - @param mode: LayerMode - @param controller: controller use to inform orders + @param listener: listener use to inform orders """ + mode = None + if isinstance(listener, PDUClientListener): + mode = LayerMode.CLIENT + #set client listener + self._clientListener = listener + else: + raise InvalidType("PDU Layer expect PDUClientListener as listener") + LayerAutomata.__init__(self, mode, None) #logon info send from client to server self._info = RDPInfo(extendedInfoConditional = lambda:self._transport.getGCCServerSettings().core.rdpVersion.value == gcc.Version.RDP_VERSION_5_PLUS) @@ -768,8 +868,7 @@ class PDU(LayerAutomata): #share id between client and server self._shareId = 0 - #rdp controller - self._controller = controller + self._isConnected = False def connect(self): """ @@ -809,9 +908,9 @@ class PDU(LayerAutomata): data.readType(validClientPdu) if not validClientPdu.errorMessage._is_readed: - raise InvalidExpectedDataException("Waiting valid client pdu : rdpy doesn't support licensing neg") + raise InvalidExpectedDataException("Waiting valid client PDU : rdpy doesn't support licensing nego") - if not (validClientPdu.errorMessage.dwErrorCode == lic.ErrorCode.STATUS_VALID_CLIENT and validClientPdu.errorMessage.dwStateTransition == lic.StateTransition.ST_NO_TRANSITION): + if not (validClientPdu.errorMessage.dwErrorCode.value == lic.ErrorCode.STATUS_VALID_CLIENT and validClientPdu.errorMessage.dwStateTransition.value == lic.StateTransition.ST_NO_TRANSITION): raise InvalidExpectedDataException("Server refuse licensing negotiation") self.setNextState(self.recvDemandActivePDU) @@ -865,7 +964,7 @@ class PDU(LayerAutomata): def recvServerControlCooperatePDU(self, data): """ - Receive control cooperate pdu from server + Receive control cooperate PDU from server @param data: Stream from transport layer """ dataPDU = self.readDataPDU(data) @@ -891,9 +990,9 @@ class PDU(LayerAutomata): dataPDU = self.readDataPDU(data) if dataPDU.shareDataHeader.pduType2.value != PDUType2.PDUTYPE2_FONTMAP: raise InvalidExpectedDataException("Error in PDU layer automata : expected fontMapPDU") - print "client is now connected" - if not self._presentation is None: - self._presentation.connect() + + #here i'm connected + self._isConnected = True self.setNextState(self.recvDataPDU) def recvDataPDU(self, data): @@ -903,7 +1002,7 @@ class PDU(LayerAutomata): """ dataPDU = self.readDataPDU(data) if dataPDU.shareDataHeader.pduType2.value == PDUType2.PDUTYPE2_UPDATE and dataPDU.pduData._value.updateType.value == UpdateType.UPDATETYPE_BITMAP: - self._controller.recvBitmapUpdateDataPDU(dataPDU.pduData._value.updateData._value) + self._clientListener.recvBitmapUpdateDataPDU(dataPDU.pduData._value.updateData._value.rectangles._array) def sendConfirmActivePDU(self): @@ -958,14 +1057,23 @@ class PDU(LayerAutomata): controlRequestPDU = DataPDU(PDUType2.PDUTYPE2_CONTROL, ControlDataPDU(Action.CTRLACTION_REQUEST_CONTROL), self._transport.getUserId(), self._shareId) self._transport.send(controlRequestPDU) - #send persistent list pdu I don't know why this packet is rejected maybe beacause we made a 0 size bitmapcache capability + #send persistent list pdu I don't know why this packet is rejected maybe because we made a 0 size bitmapcache capability #persistentListPDU = PersistentListPDU(self._transport.getUserId(), self._shareId) #persistentListPDU.bitMask = UInt16Le(PersistentKeyListFlag.PERSIST_FIRST_PDU | PersistentKeyListFlag.PERSIST_LAST_PDU) #self._transport.send(persistentListPDU) - #deprecated font list pdu fontListPDU = DataPDU(PDUType2.PDUTYPE2_FONTLIST, FontListDataPDU(), self._transport.getUserId(), self._shareId) self._transport.send(fontListPDU) self.setNextState(self.recvServerSynchronizePDU) + + def sendInputEvents(self, pointerEvents): + """ + send client input events + @param pointerEvents: list of pointer events + """ + pdu = ClientInputEventPDU(self._transport.getUserId(), self._shareId) + pdu.slowPathInputEvents._array = [SlowPathInputEvent(x) for x in pointerEvents] + self._transport.send(pdu) + \ No newline at end of file diff --git a/rdpy/protocol/rdp/rdp.py b/rdpy/protocol/rdp/rdp.py index 68fb609..82910f6 100644 --- a/rdpy/protocol/rdp/rdp.py +++ b/rdpy/protocol/rdp/rdp.py @@ -3,46 +3,126 @@ ''' from twisted.internet import protocol from rdpy.network.layer import LayerMode -from rdpy.network.error import CallPureVirtualFuntion +from rdpy.network.error import CallPureVirtualFuntion, InvalidValue import tpkt, tpdu, mcs, pdu -class RDPController(object): +class RDPController(pdu.PDUClientListener): """ use to decode and dispatch to observer PDU messages and orders """ def __init__(self, mode): - ''' - @param mode: mode of generate layer by controller + """ + @param mode: mode of generate layer by listener @param observer: observer - ''' + """ #list of observer self._clientObserver = [] #transport layer - self._pduLayer = pdu.PDU(mode, self) + self._pduLayer = pdu.PDU(self) def getPDULayer(self): """ - @return: pdu layer use by controller + @return: PDU layer use by controller """ return self._pduLayer def addClientObserver(self, observer): - ''' - add observer to rdp protocol + """ + add observer to RDP protocol @param observer: new observer to add - ''' + """ self._clientObserver.append(observer) - observer._controller = self + observer._clientListener = self - def recvBitmapUpdateDataPDU(self, bitmapUpdateData): - ''' - call when a bitmap data is received from update pdu - @param bitmapData: pdu.BitmapData struct - ''' + def recvBitmapUpdateDataPDU(self, rectangles): + """ + call when a bitmap data is received from update PDU + @param rectangles: [pdu.BitmapData] struct + """ for observer in self._clientObserver: #for each rectangle in update PDU - for rectangle in bitmapUpdateData.rectangles._array: + for rectangle in rectangles: observer.onBitmapUpdate(rectangle.destLeft.value, rectangle.destTop.value, rectangle.destRight.value, rectangle.destBottom.value, rectangle.width.value, rectangle.height.value, rectangle.bitsPerPixel.value, rectangle.flags.value & pdu.BitmapFlag.BITMAP_COMPRESSION, rectangle.bitmapDataStream.value) + + def sendPointerEvent(self, x, y, button, isPressed): + """ + send pointer events + @param x: x position of pointer + @param y: y position of pointer + @param button: 1 or 2 or 3 + @param isPressed: true if button is pressed or false if it's released + """ + if not self._pduLayer._isConnected: + return + + try: + event = pdu.PointerEvent() + if isPressed: + event.pointerFlags.value |= pdu.PointerFlag.PTRFLAGS_DOWN + + if button == 1: + event.pointerFlags.value |= pdu.PointerFlag.PTRFLAGS_BUTTON1 + elif button == 2: + event.pointerFlags.value |= pdu.PointerFlag.PTRFLAGS_BUTTON2 + elif button == 3: + event.pointerFlags.value |= pdu.PointerFlag.PTRFLAGS_BUTTON3 + else: + event.pointerFlags.value |= pdu.PointerFlag.PTRFLAGS_MOVE + + #position + event.xPos.value = x + event.yPos.value = y + + #send proper event + self._pduLayer.sendInputEvents([event]) + + except InvalidValue: + print "try send pointer event with incorrect position" + + def sendKeyEventScancode(self, code, isPressed): + """ + Send a scan code to RDP stack + @param code: scan code + @param isPressed: True if key is pressed and false if it's released + """ + if not self._pduLayer._isConnected: + return + + try: + event = pdu.ScancodeKeyEvent() + event.keyCode.value = code + if isPressed: + event.keyboardFlags.value |= pdu.KeyboardFlag.KBDFLAGS_DOWN + else: + event.keyboardFlags.value |= pdu.KeyboardFlag.KBDFLAGS_RELEASE + + #send event + self._pduLayer.sendInputEvents([event]) + + except InvalidValue: + print "try send bad key event" + + def sendKeyEventUnicode(self, code, isPressed): + """ + Send a scan code to RDP stack + @param code: unicode + @param isPressed: True if key is pressed and false if it's released + """ + if not self._pduLayer._isConnected: + return + + try: + event = pdu.UnicodeKeyEvent() + event.unicode.value = code + if not isPressed: + event.keyboardFlags.value |= pdu.KeyboardFlag.KBDFLAGS_RELEASE + + #send event + self._pduLayer.sendInputEvents([event]) + + except InvalidValue: + print "try send bad key event" + class ClientFactory(protocol.Factory): """ @@ -82,18 +162,18 @@ class ServerFactory(protocol.Factory): Factory of Serrve RDP protocol ''' def __init__(self, privateKeyFileName, certificateFileName): - ''' + """ @param privateKeyFileName: file contain server private key @param certficiateFileName: file that contain publi key - ''' + """ self._privateKeyFileName = privateKeyFileName self._certificateFileName = certificateFileName def buildProtocol(self, addr): - ''' + """ Function call from twisted and build rdp protocol stack @param addr: destination address - ''' + """ pduLayer = pdu.PDU(LayerMode.SERVER) #pduLayer.getController().addObserver(self.buildObserver()) return tpkt.TPKT(tpdu.createServer(mcs.createServer(pduLayer), self._privateKeyFileName, self._certificateFileName)); @@ -110,11 +190,12 @@ class RDPClientObserver(object): ''' def __init__(self, controller): """ - @param controller: RDP controller use to interact with protocol + @param listener: RDP listener use to interact with protocol """ self._controller = controller self._controller.addClientObserver(self) + def onBitmapUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): ''' notify bitmap update diff --git a/rdpy/protocol/rfb/pyDes.py b/rdpy/protocol/rfb/pyDes.py new file mode 100644 index 0000000..0f307e7 --- /dev/null +++ b/rdpy/protocol/rfb/pyDes.py @@ -0,0 +1,852 @@ +############################################################################# +# Documentation # +############################################################################# + +# Author: Todd Whiteman +# Date: 16th March, 2009 +# Verion: 2.0.0 +# License: Public Domain - free to do as you wish +# 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, utilising 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/decrpt operations done with this instance. +padmode -> Optional argument, set the padding mode (PAD_NORMAL or PAD_PKCS5) + to use during all encrypt/decrpt 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/decrpt operations done with this instance. + padmode -> Optional argument, set the padding mode (PAD_NORMAL or + PAD_PKCS5) to use during all encrypt/decrpt 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/decrpt operations done with this instance. + padmode -> Optional argument, set the padding mode (PAD_NORMAL or + PAD_PKCS5) to use during all encrypt/decrpt 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) \ No newline at end of file diff --git a/rdpy/protocol/rfb/rfb.py b/rdpy/protocol/rfb/rfb.py index 62d467c..14097c1 100644 --- a/rdpy/protocol/rfb/rfb.py +++ b/rdpy/protocol/rfb/rfb.py @@ -171,11 +171,13 @@ class RFB(RawLayer): def __init__(self, listener): """ @param mode: LayerMode client or server - @param controller: controller use to inform new orders + @param listener: listener use to inform new orders """ mode = None if isinstance(listener, RFBClientListener): mode = LayerMode.CLIENT + #set client listener + self._clientListener = listener else: raise InvalidType("RFB Layer expect RFBClientListener as listener") @@ -199,8 +201,6 @@ class RFB(RawLayer): self._nbRect = 0 #current rectangle header self._currentRect = Rectangle() - #client or server adaptor - self._listener = listener #ready to send events self._ready = False @@ -390,7 +390,7 @@ class RFB(RawLayer): Read body of rectangle update @param data: Stream that contains well formed packet """ - self._listener.recvRectangle(self._currentRect, self._pixelFormat, data.getvalue()) + self._clientListener.recvRectangle(self._currentRect, self._pixelFormat, data.getvalue()) self._nbRect = self._nbRect - 1 #if there is another rect to read @@ -489,7 +489,7 @@ class RFBController(RFBClientListener): @param observer: new observer """ self._clientObservers.append(observer) - observer._controller = self + observer._clientListener = self def recvRectangle(self, rectangle, pixelFormat, data): """ @@ -579,7 +579,7 @@ class RFBClientObserver(object): """ Send a key event @param isPressed: state of key - @param key: ascii code of key + @param key: ASCII code of key """ self._controller.sendKeyEvent(isPressed, key) diff --git a/rdpy/ui/qt4.py b/rdpy/ui/qt4.py index 1ec8148..6048654 100644 --- a/rdpy/ui/qt4.py +++ b/rdpy/ui/qt4.py @@ -26,58 +26,58 @@ QRemoteDesktop is a widget use for render in rdpy from PyQt4 import QtGui, QtCore from rdpy.protocol.rfb.rfb import RFBClientObserver from rdpy.protocol.rdp.rdp import RDPClientObserver +from rdpy.network.error import CallPureVirtualFuntion import rle class QAdaptor(object): - ''' - adaptor model with link between protocol - and qt widget - ''' - - def sendMouseEvent(self, e): - ''' - interface to send mouse event - to protocol stack - @param e: qEvent - ''' - pass - - def sendKeyEvent(self, e): - ''' - interface to send key event - to protocol stack - @param e: qEvent - ''' - pass + """ + Adaptor model with link between protocol + And Qt widget + """ + def sendMouseEvent(self, e, isPressed): + """ + Interface to send mouse event to protocol stack + @param e: QMouseEvent + @param isPressed: event come from press or release action + """ + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendMouseEvent", "QAdaptor")) + + def sendKeyEvent(self, e, isPressed): + """ + Interface to send key event to protocol stack + @param e: QEvent + @param isPressed: event come from press or release action + """ + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "sendKeyEvent", "QAdaptor")) def getWidget(self): - ''' + """ @return: widget use for render - ''' - pass - + """ + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getWidget", "QAdaptor")) + class RFBClientQt(RFBClientObserver, QAdaptor): - ''' + """ QAdaptor for specific RFB protocol stack is to an RFB observer - ''' + """ def __init__(self, controller): """ - @param controller: controller for obser + @param controller: controller for observer """ RFBClientObserver.__init__(self, controller) self._widget = QRemoteDesktop(self) def getWidget(self): - ''' + """ @return: widget use for render - ''' + """ return self._widget def onUpdate(self, width, height, x, y, pixelFormat, encoding, data): - ''' - implement RFBClientObserver interface + """ + Implement RFBClientObserver interface @param width: width of new image @param height: height of new image @param x: x position of new image @@ -85,7 +85,7 @@ class RFBClientQt(RFBClientObserver, QAdaptor): @param pixelFormat: pixefFormat structure in rfb.message.PixelFormat @param encoding: encoding type rfb.message.Encoding @param data: image data in accordance with pixel format and encoding - ''' + """ imageFormat = None if pixelFormat.BitsPerPixel.value == 32 and pixelFormat.RedShift.value == 16: imageFormat = QtGui.QImage.Format_RGB32 @@ -96,11 +96,12 @@ class RFBClientQt(RFBClientObserver, QAdaptor): image = QtGui.QImage(data, width, height, imageFormat) self._widget.notifyImage(x, y, image) - def sendMouseEvent(self, e): - ''' - convert qt mouse event to RFB mouse event + def sendMouseEvent(self, e, isPressed): + """ + Convert Qt mouse event to RFB mouse event @param e: qMouseEvent - ''' + @param isPressed: event come from press or release action + """ button = e.button() buttonNumber = 0 if button == QtCore.Qt.LeftButton: @@ -111,18 +112,19 @@ class RFBClientQt(RFBClientObserver, QAdaptor): buttonNumber = 3 self.mouseEvent(buttonNumber, e.pos().x(), e.pos().y()) - def sendKeyEvent(self, e): - ''' - convert Qt key press event to RFB press event + def sendKeyEvent(self, e, isPressed): + """ + Convert Qt key press event to RFB press event @param e: qKeyEvent - ''' - self.keyEvent(True, e.nativeVirtualKey()) + @param isPressed: event come from press or release action + """ + self.keyEvent(isPressed, e.nativeVirtualKey()) class RDPClientQt(RDPClientObserver, QAdaptor): - ''' + """ Adaptor for RDP client - ''' + """ def __init__(self, controller): """ @param controller: RDP controller @@ -131,14 +133,38 @@ class RDPClientQt(RDPClientObserver, QAdaptor): self._widget = QRemoteDesktop(self) def getWidget(self): - ''' + """ @return: widget use for render - ''' + """ return self._widget + def sendMouseEvent(self, e, isPressed): + """ + Convert Qt mouse event to RDP mouse event + @param e: qMouseEvent + @param isPressed: event come from press(true) or release(false) action + """ + button = e.button() + buttonNumber = 0 + if button == QtCore.Qt.LeftButton: + buttonNumber = 1 + elif button == QtCore.Qt.MidButton: + buttonNumber = 2 + elif button == QtCore.Qt.RightButton: + buttonNumber = 3 + self._controller.sendPointerEvent(e.pos().x(), e.pos().y(), buttonNumber, isPressed) + + def sendKeyEvent(self, e, isPressed): + """ + Convert Qt key press event to RFB press event + @param e: QKeyEvent + @param isPressed: event come from press or release action + """ + self._controller.sendKeyEventUnicode(ord(unicode(e.text().toUtf8(), encoding="UTF-8")), isPressed) + def onBitmapUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): - ''' - notify bitmap update + """ + Notify bitmap update @param destLeft: xmin position @param destTop: ymin position @param destRight: xmax position because RDP can send bitmap with padding @@ -148,7 +174,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor): @param bitsPerPixel: number of bit per pixel @param isCompress: use RLE compression @param data: bitmap data - ''' + """ image = None if bitsPerPixel == 16: if isCompress: @@ -178,13 +204,13 @@ class RDPClientQt(RDPClientObserver, QAdaptor): class QRemoteDesktop(QtGui.QWidget): - ''' - qt display widget - ''' + """ + Qt display widget + """ def __init__(self, adaptor): - ''' - constructor - ''' + """ + @param adaptor: QAdaptor + """ super(QRemoteDesktop, self).__init__() #adaptor use to send self._adaptor = adaptor @@ -198,22 +224,22 @@ class QRemoteDesktop(QtGui.QWidget): self.setMouseTracking(True) def notifyImage(self, x, y, qimage): - ''' - function call from Qadaptor + """ + Function call from QAdaptor @param x: x position of new image @param y: y position of new image - @param qimage: new qimage - ''' + @param qimage: new QImage + """ #save in refresh list (order is important) self._refresh.append({"x" : x, "y" : y, "image" : qimage}) #force update self.update() def paintEvent(self, e): - ''' - call when QT renderer engine estimate that is needed - @param e: qevent - ''' + """ + Call when Qt renderer engine estimate that is needed + @param e: QEvent + """ #if there is no refresh -> done if self._refresh == []: return @@ -228,25 +254,39 @@ class QRemoteDesktop(QtGui.QWidget): self._lastReceive = [] def mouseMoveEvent(self, event): - ''' - call when mouse move - @param event: qMouseEvent - ''' + """ + Call when mouse move + @param event: QMouseEvent + """ if self._adaptor is None: print "No adaptor to send mouse move event" - self._adaptor.sendMouseEvent(event) + self._adaptor.sendMouseEvent(event, False) def mousePressEvent(self, event): - ''' - call when button mouse is pressed - @param event: qMouseEvent - ''' - self._adaptor.sendMouseEvent(event) + """ + Call when button mouse is pressed + @param event: QMouseEvent + """ + self._adaptor.sendMouseEvent(event, True) + + def mouseReleaseEvent(self, event): + """ + Call when button mouse is released + @param event: QMouseEvent + """ + self._adaptor.sendMouseEvent(event, False) def keyPressEvent(self, event): - ''' - call when button key is pressed - @param event: qKeyEvent - ''' - self._adaptor.sendKeyEvent(event) + """ + Call when button key is pressed + @param event: QKeyEvent + """ + self._adaptor.sendKeyEvent(event, True) + + def keyReleaseEvent(self, event): + """ + Call when button key is released + @param event: QKeyEvent + """ + self._adaptor.sendKeyEvent(event, False) \ No newline at end of file