├── .gitignore ├── README.md ├── aes.py └── vault.py /.gitignore: -------------------------------------------------------------------------------- 1 | file.db 2 | *.pyc 3 | *.swp 4 | *.swo 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | What is Vault? 2 | ============== 3 | 4 | Vault is a minimalistic password manager for hackers. It takes out all the flair and complexity that comes with traditional password managers and condenses the idea into a simple commandline 5 | interface. Vault can be deployed anywhere Python is installed. Vault does not use any third party dependencies which means all you have to do is clone this repo to get started. Simply clone 6 | the repo and run vault.py. 7 | 8 | Why use Vault? 9 | ============== 10 | 11 | In todays world it is important to have a different password for each of the different services that we use in our every day lives. Unfortunately, managing a lot of different passwords is hard. 12 | There are solutions out there that try to solve the problem, but they are all bloated and seem to do the job too well at times. Vault is not for those who want flair, or fancy user interfaces. Vault 13 | is for hackers, people who live in the terminal. Vault takes out all of the complexities that are present in other technologies and condenses them into three basic concepts, encryption, password 14 | stores with a database, and easy lookup. That's it. That's all we want it to be. 15 | 16 | How does it work and how do I get started? 17 | ===================== 18 | 19 | Getting started with Vault is very easy. Simple clone this repo and run vault.py in the directory of your choosing. If Vault does not detect a file.db file in your current directory it will 20 | create one and ask you for a new password. This password will be the master password that is used to encrypt your data. Vault uses the aes module from the SlowAES project, we have not written our 21 | own implementation. Every time Vault is run you will be asked to insert this master password in order to access your data. If you forget the master password all the data will be lost. 22 | 23 | What are the features of Vault? 24 | =============================== 25 | 26 | Vault provides the very basics of storing passwords and keeping the data safe. 27 | 28 | - AES Encryption 29 | - 30 second inactivity timeout 30 | - Password labels 31 | - Password lookup 32 | - Simple commandline interface 33 | 34 | What are the commands in the Vault shell? 35 | ========================================= 36 | 37 | * read - This will return all the passwords stored in plain text 38 | * add - This will bring up the prompt to add a new password 39 | * remove - This will give you the prompt to remove a password 40 | * search - This will give you the prompt to search for a password 41 | * exit - Safely exits Vault 42 | 43 | How can I contribute? 44 | ===================== 45 | 46 | Easily! Just fork the repo and push your change. It will be reviewed and if it's something we feel will help Vault then we will merge it. 47 | 48 | -------------------------------------------------------------------------------- /aes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # aes.py: implements AES - Advanced Encryption Standard 4 | # from the SlowAES project, http://code.google.com/p/slowaes/ 5 | # 6 | # Copyright (c) 2008 Josh Davis ( http://www.josh-davis.org ), 7 | # Alex Martelli ( http://www.aleax.it ) 8 | # 9 | # Ported from C code written by Laurent Haan ( http://www.progressive-coding.com ) 10 | # 11 | # Licensed under the Apache License, Version 2.0 12 | # http://www.apache.org/licenses/ 13 | # 14 | import os 15 | import sys 16 | import math 17 | 18 | class AES(object): 19 | '''AES funtions for a single block 20 | ''' 21 | # Very annoying code: all is for an object, but no state is kept! 22 | # Should just be plain functions in a AES modlule. 23 | 24 | # valid key sizes 25 | keySize = dict(SIZE_128=16, SIZE_192=24, SIZE_256=32) 26 | 27 | # Rijndael S-box 28 | sbox = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 29 | 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 30 | 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 31 | 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 32 | 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 33 | 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 34 | 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 35 | 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 36 | 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 37 | 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 38 | 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 39 | 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 40 | 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 41 | 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 42 | 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 43 | 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 44 | 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 45 | 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 46 | 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 47 | 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 48 | 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 49 | 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 50 | 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 51 | 0x54, 0xbb, 0x16] 52 | 53 | # Rijndael Inverted S-box 54 | rsbox = [0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 55 | 0x9e, 0x81, 0xf3, 0xd7, 0xfb , 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 56 | 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb , 0x54, 57 | 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 58 | 0x42, 0xfa, 0xc3, 0x4e , 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 59 | 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25 , 0x72, 0xf8, 60 | 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 61 | 0x65, 0xb6, 0x92 , 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 62 | 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84 , 0x90, 0xd8, 0xab, 63 | 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 64 | 0x45, 0x06 , 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 65 | 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b , 0x3a, 0x91, 0x11, 0x41, 66 | 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 67 | 0x73 , 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 68 | 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e , 0x47, 0xf1, 0x1a, 0x71, 0x1d, 69 | 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b , 70 | 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 71 | 0xfe, 0x78, 0xcd, 0x5a, 0xf4 , 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 72 | 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f , 0x60, 73 | 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 74 | 0x93, 0xc9, 0x9c, 0xef , 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 75 | 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61 , 0x17, 0x2b, 76 | 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 77 | 0x21, 0x0c, 0x7d] 78 | 79 | def getSBoxValue(self,num): 80 | """Retrieves a given S-Box Value""" 81 | return self.sbox[num] 82 | 83 | def getSBoxInvert(self,num): 84 | """Retrieves a given Inverted S-Box Value""" 85 | return self.rsbox[num] 86 | 87 | def rotate(self, word): 88 | """ Rijndael's key schedule rotate operation. 89 | 90 | Rotate a word eight bits to the left: eg, rotate(1d2c3a4f) == 2c3a4f1d 91 | Word is an char list of size 4 (32 bits overall). 92 | """ 93 | return word[1:] + word[:1] 94 | 95 | # Rijndael Rcon 96 | Rcon = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 97 | 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 98 | 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 99 | 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 100 | 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 101 | 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 102 | 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 103 | 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 104 | 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 105 | 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 106 | 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 107 | 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 108 | 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 109 | 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 110 | 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 111 | 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 112 | 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 113 | 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 114 | 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 115 | 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 116 | 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 117 | 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 118 | 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 119 | 0xe8, 0xcb ] 120 | 121 | def getRconValue(self, num): 122 | """Retrieves a given Rcon Value""" 123 | return self.Rcon[num] 124 | 125 | def core(self, word, iteration): 126 | """Key schedule core.""" 127 | # rotate the 32-bit word 8 bits to the left 128 | word = self.rotate(word) 129 | # apply S-Box substitution on all 4 parts of the 32-bit word 130 | for i in range(4): 131 | word[i] = self.getSBoxValue(word[i]) 132 | # XOR the output of the rcon operation with i to the first part 133 | # (leftmost) only 134 | word[0] = word[0] ^ self.getRconValue(iteration) 135 | return word 136 | 137 | def expandKey(self, key, size, expandedKeySize): 138 | """Rijndael's key expansion. 139 | 140 | Expands an 128,192,256 key into an 176,208,240 bytes key 141 | 142 | expandedKey is a char list of large enough size, 143 | key is the non-expanded key. 144 | """ 145 | # current expanded keySize, in bytes 146 | currentSize = 0 147 | rconIteration = 1 148 | expandedKey = [0] * expandedKeySize 149 | 150 | # set the 16, 24, 32 bytes of the expanded key to the input key 151 | for j in range(size): 152 | expandedKey[j] = key[j] 153 | currentSize += size 154 | 155 | while currentSize < expandedKeySize: 156 | # assign the previous 4 bytes to the temporary value t 157 | t = expandedKey[currentSize-4:currentSize] 158 | 159 | # every 16,24,32 bytes we apply the core schedule to t 160 | # and increment rconIteration afterwards 161 | if currentSize % size == 0: 162 | t = self.core(t, rconIteration) 163 | rconIteration += 1 164 | # For 256-bit keys, we add an extra sbox to the calculation 165 | if size == self.keySize["SIZE_256"] and ((currentSize % size) == 16): 166 | for l in range(4): t[l] = self.getSBoxValue(t[l]) 167 | 168 | # We XOR t with the four-byte block 16,24,32 bytes before the new 169 | # expanded key. This becomes the next four bytes in the expanded 170 | # key. 171 | for m in range(4): 172 | expandedKey[currentSize] = expandedKey[currentSize - size] ^ \ 173 | t[m] 174 | currentSize += 1 175 | 176 | return expandedKey 177 | 178 | def addRoundKey(self, state, roundKey): 179 | """Adds (XORs) the round key to the state.""" 180 | for i in range(16): 181 | state[i] ^= roundKey[i] 182 | return state 183 | 184 | def createRoundKey(self, expandedKey, roundKeyPointer): 185 | """Create a round key. 186 | Creates a round key from the given expanded key and the 187 | position within the expanded key. 188 | """ 189 | roundKey = [0] * 16 190 | for i in range(4): 191 | for j in range(4): 192 | roundKey[j*4+i] = expandedKey[roundKeyPointer + i*4 + j] 193 | return roundKey 194 | 195 | def galois_multiplication(self, a, b): 196 | """Galois multiplication of 8 bit characters a and b.""" 197 | p = 0 198 | for counter in range(8): 199 | if b & 1: p ^= a 200 | hi_bit_set = a & 0x80 201 | a <<= 1 202 | # keep a 8 bit 203 | a &= 0xFF 204 | if hi_bit_set: 205 | a ^= 0x1b 206 | b >>= 1 207 | return p 208 | 209 | # 210 | # substitute all the values from the state with the value in the SBox 211 | # using the state value as index for the SBox 212 | # 213 | def subBytes(self, state, isInv): 214 | if isInv: getter = self.getSBoxInvert 215 | else: getter = self.getSBoxValue 216 | for i in range(16): state[i] = getter(state[i]) 217 | return state 218 | 219 | # iterate over the 4 rows and call shiftRow() with that row 220 | def shiftRows(self, state, isInv): 221 | for i in range(4): 222 | state = self.shiftRow(state, i*4, i, isInv) 223 | return state 224 | 225 | # each iteration shifts the row to the left by 1 226 | def shiftRow(self, state, statePointer, nbr, isInv): 227 | for i in range(nbr): 228 | if isInv: 229 | state[statePointer:statePointer+4] = \ 230 | state[statePointer+3:statePointer+4] + \ 231 | state[statePointer:statePointer+3] 232 | else: 233 | state[statePointer:statePointer+4] = \ 234 | state[statePointer+1:statePointer+4] + \ 235 | state[statePointer:statePointer+1] 236 | return state 237 | 238 | # galois multiplication of the 4x4 matrix 239 | def mixColumns(self, state, isInv): 240 | # iterate over the 4 columns 241 | for i in range(4): 242 | # construct one column by slicing over the 4 rows 243 | column = state[i:i+16:4] 244 | # apply the mixColumn on one column 245 | column = self.mixColumn(column, isInv) 246 | # put the values back into the state 247 | state[i:i+16:4] = column 248 | 249 | return state 250 | 251 | # galois multiplication of 1 column of the 4x4 matrix 252 | def mixColumn(self, column, isInv): 253 | if isInv: mult = [14, 9, 13, 11] 254 | else: mult = [2, 1, 1, 3] 255 | cpy = list(column) 256 | g = self.galois_multiplication 257 | 258 | column[0] = g(cpy[0], mult[0]) ^ g(cpy[3], mult[1]) ^ \ 259 | g(cpy[2], mult[2]) ^ g(cpy[1], mult[3]) 260 | column[1] = g(cpy[1], mult[0]) ^ g(cpy[0], mult[1]) ^ \ 261 | g(cpy[3], mult[2]) ^ g(cpy[2], mult[3]) 262 | column[2] = g(cpy[2], mult[0]) ^ g(cpy[1], mult[1]) ^ \ 263 | g(cpy[0], mult[2]) ^ g(cpy[3], mult[3]) 264 | column[3] = g(cpy[3], mult[0]) ^ g(cpy[2], mult[1]) ^ \ 265 | g(cpy[1], mult[2]) ^ g(cpy[0], mult[3]) 266 | return column 267 | 268 | # applies the 4 operations of the forward round in sequence 269 | def aes_round(self, state, roundKey): 270 | state = self.subBytes(state, False) 271 | state = self.shiftRows(state, False) 272 | state = self.mixColumns(state, False) 273 | state = self.addRoundKey(state, roundKey) 274 | return state 275 | 276 | # applies the 4 operations of the inverse round in sequence 277 | def aes_invRound(self, state, roundKey): 278 | state = self.shiftRows(state, True) 279 | state = self.subBytes(state, True) 280 | state = self.addRoundKey(state, roundKey) 281 | state = self.mixColumns(state, True) 282 | return state 283 | 284 | # Perform the initial operations, the standard round, and the final 285 | # operations of the forward aes, creating a round key for each round 286 | def aes_main(self, state, expandedKey, nbrRounds): 287 | state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0)) 288 | i = 1 289 | while i < nbrRounds: 290 | state = self.aes_round(state, 291 | self.createRoundKey(expandedKey, 16*i)) 292 | i += 1 293 | state = self.subBytes(state, False) 294 | state = self.shiftRows(state, False) 295 | state = self.addRoundKey(state, 296 | self.createRoundKey(expandedKey, 16*nbrRounds)) 297 | return state 298 | 299 | # Perform the initial operations, the standard round, and the final 300 | # operations of the inverse aes, creating a round key for each round 301 | def aes_invMain(self, state, expandedKey, nbrRounds): 302 | state = self.addRoundKey(state, 303 | self.createRoundKey(expandedKey, 16*nbrRounds)) 304 | i = nbrRounds - 1 305 | while i > 0: 306 | state = self.aes_invRound(state, 307 | self.createRoundKey(expandedKey, 16*i)) 308 | i -= 1 309 | state = self.shiftRows(state, True) 310 | state = self.subBytes(state, True) 311 | state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0)) 312 | return state 313 | 314 | # encrypts a 128 bit input block against the given key of size specified 315 | def encrypt(self, iput, key, size): 316 | output = [0] * 16 317 | # the number of rounds 318 | nbrRounds = 0 319 | # the 128 bit block to encode 320 | block = [0] * 16 321 | # set the number of rounds 322 | if size == self.keySize["SIZE_128"]: nbrRounds = 10 323 | elif size == self.keySize["SIZE_192"]: nbrRounds = 12 324 | elif size == self.keySize["SIZE_256"]: nbrRounds = 14 325 | else: return None 326 | 327 | # the expanded keySize 328 | expandedKeySize = 16*(nbrRounds+1) 329 | 330 | # Set the block values, for the block: 331 | # a0,0 a0,1 a0,2 a0,3 332 | # a1,0 a1,1 a1,2 a1,3 333 | # a2,0 a2,1 a2,2 a2,3 334 | # a3,0 a3,1 a3,2 a3,3 335 | # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3 336 | # 337 | # iterate over the columns 338 | for i in range(4): 339 | # iterate over the rows 340 | for j in range(4): 341 | block[(i+(j*4))] = iput[(i*4)+j] 342 | 343 | # expand the key into an 176, 208, 240 bytes key 344 | # the expanded key 345 | expandedKey = self.expandKey(key, size, expandedKeySize) 346 | 347 | # encrypt the block using the expandedKey 348 | block = self.aes_main(block, expandedKey, nbrRounds) 349 | 350 | # unmap the block again into the output 351 | for k in range(4): 352 | # iterate over the rows 353 | for l in range(4): 354 | output[(k*4)+l] = block[(k+(l*4))] 355 | return output 356 | 357 | # decrypts a 128 bit input block against the given key of size specified 358 | def decrypt(self, iput, key, size): 359 | output = [0] * 16 360 | # the number of rounds 361 | nbrRounds = 0 362 | # the 128 bit block to decode 363 | block = [0] * 16 364 | # set the number of rounds 365 | if size == self.keySize["SIZE_128"]: nbrRounds = 10 366 | elif size == self.keySize["SIZE_192"]: nbrRounds = 12 367 | elif size == self.keySize["SIZE_256"]: nbrRounds = 14 368 | else: return None 369 | 370 | # the expanded keySize 371 | expandedKeySize = 16*(nbrRounds+1) 372 | 373 | # Set the block values, for the block: 374 | # a0,0 a0,1 a0,2 a0,3 375 | # a1,0 a1,1 a1,2 a1,3 376 | # a2,0 a2,1 a2,2 a2,3 377 | # a3,0 a3,1 a3,2 a3,3 378 | # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3 379 | 380 | # iterate over the columns 381 | for i in range(4): 382 | # iterate over the rows 383 | for j in range(4): 384 | block[(i+(j*4))] = iput[(i*4)+j] 385 | # expand the key into an 176, 208, 240 bytes key 386 | expandedKey = self.expandKey(key, size, expandedKeySize) 387 | # decrypt the block using the expandedKey 388 | block = self.aes_invMain(block, expandedKey, nbrRounds) 389 | # unmap the block again into the output 390 | for k in range(4): 391 | # iterate over the rows 392 | for l in range(4): 393 | output[(k*4)+l] = block[(k+(l*4))] 394 | return output 395 | 396 | 397 | class AESModeOfOperation(object): 398 | '''Handles AES with plaintext consistingof multiple blocks. 399 | Choice of block encoding modes: OFT, CFB, CBC 400 | ''' 401 | # Very annoying code: all is for an object, but no state is kept! 402 | # Should just be plain functions in an AES_BlockMode module. 403 | aes = AES() 404 | 405 | # structure of supported modes of operation 406 | modeOfOperation = dict(OFB=0, CFB=1, CBC=2) 407 | 408 | # converts a 16 character string into a number array 409 | def convertString(self, string, start, end, mode): 410 | if end - start > 16: end = start + 16 411 | if mode == self.modeOfOperation["CBC"]: ar = [0] * 16 412 | else: ar = [] 413 | 414 | i = start 415 | j = 0 416 | while len(ar) < end - start: 417 | ar.append(0) 418 | while i < end: 419 | ar[j] = ord(string[i]) 420 | j += 1 421 | i += 1 422 | return ar 423 | 424 | # Mode of Operation Encryption 425 | # stringIn - Input String 426 | # mode - mode of type modeOfOperation 427 | # hexKey - a hex key of the bit length size 428 | # size - the bit length of the key 429 | # hexIV - the 128 bit hex Initilization Vector 430 | def encrypt(self, stringIn, mode, key, size, IV): 431 | if len(key) % size: 432 | return None 433 | if len(IV) % 16: 434 | return None 435 | # the AES input/output 436 | plaintext = [] 437 | iput = [0] * 16 438 | output = [] 439 | ciphertext = [0] * 16 440 | # the output cipher string 441 | cipherOut = [] 442 | # char firstRound 443 | firstRound = True 444 | if stringIn != None: 445 | for j in range(int(math.ceil(float(len(stringIn))/16))): 446 | start = j*16 447 | end = j*16+16 448 | if end > len(stringIn): 449 | end = len(stringIn) 450 | plaintext = self.convertString(stringIn, start, end, mode) 451 | # print 'PT@%s:%s' % (j, plaintext) 452 | if mode == self.modeOfOperation["CFB"]: 453 | if firstRound: 454 | output = self.aes.encrypt(IV, key, size) 455 | firstRound = False 456 | else: 457 | output = self.aes.encrypt(iput, key, size) 458 | for i in range(16): 459 | if len(plaintext)-1 < i: 460 | ciphertext[i] = 0 ^ output[i] 461 | elif len(output)-1 < i: 462 | ciphertext[i] = plaintext[i] ^ 0 463 | elif len(plaintext)-1 < i and len(output) < i: 464 | ciphertext[i] = 0 ^ 0 465 | else: 466 | ciphertext[i] = plaintext[i] ^ output[i] 467 | for k in range(end-start): 468 | cipherOut.append(ciphertext[k]) 469 | iput = ciphertext 470 | elif mode == self.modeOfOperation["OFB"]: 471 | if firstRound: 472 | output = self.aes.encrypt(IV, key, size) 473 | firstRound = False 474 | else: 475 | output = self.aes.encrypt(iput, key, size) 476 | for i in range(16): 477 | if len(plaintext)-1 < i: 478 | ciphertext[i] = 0 ^ output[i] 479 | elif len(output)-1 < i: 480 | ciphertext[i] = plaintext[i] ^ 0 481 | elif len(plaintext)-1 < i and len(output) < i: 482 | ciphertext[i] = 0 ^ 0 483 | else: 484 | ciphertext[i] = plaintext[i] ^ output[i] 485 | for k in range(end-start): 486 | cipherOut.append(ciphertext[k]) 487 | iput = output 488 | elif mode == self.modeOfOperation["CBC"]: 489 | for i in range(16): 490 | if firstRound: 491 | iput[i] = plaintext[i] ^ IV[i] 492 | else: 493 | iput[i] = plaintext[i] ^ ciphertext[i] 494 | # print 'IP@%s:%s' % (j, iput) 495 | firstRound = False 496 | ciphertext = self.aes.encrypt(iput, key, size) 497 | # always 16 bytes because of the padding for CBC 498 | for k in range(16): 499 | cipherOut.append(ciphertext[k]) 500 | return mode, len(stringIn), cipherOut 501 | 502 | # Mode of Operation Decryption 503 | # cipherIn - Encrypted String 504 | # originalsize - The unencrypted string length - required for CBC 505 | # mode - mode of type modeOfOperation 506 | # key - a number array of the bit length size 507 | # size - the bit length of the key 508 | # IV - the 128 bit number array Initilization Vector 509 | def decrypt(self, cipherIn, originalsize, mode, key, size, IV): 510 | # cipherIn = unescCtrlChars(cipherIn) 511 | if len(key) % size: 512 | return None 513 | if len(IV) % 16: 514 | return None 515 | # the AES input/output 516 | ciphertext = [] 517 | iput = [] 518 | output = [] 519 | plaintext = [0] * 16 520 | # the output plain text character list 521 | chrOut = [] 522 | # char firstRound 523 | firstRound = True 524 | if cipherIn != None: 525 | for j in range(int(math.ceil(float(len(cipherIn))/16))): 526 | start = j*16 527 | end = j*16+16 528 | if j*16+16 > len(cipherIn): 529 | end = len(cipherIn) 530 | ciphertext = cipherIn[start:end] 531 | if mode == self.modeOfOperation["CFB"]: 532 | if firstRound: 533 | output = self.aes.encrypt(IV, key, size) 534 | firstRound = False 535 | else: 536 | output = self.aes.encrypt(iput, key, size) 537 | for i in range(16): 538 | if len(output)-1 < i: 539 | plaintext[i] = 0 ^ ciphertext[i] 540 | elif len(ciphertext)-1 < i: 541 | plaintext[i] = output[i] ^ 0 542 | elif len(output)-1 < i and len(ciphertext) < i: 543 | plaintext[i] = 0 ^ 0 544 | else: 545 | plaintext[i] = output[i] ^ ciphertext[i] 546 | for k in range(end-start): 547 | chrOut.append(chr(plaintext[k])) 548 | iput = ciphertext 549 | elif mode == self.modeOfOperation["OFB"]: 550 | if firstRound: 551 | output = self.aes.encrypt(IV, key, size) 552 | firstRound = False 553 | else: 554 | output = self.aes.encrypt(iput, key, size) 555 | for i in range(16): 556 | if len(output)-1 < i: 557 | plaintext[i] = 0 ^ ciphertext[i] 558 | elif len(ciphertext)-1 < i: 559 | plaintext[i] = output[i] ^ 0 560 | elif len(output)-1 < i and len(ciphertext) < i: 561 | plaintext[i] = 0 ^ 0 562 | else: 563 | plaintext[i] = output[i] ^ ciphertext[i] 564 | for k in range(end-start): 565 | chrOut.append(chr(plaintext[k])) 566 | iput = output 567 | elif mode == self.modeOfOperation["CBC"]: 568 | output = self.aes.decrypt(ciphertext, key, size) 569 | for i in range(16): 570 | if firstRound: 571 | plaintext[i] = IV[i] ^ output[i] 572 | else: 573 | plaintext[i] = iput[i] ^ output[i] 574 | firstRound = False 575 | if originalsize is not None and originalsize < end: 576 | for k in range(originalsize-start): 577 | chrOut.append(chr(plaintext[k])) 578 | else: 579 | for k in range(end-start): 580 | chrOut.append(chr(plaintext[k])) 581 | iput = ciphertext 582 | return "".join(chrOut) 583 | 584 | 585 | def append_PKCS7_padding(s): 586 | """return s padded to a multiple of 16-bytes by PKCS7 padding""" 587 | numpads = 16 - (len(s)%16) 588 | return s + numpads*chr(numpads) 589 | 590 | def strip_PKCS7_padding(s): 591 | """return s stripped of PKCS7 padding""" 592 | if len(s)%16 or not s: 593 | raise ValueError("String of len %d can't be PCKS7-padded" % len(s)) 594 | numpads = ord(s[-1]) 595 | if numpads > 16: 596 | raise ValueError("String ending with %r can't be PCKS7-padded" % s[-1]) 597 | return s[:-numpads] 598 | 599 | def encryptData(key, data, mode=AESModeOfOperation.modeOfOperation["CBC"]): 600 | """encrypt `data` using `key` 601 | 602 | `key` should be a string of bytes. 603 | 604 | returned cipher is a string of bytes prepended with the initialization 605 | vector. 606 | 607 | """ 608 | key = map(ord, key) 609 | if mode == AESModeOfOperation.modeOfOperation["CBC"]: 610 | data = append_PKCS7_padding(data) 611 | keysize = len(key) 612 | assert keysize in AES.keySize.values(), 'invalid key size: %s' % keysize 613 | # create a new iv using random data 614 | iv = [ord(i) for i in os.urandom(16)] 615 | moo = AESModeOfOperation() 616 | (mode, length, ciph) = moo.encrypt(data, mode, key, keysize, iv) 617 | # With padding, the original length does not need to be known. It's a bad 618 | # idea to store the original message length. 619 | # prepend the iv. 620 | return ''.join(map(chr, iv)) + ''.join(map(chr, ciph)) 621 | 622 | def decryptData(key, data, mode=AESModeOfOperation.modeOfOperation["CBC"]): 623 | """decrypt `data` using `key` 624 | 625 | `key` should be a string of bytes. 626 | 627 | `data` should have the initialization vector prepended as a string of 628 | ordinal values. 629 | """ 630 | 631 | key = map(ord, key) 632 | keysize = len(key) 633 | assert keysize in AES.keySize.values(), 'invalid key size: %s' % keysize 634 | # iv is first 16 bytes 635 | iv = map(ord, data[:16]) 636 | data = map(ord, data[16:]) 637 | moo = AESModeOfOperation() 638 | decr = moo.decrypt(data, None, mode, key, keysize, iv) 639 | if mode == AESModeOfOperation.modeOfOperation["CBC"]: 640 | decr = strip_PKCS7_padding(decr) 641 | return decr 642 | 643 | def generateRandomKey(keysize): 644 | """Generates a key from random data of length `keysize`. 645 | The returned key is a string of bytes. 646 | """ 647 | if keysize not in (16, 24, 32): 648 | emsg = 'Invalid keysize, %s. Should be one of (16, 24, 32).' 649 | raise ValueError, emsg % keysize 650 | return os.urandom(keysize) 651 | 652 | def testStr(cleartext, keysize=16, modeName = "CBC"): 653 | '''Test with random key, choice of mode.''' 654 | print 'Random key test', 'Mode:', modeName 655 | print 'cleartext:', cleartext 656 | key = generateRandomKey(keysize) 657 | print 'Key:', [ord(x) for x in key] 658 | mode = AESModeOfOperation.modeOfOperation[modeName] 659 | cipher = encryptData(key, cleartext, mode) 660 | print 'Cipher:', [ord(x) for x in cipher] 661 | decr = decryptData(key, cipher, mode) 662 | print 'Decrypted:', decr 663 | 664 | 665 | if __name__ == "__main__": 666 | moo = AESModeOfOperation() 667 | cleartext = "This is a test with several blocks!" 668 | cypherkey = [143,194,34,208,145,203,230,143,177,246,97,206,145,92,255,84] 669 | iv = [103,35,148,239,76,213,47,118,255,222,123,176,106,134,98,92] 670 | mode, orig_len, ciph = moo.encrypt(cleartext, moo.modeOfOperation["CBC"], 671 | cypherkey, moo.aes.keySize["SIZE_128"], iv) 672 | print 'm=%s, ol=%s (%s), ciph=%s' % (mode, orig_len, len(cleartext), ciph) 673 | decr = moo.decrypt(ciph, orig_len, mode, cypherkey, 674 | moo.aes.keySize["SIZE_128"], iv) 675 | print decr 676 | testStr(cleartext, 16, "CBC") 677 | 678 | 679 | 680 | 681 | 682 | 683 | -------------------------------------------------------------------------------- /vault.py: -------------------------------------------------------------------------------- 1 | import platform 2 | import thread 3 | import aes 4 | import getpass 5 | import os 6 | import string 7 | import random 8 | import hashlib 9 | import sys 10 | import time 11 | import os 12 | 13 | class Vault: 14 | global lastcommand 15 | lastcommand = time.time() 16 | 17 | def __init__(self): 18 | self.password = None 19 | self.data = "" 20 | self.file_ = "file.db" 21 | self.commands = { 22 | 23 | "add":self.add, 24 | "read":self.read_passwords, 25 | "help":self.help_, 26 | "remove":self.remove, 27 | "search":self.search, 28 | } 29 | 30 | def help_(self): 31 | print """ 32 | 33 | add - Adds a new password 34 | remove - remove a password 35 | read - Shows all saved passwords 36 | search - Search for specific Password based on description 37 | help - Displays this prompt 38 | 39 | """ 40 | 41 | def main(self): 42 | if not os.path.exists(self.file_): 43 | self.create() 44 | if not self.password: 45 | self.decryptData() 46 | thread.start_new_thread(self.inactivity, ()) 47 | global lastcommand 48 | while True: 49 | command = raw_input("Vault Shell> ") 50 | lastcommand = time.time() 51 | if command in self.commands: 52 | self.commands[command]() 53 | elif command == "exit": 54 | lastcommand = 1000000 55 | self.inactivity() 56 | sys.exit("Bye!") 57 | 58 | def inactivity(self): 59 | global lastcommand 60 | while True: 61 | if time.time() - lastcommand > 30: 62 | if platform.system() == "Linux": 63 | os.system("clear") 64 | elif platform.system() == "Windows": 65 | os.system("cls") 66 | os._exit(1) 67 | 68 | def search(self): 69 | searchTerm = raw_input("Input keywords to search for: ").lower() 70 | for x in self.data.split("\n"): 71 | z = x.lower() 72 | if z.find(searchTerm) != -1: 73 | print x 74 | else: 75 | for y in searchTerm.split(): 76 | if z.find(searchTerm) != -1: 77 | print x 78 | break 79 | def create(self): 80 | print "No passwords found stored, let's set it up." 81 | password = getpass.getpass("Create Password: ") 82 | confirm = getpass.getpass("Confirm Password: ") 83 | if password != confirm: 84 | print "Passwords did not match, restarting." 85 | self.create() 86 | return 87 | 88 | self.password = password 89 | self.add() 90 | 91 | def read_passwords(self): 92 | print self.data 93 | 94 | def remove(self): 95 | data = raw_input("Paste complete password data: ") 96 | self.data = self.data.replace(data+"\n", '') 97 | self.write() 98 | print "Password Removed!" 99 | 100 | def add(self): 101 | title = raw_input("Title given to password: ") 102 | password = getpass.getpass("Enter password (leave blank to generate random): ") 103 | confirm = getpass.getpass("Confirm Password: ") 104 | if password != confirm: 105 | print "Passwords did not match, restarting." 106 | self.add() 107 | return 108 | 109 | if not password: 110 | length = raw_input("Short or long password? ").lower() 111 | if length == "short": 112 | password = self.generate()[:8] 113 | elif length != "short" and length != "long": 114 | print "Okay, I don't know what that means so I'll just make it long." 115 | if not password: 116 | password = self.generate() 117 | print "Password is: {}".format(password) 118 | 119 | self.data += "{0} - {1}\n".format(title, password) 120 | self.write() 121 | 122 | def write(self): 123 | if not self.password: 124 | return 125 | 126 | data = aes.encryptData(hashlib.md5(self.password).hexdigest(), self.data) 127 | with open(self.file_, "wb") as file: 128 | file.write(data) 129 | print "Data saved!" 130 | 131 | def decryptData(self): 132 | self.password = getpass.getpass("Password: ") 133 | with open(self.file_, "rb") as file: 134 | try: 135 | self.data = aes.decryptData(hashlib.md5(self.password).hexdigest(), file.read()) 136 | except: 137 | print "Password incorrect" 138 | self.decryptData() 139 | 140 | def generate(self): 141 | return ''.join([random.choice(string.uppercase+string.lowercase+string.digits) for x in range(20)]) 142 | 143 | 144 | 145 | if __name__ == "__main__": 146 | Vault().main() 147 | --------------------------------------------------------------------------------