Java RSA Server Authentication - Is this secure? - java

I'm slightly new to RSA, and I'm attempting to make a server-client system where the client can only connect to the official server. The way I want to achieve this is with high bit rsa encryption. What I've done is create a random string of bits both from the server and client, combined them together, and had the client encrypt said bytes with the public key. This encrypted data is then sent to the server, for decryption, and after it is encrypted, it will be sent back to the client where it can check it against the original message, assuring that the server is indeed the one it is attempting to communicate with. My question is, is this secure as possible, using the specified bit length? (64 public key, 4096 private)
public static boolean auth(DataInputStream in, DataOutputStream out) throws IOException {
byte[] clientKey = new byte[2048];
new SecureRandom().nextBytes(clientKey);
out.write(clientKey);
out.flush();
byte[] serverKey = new byte[2048];
in.readFully(serverKey);
byte[] plainKey = new byte[4096];
System.arraycopy(clientKey, 0, plainKey, 0, 2048);
System.arraycopy(serverKey, 0, plainKey, 2048, 2048);
byte[] encryptKey = new byte[4096];
toByteArray(new BigInteger(1, plainKey).modPow(RSA_PUBLIC, RSA_MODULUS), encryptKey, 0, 4096);
out.write(encryptKey);
out.flush();
byte[] serverAttempt = encryptKey;
in.readFully(serverAttempt);
return Arrays.equals(plainKey, serverAttempt);
}

Related

Lost bytes in input stream?

I'm writing a program where I send bytes of a key from a keypair that I created over an output socket and use them to recreate the key on the other side.
Server:
KeyPairGenerator dsaKeyPairGenerator =
KeyPairGenerator.getInstance("DSA");
dsaKeyPairGenerator.initialize(1024);
KeyPair dsakeyPair = dsaKeyPairGenerator.generateKeyPair();
PrivateKey dsaPrivate = dsakeyPair.getPrivate();
PublicKey dsaPublic = dsakeyPair.getPublic();
byte[] dsaPublicbytes = dsaPublic.getEncoded();
clientSocket.getOutputStream().write(dsaPublicbytes.length);
clientSocket.getOutputStream().write(dsaPublicbytes);
Client:
int dsalength = clientSocket.getInputStream().read();
byte[] dsaPublicbytes = new byte[dsalength];
clientSocket.getInputStream().read(dsaPublicbytes);
X509EncodedKeySpec dsaspec = new X509EncodedKeySpec(dsaPublicbytes);
KeyFactory dsakeyFactory = KeyFactory.getInstance("DSA");
PublicKey dsaKey = dsakeyFactory.generatePublic(dsaspec);
However, on this line I get an error:
PublicKey dsaKey = dsakeyFactory.generatePublic(dsaspec);
The trace for the error itself:
Exception in thread "main" java.security.spec.InvalidKeySpecException: Inappropriate key specification: IOException: Detect premature EOF
at sun.security.provider.DSAKeyFactory.engineGeneratePublic(DSAKeyFactory.java:119)
at java.security.KeyFactory.generatePublic(KeyFactory.java:334)
at Client.main(Client.java:36)
I have researched and I've seen that the EOF occurs because there aren't enough bytes to create the key, which leads me to believe that it is a problem with how I am sending the bytes. Am I sending the bytes incorrectly?
Lost bytes in input stream
Unread bytes in input stream. You assumed that read() filled the buffer. It isn't obliged to do that. Use DataInputStream.readFully().
You're also limiting yourself to 128 key bytes by using write(int), and read() with no parameters, for sending/receiving the length word. Use DataOutputStream.writeInt() and DataInputStream.readInt() for that.
Assuming that your first byte is not sending the size of key byte array or you are using the key that has size bigger than 256-bit, then the array will be incomplete.
Try using DataOutputStream methods of writeLong() or writeInt() to send byte size to initiate the right size array. Secondly try using buffers to read when its being send.
Here is little bit of my code from my file socket sender:
This is sending part:
OutputStream os = sock.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeInt(mybytearray.length);
dos.write(mybytearray, 0, mybytearray.length);
This is recieving part:
InputStream in = sock.getInputStream();
DataInputStream dis = new DataInputStream(in);
int byteSize = clientData.readInt();
byte[] byteData = new Byte[byteSize];
dis.read(byteData);
You may want to buffer receiving part by telling how many bytes to read using method of DIS read(byte[],int,int) until all of the bytes were read. I tested my code on same machine with very small size data so the connection stability was not a factor.

Using ObjectOutputStream to send a byte array of a public key, encrypt something, and send the encrypted version back

So I have a server side public key and private key, my aim is to send the client the public key, the client will encrypt a string with the key, then send the bytes through a stream, and the server will decrypt the byte array.
Exception:
javax.crypto.BadPaddingException: Decryption error
Code:
Sending the encoded key.
handler.getOos().writeObject(publicKey.getEncoded());
handler.getOos().flush();
Receiving the byte array (of the encoded key):
Object o = ois.readObject();
if (o instanceof byte[]) {
JChat.get().setServerPublicKey(KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec((byte[]) o)));
JChat.get().go();
}
The go() method (here I use a DataOutputStream to send the byte array):
public void go() {
String text = "hello darkness my old friend";
byte[] encrypted = encrypt(text, serverPublicKey);
try {
handler.getDos().write(encrypted);
handler.getDos().flush();
} catch (IOException e) {
e.printStackTrace();
}
}
Reading the byte array, on the server side:
int count = dis.available();
byte[] in = new byte[count];
dis.readFully(in);
System.out.println(Server.decrypt(in, Server.get().getPrivateKey()));
The decryption method throws this exception:
javax.crypto.BadPaddingException: Decryption error
at sun.security.rsa.RSAPadding.unpadV15(RSAPadding.java:380)
at sun.security.rsa.RSAPadding.unpad(RSAPadding.java:291)
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:363)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at com.archiepking.Server.decrypt(Server.java:97)
at com.archiepking.net.ClientHandler$1.run(ClientHandler.java:44)
at java.lang.Thread.run(Thread.java:745)
Any suggestions as to what I am doing wrong? Please note:
Dos = DataOutputStream Dis = DataInputStream Oos = ObjectOutputStream
Ois = ObjectInputStream
I am using two different sockets, one for sending objects and one for datatypes (as my chat application will need both).
What can I do to fix this error?
FURTHER INFORMATION:
Generation of keys:
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
FileOutputStream fosPublic = new FileOutputStream("public");
fosPublic.write(publicKeyBytes);
fosPublic.close();
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
FileOutputStream fosPrivate = new FileOutputStream("private");
fosPrivate.write(privateKeyBytes);
fosPrivate.close();
publicKey = keyPair.getPublic();
privateKey = keyPair.getPrivate();
The problem is that you are using DataInputStream.available() to determine how many bytes to read. That method does not do what you apparently think that it does.
From the Javadoc of this method:
Returns an estimate of the number of bytes that can be read (or
skipped over) from this input stream without blocking by the next
caller of a method for this input stream. The next caller might be the
same thread or another thread. A single read or skip of this many
bytes will not block, but may read or skip fewer bytes.
It just returns the number of bytes that can be read without blocking, which can be far less than the actual number of bytes that you sent, especially if you are using network Sockets to send/receive that data.
The solution:
before writing the bytes, write an int with the writeInt method that contains the number of bytes that you're writing
before reading the bytes, call readInt to read the number of bytes that will follow, and construct a byte array of the right length from that number.
If you are using an ObjectOutputStream why bother converting the public key to a byte array using getEncoded? You can serialize the object directly. e.g.
handler.getOos().writeObject(publicKey);
Or if you have to use the encoded version, then remove the ObjectOutputStream and use ByteArrayOutputStream instead.

AES File decrypting “given final block not properly padded”

I want to encrypt and then decrypt file use AES. I have read many topics about error "Given final block not properly padded". But i don't find solution for me.
Sorry about specify the language of my code, i don't know write language java
Here is my code :
Variables
// IV, secret, salt in the same time
private byte[] salt = { 'h', 'u', 'n', 'g', 'd', 'h', '9', '4' };
public byte[] iv;
public SecretKey secret;
createSecretKey
public void createSecretKey(String password){
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
secret = new SecretKeySpec(tmp.getEncoded(), "AES");
}
method Encrypt
public void encrypt(String inputFile){
FileInputStream fis = new FileInputStream(inputFile);
// Save file: inputFile.enc
FileOutputStream fos = new FileOutputStream(inputFile + ".enc");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
// Gen Initialization Vector
iv = (byte[]) ((IvParameterSpec) params
.getParameterSpec(IvParameterSpec.class)).getIV();
// read from file (plaint text) -----> save with .enc
int readByte;
byte[] buffer = new byte[1024];
while ((readByte = fis.read(buffer)) != -1) {
fos.write(cipher.doFinal(buffer), 0, readByte);
}
fis.close();
fos.flush();
fos.close();
}
method Decrypt
public void decrypt(String inputFile){
FileInputStream fis = new FileInputStream(inputFile);
// Save file: filename.dec
FileOutputStream fos = new FileOutputStream(inputFile.substring(0,
inputFile.length() - 4) + ".dec");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
// Read from file encrypted ---> .dec
int readByte;
byte[] buffer = new byte[1024];
while ((readByte = fis.read(buffer)) != -1) {
fos.write(cipher.doFinal(buffer), 0, readByte);
}
fos.flush();
fos.close();
fis.close();
}
Update
Solution: edit size of buffer is multiples of 16. Use CipherInput/ Output for read/ write file.
Tks Artjom B.
AES is a block cipher and as such only works on blocks of 16 bytes. A mode of operation such as CBC enables you to chain multiple blocks together. A padding such as PKCS#5 padding enables you to encrypt arbitrary length plaintext by filling the plaintext up to the next multiple of the block size.
The problem is that you're encrypting every 1024 bytes separately. Since 1024 divides the block size, the padding adds a full block before encryption. The ciphertext chunks are therefore 1040 bytes long. Then during decryption, you're only reading 1024 missing the padding. Java tries to decrypt it and then tries to remove the padding. If the padding is malformed (because it's not there), then the exception is thrown.
Easy fix
Simply increase your buffer for decryption to 1040 bytes.
Proper fix
Don't encrypt it in separate chunks, but either use Cipher#update(byte[], int, int) instead of Cipher.doFinal to update the ciphertext for every buffer you read or use a CipherInputStream.
Other security considerations:
You're missing a random IV. Without it, it may be possible for an attacker to see that you encrypted the same plaintext under the same key only by observing the ciphertexts.
You're missing ciphertext authentication. Without it, you can't reliably detect (malicious) changes in the ciphertexts and may open your system to attacks such as padding oracle attack. Either use an authenticated mode like GCM or run your created ciphertext through HMAC to create an authentication tag and write it to the end. Then you can verify the tag during/before decryption.
You are under the false assumption that the length of the encrypted data equals the length of the plain data, but the encrypted AES data is always a multiple of the AES block size (16 bytes) and can have an additional full padding block.
The most efficient way of dealing with stream encryption would be to use JCE's CipherOutputStream and CipherInputStream (http://docs.oracle.com/javase/7/docs/api/javax/crypto/CipherInputStream.html). These classes do all the work for you.
Also, make sure you always save the newly generated IV in your encryption method to be able to use it for the decryption.

Bouncycastle in Java odd encryption and decryption results

So I have been working with the Bouncycastle libraries in an attempt to connect with a remote server. This process has been problematic from the get go and now I'm close to getting everything working but some odd things are happening.
When I first started building out the encryption process I was told to use AES 256 with PKCS7Padding. After some nagging I was provided with a c++ example of the server code. It turned out that the IV is 256 bit so I had to use the RijndaelEngine instead. Also in order for this to work correctly I have to use ZeroBytePadding.
Here is my code:
socket = new Socket(remoteIP, port);
outputStream = new PrintWriter(socket.getOutputStream());
inputStream = new BufferedReader(new InputStreamReader(socket.getInputStream()));
byte[] base_64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes("UTF-8");
Security.addProvider(new BouncyCastleProvider());
public String AESEncrypt(String out) throws IOException, DataLengthException, IllegalStateException, InvalidCipherTextException {
byte[] EncKey = key;
byte randKey;
Random randNumber = new Random();
randKey = base_64[randNumber.nextInt(base_64.length)];
EncKey[randKey&0x1f] = randKey;
RijndaelEngine rijndaelEngine = new RijndaelEngine(256);
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(rijndaelEngine), new ZeroBytePadding());
ParametersWithIV keyParameter = new ParametersWithIV(new KeyParameter(EncKey), iv);
cipher.init(true, keyParameter);
byte[] txt = out.getBytes();
byte[] encoded = new byte[cipher.getOutputSize(txt.length)];
int len = cipher.processBytes(txt, 0, txt.length, encoded, 0);
cipher.doFinal(encoded, len);
char keyChar = (char) randKey;
String encString = new String(Base64.encode(encoded));
encString = encString.substring(0, encString.length()-1) + randKey;
return encString;
}
public void AESDecrypt(String in) throws DataLengthException, IllegalStateException, IOException, InvalidCipherTextException {
byte[] decKey = key;
byte[] msg = in.getBytes();
byte randKey = msg[msg.length-1];
decKey[randKey&0x1f] = randKey;
byte[] trimMsg = new byte[msg.length-1];
System.arraycopy(msg, 0, trimMsg, 0, trimMsg.length);
in = new String(trimMsg);
RijndaelEngine rijndaelEngine = new RijndaelEngine(256);
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(rijndaelEngine), new ZeroBytePadding());
ParametersWithIV keyParameter = new ParametersWithIV(new KeyParameter(decKey), iv);
cipher.init(false, keyParameter);
byte[] encoded = Base64.decode(in.trim());
byte[] decoded = new byte[cipher.getOutputSize(encoded.length)];
int len = cipher.processBytes(encoded, 0, encoded.length, decoded, 0);
cipher.doFinal(decoded, len);
String decString = new String(decoded);
}
Here is a test function I am using to send and receive messages:
public void serverTest() throws DataLengthException, IllegalStateException, InvalidCipherTextException, IOException {
//out = AESEncrypt(out);
outputStream.write(out + "\n");
outputStream.flush();
String msg = "";
while ((msg = inputStream.readLine()) != null) {
AESDecrypt(msg);
}
}
The key and iv don't change with the exception of the last byte in the key. If I am encrypting I get a random base64 char and change the last byte to that. If its decryption I get the last byte from the message and set the last value of the key to it for decryption.
In the c++ example there was an unencrypted message and two encrypted messages. I could deal with those fine.
Here is the problem, when I send my message to the remote server "encrypted" the app waits for a response until the connection times out but never gets one. If I send the message unencrypted I get either 7 responses which I can successfully decrypt and finally
org.bouncycastle.util.encoders.DecoderException: unable to decode base64 string:
String index out of range: -4 at org.bouncycastle.util.encoders.Base64.decode(Unknown Source)
or my last line before the error will look like this:
?"??n?i???el????s???!_S=??ah????CR??l6??]?{?l??Y?????Gn???+?????9!'??gU&4>??{X????G?.$c=??0?5??GP???_Q5????8??Z\?~???<Kr?????[2\ ???a$?C??z%?W???{?.?????eR?j????~?B"$??"z??W;???<?Yu??Y*???Z?K?e!?????f?;O(?Zw0B??g<???????????,)?L>???A"?????<?????W??#\???f%??j ?EhY/?? ?5R?34r???#?1??I??????M
If I set the encryption/decryption to use PKCS7Padding I get no response when my message is encrypted still but with decryption from the server I get between 2 to 6 responses and then
org.bouncycastle.crypto.InvalidCipherTextException: pad block corrupted
I am at a loss with this. I don't know what I might be doing wrong so I have come here. I'm hoping the so community can point out my errors and guide me in the right direction.
I have a bit of an update I found my error in the encryption. I wasn't placing the random base64 value at the end of the encrypted string correctly so now I am doing like this.
encString += (char)randKey;
I can get response from the server now. Now the problem is I will some times get one or two readable lines but the rest are all garbage. I asked the individuals who run the server about it and they said in some c# code that they reference the have
return UTF8Encoding.UTF8.GetString(resultArray);
and thats all I have to go off of. I have tried UTF-8 encoding any place where I do getBytes or new String, and I have tried making the BurrferReader stream UTF-8 but it's still garbage.
Have you seedn the BCgit? this has bouncycastle code and examples. I am using the Csharp version in this repository. https://github.com/bcgit/bc-java
All crypto primitive examples are stored here: https://github.com/bcgit/bc-java/tree/master/core/src/test/java/org/bouncycastle/crypto/test
Try this code for testing Aes-CBC
private void testNullCBC()
throws InvalidCipherTextException
{
BufferedBlockCipher b = new BufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
KeyParameter kp = new KeyParameter(Hex.decode("5F060D3716B345C253F6749ABAC10917"));
b.init(true, new ParametersWithIV(kp, new byte[16]));
byte[] out = new byte[b.getOutputSize(tData.length)];
int len = b.processBytes(tData, 0, tData.length, out, 0);
len += b.doFinal(out, len);
if (!areEqual(outCBC1, out))
{
fail("no match on first nullCBC check");
}
b.init(true, new ParametersWithIV(null, Hex.decode("000102030405060708090a0b0c0d0e0f")));
len = b.processBytes(tData, 0, tData.length, out, 0);
len += b.doFinal(out, len);
if (!areEqual(outCBC2, out))
{
fail("no match on second nullCBC check");
}
}

How to encrypt an InputStream with JNCryptor

I'm developing an iPad app and using RNCryptor for the encryption and decryption on the device. There is a Java version of this encryption format available in the form of JNCryptor.
I now have data to be read from an InputStream, but I want to encrypt the data before it is read. I found a class called CipherInputStream, which seems to do exactly what I'm looking for. Only thing is, I need a Cipher (and Provider) to specify the encryption method, and I don't know how to do that. Is it even possible to define a custom Provider?
Does anyone have suggestions on alternative ways to use JNCryptor for the encryption of an InputStream?
In the end I ended up writing a class to read the InputStream, encrypt the data parts at a time, and write to a PipedOutputStream. This PipedOutputStream I then connected to a PipedInputStream, which I eventually returned. The encryption and writing to the PipedOutputStream happens on a separate thread to avoid deadlock.
PipedInputStream pin = new PipedInputStream();
PipedOutputStream pout = new PipedOutputStream(pin);
EncryptionPipe pipe = new EncryptionPipe(5, pout, in, cipher, mac, metaData);
//EncryptionPipe(int interval, OutputStream out, InputStream in
// ,Cipher cipher, Mac mac, byte[] metaData)
pipe.start();
return pin;
And in EncryptionPipe:
public class EncryptionPipe extends Thread {
...
#Override
public void run() {
try {
mac.update(metaData);
out.write(metaData);
byte[] buf = new byte[1024];
int bytesRead = 0;
byte[] crypted;
byte[] hmac;
while ((bytesRead = in.read(buf)) != -1) {
if (bytesRead < buf.length) {
//the doFinal methods add padding if necessary, important detail!
crypted = cipher.doFinal(buf, 0, bytesRead);
hmac = mac.doFinal(crypted);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bytes.write(crypted);
bytes.write(hmac);
crypted = bytes.toByteArray();
bytesRead = crypted.length;
bytes.close();
} else {
crypted = cipher.update(buf, 0, bytesRead);
mac.update(crypted, 0, bytesRead);
}
out.write(crypted, 0, bytesRead);
synchronized (this) {
this.wait(interval);
}
}
out.close();
...
}
}
}
JNCryptor v1.1.0 was released yesterday and provides support for streaming encryption and decryption.
Use AES256JNCryptorInputStream to decrypt and AES256JNCryptorOutputStream to encrypt.
You can use the default Java provider. To instantiate a cipher you would use
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
This uses AES and Cipher Block Chaining mode. CBC only works on multiples of 16 bytes, so you're also specifying a way to pad your input to multiples of 16 bytes.
Here is some more sample AES code to get you started

Categories