I want to use AES256 symmetric encryption my iPhone app and my Java server socket. I am currently using Rob Napier's RNCryptor/JNCryptor library. Encryption on the iPhone seems to work well, as I am able to decrypt the encrypted strings back again. But as soon as I try to decrypt a string on my Java server socket, the following exception is thrown:
com.acme.crypto.InvalidHMACException: Incorrect HMAC value.
at com.acme.crypto.AES256JNCryptor.decryptV3Data(AES256JNCryptor.java:248)
at com.acme.crypto.AES256JNCryptor.decryptV3Data(AES256JNCryptor.java:323)
at com.acme.crypto.AES256JNCryptor.decryptData(AES256JNCryptor.java:280)
com.acme.crypto.CryptorException: Unrecognised version number: 61.
at com.acme.crypto.AES256JNCryptor.decryptData(AES256JNCryptor.java:283)
Here is the relevant client code snippet for sending the encrypted data (iOS/Objective-C):
// add line break and send message to server
NSString* message = [NSString stringWithFormat:@"%@\n", output];
NSData* data = [[NSData alloc] initWithData:[message dataUsingEncoding:NSUTF8StringEncoding
allowLossyConversion:NO]];
// encrypt outgoing data with AES-256
NSError *error1;
NSData *cypher = [RNEncryptor encryptData:data
withSettings:kRNCryptorAES256Settings
password:@"mypassword"
error:&error1];
// write encrypted data to output stream
if (error1==nil) {
NSInteger result = [outputStream write:[cypher bytes] maxLength:[cypher length]];
} else {
NSLog(@"Encryption of outgoing data failed: %@", error1);
}
And here's is the corresponding server code that receives the encrypted data on its socket (Linux/Java):
// initializing cryptor object during object construction
JNCryptor cryptor = new AES256JNCryptor();
// setting up the input stream on the server socket
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
// this is within the client thread ...
String line;
while((line=input.readLine())!=null) {
try {
// ... the exception is thrown at the following line ...
byte[] decrypted = cryptor.decryptData(line.getBytes(), password.toCharArray());
line = new String(decrypted, StandardCharsets.UTF_8);
// message handling ...
} catch (Exception ex) {
// print exception ...
}
}
Somebody any idea what I am doing wrong? Do I have to use Base64 or similiar for encoding before sending the data? Any help highly appreciated.
EDIT: Here's the solution. Instead of using a character based input-stream, I now use the InputStream from the socket directly in order to read the raw bytes to feed the decryption algorithm:
@Override
public void run() {
try {
int bytes;
byte[] buffer = new byte[4096];
while((bytes=input.read(buffer))!=-1) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(buffer, 0, bytes);
byte[] decrypted = cryptor.decryptData(baos.toByteArray(), password.toCharArray());
String line = new String(decrypted, StandardCharsets.UTF_8);
// handle input ...
} catch (Exception ex) {
// handle exception ...
}
}
} catch (Exception ex) {
// handle exception ...
}
}
InvalidHMACException
means either your password is incorrect or your data is corrupted.
(My Java is somewhat weak, but I'm fairly certain I'm correct in my understanding of the documentation here.)
I suspect your problem is that you're using InputStreamReader
. This bridges from byte streams to character streams (in you case a UTF-8 stream). Ciphertext is totally random bytes, which will in most cases be illegal UTF-8 characters (not just "gibberish" but actually illegal and not decodable). I would certainly expect corruption during a round-trip from bytes->utf8->bytes.
You shouldn't need to use InputStreamReader
here at all. Jut get rid of it.