Extra Characters appearing after RC4 decryption - java

I am currently creating a UDP program in Java to simulate a secure communication channel. Between the Client and Server, I want to encrypt data before it is sent over to the other side. The problem I am facing now is that there are a lot of weird characters appearing after the decrypted message(example below). Why is this happening and how can I get rid of these characters?
Server-side code:
String testMessage = "this is a test message";
// initialise the key for RC4 encryption
Key key = new SecretKeySpec("password".getBytes(StandardCharsets.UTF_8), "RC4");
System.out.println("key: " + key);
// initialise cipher for encryption
Cipher encryptCipher = Cipher.getInstance("RC4");
encryptCipher.init(Cipher.ENCRYPT_MODE, key);
// encrypt the message
byte[] encryptedMessage = encryptCipher.doFinal(testMessage.getBytes(StandardCharsets.UTF_8));
// array of bytes to store encrypted data to be sent
byte[] sendData = encryptedMessage;
// send message
// byte[] testMessageBytes = testMessage.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, inet, senderPort);
socket.send(sendPacket);
Client-side code:
// initialise the key for RC4 encryption
Key key = new SecretKeySpec("password".getBytes(StandardCharsets.UTF_8), "RC4");
System.out.println("key: " + key);
// Array of bytes to store data received in a packet
byte[] receiveData = new byte[1024];
// DatagramPacket to receive data
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
// receive data and print it to the console
socket.receive(receivePacket);
String receiveMessage = new String(receivePacket.getData(), StandardCharsets.UTF_8);
System.out.println("Message before decryption: " + receiveMessage);
// initialise cipher for decryption
Cipher decryptCipher = Cipher.getInstance("RC4");
decryptCipher.init(Cipher.DECRYPT_MODE, key);
// decrypt the received message
byte[] decryptedMessage = decryptCipher.doFinal(receivePacket.getData());
receiveMessage = new String(decryptedMessage, StandardCharsets.UTF_8);
System.out.println("Message after decryption: " + receiveMessage);
This is the result i am getting:
Message before decryption: ??Q}lW?q?↓A??♂?r?(?
Message after decryption: this is a test message&☺rL;?kj?W?♥?q???;g5z$'D??RC?e?gj??P?n[J?|J#K}#?y&??S??R♦?9??X?/m▬????♣J?M??]??|♫???AN
??P?(?♂{^M???q?K?▬s?T♥?Ø????l?R☼☻z ?♂V??}?49?P?????ZTG????/?/???J"#9z???♀??I??9??{??????K?z?♠?.♣?????mH →???↓☻???g?5??{?A1\-f*?♥n%??1 V?q=C►?§9♦U???☺??*?"R%?#♦??☺?g? 4???x??Pht$◄?F►??◄g◄l?P9???▲?>???IG?t???H??t↓??V?bC?:?▲?y#U)g↑g,t?f?U#?r?♠??[7%?e??XR↑??a??Vv??►_?u???#B?☻??FU?¶???????y▲?$??x=44?}??/?cn?↨??+{???↕?6?:?¶?B?]?)=+8d↨?p\?}3?q∟?▬?k-??o????}?W?M?Z◄¦¿?h??L?9?N?\♥V??☻^~?e?W3J?↑?^↔▲m%?>??P?H♫tCm☼*v☺?}??U']T?a 2?↑?↕???CN???fL?#C???dUCZ_↓?)]???▬DZc?⌂x?⌂??????#? ??wHt????b ?#??(→?KMZ[wx?6???b?Y."c??☻?Q?☻é☻??c?-.??☼V9???☺6???ep~?q?↨&???§?? />??c???♥??C36???►K???↨?.??Lr
I am very new to socket programming and cryptography, so pardon any inefficient code. I do know that RC4 is deprecated and should not be used anymore, but this is just for educational purposes. Please let me know if there is any information that I am lacking for this post. I appreciate any and all help.

You need to ensure that you're only dealing with the portion of the packet that contains the message as opposed to its entire buffer, so:
byte[] decryptedMessage = decryptCipher.doFinal(receivePacket.getData(), 0, receivePacket.getLength());

Related

Unknown ciphertext appended to decrypted plaintext

I'm using a RC4 stream cipher to encrypt/decrypt data sent through a Java UDP client-server program. My code works as intended on the host program and displays the correct plaintext and ciphertext, but when the data is sent over to the client via UDP, the ciphertext in particular get a super long ciphertext appended to it.
The client is still able to decrypt it properly, I just don't understand where the random appended ciphertext is coming from.
Host program:
public class Host {
private static final String ENCRYPTION_ALGORITHM = "ARCFOUR"; // or "RC4"
public static void main(String[] args) throws Exception {
//Use Java's built-in DatagramSocket and DatagramPacket to implement UDP
DatagramSocket socket = new DatagramSocket(1500); //Create a socket to listen at port 1500
byte[] buf = new byte[65535]; //Byte array to wrap
//Initialize P, G, H(PW), A variables
BigInteger P = null;
BigInteger G = null;
String hashedPW = null; //Hashed PW using SHA-1
BigInteger A = new BigInteger(10, new Random()); //Random number selected by Alice, maximum limit of 1000
BigInteger aliceKey = null;
//Read in Diffie-Hellman parameters from param.txt
File file = new File("param.txt");
Scanner scan = new Scanner(file);
//Loop through the entire file
while(scan.hasNextLine()) {
int counter = 0;
String delimiter = ","; //Content in the file is separated using a comma
String[] tokenValue = scan.nextLine().split(delimiter);
for (String token : tokenValue) {
switch (counter++) {
case 0: //Diffie-Hellman's P
P = new BigInteger(token);
break;
case 1: //Diffie-Hellman's G
G = new BigInteger(token);
break;
case 2: //Password hashed using SHA-1
hashedPW = token;
break;
}
}
}
scan.close(); //Close scanner to prevent memory leaks
aliceKey = G.modPow(A, P); //Calculate g^a mod p to obtain Alice's public key
byte[] passwordBytes = hashedPW.getBytes();
//Setting up RC4 stream cipher
SecretKey secretKey = new SecretKeySpec(passwordBytes, 0 , passwordBytes.length, "RC4"); //Generate secret key using common password
Cipher rc4 = Cipher.getInstance(ENCRYPTION_ALGORITHM);
System.out.println("Parameters successfully read. Listening on port 1500...");
//While-loop to keep host running until terminated
while (true) {
DatagramPacket message = new DatagramPacket(buf, buf.length); //Create a packet to receive message
socket.receive(message); //Receive the message from Bob
//If Alice receives a packet with the message "Bob"
if(new String(message.getData(),0,message.getLength()).equals("Bob")) {
System.out.println("Bob has sent a connection request.");
String msgToBob = hashedPW + "," + P + "," + G + "," + aliceKey; //H(PW), P, G, G^A mod P
message.setData(encrypt(msgToBob, secretKey, rc4)); //Encrypt data using RC4
System.out.println("Ciphertext sent: " + new String(message.getData(),0,message.getLength()));
System.out.println("Decrypted text: " + decrypt(secretKey, rc4, message.getData()));
socket.send(message);
}
}
}
Client program:
public class Client {
private static final String ENCRYPTION_ALGORITHM = "ARCFOUR"; // or "RC4"
public static void main(String[] args) throws Exception {
//Use Java's built-in DatagramSocket and DatagramPacket to implement UDP
DatagramSocket socket = new DatagramSocket(); //Create socket object to send data
byte[] buffer = new byte[65535];
socket.setSoTimeout(5000); //Throw an exception if no data received within 5000ms
//Create scanner to grab user input
Scanner input = new Scanner(System.in);
System.out.print("Please enter the common password: ");
String commonPW = input.nextLine(); //Storing the password
commonPW = encryptThisString(commonPW); //Hash using SHA-1
byte[] passwordBytes = commonPW.getBytes(); //Convert password to byte-array
//Setting up RC4 stream cipher
SecretKey secretKey = new SecretKeySpec(passwordBytes, 0 , passwordBytes.length, "RC4"); //Generate secret key using common password
Cipher rc4 = Cipher.getInstance(ENCRYPTION_ALGORITHM);
System.out.print("Enter a message to send to Alice: ");
String bobInput = input.nextLine();
//Create packet containing message
DatagramPacket message = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("localhost"), 1500);
message.setData(bobInput.getBytes());
socket.send(message); //Send message to host
//Create separate packet to receive response (so that its length is big enough to accept)
DatagramPacket response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
System.out.println("Received from Alice: " + new String(response.getData(),0,response.getLength()));
System.out.println("Decrypted text: " + decrypt(secretKey, rc4, response.getData()));
socket.close();
}
And here's the same methods in both programs used to encrypt/decrypt data:
private static byte[] encrypt(String plaintext, SecretKey secretKey, Cipher rc4) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
rc4.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] plaintextBytes = plaintext.getBytes();
byte[] ciphertextBytes = rc4.doFinal(plaintextBytes);
//System.out.println("RC4 ciphertext base64 encoded: " + Base64.encodeBase64String(ciphertextBytes));
return ciphertextBytes;
}
private static String decrypt(SecretKey secretKey, Cipher rc4, byte[] ciphertextBytes) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
rc4.init(Cipher.DECRYPT_MODE, secretKey, rc4.getParameters());
byte[] byteDecryptedText = rc4.doFinal(ciphertextBytes);
String plaintextBack = new String(byteDecryptedText);
return plaintextBack;
}
The console output for host:
Parameters successfully read. Listening on port 1500...
Bob has sent a connection request.
q?Md|-A????±?h??♥?a????h¶^O eÆá<±?ÿ.♂/?9§}5?aaJj??fë???7óIp§T▬??ñk??lümµÅ⌂»??=L?rúk?§}x☻?.☻j:A·æu?8ë∟2äq*k∟♥?C?$??VZIù?!0|?á=I?X{♥²"‼°▲|c??2²ht??²☻P=?T?"?,9?ƒZ▼?X??M=d uöåä$M☼ñ?"♂♀ö«2&??SC?î?Å?|?#?B??⌂b9?♀??T?lƃî D^í?àêoO3{N3♦^L²¼?=ë??-?|?O{???B?♣ #Æ??½.ìV:ó?±]Ü4▲?|rA£jEGJé??♠?$K"'=«??D»m←ÿ4?J↕¥§y¶[£↨?U¬y-?ÿpëñù«←x→↨[²äueN???↨}☻zƒy??si??z?U[q??Q
?+?÷¡,7Ü?S{6?l???²??↨?%?+üá???
'½?J??«?¶?T.±#=Q!ó??>½ëY2?P☻a♂**?iLO???☺⌂♫?ñ=N?ö?k$4??äîóí←?si?↓ìÜg∟Z6↕?µáù½Rû▲b?9µ,???g?6ù?→nv?ûyµö?M¿U♥â?
e[?V?Zô$¿?¢ôq♥j????G??|?~0ÇGZ?z♥68&MoK??v·$<?âZ^??Ys??[g?? ½å\▬ß¿?·↔!N???.?'?AD?Vg?ën¢-6ô ???0,ö???}lä??Y?D?Xñ¬?º??²☺r¥ï??1?#pq?►p?▬??FMÜ-m>ßñè,?¡r??µ±#?C?¶£?¿→?oi O??♥óeòM£?N ë_?▲èv¡ï?Ü(?öW♥w²ù]q???e♠à♠íô?í^??QM?^?5éÑ??V(??? V?>è?à%?D-r∟Ü1??
N↑??·*?A?tTßæi?[??ú¬c¢ƒl??↓▲ÿie→?♠tL¡??'Ñe÷∟ ƒí?♠↑4 Ä?R;}?:'¡!n6◄▲
☻'??r?I???↨¬z?→XÄ??☼Ä{ñ:â⌂$ê{????àß?FRí???☻ª3?#9‼/¿?$/ï?{ç
öûu½Rz?▼?ô,??◄K[P∟►§PS??))?â?d▲í♥?9??Ü?¡? ««+?☼☺?<<°n¼?♂%óî↔úög
Decrypted text:
62a91401ef53536f5e4a282ca75ce2505b7f8dfe,133369106234410027239548844977121529173266794839390492022055432118963330212454946189658890281117740419231366102782626716256746218028068963594259491015958826219687167085932845323487318056081767660358673429760060452507829995471137991271021787608959982667244524929925606086596447565032707095625628293300812984091,15635507126291796032175381295261956049808038047523713881770866167494167302066120851790523759612392465402233123769308400911864440257625348397936936978922581645934310469147780992255584968935730234568508327975497520199946653504137607154138430285573487119797346741445139288140860073660395495603540644148828628224,96950246366083756365146382678616471348261490848687079335489625506702434220187716384031671233394087317377823909861019537627642960196032766557467991541000545780675424518198042877768900211198791646583260247888450303207379340549434158094990174523043323964083031376798844776809887666868545542060025549549427788304
Output for Client is similar except for the decrypted plaintext:
Please enter the common password: Popcorn
Enter a message to send to Alice: Bob
q?Md|-A????±?h??♥?a????h¶^O eÆá<±?ÿ.♂/?9§}5?aaJj??fë???7óIp§T▬??ñk??lümµÅ⌂»??=L?rúk?§}x☻?.☻j:A·æu?8ë∟2äq*k∟♥?C?$??VZIù?!0|?á=I?X{♥²"‼°▲|c??2²ht??²☻P=?T?"?,9?ƒZ▼?X??M=d uöåä$M☼ñ?"♂♀ö«2&??SC?î?Å?|?#?B??⌂b9?♀??T?lƃî D^í?àêoO3{N3♦^L²¼?=ë??-?|?O{???B?♣ #Æ??½.ìV:ó?±]Ü4▲?|rA£jEGJé??♠?$K"'=«??D»m←ÿ4?J↕¥§y¶[£↨?U¬y-?ÿpëñù«←x→↨[²äueN???↨}☻zƒy??si??z?U[q??Q
?+?÷¡,7Ü?S{6?l???²??↨?%?+üá???
'½?J??«?¶?T.±#=Q!ó??>½ëY2?P☻a♂**?iLO???☺⌂♫?ñ=N?ö?k$4??äîóí←?si?↓ìÜg∟Z6↕?µáù½Rû▲b?9µ,???g?6ù?→nv?ûyµö?M¿U♥â?
e[?V?Zô$¿?¢ôq♥j????G??|?~0ÇGZ?z♥68&MoK??v·$<?âZ^??Ys??[g?? ½å\▬ß¿?·↔!N???.?'?AD?Vg?ën¢-6ô ???0,ö???}lä??Y?D?Xñ¬?º??²☺r¥ï??1?#pq?►p?▬??FMÜ-m>ßñè,?¡r??µ±#?C?¶£?¿→?oi O??♥óeòM£?N ë_?▲èv¡ï?Ü(?öW♥w²ù]q???e♠à♠íô?í^??QM?^?5éÑ??V(??? V?>è?à%?D-r∟Ü1??
N↑??·*?A?tTßæi?[??ú¬c¢ƒl??↓▲ÿie→?♠tL¡??'Ñe÷∟ ƒí?♠↑4 Ä?R;}?:'¡!n6◄▲
☻'??r?I???↨¬z?→XÄ??☼Ä{ñ:â⌂$ê{????àß?FRí???☻ª3?#9‼/¿?$/ï?{ç
öûu½Rz?▼?ô,??◄K[P∟►§PS??))?â?d▲í♥?9??Ü?¡? ««+?☼☺?<<°n¼?♂%óî↔úög
Decrypted text:
62a91401ef53536f5e4a282ca75ce2505b7f8dfe,133369106234410027239548844977121529173266794839390492022055432118963330212454946189658890281117740419231366102782626716256746218028068963594259491015958826219687167085932845323487318056081767660358673429760060452507829995471137991271021787608959982667244524929925606086596447565032707095625628293300812984091,15635507126291796032175381295261956049808038047523713881770866167494167302066120851790523759612392465402233123769308400911864440257625348397936936978922581645934310469147780992255584968935730234568508327975497520199946653504137607154138430285573487119797346741445139288140860073660395495603540644148828628224,96950246366083756365146382678616471348261490848687079335489625506702434220187716384031671233394087317377823909861019537627642960196032766557467991541000545780675424518198042877768900211198791646583260247888450303207379340549434158094990174523043323964083031376798844776809887666868545542060025549549427788304?úmB½Ä????←ê???►\d⌂h♦4LM♣☼»??*Ñ?ì?çtº???ì?.◄☺????xK♥??Ä4öó↑↨
4?á☼mëQ«a
The actual output is much longer for the client program, I didn't copy the entire appended ciphertext. Not sure if it's relevant, but the console makes some Windows notification/error noise when it runs as well, although it doesn't actually give any compilation errors. I'm running my program using command prompt.
I don't think the issue lies with the decryption as I tested with some text on both the host and client programs and I managed to get the same decrypted plaintext just fine. Only when I send the data over UDP it causes this issue it seems.
The text infront of the original ciphertext such as "Ciphertext sent: " gets omitted in the output as well.
What am I missing?
I was trying to decrypt the entire packet buffer instead of that fraction of it that contains the actual message, which resulted in the large wall of text after the actual decrypted message.
Altering the decrypt function used to instead take in the appropriate packet size fixed the issue.

Why is my Java socket receiving an array size larger than what was sent?

I am creating a client/server program to perform encryption with 256 bit AES. I am deriving my key from ECDH. I send the size of the byte arrays I am using to represent my various keys and strings. The issue I am having is that when I try to send the size of my encrypted string from my client to my server, my server says that I sent a much larger size than I actually did. Sending sizes worked for all other byte arrays that needed to be sent. The size of the encrypted string sent from the client is 16 bytes. The server receives an integer size of 276032497 bytes. I have checked that I am in fact sending 16 bytes from the client end.
Any idea of what the issue could be?
Server code:
//generate public key for server
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(256);
KeyPair kp = kpg.generateKeyPair();
byte[] ourPk = kp.getPublic().getEncoded();
String format = kp.getPublic().getFormat();
int ourPkLength = ourPk.length;
int arrSize;
//send client our pk
out.writeInt(ourPkLength);
out.write(ourPk);
System.out.println("sent PK!");
//receive pk from client
arrSize = fromClient.readInt();
byte[] otherPk = new byte[arrSize];
fromClient.read(otherPk);
System.out.println("recived client PK!");
KeyFactory kf = KeyFactory.getInstance("EC");
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(otherPk);
PublicKey otherPublicKey = kf.generatePublic(pkSpec);
//Perform key agreement
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(kp.getPrivate());
ka.doPhase(otherPublicKey, true);
// Send shared secret
byte[] sharedSecret = ka.generateSecret();
// Derive a key from the shared secret and both public keys
MessageDigest hash = MessageDigest.getInstance("SHA-256");
hash.update(sharedSecret);
// Simple deterministic ordering
List<ByteBuffer> keys = Arrays.asList(ByteBuffer.wrap(ourPk), ByteBuffer.wrap(otherPk));
Collections.sort(keys);
hash.update(keys.get(0));
hash.update(keys.get(1));
byte[] derivedKey = hash.digest();
System.out.println("derived key: " + derivedKey + " length: " + derivedKey.length);
//Convert byte [] to secret key
//Define cipher
SecretKeySpec symmetricKey = new SecretKeySpec(derivedKey, 0, 32, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, symmetricKey, new IvParameterSpec(new byte[16]));
//receive encrypted message from client and try to decrypt.
arrSize = fromClient.readInt();
System.out.println("array size sent: " + arrSize);
byte[] decryptArr = new byte[arrSize];
fromClient.read(decryptArr);
System.out.println("Recieved encrypted string: " + decryptArr + " length: " + decryptArr.length);
String decryptStr = Base64.getEncoder().encodeToString(cipher.doFinal(decryptArr));
System.out.println("Decrypted String: " + decryptStr);
Client code:
//generate public key for client
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(256);
KeyPair kp = kpg.generateKeyPair();
byte[] ourPk = kp.getPublic().getEncoded();
//String format = kp.getPublic().getFormat();
int ourPkLength = ourPk.length;
int arrSize;
//Receive generated public key from the Server
arrSize = fromServ.readInt();
byte[] otherPk = new byte[arrSize];
fromServ.read(otherPk);
System.out.println("recived server PK!");
//Send the server our public key
out.writeInt(ourPkLength);
out.write(ourPk);
System.out.println("sent PK!");
KeyFactory kf = KeyFactory.getInstance("EC");
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(otherPk);
PublicKey otherPublicKey = kf.generatePublic(pkSpec);
//Perform key agreement
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(kp.getPrivate());
ka.doPhase(otherPublicKey, true);
// Generate a shared secret
byte[] sharedSecret = ka.generateSecret();
// Derive a key from the shared secret and both public keys
MessageDigest hash = MessageDigest.getInstance("SHA-256");
hash.update(sharedSecret);
// Simple deterministic ordering
List<ByteBuffer> keys = Arrays.asList(ByteBuffer.wrap(ourPk), ByteBuffer.wrap(otherPk));
Collections.sort(keys);
hash.update(keys.get(0));
hash.update(keys.get(1));
byte[] derivedKey = hash.digest();
System.out.println("derived key: " + derivedKey + " length: " + derivedKey.length);
//Convert the derivedkey from a byte array to a Secret key Spec of type AES
SecretKeySpec secretKey = new SecretKeySpec(derivedKey, 0, 32, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(new byte[16]));
String plainText = "Testing!";
byte[] cipherText = cipher.doFinal(plainText.getBytes());
System.out.println("Encrypted str: " + cipherText + " length: "+ cipherText.length);
//Send encrypted string to Server
int len = cipherText.length;
System.out.println("length: " + len);
out.write(len);
out.write(cipherText);
System.out.println("Sent encrypted string!");
In your client, you wrote: out.write(len). Your code snippet does not explain what out is, but I'm guessing that .write(someIntValue) is a pass-through from java.io.OutputStream which has that method (.write(int)). The thing is, this writes a single byte, lopping off all bits in your int except the bottom 8.
The matching call in your server code is:
arrSize = fromClient.readInt();
this is not a call that InputStream has (I'm guessing you have some class that extends InputStream and adds these), presumably, what readInt does, is read 4 bytes, and reconstitutes them into a single java int by assuming Big Endian ordering.
So, you send 1 byte from the client and then the string, but the server will read 4 bytes for the length: That 1 byte (the actual length sent by client), plus the first 3 of your string, and tries to interpret that as a length, causing wildly different numbers.
The fact that 276032497 is a number, that, if put in bytes via big endian ordering, starts with a byte with value 16, which is exactly the length you sent, is strongly suggestive that this is your problem.
The fix seems rather trivial; turn out.write(len) into out.writeInt(len). And if you're going to be doing byte-level protocol like this, you need a better testing and debugging plan than ¯\_(ツ)_/¯ I guess I'll ask StackOverflow. This is probably why most folks use different solutions that handrolling raw byte protocols (so, investigate ProtoBuf and friends). At the very least, make the actual pipe a pluggable concept, so that you can plug in a dummy pipe that doesn't encrypt anything, so you can eyeball the bytes flying over the wire to find issues like this; it is unlikely this will be the last time you mismatch your server and client's code.

How to update part of the encrypted data with newly encrypted data?

I need to encrypt an audio file while it is being generated. I am encrypting header with dummy data(because I don't know the actual size of audio data) at the starting and encrypting the audio data on the fly. My plan is to update the header at the end with actual data size of audio file.
But, When I tried to overwrite the encrypted header data with newly encrypted header data of same size by using same key and IV and try to decrypt later, I am getting junk data generated.
Why is this happening even though I am using same key and IV? In the below code I tried to simulate what I am doing. Encrypted file of size 64 bytes generated and decrypted file of size 50 bytes generated.
Without updation: abcdabcdab0123456789012345678901234567890123456789
With header updation: ABCDABCDAB÷‹þ#óMCKL­ZƒÖ^Ô234567890123456789
Expected output: ABCDABCDAB0123456789012345678901234567890123456789
Is this the right approach to achieve partial update of already encrypted data?
protected void Encrypt()
{
byte[] numBytes = {'0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9', '0','1','2','3','4','5','6','7','8','9', '0','1','2','3','4','5','6','7','8','9'};
byte[] smallCase = {'a','b','c','d','a','b','c','d','a','b','c','d','a','b','c','d'};
byte[] capitalCase = {'A','B','C','D','A','B','C','D','A','B','C','D','A','B','C','D'};
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1And8BIT");
KeySpec spec = new PBEKeySpec("junglebook".toCharArray(), "Salt".getBytes(), 65536, 256);
SecretKey tmp = null;
tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
/* Encryption cipher initialization. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
Log.d("Encryption" + "iv data :", iv.toString());
/*Open two Cipher ouput streams to the same encrypted file*/
FileOutputStream os = new FileOutputStream(sdCard.getAbsolutePath() + "/Notes/sample.encrypted");
CipherOutputStream cos = new CipherOutputStream(os,cipher);
FileOutputStream os1 = new FileOutputStream(sdCard.getAbsolutePath() + "/Notes/sample.encrypted");
CipherOutputStream cos1 = new CipherOutputStream(os1,cipher);
int offset = 0;
Log.d("Encryption", "Writing cipher text to output file");
//Write 16 bytes header data with smallCase array
cos.write(smallCase, offset, 16);
// write 40 bytes actual data
cos.write(numBytes, offset, 40);
FileOutputStream ivStream = new FileOutputStream(sdCard.getAbsolutePath() + "/Notes/iv.dat");
if (ivStream != null) {
Log.d("Encryption", "Writing iv data to output file");
ivStream.write(iv);
}
cos.close();
// Overwrite header data with capitalCase array data
cos1.write(capitalCase, offset, 16);
cos1.close();
ivStream.close();
}catch (Exception e) {
e.printStackTrace();
}
}
protected void Decrypt()
{
byte[] dBytes = new byte[200];
try {
Log.d("Decryption", "Reading iv data ");
File f1 = new File(sdCard.getAbsolutePath()+"/Notes/iv.dat");
byte[] newivtext = new byte[(int)f1.length()];
FileInputStream readivStream = new FileInputStream(sdCard.getAbsolutePath()+"/Notes/iv.dat");
if(readivStream != null) {
readivStream.read(newivtext);
}
// Generate the secret key from same password and salt used in encryption
SecretKeyFactory dfactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1And8BIT");
KeySpec dspec = new PBEKeySpec("junglebook".toCharArray(), "Salt".getBytes(), 65536, 256);
SecretKey dtmp = dfactory.generateSecret(dspec);
SecretKey dsecret = new SecretKeySpec(dtmp.getEncoded(), "AES");
// Initialize dcipher
Cipher dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
dcipher.init(Cipher.DECRYPT_MODE, dsecret, new IvParameterSpec(newivtext));
FileInputStream inputStream = new FileInputStream(sdCard.getAbsolutePath()+"/Notes/sample.encrypted");
CipherInputStream cis = new CipherInputStream(inputStream,dcipher);
FileOutputStream os = new FileOutputStream(sdCard.getAbsolutePath() + "/Notes/sample.decrypted");
int b = cis.read(dBytes);
while(b != -1) {
Log.d("Decryption","Bytes decrypted" + b);
os.write(dBytes, 0, b);
b = cis.read(dBytes);
}
cis.close();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
I suggest you update several things:
you are opening multiple outputstreams to the SAME file, which is very strange, the runtime should not allow you to do that. So - write only with a single output if you want any predictable results.
You may read about the mode of operations see the CRT mode uses no padding and allows you to update only a portion of the ciphertext (assuming you use no authenticated encryption). So AES/CTR/NoPadding could solve your problem. (and there should be no extra bytes if you do it correctly)
you can update a portion of the file using the RandomAccessFile and overwrite portion of the ciphertext what is needed.

Java and Javascript Blowfish

I need to send some data encrypted with Blowfish from a java-based server to a client. I can successfully encrypt data but I can't decrypt it on the client side.
Here is my java code:
byte[] kd = key.getBytes("UTF-8");
SecretKeySpec ks = new SecretKeySpec(kd, "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, ks);
byte[] encrypted = cipher.doFinal(text.getBytes("UTF-8"));
String str = new String(encrypted, "UTF-8");
As for js library I decided to use this one.
out = blowfish.decrypt(code, skey, {cipherMode: 1, outputType: 0})
As a result I get some strange characters. What's wrong with my code?
UPD:
This code works perfectly:
byte[] kd = key.getBytes("UTF-8");
SecretKeySpec ks = new SecretKeySpec(kd, "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.ENCRYPT_MODE, ks);
byte[] encrypted = cipher.doFinal(text.getBytes("UTF-8"));
String str = new String(Base64.encodeBase64(encrypted), "UTF-8");
JS:
out = blowfish.decrypt(code, skey, {cipherMode: 0, outputType: 0})
Sending text with \u0000 bytes in it to a browser can lead to all kinds of odd problems. That's why you should encode the data BASE64, send it to the client and then decode it locally.
Another issue is new String(encrypted, "UTF-8"); since the encoded byte array will contain illegal UTF-8 sequences. Try new String(encrypted, "iso-8859-1"); instead, it's a 1:1 encoding for arbitrary bytes. But again, the 0 bytes could confuse some component in between.

Java NIO + AES Encryption from Client to Server - ByteBuffer issue

I'm quite a newbie regarding encryption and NIO,
I have the following code for client:
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] encrypted = cipher.doFinal(msg.getBytes());
System.out.println("Encrypted info: " + encrypted);
String send = encrypted.toString();
bytebuf = ByteBuffer.allocate(48);
bytebuf.clear();
bytebuf.put(send.getBytes());
bytebuf.flip();
while(bytebuf.hasRemaining()) {
nBytes += client.write(bytebuf);
}
and the following code for server:
// Server receives data and decrypts
SocketChannel socket = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
nBytes = socket.read(buf);
String data = new String(buf.array()).trim();
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secret);
byte[] decrypted = cipher.doFinal(data.getBytes());
System.out.println("Decrypted Info: " + new String(decrypted));
When a message is sent from the Client to the Server, "HELLO" for example is encrypted to [B#34d74aa5 and on the Server side I get *Data packet found as [B#34d74aa5.
Till here everything looks fine, but I get the following exception:
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
I suspect that I have some issue with the way the data is coming out of the buffer on the server side?
Any ideas on this?
UPDATE:
**Based on Erickson's answer this is the final solution
javax.crypto.BadPaddingException: Given final block not properly padded
Client Code:
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
IvParameterSpec ivspec = new IvParameterSpec(iv);
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret, ivspec);
byte[] encrypted = cipher.doFinal(msg.getBytes(StandardCharsets.UTF_8));
String text = DatatypeConverter.printBase64Binary(encrypted);
System.out.println("Encrypted info: " + text);
bytebuf = ByteBuffer.allocate(32);
bytebuf.clear();
bytebuf.put(text.getBytes());
bytebuf.flip();
while(bytebuf.hasRemaining()) {
nBytes += client.write(bytebuf);
}
Server Code:
LOGGER.info("Confirming write");
String data = new String(buf.array());
LOGGER.info("Data packet found as {}", data);
/*******************************************************/
byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
IvParameterSpec ivspec = new IvParameterSpec(iv);
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, ivspec);
byte[] encrypted = DatatypeConverter.parseBase64Binary(data);
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Decrypted Info: " + new String(decrypted, StandardCharsets.UTF_8));
Your cipher text, encrypted, is a byte[], and invoking toString() on an array doesn't render the array content, it returns type ([B) and hash code (#34d74aa5) information as described by Object.toString().
You can't just use new String(encrypted) either. When a byte array is decoded to text, the decoder will replace any invalid byte sequences with the replacement character, \uFFFD (�). Thus, information is lost and subsequent decryption will fail.
Use an encoding like base-64 to convert byte sequences to printable characters instead. Don't junk up your code with third-party libraries for this; you can use javax.xml.bind.DatatypeConverter.
/* Client: */
byte[] encrypted = cipher.doFinal(msg.getBytes(StandardCharsets.UTF_8));
String text = DatatypeConverter.printBase64Binary(encrypted);
…
/* Server: */
byte[] encrypted = DatatypeConverter.parseBase64Binary(data);
byte[] decrypted = Cipher.doFinal(encrypted);
System.out.println(new String(decrypted, StandardCharsets.UTF_8);
You should also be explicit in selecting your mode and padding (like "AES/CBC/PKCS5Padding") because there's no guarantee the recipient will use the same provider, or that the same provider will use the same defaults over time. Same goes for specifying character encodings, like UTF-8.
The AES scheme is a "block cipher" it works on fixed-size blocks of data. You are creating a "raw" Cipher instance, which will expect you to make sure that every byte array that you pass to the cipher is aligned to the cipher's "native" block length. That's usually not what you want to do.
An additional problem that you are exposing yourself to in using the cipher "raw", although it's not causing an actual error, is that if you were to pass it the same block of data on separate occasions, each time, that block would be encrypted identically, therefore giving an attacker clues as to the structure of the data. Again, that's usually not what you want to do in a practical application.
So usually, you need to specify two extra things: a padding scheme, which determines what happens when sections of data are not exactly aligned to a block size, and a block mode, which determines what scheme the cipher will use to avoid identical input blocks being encrypted to identical output blocks. The block mode generally needs initialising with a "starting state" called the initialisation vector (you could use a default state of "all zero", but that's less secure).
So you need to do two things:
You need to initialise you cipher with a padding scheme and block
mode, e.g. "AES/CBC/PKCS5PADDING"
For additional security, you would also usually set up (and transmit
before the data) a random initialisation vector. See this example for more
information.
You are converting the ciphertext, which is a byte[], to a String here:
byte[] encrypted = cipher.doFinal(msg.getBytes());
String send = encrypted.toString();
This is incorrect. You also cannot do new String(byte[]) because the byte[] is random, not a stream of character data in the platform default encoding assumed by new String(byte[]). You should convert the byte[] data to a String by using a hex or base64 encoding (I recommend Apache Commons Codec) e.g.
hexEncodedCipherText = new String(Hex.encodeHex(binaryCipherText))
On the server-side, use the opposite operation to convert the hex or base64 encoded data back to a byte[] before decryption e.g.
binaryCipherText = Hex.decodeHex(hexEncodedCipherText.toCharArray());
UPDATE:
The updated question is not working during decryption because of the incorrect use of the initialization vector. You don't specify an IV during encryption, which means Java will generate a random one. You need to obtain this random IV from the cipher by calling cipher.getIV() after the encryption (or specify it explicitly, though generating a random one is more secure). Then, during the decryption, create the IvParameterSpec using the IV created during encryption. In addition, you will need to encode/decode the IV in the same manner as the ciphertext, since it is also binary data.
UPDATE 2:
I see you have updated your question with the IV, but you are using a null IV. Generally, this is only "safe" when you have a unique key for every message you send. If your key is fixed or re-used for any significant length of time, you should generate a unique IV for each encryption/decryption. Otherwise, you are leaving yourself open to cryptanalysis based on multiple ciphertexts encrypted with the same key and IV.

Categories