1

I am creating an application that allows a user to create an account, with a password, and have a set of public and private keys symmetrically encrypted with the password, so that they can be retrieved later when necessary, but only if the password is correctly entered.

For both the password and the key pair, I am creating a salt derived from OpenSSL's random number generator.

For the password, I am using bcrypt to encrypt with the aforementioned salt.

For the key pair, it's a little more complicated. I am using OpenSSL with aes-128-ctr to encrypt the pair, and the key for aes is derived from the password and the salt. Specifically, here's the code I am using in Ruby:

iter = 20000
key_len = 16
key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, salt, iter, key_len)
# unhashed password and salt from before

cipher = OpenSSL::Cipher.new('AES-128-CTR')
cipher.encrypt
cipher.key = key
iv = cipher.random_iv
cipher.iv = iv
encrypted = cipher.update(data) + cipher.final #key pair

My question is, is this system secure? Am I using best practices? As far as I can tell, I am, but I also want an outside opinion to ensure I'm not missing something major. If necessary I can provide the code for the password hashing, which is done using Ruby's bcrypt library.

Edit: I want to clarify in case it is asked that I'm not asking about the validity of my code but the security of the code.

  • Good question! Go forward in continuing to ask this (even to yourself)! You're on the right way! – F. Hauri Mar 22 '14 at 7:58
  • Without reviewing your overall design, 20,000 round PBKDF2 is pretty meh. It would be better if it was 10 times that, or maybe even more. I'm also curious why you're using Blowfish of all things. It's a perfectly nice cipher, but people normally use AES nowadays. – Matt Nordhoff Mar 22 '14 at 10:23
  • I thought that blowfish was better than AES, but it has been a while since I did the research and my recent (just now) research indicates that you're correct. I guess I thought since bcrypt uses blowfish that it's the best algorithm to use...? – josh Mar 22 '14 at 10:39
  • If it is possible to program on the client side, is it not better to make the client do the encryption and decryption so to avoid leaking the data due to server-side issues? You can derive the password with some salt and nonce (from server side) on the client side for authentication purposes (so an attacker can't do an offline attack). – billc.cn Mar 22 '14 at 12:12
2

The whole setup seems kosher to me. But I feel that too little is known about the application itself; did you consider the possibility of the password being leaked, or the decoded keys?

On some (web-based) systems, you'd have to "store" some or all of that data in a session object, which could well mean that the information is archived in plain text in a file on disk, or a key-value pair server, potentially accessible by other users as well as rogue processes, and possibly remaining accessible long after the user has logged out and disconnected. On such platforms you'd need to manually sanitize your data structures as far as possible; also, key-value assignment is non-trivial and typically requires a fairly unguessable session ID generator (which most aren't; unguessable, I mean).

You appear to already know about this, but I'll refer to session fixation nonetheless. Also, memory databases and key-value stores might, for performance's sake, not properly initialize their memory and allow an attacker to recover sensitive data; typically you use low level API to request a 4K block, and read off it before initializing it. If the server did its homework properly you'll get 4096 zeroes. If it did not, you'll get whatever was stored in that block by the user before you (I saw this happen in a BANK, of all things).

Then, access to those databases must be protected or secured somehow - a third party shouldn't be able to access data it has no right to (for example by enumerating stored datasets and querying their contents). On some systems you could use a different, totally unrelated (and vulnerable) virtual host to gain access to the content of such a keystore, by misdirecting the server into retrieving the wrong keyvalues. This caused an error, but sometimes the error text included precious information:

We're sorry. There is no document called 
    *?=|1395481948749|1346625
   |lserni
   |62609098affb79cd2e7c31b6793b539e
   |Fl==SPQR].
(Error: DB001A key was not found)
Try using the Search function, or contact us with the details of this issue.

Hypothetical example, attack #1

website process with PID 12345 connects to keyserverd:
    >> "store value '<PRIVATEKEY>' into key 'pk-12345-1395494389'"
    << "OK"

rogue process espies PID 12345 and knows the timestamp. It also connects:
    >> "Get me key 'pk-12345-1395494387'"
    << "ERROR: no such key"
    >> "Get me key 'pk-12345-1395494388'"
    << "ERROR: no such key"
    >> "Get me key 'pk-12345-1395494389'"
    << "OK: <PRIVATE KEY>"

This may happen during the process lifetime, or can happen afterwards. Unless of course:

    >> "Logging out. Delete key pk-12345-1395494389"
    << "OK"

Hypothetical example, attack #2

Just as before, but this time the API is more complicated (at low level, of which you might not be aware):

  • application (to library): "Store value '' into key 'pk'"
    • library: generates unique pk-12345-1395494389
    • library: connects
    • library: requests 1880 bytes for pk-12345-1395494389
      • server: allocates 1880 bytes plus necessary housekeeping structures
      • server: returns handle 123
    • library: stores '' into handle 123 ...
  • application (to library): "Delete value 'pk'"

    • library: determine pk-12345-1395494389
    • library: deletes pk-12345-1395494389
      • server: marks 1880 bytes as "free" but DOES NOT OVERWRITE THEM
      • server: destroys handle 123
  • attacker: request 1880 bytes for key "pwn"

    • server: allocates 1880 bytes plus ... and these are THE SAME BYTES FREED A MOMENT AGO
  • attacker: does NOT write anything to key 'pwn'
  • attacker: read content of "pwn" memory block (sometimes this is not allowed, but the memory is nonetheless accessible in some roundabout way. Some other times it isn't, but you can't count on getting a break)
  • attacker: finds "" (or sometimes "@#£!VATE KEY>")

In such a case:

    >> "Logging out. Store '##[...1.8Kb of #'s...]###NICE TRY!' 
                into key pk-12345-1395494389"
    << "OK"
    >> "Delete key pk-12345-1395494389"
    << "OK"

is slower, but more secure ("memory shredding" or "memory scrubbing").

In such a scenario, storing data to the database is as good as having passwords in cleartext; actually it's worse, because while a user must have file system access to the database files or authentication to the database server in order to reap the passwords, in many instances keystores are designed for "we're among friends" access, and anybody with local access can retrieve data (e.g. Redis "is designed to be accessed by trusted clients inside trusted environments"). This is OK, mind you, as long as you know what's happening and take countermeasures. After all, the Internet is insecure and yet here we are creating websites - but we use authentication and SSL when we need them, for we know of the insecurity. With Redis for example we could turn on authentication; now the attacker has to have both local access and the password, but the password is in the webapp files, so he needs read access to those files, too. If we make it so that this equals to impersonating the webapp user, we're (more or less) done: because a local attacker with webapp impersonation capabilities could just ask the password to any user and/or mount a man-in-the-middle attack. Of course this is all very, very rough; you need to examine your own workflow and ask yourself, "What could one do who arrived at this point? What else could he do? How do I take him out? How do I keep him out?".

Desktop applications also have their own insecurities related to password and plaintext data persistence both in memory and on disk (you have to treat specially the sensitive memory region to prevent it from being cleartext swapped on disk, and have to scrub the memory manually before freeing).

  • As in being leaked in the logs? I use Ruby on Rails, so I would set it to redact the private keys from the logs if necessary (the passwords are already by default). It is a web based system, so there's always the possibility that someone in the middle could get the information, but I would also use SSL (and possibly other encryption) to protect the session. I have other mechanisms in place (e.g. 15-min limit on unencrypted private keys, stored in memory database). I was mostly worried that the key pairs would not be stored correctly, and that I'm not making any major mistakes wrt security. – josh Mar 22 '14 at 9:13
  • No, I wasn't too worried about logs, but about private key storage, for example. It looks like you took care of that too :-) (the memory DB you use sanitizes data when releasing memory, right? Just being paranoid, is all). So you also considered session fixation at the user's end, and sanitize records on expiry. I believe you've got better security than 99.9% of sites out there :-) – LSerni Mar 22 '14 at 9:42
  • Oh, like how I would store the private keys when they're unencrypted? I also think this approach is good in case two passwords would be used to encrypt the key pair (general login password and specific password for key pair, for example). And thanks! :-) I tried to really map out this system to be secure. – josh Mar 22 '14 at 9:47
  • I'm not quite understanding what you mean by the memory issue some in-memory databases may have. Is this a common issue? I'll be using a unix socket rather than a TCP port for the db, so it's less likely someone can get access publicly. But the app I'm creating will deal with a crypto currency, so I want to be as secure as a bank should be, if not more so. – josh Mar 22 '14 at 10:14
  • I've added some more musings on the topic :-) – LSerni Mar 22 '14 at 13:45
-3

What’s bcrypt?

bcrypt() is the password hashing algorithm used by OpenBSD.

It’s awesome because:

Developed by The OpenBSD Project specifically for hashing passwords. They don’t screw around with security.
Salts are automatically generated and managed for you.
It’s orders of magnitude harder to crack than MD5, SHA2, and other standard hash algorithms.
It has a cost parameter which allows you to ratchet up the computational expense of checking a password — it can be low for low security situations or high for high security situations.

(make sure you have a C compiler and OpenSSL)

To use:

require 'bcrypt'

my_password = BCrypt::Password.create("my password") #=> "$2a$10$vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa"

my_password.version              #=> "2a"
my_password.cost                 #=> 10
my_password == "my password"     #=> true
my_password == "not my password" #=> false

my_password = BCrypt::Password.new("$2a$10$vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa")
my_password == "my password"     #=> true
my_password == "not my password" #=> false
  • 2
    I am not asking about bcrypt and I already know how to use it in Ruby. – josh Mar 22 '14 at 8:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.