Firefox
查詢火狐密碼數據庫
Braiam表示,Firefox 將登錄網站的密碼數據儲存在
~/.mozilla/firefox/key3.db
文件~/.mozilla/firefox/signons.sqlite
中。這些文件可以用一些 sqlite 編輯器讀取。我嘗試從 Firefox 的數據庫中查詢我的網站使用者名和密碼(例如https://sourceforge.net/account/login.php )。我無法通過 Firefox 來完成,因為我的 Firefox GUI 無法正常工作,而且我對學習使用數據庫來完成這項工作還很陌生,也很感興趣。
key3.db
和的不同作用是signons.sqlite
什麼?- 我在網際網路上搜尋,我應該使用
sqlite3
打開數據庫是否正確?$ sqlite3 key3.db SQLite version 3.7.9 2011-11-01 00:52:41 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> .tables Error: file is encrypted or is not a database
我猜失敗的原因是,在 Firefox 中,我設置了一個主關鍵字來訪問它儲存的密碼。我應該如何繼續查詢給定網站的密碼?
我的作業系統是 Ubuntu,這是文件類型
key3.db
:$ file key3.db key3.db: Berkeley DB 1.85 (Hash, version 2, native byte-order)
- 為了從給定的網站名稱查詢密碼,我應該閱讀和學習什麼?
閱讀http://www.sqlite.org/cli.html會有幫助嗎?
致 garethTheRed:
我試過你的命令。但是不返回任何東西。輸出很糟糕:
$ sqlite3 signons.sqlite SQLite version 3.7.9 2011-11-01 00:52:41 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> .tables moz_deleted_logins moz_disabledHosts moz_logins sqlite> select * from moz_logins; ... 55|https://sourceforge.net||https://sourceforge.net|form_loginname|form_pw|MDIEEPgAAAAAAAAAAAAAAAAAAAEwF\AYIKoZIhvcNAwcECCPrVdOzWamBBAjPs0DI8FrUnQ==|MDoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECCnZved1LRQMBBBV\DtXpOvAp0TQHibFeX3NL|{16e782de-4c65-426f-81dc-ee0361816262}|1|1327675445094|1403706275829|1327675445094|\4 ...
無論是否有主密鑰,Firefox 都會加密密碼嗎?如果是,我們可以在命令行中解密它們嗎(我的 firefox CLI 可能仍然有效)?
或者,Chrome瀏覽器是否可以讀取和導入Firefox儲存的密碼?
有些人似乎在這裡將所有必要的程式碼粘在一起:
#!/usr/bin/env python "Recovers your Firefox or Thunderbird passwords" import base64 from collections import namedtuple from ConfigParser import RawConfigParser, NoOptionError from ctypes import (Structure, CDLL, byref, cast, string_at, c_void_p, c_uint, c_ubyte, c_char_p) from getpass import getpass import logging from optparse import OptionParser import os try: from sqlite3 import dbapi2 as sqlite except ImportError: from pysqlite2 import dbapi2 as sqlite from subprocess import Popen, CalledProcessError, PIPE import sys LOGLEVEL_DEFAULT = 'warn' log = logging.getLogger() PWDECRYPT = 'pwdecrypt' SITEFIELDS = ['id', 'hostname', 'httpRealm', 'formSubmitURL', 'usernameField', 'passwordField', 'encryptedUsername', 'encryptedPassword', 'guid', 'encType', 'plain_username', 'plain_password' ] Site = namedtuple('FirefoxSite', SITEFIELDS) '''The format of the SQLite database is: (id INTEGER PRIMARY KEY,hostname TEXT NOT NULL,httpRealm TEXT,formSubmitURL TEXT,usernameField TEXT NOT NULL,passwordField TEXT NOT NULL,encryptedUsername TEXT NOT NULL,encryptedPassword TEXT NOT NULL,guid TEXT,encType INTEGER); ''' #### These are libnss definitions #### class SECItem(Structure): _fields_ = [('type',c_uint),('data',c_void_p),('len',c_uint)] class secuPWData(Structure): _fields_ = [('source',c_ubyte),('data',c_char_p)] (PW_NONE, PW_FROMFILE, PW_PLAINTEXT, PW_EXTERNAL) = (0, 1, 2, 3) # SECStatus (SECWouldBlock, SECFailure, SECSuccess) = (-2, -1, 0) #### End of libnss definitions #### def get_default_firefox_profile_directory(dir='~/.mozilla/firefox'): '''Returns the directory name of the default profile If you changed the default dir to something like ~/.thunderbird, you would get the Thunderbird default profile directory.''' profiles_dir = os.path.expanduser(dir) profile_path = None cp = RawConfigParser() cp.read(os.path.join(profiles_dir, "profiles.ini")) for section in cp.sections(): if not cp.has_option(section, "Path"): continue if (not profile_path or (cp.has_option(section, "Default") and cp.get(section, "Default").strip() == "1")): profile_path = os.path.join(profiles_dir, cp.get(section, "Path").strip()) if not profile_path: raise RuntimeError("Cannot find default Firefox profile") return profile_path def get_encrypted_sites(firefox_profile_dir=None): 'Opens signons.sqlite and yields encryped password data' if firefox_profile_dir is None: firefox_profile_dir = get_default_firefox_profile_directory() password_sqlite = os.path.join(firefox_profile_dir, "signons.sqlite") query = '''SELECT id, hostname, httpRealm, formSubmitURL, usernameField, passwordField, encryptedUsername, encryptedPassword, guid, encType, 'noplainuser', 'noplainpasswd' FROM moz_logins;''' # We don't want to type out all the column from the DB as we have ## stored them in the SITEFIELDS already. However, we have two ## components extra, the plain usename and password. So we remove ## that from the list, because the table doesn't have that column. ## And we add two literal SQL strings to make our "Site" data ## structure happy #queryfields = SITEFIELDS[:-2] + ["'noplainuser'", "'noplainpassword'"] #query = '''SELECT %s # FROM moz_logins;''' % ', '.join(queryfields) connection = sqlite.connect(password_sqlite) try: cursor = connection.cursor() cursor.execute(query) for site in map(Site._make, cursor.fetchall()): yield site finally: connection.close() def decrypt(encrypted_string, firefox_profile_directory, password = None): '''Opens an external tool to decrypt strings This is mostly for historical reasons or if the API changes. It is very slow because it needs to call out a lot. It uses the "pwdecrypt" tool which you might have packaged. Otherwise, you need to build it yourself.''' log = logging.getLogger('firefoxpasswd.decrypt') execute = [PWDECRYPT, '-d', firefox_profile_directory] if password: execute.extend(['-p', password]) process = Popen(execute, stdin=PIPE, stdout=PIPE, stderr=PIPE) output, error = process.communicate(encrypted_string) log.debug('Sent: %s', encrypted_string) log.debug('Got: %s', output) NEEDLE = 'Decrypted: "' # This string is prepended to the decrypted password if found output = output.strip() if output == encrypted_string: log.error('Password was not correct. Please try again without a ' 'password or with the correct one') index = output.index(NEEDLE) + len(NEEDLE) password = output[index:-1] # And we strip the final quotation mark return password class NativeDecryptor(object): 'Calls the NSS API to decrypt strings' def __init__(self, directory, password = ''): '''You need to give the profile directory and optionally a password. If you don't give a password but one is needed, you will be prompted by getpass to provide one.''' self.directory = directory self.log = logging.getLogger('NativeDecryptor') self.log.debug('Trying to work on %s', directory) self.libnss = CDLL('libnss3.so') if self.libnss.NSS_Init(directory) != 0: self.log.error('Could not initialize NSS') # Initialize to the empty string, not None, because the password # function expects rather an empty string self.password = password = password or '' slot = self.libnss.PK11_GetInternalKeySlot() pw_good = self.libnss.PK11_CheckUserPassword(slot, c_char_p(password)) while pw_good != SECSuccess: msg = 'Password is not good (%d)!' % pw_good print >>sys.stderr, msg password = getpass('Please enter password: ') pw_good = self.libnss.PK11_CheckUserPassword(slot, c_char_p(password)) #raise RuntimeError(msg) # That's it, we're done with passwords, but we leave the old # code below in, for nostalgic reasons. if password is None: pwdata = secuPWData() pwdata.source = PW_NONE pwdata.data = 0 else: # It's not clear whether this actually works pwdata = secuPWData() pwdata.source = PW_PLAINTEXT pwdata.data = c_char_p (password) # It doesn't actually work :-( # Now follow some attempts that were not succesful! def setpwfunc(): # One attempt was to use PK11PassworFunc. Didn't work. def password_cb(slot, retry, arg): #s = self.libnss.PL_strdup(password) s = self.libnss.PL_strdup("foo") return s PK11PasswordFunc = CFUNCTYPE(c_void_p, PRBool, c_void_p) c_password_cb = PK11PasswordFunc(password_cb) #self.libnss.PK11_SetPasswordFunc(c_password_cb) # To be ignored def changepw(): # Another attempt was to use ChangePW. Again, no effect. #ret = self.libnss.PK11_ChangePW(slot, pwdata.data, 0); ret = self.libnss.PK11_ChangePW(slot, password, 0) if ret == SECFailure: raise RuntimeError('Setting password failed! %s' % ret) #self.pwdata = pwdata def __del__(self): self.libnss.NSS_Shutdown() def decrypt(self, string, *args): 'Decrypts a given string' libnss = self.libnss uname = SECItem() dectext = SECItem() #pwdata = self.pwdata cstring = SECItem() cstring.data = cast( c_char_p( base64.b64decode(string)), c_void_p) cstring.len = len(base64.b64decode(string)) #if libnss.PK11SDR_Decrypt (byref (cstring), byref (dectext), byref (pwdata)) == -1: self.log.debug('Trying to decrypt %s (error: %s)', string, libnss.PORT_GetError()) if libnss.PK11SDR_Decrypt (byref (cstring), byref (dectext)) == -1: error = libnss.PORT_GetError() libnss.PR_ErrorToString.restype = c_char_p error_str = libnss.PR_ErrorToString(error) raise Exception ("%d: %s" % (error, error_str)) decrypted_data = string_at(dectext.data, dectext.len) return decrypted_data def encrypted_sites(self): 'Yields the encryped passwords from the profile' sites = get_encrypted_sites(self.directory) return sites def decrypted_sites(self): 'Decrypts the encrypted_sites and yields the results' sites = self.encrypted_sites() for site in sites: plain_user = self.decrypt(site.encryptedUsername) plain_password = self.decrypt(site.encryptedPassword) site = site._replace(plain_username=plain_user, plain_password=plain_password) yield site def get_firefox_sites_with_decrypted_passwords(firefox_profile_directory = None, password = None): 'Old school decryption of passwords using the external tool' if not firefox_profile_directory: firefox_profile_directory = get_default_firefox_profile_directory() #decrypt = NativeDecryptor(firefox_profile_directory).decrypt for site in get_encrypted_sites(firefox_profile_directory): plain_user = decrypt(site.encryptedUsername, firefox_profile_directory, password) plain_password = decrypt(site.encryptedPassword, firefox_profile_directory, password) site = site._replace(plain_username=plain_user, plain_password=plain_password) log.debug("Dealing with Site: %r", site) log.info("user: %s, passwd: %s", plain_user, plain_password) yield site def main_decryptor(firefox_profile_directory, password, thunderbird=False): 'Main function to get Firefox and Thunderbird passwords' if not firefox_profile_directory: if thunderbird: dir = '~/.thunderbird/' else: dir = '~/.mozilla/firefox' firefox_profile_directory = get_default_firefox_profile_directory(dir) decryptor = NativeDecryptor(firefox_profile_directory, password) for site in decryptor.decrypted_sites(): print site if __name__ == "__main__": parser = OptionParser() parser.add_option("-d", "--directory", default=None, help="the Firefox profile directory to use") parser.add_option("-p", "--password", default=None, help="the master password for the Firefox profile") parser.add_option("-l", "--loglevel", default=LOGLEVEL_DEFAULT, help="the level of logging detail [debug, info, warn, critical, error]") parser.add_option("-t", "--thunderbird", default=False, action='store_true', help="by default we try to find the Firefox default profile." " But you can as well ask for Thunderbird's default profile." " For a more reliable way, give the directory with -d.") parser.add_option("-n", "--native", default=True, action='store_true', help="use the native decryptor, i.e. make Python use " "libnss directly instead of invoking the helper program" "DEFUNCT! this option will not be checked.") parser.add_option("-e", "--external", default=False, action='store_true', help="use an external program `pwdecrypt' to actually " "decrypt the passwords. This calls out a lot and is dead " "slow. " "You need to use this method if you have a password " "protected database though.") options, args = parser.parse_args() loglevel = {'debug': logging.DEBUG, 'info': logging.INFO, 'warn': logging.WARN, 'critical':logging.CRITICAL, 'error': logging.ERROR}.get(options.loglevel, LOGLEVEL_DEFAULT) logging.basicConfig(level=loglevel) log = logging.getLogger() password = options.password if not options.external: sys.exit (main_decryptor(options.directory, password, thunderbird=options.thunderbird)) else: for site in get_firefox_sites_with_decrypted_passwords(options.directory, password): print site
請參閱 Mozilla 論壇中的相關討論。