Java Forum / Security / January 2007
Java Stream Encryption
JMecc - 24 Dec 2006 00:27 GMT I am trying to encrypt a stream that I am sending from a port on one machine to another machine (both sides using Java classes). The standard writing across ports works:
Server: serverSocket = new ServerSocket(port); Socket sock = serverSocket.accept(); PrintWriter out = new PrintWriter(sock.getOutputStream(), true); out.println("message");
Client: Socket sock = new Socket(server, port); BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream())); String success = in.readLine();
I am trying to RSA encrypt this stream by using the CipherInputStream & CipherOutputStream classes:
Server: serverSocket = new ServerSocket(port); Socket sock = serverSocket.accept(); CipherOutputStream cos = new CipherOutputStream(socket.getOutputStream(),cyO); PrintWriter out = new PrintWriter(cos, true); out.println("message");
Client: Socket sock = new Socket(server, port); CipherInputStream cis = new CipherInputStream(sock.getInputStream(),cyI); BufferedReader in = new BufferedReader(new InputStreamReader(cis)); String success = in.readLine();
I retrieve the public & private keys from files (keys are serializable objects) and then create ciphers: cyI = Cipher.getInstance("RSA"); //Input stream cipher to decrypt cyI.init(Cipher.DECRYPT_MODE, privateKey); cyO = Cipher.getInstance("RSA"); //Output stream cipher to encrypt cyO.init(Cipher.ENCRYPT_MODE, publicKey);
the keys were originally created via KeyPairGenerator: KeyPairGenerator KPG = KeyPairGenerator.getInstance("RSA"); KPG.initialize(1024); //Key length KP = KPG.generateKeyPair(); priKey = (RSAPrivateKey) KP.getPrivate(); pubKey = (RSAPublicKey) KP.getPublic();
The program just hangs, so running eclipse debugging on both gets me to the "String success = in.readLine();" line on the client, where it blocks waiting for the server. The server has no problem running its lines to send the data but for some reason the client does not recognize that anything is coming in and just keeps blocking. Anyone see what my pitfall is? Thanks, Jo
sgoo - 26 Dec 2006 00:06 GMT Not sure, maybe the server side needs a flush after the println?
JMecc - 26 Dec 2006 15:15 GMT > Not sure, maybe the server side needs a flush after the println? Thanks for the reply. Alas no, out.flush() didn't change anything. What I find odd is that the reading and writing semantics are good without encryption and then are not with encryption. SInce I am using a natural pair to do this (CipherInputStream & CipherOutputStream) there shouldn't be an issue so I am thinking that either:
a) I am using the Cipher[In/Out]putStream functions incorrectly. b) The way I generate the RSA keys (KeyPairGenerator.getInstance("RSA")) is incompatible with CipherInputStream.
Any thoughts? Thanks, Jo
Ralf Ullrich - 26 Dec 2006 16:10 GMT > The server has no problem running its >lines to send the data but for some reason the client does not >recognize that anything is coming in and just keeps blocking. Anyone >see what my pitfall is? See JavaDoc for CipherOutputStream.flush() where it reads: "Any bytes buffered by the encapsulated cipher and waiting to be processed by it will not be written out. For example, if the encapsulated cipher is a block cipher, and the total number of bytes written using one of the write methods is less than the cipher's block size, no bytes will be written out."
And regarding Block-Ciphers see also JavaDoc for Cipher: "Using modes such as CFB and OFB, block ciphers can encrypt data in units smaller than the cipher's actual block size. When requesting such a mode, you may optionally specify the number of bits to be processed at a time by appending this number to the mode name as shown in the "DES/CFB8/NoPadding" and "DES/OFB32/PKCS5Padding" transformations. If no such number is specified, a provider-specific default is used. (For example, the SunJCE provider uses a default of 64 bits for DES.) Thus, block ciphers can be turned into byte-oriented stream ciphers by using an 8 bit mode such as CFB8 or OFB8."
So, unless you created your Cipher as "CFB8" or "OFB8", what you observe is expected behavior. Works as designed.
As for RSA, well RSA unfortunately supports only ECB as mode, so you cannot create it with "CFB8" or "OFB8" as mode, and hence you wont be able to force a 'flush' on the Cipher through an CipherOutputStream other than through a stream close operation, which will (in your case) also effectively close the network connection.
My advice: Use DES/CFB8/NoPadding (or something similar with larger keys) for your encrypted network connection. You might want to encrypt the DES symmetric key via RSA and send it upfront on your network socket, before wrapping the socket in a Cipher*Stream, so you can establish a 'session key' for DES encryption, using your possibly already deployed asymetric RSA public/private keys.
HTH
cu
JMecc - 29 Dec 2006 18:38 GMT Thanks Ralf. I am still struggling to get this working however as I cannot create CFB8 keys (KeyPairGenerator KPG = KeyPairGenerator.getInstance("CFB8");). I have only successfully created RSA ones this way (maybe there are alternative ways to create keys). Of course with RSA keys I get to the last line of:
PrivateKey KI = Keys.readPrivate("cliPri.key"); cyI = Cipher.getInstance("DES/CFB8/NoPadding"); cyI.init(Cipher.DECRYPT_MODE, KI);
and have the error:
java.security.InvalidKeyException: No installed provider supports this key: sun.security.rsa.RSAPrivateCrtKeyImpl at javax.crypto.Cipher.a(DashoA12275) at javax.crypto.Cipher.init(DashoA12275) at javax.crypto.Cipher.init(DashoA12275) at client.setCipher(client.java:75) at client.<init>(client.java:55) at client.main(client.java:17)
I guess I am not restricted to RSA, it is just the only one I've heard of but I need some way of encrypting client-server information passage after an initial handshake.
Thanks, Jo
Ralf Ullrich - 29 Dec 2006 20:18 GMT >I guess I am not restricted to RSA, it is just the only one I've heard >of but I need some way of encrypting client-server information passage >after an initial handshake. Jo,
there are asymmetric encryption schemes, like RSA, those use public/private key pairs, i.e. one of both keys on each end, and there are symmetric encryption schemes, like DES, those use the same key on both ends. This is basic encryption know how, if you do not know this, you should try to read a book about encryption before you try to code a Java program using encryption, because chances are, that out of ignorance you make it wrong, and hence your program wont be more secure than it would be without using encryption.
However, if you use DES, then both sides have to use the same key, and in Java you do not create a KeyPair from KeyPairGenerator and the feed the private (or public) key of the pair to the DES Cipher, as you tried, but instead you use a KeyGenerator, to create a Key, and you have to share this key on both ends of your communication. However, anybody who intercepts this shared key, can read your communication, so it is only as secure, as your shared key is. But: it is easy to create a Cipher, that enables a per byte encryption, and hence allows a Cipher*Stream to transmit single bytes, just as you need it:
KeyGenerator kg = KeyGenerator.getInstance("DES"); kg.init(56); //Key length Key k = kg.generateKey(); // that is the key to be shared!
Cipher c = Cipher.getInstance("DES/CFB8/NoPadding"); c.init(Cipher.ENCRYPT_MODE, k); System.out.println(c.getBlockSize());
CipherOutputStream cos = new CipherOutputStream(System.out, c); cos.write('A'); cos.flush();
To avoid this, asymmetric encryption schemes like RSA use public/private key pairs, where you can freely distribute the public key, used for encryption, while keeping the private key, the one used for decryption, to yourself. Because you never transport your private key, it can never be intercepted, and hence this scheme can be much more easily protected against eavesdropper, than symmetric schemes. However, as you know by now, you cannot create a asymmetric Cipher, that allows per byte encryption as shown above. Hence such a Cipher used for a Cipher*Stream will only transmit encrypted bytes, if either enough data has been written or the stream is closed an the encryption scheme gets finalized. Also such asymmetric Cipher are much much slower than symmetric Ciphers and are not suitable to encrypt large amounts of data.
So in order to get both:
a) better secrecy of asymmetric encryption,
b) better speed and bytewise operation of symmetric encryption,
you have to combine them:
A) Create a KeyPair (e.g. for RSA). Keep the private key at the receiving end, and transport the public key to the sender. (Well, if nothing else works, put it in the newspaper, so the sender can read it there!)
B) Open a unencrypted Socket between Sender and Receiver.
C0) Create a Random symmetric Key (e.g. for DES) at the sending end.
C1) Convert this key to a sequence of bytes.
C2) encrypt this sequence of bytes using the public key and a (RSA)Cipher. The result is again a sequence of now encrypted bytes.
C3) Transmit this encrypted bytes openly through the socket to the receiving end.
C4) At the receiving end decrypt those bytes using the private key and (RSA) Cipher. You will get the same sequence of unencrypted bytes as in C1, but now you have this sequence on both ends, that is you have established a shared secret.
C5) At the receiving end create a symmetric Key but not random as in C0) instead use the sequence of bytes as initialization data. You will now have a Key-Object on both ends and both objects wil represent the same symmetric key.
D) Now use the Key object to create a symmetric Cipher. (Dont forget to tell it to use CFB8 or OFB8 mode!) And use this Cipher for your Cipher*Streams.
E) You have now established a secure data transport from the sending CipherOutputStream to the receiving CipherInputStream, which will actually transmit data, if you flush it.
If you cannot build it from this explanation, then please refrain from coding encryption part yourself and pay some expert to do it for you, because using encryption in a wrong way, is often not better than using no encryption at all. And if cou cannot do it with this explanation, then you will probably do it wrong anyway.
cu
Mike Amling - 29 Dec 2006 21:01 GMT > C0) Create a Random symmetric Key (e.g. for DES) at the sending end. Meaning the sending end for the symmetric key, which may or may not be the sending end for the message.
> E) You have now established a secure data transport from the sending > CipherOutputStream to the receiving CipherInputStream, which will > actually transmit data, if you flush it. Well, it's only as secure as DES, which is obsolete. Use AES or triple-DES. Don't forget proper RSA padding, such as OAEP or SAEP+, when sending the symmetric key. Don't forget to use authentication, such as a MAC or digital signature, on the message.
--Mike Amling
JMecc - 03 Jan 2007 21:25 GMT OK so I am on my way to doing this correctly - following Ralf's procedure:
1) Client sends RSA public key to server (unencrypted). 2) Server sends its public key back, also unencrypted (now we have 2-way RSA encrypted traffic and this works although slowly for big files as you told me it would) 3) Client makes password string including machine data, RSA encrypts it and sends that to the server. 4) Server verifies password and if ok, creates DES key and sends it to client (RSA-encrypted). 5) Client decrypts this DES key, client and server make input & output Stream*Ciphers with the DES key as initializer (this is where I am still having a problem). 6) Any further communication is done using these Stream*Ciphers.
I didn't get how to initialize a key with a byte sequence but I found on the web that I should be getting the parameters from my encryption cipher using cipher.getParameters() and then pass those such that they could be used to initialize the decryption cipher:
decrCipher.init(Cipher.DECRYPT_MODE, key, params);
The AlgorithmParameters class is not serializable though, so I use params.getEncoded() to get a byte[], pass that over and reconstruct params as:
params = AlgorithmParameters.getInstance("DES"); params.init(bytearray);
While this conversion performs fine within the same class, when sent from one machine to another, trying to init a new cipher with the recieved key and reconstructed params yields an InvalidKeyException: Illegal Key Size.
Googling this "Illegal Key Size" universally pointed the finger at the need for Sun's Unlimited Strength Java(TM) Cryptography Extension Policy (bottom of http://java.sun.com/javase/downloads/index_jdk5.jsp ). I downloaded these and used them to overwrite Java's regular ploicy files in all JRE (even within JDK) locations on both the client and server and rebooted both machines. The error persists. I would like to get this working before I try any triple-DES or anything more difficult.
Anybody experience this error? Thanks, Jo
JMecc - 04 Jan 2007 00:30 GMT Update: I got rid of the InvalidKeyException by changing:
secret = (SecretKey) cyUnwrap.unwrap(wrapped, "DES/CFB8/NoPadding", Cipher.SECRET_KEY);
to
secret = (SecretKey) cyUnwrap.unwrap(wrapped, "DES", Cipher.SECRET_KEY);
I'm now trying to set up the stream ciphers to see if the whole thing works.
Jo
JMecc - 04 Jan 2007 08:41 GMT OK it seems to be working fine now and man is DES faster than RSA! Thanks a lot for the help! I was still blocking on readLine() but changed this in favor of datainputstream.readFully() after a readInt - I send an int stating how many bytes to expect and then the bytes themselves. This makes everything run much smoother.
Thanks again for all the replies, Jo
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|