查询 Firefox 密码数据库

查询 Firefox 密码数据库

布赖安据称,Firefox 将登录网站的密码数据存储在~/.mozilla/firefox/key3.db~/.mozilla/firefox/signons.sqlite文件中。这些文件可以使用某些 sqlite 编辑器来读取。

我尝试查询网站的用户名和密码(例如https://sourceforge.net/account/login.php)来自 Firefox 的数据库。我无法通过 Firefox 来完成此操作,因为我的 Firefox GUI 无法正常工作,而且我对使用数据库来完成这项工作相当陌生并且也有兴趣学习。

  1. key3.db和的不同作用是什么signons.sqlite
  2. 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)
    
  3. 为了从给定的网站名称查询密码,我应该阅读和学习什么?

    会读书http://www.sqlite.org/cli.html帮助?


致加雷斯TheRed:

我尝试了你的命令。但不返回任何东西。输出结果很糟糕:

$ 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存储的密码?

答案1

有些人似乎已经将所有必要的代码粘在一起了这里:

#!/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 论坛中。

答案2

不幸的是,其他答案中链接的两个 Python 脚本都因我的系统上的分段错误而失败。我发现的是nss 密码,它似乎是用 OCAML 和 C 编写的,所以我不确定它在任何地方都适用,但它至少可以轻松安装在基于 Debian 的发行版(如 Ubuntu)中:

$ sudo apt install nss-passwords
[...]
$ nss-passwords stackoverflow
[it asks for the main password]
| https://stackoverflow.com | [email protected] | this_is_not_my_real_password |

我只是用它来查找 stackverflow 的密码,这样我就可以写下它,它效果很好!那是多么元啊!

答案3

尽管file显示key3.db为 Berleley DB 1.85 格式,但事实并非如此。它采用 Mozilla 专有格式。它用于加密 中的用户名和密码signons.sqlite

您可以使用以下命令查看日期signons.sqlite(但不能解密用户名和密码)sqlite3

sqlite3 signons.sqlite
SQLite version 3.8.5 2014-06-04 14:06:34
Enter ".help" for usage hints.
sqlite> .tables
moz_deleted_logins  moz_disabledHosts   moz_logins        
sqlite> select * from moz_logins;
1|https://bugs.archlinux.org||https://bugs.archlinux.org|user_name|password|MDoEEP...
[more here]

要搜索特定网站,请使用基本 SQL 查询:

sqlite> select * FROM moz_logins WHERE hostname LIKE "%arch%";
32|https://bbs.archlinux.org||https://bbs.archlinux.org|req_username|req_password|MD...

请注意,他们的搜索短语用双引号引起来。是%通配符,因此在上面的示例中,它会查找任何文本,后跟arch,然后是任何文本。这涵盖了http://bbs.archlinux.org.

答案4

该文件key3.db包含用于加密存储在 中的密码的密钥signons.sqlite

由于它是自定义格式,因此需要特殊的程序来处理它,而不是使用标准数据库命令。

似乎有一个Windows工具可以利用key3.db文件,请参阅以下问题的答案:Firefox 配置文件中 key3.db 数据库的加密密钥是什么?

@StéphaneChazelas 的答案提供了一个应该在 Linux 上运行的 python 脚本;
最新版本在这里:https://hg.cryptobitch.de/firefox-passwords/file/

相关内容