views:

399

answers:

7

I have an internal WPF client application that accesses a database.

The application is a central resource for a Support team and as such includes Remote Access/Login information for clients. At the moment this database is not available via a web interface etc, but one day is likely to.

The remote access information includes the username and passwords for the client's networks so that our client's software applications can be remotely supported by us. I need to store the usernames and passwords in the database and provide the support consultants access to them so that they can login to the client's system and then provide support. Hope this is making sense.

So the dilemma is that I don't want to store the usernames and passwords in cleartext on the database to ensure that if the DB was ever compromised, I am not then providing access to our client's networks to whomever gets the database.

I have looked at two-way encryption of the passwords, but as they say, two-way is not much different to cleartext as if you can decrypt it, so can an attacker... eventually. The problem here is that I have setup a method to use a salt and a passcode that are stored in the application, I have used a salt that is stored in the db, but all have their weaknesses, ie if the app was reflected it exposes the salts etc.

How can I secure the usernames and passwords in my database, and yet still provide the ability for my support consultants to view the information in the application so they can use it to login?

This is obviously different to storing user's passwords as these are one way because I don't need to know what they are. But I do need to know what the client's remote access passwords are as we need to enter them in at the time of remoting to them.

Anybody have some theories on what would be the best approach here?

update The function I am trying to build is for our CRM application that will store the remote access details for the client. The CRM system provides call/issue tracking functionality and during the course of investigating the issue, the support consultant will need to remote in. They will then view the client's remote access details and make the connection

A: 

If you need to retrieve the clear text password from the database then you're going to need to use a 2-way encryption scheme.

Don't put the key into your client application.

This way you can re-encrypt the data / change the key regularly if you're worried about the key being compromised. Though if your key is strong enough and isn't publicly available you should be safe

When sending the passwords over a network connection make sure to use SSL to encrypt the link.

The setup might look like this

client ---> server ---> DB

The server decrypts the the password and passes it over the SSL link to the client.

If you don't have a server and your clients are connecting directly to a DB then you can have your client call a stored procedure to retrieve the plain text password.

Glen
A: 

This sounds like a scenario DPAPI was designed for. Create a middle tier which can authenticate your clients, and then use DPAPI for encrypting data before storing it in the DB

Chris Ballard
A: 

You don’t need to keep your passwords in the database, just keep a hash of them (SHA1 as example). Then at login time just calculate the hash again and compare it with the sorted one.

Believe me, if you don't have stored passwords there is a lot of weight removed from your shoulders.

backslash17
Excellent advice if you're storing passwords so other people can log into your system. However, this is a question of storing passwords so people on his system can log into others, and therefore the password does have to be stored.
David Thornley
backslash, what you are describing is in use for user's passwords where I do not need to reverse the password. I am looking at storing the passwords securely and then being able to decrypt them again for viewing by staff or submission to third party service (ie gmail) via SSL etc.
TravisPUK
Sorry I didn't read all the question. My fault!
backslash17
+2  A: 

Store your passwords encrypted with AES and a good strong key. Instead of embedding the decryption key in the application, give it to your users (the support consultants). Then have your application prompt them for it when they need to look up the information. Not ideal, because everyone uses the same key, but it at least offers some protection if your database ever gets compromised.

Jason Day
Jason, I definitely like the idea of having the keys with the consultants.
TravisPUK
+5  A: 
erickson
+1. Excellent answer. Your password encryption scheme is eerily similar to what I was about to post...
Adam Paynter
I use remote access software, where the customer must initiate the connection to their computer. They can then monitor my progress during a call.
stukelly
The remote access software varies, some clients will simply have a VNC connections, some will have much more sophisticated VPNs using RSA key generators etc. It will typically come down to the client's I knowledge and budget. To be clear, we simply ask the client to provide remote access to their application/database server so we can remotely support our software products for them.
TravisPUK
The problem we have is that we are not in control over what method the client will provide us. We try to make recommendations, but they will end up doing what they like. This adds to the problem in that we need to provide the records and allow multiple sets of data for different clients. We would not be able to dictate what tech the clients use.
TravisPUK
Our support team consists of nine people who support around 89 clients, mostly based in the UK, but also in Europe, USA, SA and other countries. The majority of the time the Remote Access will be via the internet.
TravisPUK
erickson, thanks for the answer, I like the idea of the team members keys. Re the trust side, unfortunately whilst I trust my team members, I don't really have the ability to remove access to the passwords at this point, maybe in the future though.
TravisPUK
erickson, if I have nine team members and then up to 150 different remote access connections, how would I generate the key for each team member and the connections, and sore them in the DB?
TravisPUK
Can you give a bit more detail about a typical use case? For example, does one team member typically get the password during the course of a support case, directly from the customer? Or do you have some sort of account rep that gets the password and shares it with the whole support team? Have you built (or can you extend) some sort of central CRM server that your support team uses for this sort of info? Is it feasible to give your team software they run locally to decrypt the password?
erickson
erickson, The function I am trying to build is for our CRM application that will store the remote access details for the client. The CRM system provides call/issue tracking functionality and during the course of investigating the issue, the support consultant will need to remote in. They will then view the client's remote access details and make the connection.
TravisPUK
The CRM server is central, the client application will be on each support team members PC. It is definitely feasible to have software that runs locally to decrypt the passwords. At the moment the function I am building will do that within the CRM. It has been suggested to use a C++ app to to the decryption to protect against reflection.
TravisPUK
@TravisPUK: Maybe my answer can help a little.
Adam Paynter
+2  A: 

A similar situation occurs at our company, where the database administrators wish to maintain a pool of credentials amongst themselves.

I was originally going to post this idea, but erickson beat me to it. However, it may be worth while to post some pseudo code to elaborate, so I suppose my time answering the question isn't completely wasted...

Things you will need:

First off, let's set up the database schema. These tables will be demonstrated shortly.

CREATE TABLE users (
  user_id               INTEGER,
  authentication_hash   BINARY,
  authentication_salt   BINARY,
  public_key            BINARY,
  encrypted_private_key BINARY,
  decryption_key_salt   BINARY,
  PRIMARY KEY(user_id)
)

CREATE TABLE secrets (
    secret_id INTEGER,
    -- WHATEVER COLUMNS YOU REQUIRE TO ACCURATELY MODEL YOUR PASSWORDS (CLIENT INFO, ETC)
    PRIMARY KEY(secret_id)
)

CREATE TABLE granted_secrets (
  secret_id      INTEGER,
  recipient_id   INTEGER,
  encrypted_data BINARY,
  PRIMARY KEY(secret_id, recipient_id),
  FOREIGN KEY(secret_id) REFERENCES secrets(secret_id)
  FOREIGN KEY(recipient_id) REFERENCES users(user_id)
)

Before a user can begin using this system, they must be registered.

function register_user(user_id, user_password) {
    authentication_salt = generate_random_salt()
    authentication_hash = hash(authentication_salt, user_password);

    (public_key, private_key) = asymmetric_cipher_generate_random_key_pair();

    decryption_key_salt = generate_random_salt()
    decryption_key = derive_key(decryption_key_salt, user_password)
    encrypted_private_key = symmetric_cipher_encrypt(
        input => private_key,
        key   => decryption_key
    )

    // IMPORTANT: The decryption_key_hash is never stored

    execute("INSERT INTO users (user_id, authentication_hash, authentication_salt, public_key, encrypted_private_key, decryption_key_salt) VALUES (:user_id, :authentication_hash, :authentication_salt, :public_key, :encrypted_private_key, :decryption_key_salt)")
}

The user can sign in to the system.

function authenticate_user(user_id, user_password)
    correct_authentication_hash = query("SELECT authentication_hash FROM users WHERE user_id = :user_id")

    authentication_salt = query("SELECT authentication_salt FROM users WHERE user_id = :user_id")
    given_authentication_hash = hash(authentication_salt, user_password)

    return correct_authentication_hash == given_authentication_hash

A secret can then be granted to a recipient user.

function grant_secret(secret_id, secret_data, recipient_id) {
    recipient_public_key = query("SELECT public_key FROM users WHERE user_id = :recipient_id")

    encrypted_secret_data = asymmetric_cipher_encrypt(
        input      => secret_data,
        public_key => recipient_public_key
    )

    execute("INSERT INTO granted_secrets (secret_id, recipient_id, encrypted_data) VALUES (:secret_id, :recipient_id, :encrypted_secret_data)")
}

Finally, a user that have been granted access to a secret (the recipient) can retrieve it.

void retrieve_secret(secret_id, recipient_id, recipient_password)
    encrypted_recipient_private_key = query("SELECT encrypted_private_key FROM users WHERE user_id = :recipient_id")

    recipient_decryption_key_salt = query("SELECT decryption_key_salt FROM users WHERE user_id = :recipient_id")
    recipient_decryption_key = derive_key(recipient_decryption_key_salt, recipient_password)
    recipient_private_key = symmetric_cipher_decrypt(
        input => encrypted_recipient_private_key,
        key   => recipient_decryption_key
    )

    encrypted_secret_data = query("SELECT encrypted_data FROM granted_secrets WHERE secret_id = :secret_id AND recipient_id = :recipient_id")

    secret_data = asymmetric_cipher_decrypt(
        input       => encrypted_secret_data,
        private_key => recipient_private_key
    )

    return secret_data

Hopefully this can help. It certainly helped me flesh out my ideas.

Adam Paynter
Adam, thanks for the example code. I was struggling a little to understand erickson's concepts, not what he was discussing, but how to 'make it happen'. Your examples have definitely helped clarify the concept.
TravisPUK
I actually already have the user's passwords hashed and stored in a user table, along with their associated salts in a user salt table also.
TravisPUK
@TravisPUK: Travis, I modified my answer to accommodate your hash. The hash used to decrypt a user's private key SHOULD NOT BE STORED. Otherwise, anyone with access to the database can now decrypt any user's secrets. The modified answer distinguishes between the hash used to authenticate a user and the hash used to decrypt their private key.
Adam Paynter
Adam, thanks for the update. I really appreciate all of the help you guys have given me. I was also working on the assumption that I would need to have separate fields for the private key and the hashed password. ;)
TravisPUK
@Travis: Whew, I'm glad to hear! :) Let us know how things turn out.
Adam Paynter
@Adam, am I missing something? I assume that the client's passwords are stored in the secrets table. What I am not sure about, there does not appear to me to be any reference to creating that secret or actually retrieving the value of the secret for the operator to view it. I can see the 'encrypted_secret_data' assignment in void retrieve_secret', but that appears to be getting it from the 'granted_secrets' table, not the 'secrets' one. Am I missing something?
TravisPUK
@Travis: The client's password is stored in the granted_secrets table because it must be uniquely encrypted FOR EACH USER of the system. For example, if you have two employees (Alice and Bob) that need the client's password, you insert one entry encrypted using Alice's public key and another entry encrypted using Bob's public key. If this did not happen, we would be back to everyone having to share a common password again.
Adam Paynter
@Adam, I did think that each user would have their own 'version' of the client's password, so then what does the secret table actually do?
TravisPUK
@Travis: Good question. I originally did not include the secrets table. I included it so that you could add whatever columns you want to help look up a specific secret_id. For example, the secrets table for you may include server_ip and server_account_name. In truth, each user should not have a "version" of the client's password. When you want to grant access to a password to multiple users, you call grant_password once for each user. Even though encrypted_data will be different, the decrypted data should always be the same.
Adam Paynter
@Adam, at the moment I have a table that stores the clients remote access details, this currently includes the username and password to gain access to the remote network. It is the password that I want to encrypt and therefore allow the operators to view it and change it if necessary. I understand that the granted_secrets table will store an encrypted version of the same password for each user, however at the moment I do not understand how each user will get their own version.
TravisPUK
@Travis: You begin by calling register_user for each user of your system. You then select all rows from your current table. For each row in your current table, ensure a corresponding row exists in the secrets table. While you are still on this row, call grant_secret for each user of your system. Suppose there are N rows in your current table and you have X users. When you are done, the secrets table should have N rows and the granted_secrets table should have N*X rows.
Adam Paynter
@Adam, thanks for your help on this. I would take it then that my existing remote access table that contains the passwords would effectively become my secrets table. I assume that once I create my granted secrets for each user I would then remove the cleartext password from the secrets table. My next question is what happens when I get a new user? If I no longer have the actual password in cleartext anymore how would I generate their granted secret?
TravisPUK
@Travis: You could, in fact, have the granted_secrets table reference your original table and forget about the secrets table altogether. You touched on an interesting quality of this system. If you want to grant access to a new user, an existing user that already has access to the password must do it. The existing user calls retrieve_secret (to get the cleartext password) and then calls grant_secret for the new user.
Adam Paynter
@Adam, perfect. I think I have sucked enough of your brain matter on this topic and once again I really appreciate your help.
TravisPUK
@Travis: It's been a pleasure. Like I said, we're going through the same thing here, so it can only help my situation too. Keep me posted on how things go with your system! :)
Adam Paynter
Thanks for making this concrete, Adam. The only thing I'd suggest is to use a better key derivation function to encrypt the private key, like PKCS#5's PBKDF2.
erickson
I almost have this done, but I am having trouble generating the key value pair '(public_key, private_key) = asymmetric_cipher_generate_random_key_pair()'. All of the example asym functions I can find deal with RSACrypto or Rijndael.
TravisPUK
erickson: Thanks for the tip! :) The term "key derivation function" was not part of my vocabulary until now. I updated the answer accordingly.
Adam Paynter
Travis: Rijndael (also known as "AES") is a symmetric cipher, not asymmetric. What language are you writing the code in? It will probably have a library that can help you generate these keys. I personally recommend the Bouncy Castle project (http://www.bouncycastle.org/). They support Java and C#. If you're still having trouble, you could try asking a separate question. You're bound to get plenty of people with the answer to this subject.
Adam Paynter
Adam, I am writing in C#. One of our other projects streams here that I am not involved in uses Bouncy Castle, so I might take a look at that. Thanks again.
TravisPUK
@Adam, just to let you know, I have been able to complete this function with some help from a fellow dev friend of mine (provided an answer to another question of mine here on StackOverflow - http://stackoverflow.com/questions/844997/encrypting-a-bouncycastle-rsa-key-pair-and-storing-in-a-sql2008-database). This works really well. Thanks for your's and erickson's help.
TravisPUK
Travis, I am glad to hear. :)
Adam Paynter
+1  A: 

If you are worried about giving original user password to consultants then here is a food for thought.

What about generating a temporary password/token exclusively for your support member?

When a customer makes a call, create a token and put it in a map along with the userid. Send this token or encrypted token to your customer support member. Customer support member will then login by using username and token. System check for the token in validtoken map and finds its a valid token and grant access to the system.

Support memeber resolves the problem. Then token is removed from the tokenMap.

In this way you never reveal the original user password to consultants. Thus you can store the user passwords using your own favorite security techniques.

You may encrypt the token using consultant's public key before sending them.

Tokens may given an expiry time.

@kevin, thanks for your response. Like the idea you are putting forward however we are not in control of the username and passwords issued to us by the client, nor the client's preferred software. The consultants actually need to have the client's username and password to remote in as opposed to a custom one for the session.
TravisPUK