views:

126

answers:

2

Hi everybody,

I am implementing a python application that will connect to our different servers and computers. They all have different logins and passwords. I want to store all these information in the app directly and ask for one master login/password only. How can I store all these sensitive data in the application so that someone who hasn't the master password will not be able to access our servers and computers?

EDIT: would it be possible to store an encrypted file to store these data?

EDIT2: My app runs under windows for the moment. I will port it to linux and MAC OSX if possible.

EDIT3: for those interested, I used M2secret + M2Crypto to encrypt a text file. When launching the application, the user has to enter a password which is used to decrypt the file and load the needed credentials into the app. Seems to work like that...

Best regards.

A: 

This sounds like a very bad idea. You could encrypt the logins and passwords, but anyone who has access to the master password will then have access to all of the individual logins. That means you can guarantee the individual logins won't remain a secret for long and if they do leak out you'll have to change them all.

A better solution would be to give each user of your application their own login on each of your servers. Then you application can use the same login/password for every server it accesses and the password doesn't need to be stored in the application at all. If one user's password is leaked you just change their password on all their logins and the other users aren't affected.

Alternatively route all the logins through a single server proxy: the proxy can be on a secured system so none of the users every get near any of the underlying accounts and you can protect access to the proxy by individual user accounts.

I dug through some of my old code and came up with the following from a module I called 'passwordcache.py'. See if this helps:

"""Password Cache

This module provides a portable interface for caching passwords.

Operations:

    Store a password.
    Retrieve a password (which may prompt for a password if it needs it).
    Test whether or not we have a password stored.
    Clear a stored password.

    Passwords are identified by a combination key app/service/user.
"""
import sys, os, random, hashlib

random.seed() # Init random number generator from other random source or system time

class PasswordCacheBase(object):
    """Base class for common functionality between different platform implementations"""
    def __init__(self, application=None):
        """PasswordCache(application)

        Creates a new store for passwords (or opens an existing one).
        The application name may be any string, but defaults to the script name.
        """
        if application is None:
            self.application = os.path.basename(sys.argv[0])
        else:
            self.application = application

    def get(self, service, user, getpass=None, cache=False):
        """Retrieve a password from the store"""
        raise NotImplementedError()

    def set(self, service, user, password):
        """Save a password in the store"""
        raise NotImplementedError()

    def exists(self, service, user):
        """Check whether a password exists"""
        try:
            pwd = self.get(service, user)
        except KeyError:
            return False
        return True

    def clear(self, service, user):
        raise NotImplementedError()

    def salt(self, service, user):
        """Get a salt value to help prevent encryption collisions. The salt string is 16 bytes long."""
        salt = hashlib.md5("%r|%s|%s|%s" % (random.random(), self.application, service, user)).digest()
        return salt


if sys.platform=="win32":
    """Interface to Windows data protection api.

    Based on code from:
    http://osdir.com/ml/python.ctypes/2003-07/msg00091.html
    """
    from ctypes import *
    from ctypes.wintypes import DWORD
    import _winreg
    import cPickle as pickle

    LocalFree = windll.kernel32.LocalFree
    # Note that CopyMemory is defined in term of memcpy:
    memcpy = cdll.msvcrt.memcpy
    CryptProtectData = windll.crypt32.CryptProtectData
    CryptUnprotectData = windll.crypt32.CryptUnprotectData


    # See http://msdn.microsoft.com/architecture/application/default.aspx?pull=/library/en-us/dnnetsec/html/SecNetHT07.asp
    CRYPTPROTECT_UI_FORBIDDEN = 0x01

    class DATA_BLOB(Structure):
        # See d:\vc98\Include\WINCRYPT.H
        # This will not work
        # _fields_ = [("cbData", DWORD), ("pbData", c_char_p)]
        # because accessing pbData will create a new Python string which is
        # null terminated.
        _fields_ = [("cbData", DWORD), ("pbData", POINTER(c_char))]

    class PasswordCache(PasswordCacheBase):
        def set(self, service, user, password):
            """Save a password in the store"""
            salt = self.salt(service, user)
            encrypted = self.Win32CryptProtectData(
                '%s' % password, salt)
            key = self._get_regkey()
            try:
                data = self._get_registrydata(key)
                data[service, user] = (salt, encrypted)
                self._put_registrydata(key, data)
            finally:
                key.Close()

        def get(self, service, user, getpass=None, cache=False):
            data = self._get_registrydata()
            try:
                salt, encrypted = data[service, user]
                decrypted = self.Win32CryptUnprotectData(encrypted, salt)
            except KeyError:
                if getpass is not None:
                    password = getpass()
                    if cache:
                        self.set(service, user, password)
                    return password
                raise
            return decrypted

        def clear(self, service=None, user=None):
            key = self._get_regkey()
            try:
                data = self._get_registrydata(key)
                if service is None:
                    if user is None:
                        data = {}
                    else:
                        for (s,u) in data.keys():
                            if u==user:
                                del data[s,u]
                else:
                    if user is None:
                        for (s,u) in data.keys():
                            if s==service:
                                del data[s,u]
                    else:
                        if (service,user) in data:
                            del data[service,user]

                self._put_registrydata(key, data)
            finally:
                key.Close()

        def _get_regkey(self):
            return _winreg.CreateKey(
                _winreg.HKEY_CURRENT_USER,
                r'Software\Python\Passwords')

        def _get_registrydata(self, regkey=None):
            if regkey is None:
                key = self._get_regkey()
                try:
                    return self._get_registrydata(key)
                finally:
                    key.Close()

            try:
                current = _winreg.QueryValueEx(regkey, self.application)[0]
                data = pickle.loads(current.decode('base64'))
            except WindowsError:
                data = {}
            return data

        def _put_registrydata(self, regkey, data):
            pickled = pickle.dumps(data)
            _winreg.SetValueEx(regkey,
                self.application,
                None,
                _winreg.REG_SZ,
                pickled.encode('base64'))

        def getData(self, blobOut):
            cbData = int(blobOut.cbData)
            pbData = blobOut.pbData
            buffer = c_buffer(cbData)
            memcpy(buffer, pbData, cbData)
            LocalFree(pbData);
            return buffer.raw

        def Win32CryptProtectData(self, plainText, entropy):
            bufferIn = c_buffer(plainText, len(plainText))
            blobIn = DATA_BLOB(len(plainText), bufferIn)
            bufferEntropy = c_buffer(entropy, len(entropy))
            blobEntropy = DATA_BLOB(len(entropy), bufferEntropy)
            blobOut = DATA_BLOB()
            # The CryptProtectData function performs encryption on the data
            # in a DATA_BLOB structure.
            # BOOL WINAPI CryptProtectData(
            #    DATA_BLOB* pDataIn,
            #    LPCWSTR szDataDescr,
            #    DATA_BLOB* pOptionalEntropy,
            #    PVOID pvReserved,
            #    CRYPTPROTECT_PROMPTSTRUCT* pPromptStruct,
            #    DWORD dwFlags,
            #    DATA_BLOB* pDataOut
            if CryptProtectData(byref(blobIn), u"win32crypto.py", byref(blobEntropy),
                None, None, CRYPTPROTECT_UI_FORBIDDEN, byref(blobOut)):
                return self.getData(blobOut)
            else:
                return None

        def Win32CryptUnprotectData(self, cipherText, entropy):
            bufferIn = c_buffer(cipherText, len(cipherText))
            blobIn = DATA_BLOB(len(cipherText), bufferIn)
            bufferEntropy = c_buffer(entropy, len(entropy))
            blobEntropy = DATA_BLOB(len(entropy), bufferEntropy)
            blobOut = DATA_BLOB()

            if CryptUnprotectData(byref(blobIn), None, byref(blobEntropy), None, None,
                CRYPTPROTECT_UI_FORBIDDEN, byref(blobOut)):
                return self.getData(blobOut)
            else:
                return None
else: # Not Windows, try for gnome-keyring
    import gtk # ensure that the application name is correctly set
    import gnomekeyring as gkey


    class Keyring(object):
        def __init__(self, name, server, protocol):
            self._name = name
            self._server = server
            self._protocol = protocol
            self._keyring = k = gkey.get_default_keyring_sync()
            import pdb; pdb.set_trace()
            print dir(k)

    class PasswordCache(PasswordCacheBase):
        def __init__(self, application=None):
            PasswordCacheBase.__init__(self, application)
            self._keyring = gkey.get_default_keyring_sync()

        def set(self, service, user, password):
            """Save a password in the store"""
            attrs = {
                "application": self.application,
                "user": user,
                "server": service,
            }
            gkey.item_create_sync(self._keyring,
                    gkey.ITEM_NETWORK_PASSWORD, self.application, attrs, password, True)

        def get(self, service, user, getpass=None, cache=False):
            attrs = {
                "application": self.application,
                "user": user,
                "server": service}
            try:
                items = gkey.find_items_sync(gkey.ITEM_NETWORK_PASSWORD, attrs)
            except gkey.NoMatchError:
                if getpass is not None:
                    password = getpass()
                    if cache:
                        self.set(service, user, password)
                    return password
                raise KeyError((service,user))
            return items[0].secret

        def clear(self, service=None, user=None):
            attrs = {'application':self.application}
            if user is not None:
                attrs["user"] = user
            if service is not None:
                attrs["server"] = service

            try:
                items = gkey.find_items_sync(gkey.ITEM_NETWORK_PASSWORD, attrs)
            except gkey.NoMatchError:
                return
            for item in items:
                gkey.item_delete_sync(self._keyring, item.item_id)
Duncan
Thanks for your answer. However, in both solutions, if you loose the login/password, you will have to change them on all the servers.Secondly, for security reasons, I don't want to use the same passwords on all the servers.I have to add some details:1) I am supposed to be the only one to use the app.2) For the moment, all password are stored on the application. I want now to remove them so that if someone uses my computer, he will not access the servers/computers.3) Each server has its own login/password configuration.Thanks for your help!Regards.
Korchkidu
Ok, so you are the only user but you want to protect the passwords used by your app. Can you clarify please what system you are using: Windows or Linux. If Windows then there are win32 api calls which allow you to store encrypted data that is accessible only to you. You don't need a master password as the data will be accessible when you are logged on but not when someone else is logged on. If Linux then you can use gnome-keyring similarly.
Duncan
Hi, I updated the question. But yes, I simply want to protect the application knowing that it will be installed on several different systems.
Korchkidu
A: 

You can use some symmetric-key algorithm and encrypt your data using master-password as a key.

Tomasz Wysocki
Thanks for your answer. Could you elaborate a little bit more on that? How could I do this in Python? Which module should I use?
Korchkidu