├── LICENSE.md ├── README.md └── pydes.py /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Robin David 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pydes 2 | ===== 3 | 4 | Basic but pure DES implementation in Python 5 | I have written it for fun because nothing else. 6 | 7 | 8 | How it works ? 9 | -------------- 10 | 11 | Everything is made within a class called "des". This class can be instanciated once and used to cipher and decipher multiple datas. 12 | It also support padding using the PKCS5 specification. (So the data is padding even if it is multiple of 8 to be sure that the last byte il be padding data). 13 | The generation of all the keys used is made in the method generatekeys and substitute apply the SBOX permutation. 14 | The main method is run which is called by both encrypt and decrypt but in a different mode. This method do basically all the stuff, it loop 15 | throught all the blocks and for each do the 16th rounds. 16 | 17 | Be careful: This module implement DES in ECB mode, so you can't make it weaker. I didn't made it to be strong but for fun. 18 | 19 | How to use it ? 20 | --------------- 21 | 22 | I have not done any interface to take argument in command line so this module can't be used as a script. (feel free to modify it). 23 | To use it from python shell or in another module do: 24 | 25 | from pydes import des 26 | 27 | key = "secret_k" 28 | text= "Hello wo" 29 | d = des() 30 | ciphered = d.encrypt(key,text) 31 | plain = d.decrypt(key,ciphered) 32 | print "Ciphered: %r" % ciphered 33 | print "Deciphered: ", plain 34 | 35 | Note: In this exemple no padding is specified so you have to provide a text which is multiple of 8 bytes. The key is cut to 8 bytes if longer. 36 | 37 | To use padding: 38 | 39 | from pydes import des 40 | 41 | key = "secret_k" 42 | text= "Hello world !" 43 | d = des() 44 | ciphered = d.encrypt(key,text,padding=True) #Or just True in third arg 45 | plain = d.decrypt(key,ciphered,padding=True) 46 | print "Ciphered: %r" % ciphered 47 | print "Deciphered: ", plain 48 | -------------------------------------------------------------------------------- /pydes.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf8 -*- 2 | 3 | #Initial permut matrix for the datas 4 | PI = [58, 50, 42, 34, 26, 18, 10, 2, 5 | 60, 52, 44, 36, 28, 20, 12, 4, 6 | 62, 54, 46, 38, 30, 22, 14, 6, 7 | 64, 56, 48, 40, 32, 24, 16, 8, 8 | 57, 49, 41, 33, 25, 17, 9, 1, 9 | 59, 51, 43, 35, 27, 19, 11, 3, 10 | 61, 53, 45, 37, 29, 21, 13, 5, 11 | 63, 55, 47, 39, 31, 23, 15, 7] 12 | 13 | #Initial permut made on the key 14 | CP_1 = [57, 49, 41, 33, 25, 17, 9, 15 | 1, 58, 50, 42, 34, 26, 18, 16 | 10, 2, 59, 51, 43, 35, 27, 17 | 19, 11, 3, 60, 52, 44, 36, 18 | 63, 55, 47, 39, 31, 23, 15, 19 | 7, 62, 54, 46, 38, 30, 22, 20 | 14, 6, 61, 53, 45, 37, 29, 21 | 21, 13, 5, 28, 20, 12, 4] 22 | 23 | #Permut applied on shifted key to get Ki+1 24 | CP_2 = [14, 17, 11, 24, 1, 5, 3, 28, 25 | 15, 6, 21, 10, 23, 19, 12, 4, 26 | 26, 8, 16, 7, 27, 20, 13, 2, 27 | 41, 52, 31, 37, 47, 55, 30, 40, 28 | 51, 45, 33, 48, 44, 49, 39, 56, 29 | 34, 53, 46, 42, 50, 36, 29, 32] 30 | 31 | #Expand matrix to get a 48bits matrix of datas to apply the xor with Ki 32 | E = [32, 1, 2, 3, 4, 5, 33 | 4, 5, 6, 7, 8, 9, 34 | 8, 9, 10, 11, 12, 13, 35 | 12, 13, 14, 15, 16, 17, 36 | 16, 17, 18, 19, 20, 21, 37 | 20, 21, 22, 23, 24, 25, 38 | 24, 25, 26, 27, 28, 29, 39 | 28, 29, 30, 31, 32, 1] 40 | 41 | #SBOX 42 | S_BOX = [ 43 | 44 | [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7], 45 | [0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8], 46 | [4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0], 47 | [15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13], 48 | ], 49 | 50 | [[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10], 51 | [3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5], 52 | [0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15], 53 | [13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9], 54 | ], 55 | 56 | [[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8], 57 | [13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1], 58 | [13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7], 59 | [1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12], 60 | ], 61 | 62 | [[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15], 63 | [13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9], 64 | [10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4], 65 | [3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14], 66 | ], 67 | 68 | [[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9], 69 | [14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6], 70 | [4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14], 71 | [11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3], 72 | ], 73 | 74 | [[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11], 75 | [10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8], 76 | [9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6], 77 | [4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13], 78 | ], 79 | 80 | [[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1], 81 | [13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6], 82 | [1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2], 83 | [6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12], 84 | ], 85 | 86 | [[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7], 87 | [1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2], 88 | [7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8], 89 | [2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11], 90 | ] 91 | ] 92 | 93 | #Permut made after each SBox substitution for each round 94 | P = [16, 7, 20, 21, 29, 12, 28, 17, 95 | 1, 15, 23, 26, 5, 18, 31, 10, 96 | 2, 8, 24, 14, 32, 27, 3, 9, 97 | 19, 13, 30, 6, 22, 11, 4, 25] 98 | 99 | #Final permut for datas after the 16 rounds 100 | PI_1 = [40, 8, 48, 16, 56, 24, 64, 32, 101 | 39, 7, 47, 15, 55, 23, 63, 31, 102 | 38, 6, 46, 14, 54, 22, 62, 30, 103 | 37, 5, 45, 13, 53, 21, 61, 29, 104 | 36, 4, 44, 12, 52, 20, 60, 28, 105 | 35, 3, 43, 11, 51, 19, 59, 27, 106 | 34, 2, 42, 10, 50, 18, 58, 26, 107 | 33, 1, 41, 9, 49, 17, 57, 25] 108 | 109 | #Matrix that determine the shift for each round of keys 110 | SHIFT = [1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1] 111 | 112 | def string_to_bit_array(text):#Convert a string into a list of bits 113 | array = list() 114 | for char in text: 115 | binval = binvalue(char, 8)#Get the char value on one byte 116 | array.extend([int(x) for x in list(binval)]) #Add the bits to the final list 117 | return array 118 | 119 | def bit_array_to_string(array): #Recreate the string from the bit array 120 | res = ''.join([chr(int(y,2)) for y in [''.join([str(x) for x in _bytes]) for _bytes in nsplit(array,8)]]) 121 | return res 122 | 123 | def binvalue(val, bitsize): #Return the binary value as a string of the given size 124 | binval = bin(val)[2:] if isinstance(val, int) else bin(ord(val))[2:] 125 | if len(binval) > bitsize: 126 | raise "binary value larger than the expected size" 127 | while len(binval) < bitsize: 128 | binval = "0"+binval #Add as many 0 as needed to get the wanted size 129 | return binval 130 | 131 | def nsplit(s, n):#Split a list into sublists of size "n" 132 | return [s[k:k+n] for k in range(0, len(s), n)] 133 | 134 | ENCRYPT=1 135 | DECRYPT=0 136 | 137 | class des(): 138 | def __init__(self): 139 | self.password = None 140 | self.text = None 141 | self.keys = list() 142 | 143 | def run(self, key, text, action=ENCRYPT, padding=False): 144 | if len(key) < 8: 145 | raise "Key Should be 8 bytes long" 146 | elif len(key) > 8: 147 | key = key[:8] #If key size is above 8bytes, cut to be 8bytes long 148 | 149 | self.password = key 150 | self.text = text 151 | 152 | if padding and action==ENCRYPT: 153 | self.addPadding() 154 | elif len(self.text) % 8 != 0:#If not padding specified data size must be multiple of 8 bytes 155 | raise "Data size should be multiple of 8" 156 | 157 | self.generatekeys() #Generate all the keys 158 | text_blocks = nsplit(self.text, 8) #Split the text in blocks of 8 bytes so 64 bits 159 | result = list() 160 | for block in text_blocks:#Loop over all the blocks of data 161 | block = string_to_bit_array(block)#Convert the block in bit array 162 | block = self.permut(block,PI)#Apply the initial permutation 163 | g, d = nsplit(block, 32) #g(LEFT), d(RIGHT) 164 | tmp = None 165 | for i in range(16): #Do the 16 rounds 166 | d_e = self.expand(d, E) #Expand d to match Ki size (48bits) 167 | if action == ENCRYPT: 168 | tmp = self.xor(self.keys[i], d_e)#If encrypt use Ki 169 | else: 170 | tmp = self.xor(self.keys[15-i], d_e)#If decrypt start by the last key 171 | tmp = self.substitute(tmp) #Method that will apply the SBOXes 172 | tmp = self.permut(tmp, P) 173 | tmp = self.xor(g, tmp) 174 | g = d 175 | d = tmp 176 | result += self.permut(d+g, PI_1) #Do the last permut and append the result to result 177 | final_res = bit_array_to_string(result) 178 | if padding and action==DECRYPT: 179 | return self.removePadding(final_res) #Remove the padding if decrypt and padding is true 180 | else: 181 | return final_res #Return the final string of data ciphered/deciphered 182 | 183 | def substitute(self, d_e):#Substitute bytes using SBOX 184 | subblocks = nsplit(d_e, 6)#Split bit array into sublist of 6 bits 185 | result = list() 186 | for i in range(len(subblocks)): #For all the sublists 187 | block = subblocks[i] 188 | row = int(str(block[0])+str(block[5]),2)#Get the row with the first and last bit 189 | column = int(''.join([str(x) for x in block[1:][:-1]]),2) #Column is the 2,3,4,5th bits 190 | val = S_BOX[i][row][column] #Take the value in the SBOX appropriated for the round (i) 191 | bin = binvalue(val, 4)#Convert the value to binary 192 | result += [int(x) for x in bin]#And append it to the resulting list 193 | return result 194 | 195 | def permut(self, block, table):#Permut the given block using the given table (so generic method) 196 | return [block[x-1] for x in table] 197 | 198 | def expand(self, block, table):#Do the exact same thing than permut but for more clarity has been renamed 199 | return [block[x-1] for x in table] 200 | 201 | def xor(self, t1, t2):#Apply a xor and return the resulting list 202 | return [x^y for x,y in zip(t1,t2)] 203 | 204 | def generatekeys(self):#Algorithm that generates all the keys 205 | self.keys = [] 206 | key = string_to_bit_array(self.password) 207 | key = self.permut(key, CP_1) #Apply the initial permut on the key 208 | g, d = nsplit(key, 28) #Split it in to (g->LEFT),(d->RIGHT) 209 | for i in range(16):#Apply the 16 rounds 210 | g, d = self.shift(g, d, SHIFT[i]) #Apply the shift associated with the round (not always 1) 211 | tmp = g + d #Merge them 212 | self.keys.append(self.permut(tmp, CP_2)) #Apply the permut to get the Ki 213 | 214 | def shift(self, g, d, n): #Shift a list of the given value 215 | return g[n:] + g[:n], d[n:] + d[:n] 216 | 217 | def addPadding(self):#Add padding to the datas using PKCS5 spec. 218 | pad_len = 8 - (len(self.text) % 8) 219 | self.text += pad_len * chr(pad_len) 220 | 221 | def removePadding(self, data):#Remove the padding of the plain text (it assume there is padding) 222 | pad_len = ord(data[-1]) 223 | return data[:-pad_len] 224 | 225 | def encrypt(self, key, text, padding=False): 226 | return self.run(key, text, ENCRYPT, padding) 227 | 228 | def decrypt(self, key, text, padding=False): 229 | return self.run(key, text, DECRYPT, padding) 230 | 231 | if __name__ == '__main__': 232 | key = "secret_k" 233 | text= "Hello wo" 234 | d = des() 235 | r = d.encrypt(key,text) 236 | r2 = d.decrypt(key,r) 237 | print("Ciphered: %r" % r) 238 | print("Deciphered: ", r2) --------------------------------------------------------------------------------