├── README.md └── securecrtdecryptV2.py /README.md: -------------------------------------------------------------------------------- 1 | # SecureCRTdecrypt 2 | SecureCRT批量解密工具 by fengchen 3 | 4 | ``` 5 | python3 securecrtdecryptV2.py -v 2 -p "" -f "/private/tmp/tmp" -s ini 6 | 7 | optional arguments: 8 | -h, --help show this help message and exit 9 | -v VERSION, --version VERSION 10 | SecureCRT 7.x版本使用-v 1 ,8.x版本使用-v 2,默认为1 11 | -p CONFIGPASS, --configpass CONFIGPASS 12 | v2中需要ConfigPassphrase,v1默认不需要 13 | -f FILE, --file FILE Sessions文件夹,设置后会递归查询指定后缀的文件,windows中默认为 14 | %APPDATA%\VanDyke\Config\Sessions\sessionname.ini 15 | -s SUFFIX, --suffix SUFFIX 16 | 指定后缀,默认为ini 17 | ``` 18 | -------------------------------------------------------------------------------- /securecrtdecryptV2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | from Crypto.Hash import SHA256 4 | from Crypto.Cipher import AES, Blowfish 5 | import argparse 6 | import re 7 | import os.path 8 | 9 | class SecureCRTCrypto: 10 | 11 | def __init__(self): 12 | ''' 13 | Initialize SecureCRTCrypto object. 14 | ''' 15 | self.IV = b'\x00' * Blowfish.block_size 16 | self.Key1 = b'\x24\xA6\x3D\xDE\x5B\xD3\xB3\x82\x9C\x7E\x06\xF4\x08\x16\xAA\x07' 17 | self.Key2 = b'\x5F\xB0\x45\xA2\x94\x17\xD9\x16\xC6\xC6\xA2\xFF\x06\x41\x82\xB7' 18 | 19 | def Encrypt(self, Plaintext : str): 20 | ''' 21 | Encrypt plaintext and return corresponding ciphertext. 22 | 23 | Args: 24 | Plaintext: A string that will be encrypted. 25 | 26 | Returns: 27 | Hexlified ciphertext string. 28 | ''' 29 | plain_bytes = Plaintext.encode('utf-16-le') 30 | plain_bytes += b'\x00\x00' 31 | padded_plain_bytes = plain_bytes + os.urandom(Blowfish.block_size - len(plain_bytes) % Blowfish.block_size) 32 | 33 | cipher1 = Blowfish.new(self.Key1, Blowfish.MODE_CBC, iv = self.IV) 34 | cipher2 = Blowfish.new(self.Key2, Blowfish.MODE_CBC, iv = self.IV) 35 | return cipher1.encrypt(os.urandom(4) + cipher2.encrypt(padded_plain_bytes) + os.urandom(4)).hex() 36 | 37 | def Decrypt(self, Ciphertext : str): 38 | ''' 39 | Decrypt ciphertext and return corresponding plaintext. 40 | 41 | Args: 42 | Ciphertext: A hex string that will be decrypted. 43 | 44 | Returns: 45 | Plaintext string. 46 | ''' 47 | 48 | cipher1 = Blowfish.new(self.Key1, Blowfish.MODE_CBC, iv = self.IV) 49 | cipher2 = Blowfish.new(self.Key2, Blowfish.MODE_CBC, iv = self.IV) 50 | ciphered_bytes = bytes.fromhex(Ciphertext) 51 | if len(ciphered_bytes) <= 8: 52 | raise ValueError('Invalid Ciphertext.') 53 | 54 | padded_plain_bytes = cipher2.decrypt(cipher1.decrypt(ciphered_bytes)[4:-4]) 55 | 56 | i = 0 57 | for i in range(0, len(padded_plain_bytes), 2): 58 | if padded_plain_bytes[i] == 0 and padded_plain_bytes[i + 1] == 0: 59 | break 60 | plain_bytes = padded_plain_bytes[0:i] 61 | 62 | try: 63 | return plain_bytes.decode('utf-16-le') 64 | except UnicodeDecodeError: 65 | raise(ValueError('Invalid Ciphertext.')) 66 | 67 | class SecureCRTCryptoV2: 68 | 69 | def __init__(self, ConfigPassphrase : str = ''): 70 | ''' 71 | Initialize SecureCRTCryptoV2 object. 72 | 73 | Args: 74 | ConfigPassphrase: The config passphrase that SecureCRT uses. Leave it empty if config passphrase is not set. 75 | ''' 76 | self.IV = b'\x00' * AES.block_size 77 | self.Key = SHA256.new(ConfigPassphrase.encode('utf-8')).digest() 78 | 79 | def Encrypt(self, Plaintext : str): 80 | ''' 81 | Encrypt plaintext and return corresponding ciphertext. 82 | 83 | Args: 84 | Plaintext: A string that will be encrypted. 85 | 86 | Returns: 87 | Hexlified ciphertext string. 88 | ''' 89 | plain_bytes = Plaintext.encode('utf-8') 90 | if len(plain_bytes) > 0xffffffff: 91 | raise OverflowError('Plaintext is too long.') 92 | 93 | plain_bytes = \ 94 | len(plain_bytes).to_bytes(4, 'little') + \ 95 | plain_bytes + \ 96 | SHA256.new(plain_bytes).digest() 97 | padded_plain_bytes = \ 98 | plain_bytes + \ 99 | os.urandom(AES.block_size - len(plain_bytes) % AES.block_size) 100 | cipher = AES.new(self.Key, AES.MODE_CBC, iv = self.IV) 101 | return cipher.encrypt(padded_plain_bytes).hex() 102 | 103 | def Decrypt(self, Ciphertext : str): 104 | ''' 105 | Decrypt ciphertext and return corresponding plaintext. 106 | 107 | Args: 108 | Ciphertext: A hex string that will be decrypted. 109 | 110 | Returns: 111 | Plaintext string. 112 | ''' 113 | cipher = AES.new(self.Key, AES.MODE_CBC, iv = self.IV) 114 | padded_plain_bytes = cipher.decrypt(bytes.fromhex(Ciphertext)) 115 | 116 | plain_bytes_length = int.from_bytes(padded_plain_bytes[0:4], 'little') 117 | plain_bytes = padded_plain_bytes[4:4 + plain_bytes_length] 118 | if len(plain_bytes) != plain_bytes_length: 119 | raise ValueError('Invalid Ciphertext.') 120 | 121 | plain_bytes_digest = padded_plain_bytes[4 + plain_bytes_length:4 + plain_bytes_length + SHA256.digest_size] 122 | if len(plain_bytes_digest) != SHA256.digest_size: 123 | raise ValueError('Invalid Ciphertext.') 124 | 125 | if SHA256.new(plain_bytes).digest() != plain_bytes_digest: 126 | raise ValueError('Invalid Ciphertext.') 127 | 128 | return plain_bytes.decode('utf-8') 129 | 130 | if __name__ == '__main__': 131 | import sys 132 | 133 | 134 | def EncryptionRoutine(UseV2 : bool, ConfigPassphrase : str, Plaintext : str): 135 | try: 136 | if UseV2: 137 | print(SecureCRTCryptoV2(ConfigPassphrase).Encrypt(Plaintext)) 138 | else: 139 | print(SecureCRTCrypto().Encrypt(Plaintext)) 140 | return True 141 | except: 142 | print('Error: Failed to encrypt.') 143 | return False 144 | 145 | def DecryptionRoutine(UseV2 : bool, ConfigPassphrase : str, Ciphertext : str): 146 | try: 147 | if UseV2: 148 | return(SecureCRTCryptoV2(ConfigPassphrase).Decrypt(Ciphertext)) 149 | else: 150 | return(SecureCRTCrypto().Decrypt(Ciphertext)) 151 | 152 | except: 153 | print('Error: Failed to decrypt.') 154 | 155 | 156 | 157 | REGEX_HOSTNAME = re.compile(r'S:"Hostname"=([^\r\n]*)') 158 | REGEX_PASWORD = re.compile(r'S:"Password"=u([0-9a-f]+)') 159 | REGEX_PASWORDV2 = re.compile(r'S:"Password V2"=02:([0-9a-f]+)') 160 | REGEX_PORT = re.compile(r'D:"\[SSH2\] Port"=([0-9a-f]{8})') 161 | REGEX_USERNAME = re.compile(r'S:"Username"=([^\r\n]*)') 162 | 163 | def hostname(x) : 164 | m = REGEX_HOSTNAME.search(x) 165 | if m : 166 | return m.group(1) 167 | return '???' 168 | 169 | def port(x) : 170 | m = REGEX_PORT.search(x) 171 | if m : 172 | return '-p %d '%(int(m.group(1), 16)) 173 | return '' 174 | 175 | def username(x) : 176 | m = REGEX_USERNAME.search(x) 177 | if m : 178 | return m.group(1) + '@' 179 | return '' 180 | def recursive_get_files(folder_path, suffix): 181 | files = [] 182 | for dirpath, dirnames, filenames in os.walk(folder_path): 183 | for filename in filenames: 184 | if filename.endswith(suffix): 185 | file_path = os.path.join(dirpath, filename) 186 | files.append(file_path) 187 | return files 188 | 189 | 190 | def main(): 191 | parser = argparse.ArgumentParser( 192 | description='SecureCRT批量解密工具 by fengchen') 193 | 194 | 195 | parser.add_argument('-v','--version', type=str, default='1',required=True, 196 | help='SecureCRT 7.x版本使用-v 1 ,8.x版本使用-v 2,默认为1') 197 | parser.add_argument('-p','--configpass', type=str, default='',required=False, 198 | help='v2中需要ConfigPassphrase,v1默认不需要') 199 | parser.add_argument('-f','--file', type=str, default='',required=True, 200 | help='Sessions文件夹,设置后会递归查询指定后缀的文件,windows中默认为 %%APPDATA%%\\VanDyke\\Config\\Sessions\\sessionname.ini') 201 | parser.add_argument('-s','--suffix', type=str, default='ini',required=False, 202 | help='指定后缀,默认为ini') 203 | 204 | args = parser.parse_args() 205 | version = args.version 206 | ConfigPassphrase = args.configpass 207 | folder_path = args.file 208 | suffix = args.suffix 209 | 210 | if version == '1': 211 | bUseV2 = False 212 | else: 213 | bUseV2 = True 214 | # folder_path = "/private/tmp/tmp" 215 | 216 | # suffix = "ini" 217 | #suffix = "xsh" 218 | files = recursive_get_files(folder_path, suffix) 219 | 220 | for file in files: 221 | with open(file, 'r') as f: 222 | c = f.read().replace('\x00', '') 223 | print(f.name) 224 | if bUseV2 == False: 225 | m = REGEX_PASWORD.search(c) 226 | if m : 227 | print("ssh %s%s%s # %s"%(port(c), username(c), hostname(c), SecureCRTCrypto().Decrypt(m.group(1)))) 228 | else: 229 | m = REGEX_PASWORDV2.search(c) 230 | if m : 231 | print("ssh %s%s%s # %s"%(port(c), username(c), hostname(c), SecureCRTCryptoV2(ConfigPassphrase).Decrypt(m.group(1)))) 232 | 233 | # exit(Main(len(sys.argv), sys.argv)) 234 | exit(main()) 235 | --------------------------------------------------------------------------------