6

I am writing a secure file sharing application in Java. The general architecture looks like this:

  1. User wishes to encrypt a file for secure sharing between multiple users.
  2. The application generates a random UUID on the client and uses this as the AES 256 password, and encrypts the data with the UUID.
  3. The UUID is then RSA encrypted with each person's public key. Once per shared user.
  4. Each encrypted UUID packet is stored as part of the file in a custom file header.
  5. The file is then uploaded to a server where others can access it.
  6. The user's can each use their private key to read the AES encryption key and decrypt the file.

Here is the catch. The user's private key must be encrypted and stored on our servers in our database so that the files can be accessed from multiple locations. The private key will be encrypted with a user selected password on the client prior to being uploaded to the server.

I would like to do this using AES 256 bit encryption. And I would like to do the entire thing without relying on BouncyCastle libraries or any 3rd party libraries. It needs to use the standard Java 5 libraries, which is why I have chosen to use AES 256 encryption and RSA rather than something like PGP.

Can anyone find anything inherently insecure with this approach, or think of a more efficient way to do this?

Edit:

OK, I'm updating the question because all of the answers I am getting are suggesting that I not transmit the private key to the server. The reason I need the private key on the server is because the user's need to be able to access their data from multiple clients and multiple locations (ie: their iphone, their ipad, their work laptop, their home pc). They do not want to have to manage and copy their keys from device to device, which is even more insecure than storing their keys on our server because they would just end up emailing them to themselves at that point.

Nobody
  • 690
  • 2
  • 9
  • 18
  • 1
    I can't see any huge issues, just some notes; you have no protocol for revocation (you cant take someone out of the loop without starting over on a particular file). you are storing private keys serverside, that feels like it should be avoided, why not have the server as 'one of the accessors' just like any other? I would be more tempted to use keyslots, something along the lines of LUKS: http://wiki.cryptsetup.googlecode.com/git/LUKS-standard/on-disk-format.pdf (its a fairly light read, and might throw in a few ideas for you). – lynks Jul 17 '12 at 17:21
  • Thanks for the link lynks! This is very interesting, and will read it in more detail. We do not want the server to have access to the files, which is why all data will be encrypted on the client prior to being sent to the server. Each user is responsible for the security of their own files by choosing a strong password. – Nobody Jul 17 '12 at 18:02
  • Storing an encrypted key store on the server is just fine... as long as it is encrypted properly. – erickson Jul 17 '12 at 18:43
  • @erickson: Thanks, as far as encryption of the private key, I was planning on using something along the lines of your answer given to this question: http://stackoverflow.com/questions/992019/java-256-bit-aes-password-based-encryption/992413#992413 – Nobody Jul 17 '12 at 18:51
  • Thanks for all the feedback I got on this question. I have accepted erickson's answer because he provided some useful code I can utilize in my project, and because he understood the need for storing the user's private key. – Nobody Jul 17 '12 at 21:21

4 Answers4

5

The big problem with this is using UUIDs. Although UUIDs are (sort of) guaranteed to be unique, quite a bit of what they contain is quite predictable; substantial amounts remain constant across all the UUIDs generated on a single machine. As such, if a person gets access to (for example) their own key, they can probably guess many other people's keys fairly easily.

The other part that's problematic is storing user's private keys on the server. This makes the whole rest of the scheme relatively fragile, since access to those keys obviously gives access to all the rest of the data. It also (apparently) means you'll normally be decrypting the data on the server, so when the user accesses that data across the network, it'll either need to be re-encrypted for transmission, and decrypted on the users's machine, or else you'll be transmitting the data in the clear (thus rendering most of the encryption useless).

Edit: As to how I think I'd do this:

I'd have a list of public keys on the server. When a client wants to share a file with some other clients, it obtains the public keys for those clients from the server. It then generates a secure random key, and encrypts the data with that key. It then encrypts the random key with the public keys of all the other clients that are supposed to be able to access the data. Put those together into a stream, and transmit them to the server. The other clients can then download the stream, decrypt the key with their private key, and use that to decrypt the data itself.

This means each client's private key remains truly private -- it never has to leave their machine in any form. All they ever have to share with the rest of the world is their public key (which, by definition, shouldn't cause a security problem).

With that, the two obvious lines of attack are against the random number generator, and against RSA itself. For the random number generator, I'd use Java's SecureRandom -- this is exactly the sort of purpose for which it's intended, and if memory serves it's been pretty carefully examined and significant breaks against it seem fairly unlikely.

I won't try to comment on the security of RSA itself. For now, I think your primary concern is with the protocol, not the encryption algorithm proper. Suffice to say that if RSA were significantly broken, you'd obviously need to change your code, but you'd have a lot of company.

With this, it's pretty much up to the client to store their private keys securely. I like smart cards for that job, but there are quite a few alternatives. From the viewpoint of the server and protocol, it's no longer really a factor at all though.

Edit 2: As for dealing with multiple devices, I think I'd simply treat each device as a separate user, with its own public/private key pair. I'd then (probably) group those together by the actual users, so I can easily choose "Joe Blow" to give him access on all his devices -- but with a hierarchical display, I could also pretty easily restrict access to a subset of those, so if I want to share it with Joe on his office machine, but it's sensitive enough that I don't want it going where somebody might look over his shoulder while he looks at it, I can pretty easily do that too.

This keeps life simple for the users, but retains the same basic security model (i.e., private keys remain private).

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Thanks for your feedback Jerry. A couple of points though. Although I am storing the private keys on the server, they will be encrypted on the client with the user's password. So I'm not sure how access to those keys will give access to all the rest of the data. Also, all data encryption/decryption will happen on the client, not on the server. The private key is only stored on our server so that it can be downloaded by the client and re-used from various locations. – Nobody Jul 17 '12 at 17:38
  • Could you explain what you mean when you say that you can guess many other people's keys just by having access to one UUID? It is my understanding that this is not how UUIDs work. They are supposed to be cryptographically random aren't they? – Nobody Jul 17 '12 at 17:42
  • 2
    @Unsavory: No, UUIDs are not (at least normally) even close to cryptographically random. As far as the private keys go, it's pretty simple: the biggest weakness in almost any security scheme is user's passwords, which are often easy to guess. If I can get a whole file of encrypted passwords, chances are I can guess at least 1/4th of the passwords. That's true in any case, but getting the file makes something like a dictionary attack relatively easy. Salt helps, but not putting all the encrypted passwords in one place helps more. – Jerry Coffin Jul 17 '12 at 17:46
  • Thanks for the clarification. I was planning on using an 8 byte salt generated via SecureRandom, and storing the salt in the file header. We definitely understand that weak passwords foil the entire system to some degree, but remember they also need access to the user's private key. – Nobody Jul 17 '12 at 17:59
  • Thanks for your edit Jerry, but please see my addition to the question. The private key must be accessible from multiple devices and the user cannot be trusted to keep this private key safe as they transmit it from device to device. This is why we have chosen to store the private keys on our server as we feel it is more secure than having the users email their keys around between all their devices. Your edits do give me more clarity though, such as using SecureRandom instead of UUIDs for the file encryption key. – Nobody Jul 17 '12 at 18:36
  • That's a great idea Jerry! I hadn't though of that. The only issue I see with this approach that I can't think of a solution for, is how would a user go about adding a new device? Let's say he got a new ipad and wants to access his files? ALL of his files would need to be modified from one of his existing clients to encrypt the file encryption key for his new device. This is a pretty big hurdle. – Nobody Jul 17 '12 at 19:14
  • @Unsavory: Yes, he'd have to modify data from an existing client to add a new one -- but it would only involve re-encrypting keys, not the data itself. Even with thousands of files, the amount of work would be fairly small. I'd have the server coordinate: encrypt the new private key with the public key of the existing client and send to server. Server sends that along with the encrypted key for each file to the existing client, which re-encrypts the keys with the new key, and sends them back to the server. Obvious difficulty: if all existing clients become inaccessible. – Jerry Coffin Jul 17 '12 at 19:25
  • The idea is for the user to be able to access his data even if his original device is not with him or available. You are also going in circles by insisting the private key never leave the client, but with your latest comment, you contradict yourself and suggest sending the private key to the server in order to add a new client. I don't see how this is any different than my original approach, except that it adds a bunch of layers of complexity. Again, thanks for all your input on this, as you have given me some very insightful information. – Nobody Jul 17 '12 at 20:33
  • @Unsavory: Yes and no. Yes, I'd prefer that the private key never leave the client. But no, I'm not nearly as bothered by it's being sent to my other client via the server, as by its sitting on the server at all times. Still, if it doesn't fit your needs, then it's not a good solution. – Jerry Coffin Jul 17 '12 at 21:32
2

The scheme you outline is equivalent to CMS (the standard underlying S/MIME) and PGP; fundamentally, it is secure. In CMS, this mode is called "key transport". You could also use multi-party "key agreement," with an algorithm like DH or ECDH.

The only problem is that you are using poorly chosen keys for AES.

I can't think of any reason to use a random UUID, which contains non-random bits. Just use the normal key generation mechanism of the Java Cryptography Architecture. Keys, plaintext, and ciphertext should all be represented as byte sequences, unless you need to accommodate some external storage or transport that only accommodates text.

Iterable<Certificate> recipients = null;
KeyGenerator gen = KeyGenerator.getInstance("AES");
gen.init(256);
SecretKey contentEncryptionKey = gen.generateKey();

Initialize the AES cipher and let the provider choose an IV.

Cipher contentCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
contentCipher.init(Cipher.ENCRYPT_MODE, contentEncryptionKey);
AlgorithmParameters params = contentCipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();

For each recipient, initialize the RSA cipher and encrypt the AES key.

Cipher keyEncryptionCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
for (Certificate recipient : recipients) {
  keyEncryptionCipher.init(Cipher.WRAP_MODE, recipient);
  byte[] encryptedKey = keyEncryptionCipher.wrap(contentEncryptionKey);
  /* Store the encryptedKey with an identifier for the recipient... */
}
/* Store the IV... */ 
/* Encrypt the file... */

Having users select and remember passwords that give 256 bits of effective strength is unreasonable. To get that strength, you'd have to randomly choose passwords, encode them as text, and have users write them down on a card. If you really need that much strength, you could check out a smart-card–based solution for storing the users' RSA keys.

I'd highly recommend using a CMS library to store your files. It will increase your chances that the protocol you're using is safe, the code you are using has had more review, and that other tools, libraries, and systems can inter-operate with the encrypted messages. BouncyCastle's API is a little obscure, but it might be worth learning it.

(I can't remember if Java 5 supports "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"; if it does, you should use that instead of PKCS1Padding.)

erickson
  • 265,237
  • 58
  • 395
  • 493
  • Thanks erickson for the code. It would have worked, but as I've explained in my question update, I must store the user's private key on the server. – Nobody Jul 17 '12 at 18:53
  • 1
    @Unsavory Right, this is just the encryption code, using all of the users' public keys (included in certificates). For decryption, you'd fish through the encrypted keys, matching up some metadata that identifies that particular user. Pull down their encrypted key store from the server, get their password to decrypt the RSA private key, and proceed with decryption. – erickson Jul 17 '12 at 18:56
  • Gotcha! Sorry, I should have analysed your code more thourougly. This is the code to encrypt the file data and not the code to encrypt the user's private key. Thanks for the helpful code snippets! To encrypt the private key, I was planning on using something like your answer to this question here: http://stackoverflow.com/questions/992019/java-256-bit-aes-password-based-encryption/992413#992413. Does this sound about right? – Nobody Jul 17 '12 at 19:08
  • @Unsavory Yes, that's a good pattern for password-based authentication. Using GCM or another integrity mode was a good suggestion by owlsted. Or, you could actually store a PKCS12 key store for each on the server, and decrypt it using the `KeyStore` API. – erickson Jul 17 '12 at 19:18
  • Could you help me understand what the purpose of message integrity is in this context? I'm not sure I understand what it would be used for in this application, or why I would need it? Couldn't I just sign the encrypted file with the owner's RSA private key if I were concerned about ensuring the integrity of the file itself? – Nobody Jul 17 '12 at 20:42
  • 1
    @Unsavory If you don't use PKI, users could store public keys for other recipients in this key store that is maintained on the server. Using GCM would protect those public keys from tampering, while the cipher protects the user's own private key from snooping. Integrity would be very nice for the content cipher too, as you can check the integrity of the file block-by-block, rather than having to complete a pass over the content before finding out the signature was bad. – erickson Jul 17 '12 at 21:21
  • Got it, that makes sense. Thanks for explaining. I notice in your code sample that you are using ECB mode for the RSA encryption. Everything I have read says to stay away from ECB for all but a few edge cases. Is there a reason you are choosing ECB mode? – Nobody Jul 17 '12 at 21:35
  • @Unsavory There really isn't a mode for RSA. It makes sense in the context of block cipher primitives, where some information is propagated from block to block. There's no equivalent chaining of RSA operations; if the "message" is too long, you probably shouldn't be using asymmetric encryption. RSA cipher texts are randomized through their padding. ECB, in the context of RSA, really means "none" or "not applicable". – erickson Jul 17 '12 at 21:39
  • Indeed the part I always missed was storing the IV. That was a blockage for me the whole day. :). Storing the IV and using it later to decrypt solved my problems. Thanks :) – Jimmy Ilenloa Oct 30 '15 at 19:37
1

OK, this question is asking for a protocol discussion, so it is not completely according to stackoverflow's standards. That said, let's see if we can make some remarks anyway :) :

  1. The Bouncy Castle PGP libraries have a very permissive license, so you can even copy them into a sub-package within your code;
  2. Besides PGP there are also other standard container formats such as CMS or XML encryption, although the latter might not be such a good general purpose format;
  3. Instead of UUID's, I would strongly suggest to use a well seeded PRNG such as the Java JCE "SHA1PRNG" to create the AES keys - I don't see any strong reason why you should rely on something like an UUID in your scheme;
  4. AES keys are supposed to consist of random bits to have enough entropy, thinking of them as "passwords" is leading into a trap: you cannot use a String as a secure AES key;
  5. The user will have to trust your application and server, you are acting as a trusted third party: you can send user's passwords to your server, you can send incorrect public keys to the users etc. etc. etc.
  6. Your scheme is not protected against any man in the middle attacks (and many argue this cannot be done without using SSL)
  7. Instead of directly encrypting with a password, you should look into something like Password Based Encryption PBKDF2 to encrypt the RSA private key;
  8. Try and add integrity protection when encrypting, from Java 7 onwards you may use AES in GCM mode, it's well worth it.
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Thanks owlstead. There are other reasons I do not want to use Bouncy Castle. I have decided NOT to use UUID's, but to use SecureRandom instead. We understand the user will have to trust us, but our client code will be opensource so they can verify we will not send their password to the server. All transmission will use https to and from our server to protect from man in the middle attacks. Yes, we fully plan on using password based PBKDF2 for the encryption. – Nobody Jul 17 '12 at 18:58
  • Actually, some people will actually argue that you can have man-in-the-middle attacks even with SSL. See [Marlinspike at Black Hat DC 2009](http://www.thoughtcrime.org/software/sslstrip/) – Eric B. Jul 17 '12 at 19:54
  • 1
    Even though SSL (or actually, the latest versions of TLS) are pretty well written protocols, there are certainly some attacks on SSL. If you however use an up to date TLS with the correct cipher suites and use model, it should be more secure than most protocols (if you can measure such a thing). – Maarten Bodewes Jul 17 '12 at 19:58
-1

It all depends on how "secure" you want the encryption to be. Obviously RSA is a well document/accepted standard for PKI. That being said, any time you provide the plaintext as well as the encrypted text, it makes it significantly easier for a hacker to decrypt the ciphertext knowing part of the plaintext. Here, you are doing precisely that. Although you are only transmitting the encrypted UUID, by having the same plaintext encrypted with multiple keys gives an attacker significant insight into the payload. Furthermore, if the hacked is actually one of the recipients, he is able to decode the UUID, and thereby automatically knows the plaintext that is being encrypted by the other users' public keys.

This is not likely a critical issue for you, but just thought I would point out a security risk.

I am not entirely sure why you need to store the user's private key, however. Furthermore, by using a simple password to encrypt the private key, you have basically reduced the overall security of the entire system to the strength of the user's password. Finally, if the user loses his password, he is toast; no way to recover any data.

I did something similar in the past but stored the results in a DB. However, I used the BouncyCastle libraries at the time, so am not sure how to accomplish this without them.

Eric B.
  • 23,425
  • 50
  • 169
  • 316
  • Actually, there is a way to recover the data. All that's encrypted with the user's password is their private key. If you (for example) also encrypt each UUID with an administrative key, then if the user loses their password, you can still retrieve the UUID with the admin key, then overwrite their old encrypted UUID with the same UUID encrypted with their new password to re-enable their access to the data. – Jerry Coffin Jul 17 '12 at 17:29
  • Thanks Eric. I'm not providing the plaintext at all. All data will be encrypted on the client prior to being transmitted to the server. We understand the entire security of a user's files lies on the strength of the user's password. Our users understand the importance of strong pass phrases. – Nobody Jul 17 '12 at 17:48
  • 1
    I don't think you understand RSA well enough if you think you can get to the private key if you have multiple plain texts & ciphertexts stored together. – Maarten Bodewes Jul 17 '12 at 17:50
  • @Jerry - We would not be able to encrypt the UUID with an administrative key because all encryption will happen on the client side before being transmitted to the server. I'm updating my question to reflect this. – Nobody Jul 17 '12 at 17:51
  • @JerryCoffin Yes - the original data may still be accessible if you have an admin key encrypting it as well. But the user will not have any capacity of changing his password, while retaining access to any files previously transmitted/received. – Eric B. Jul 17 '12 at 18:38
  • @owlstead I'm not trying to suggest that combining known plaintext and ciphertext will allow someone to reverse engineer a private key. Otherwise, the encryption becomes extremely weak. However, I am suggesting that the more known plaintext you have as part of a ciphertext block, the easier it becomes to decrypt the entire ciphertext block. – Eric B. Jul 17 '12 at 18:40
  • @Owlstead You're right. That was incorrectly stated in my initial post; something must have gotten mixed up in the editing. I have edited the original text to rectify. Thanks for pointing that out. – Eric B. Jul 17 '12 at 19:44
  • No problem, comment removed. Not that it's correct for a correctly applied crypto algorithm, let alone RSA encryption which uses random padding to hide the plain text... – Maarten Bodewes Jul 17 '12 at 19:53