1

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 ...
    }
}
salocinx
  • 3,715
  • 8
  • 61
  • 110
  • Okay somehow / somebody deleted the extension to the answer, so I have extended the original question with the solution. – salocinx Aug 24 '15 at 14:04

2 Answers2

3

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.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • @Rob Napier: I was so cheekily and have expanded your answer :-). Thank you very much for your help. – salocinx Aug 24 '15 at 11:18
  • @Rob Napier: Another question: Is it normal to take about 100 ms to de-cypher roughly 200 bytes on a 2.5 GHz CPU (with JNCryptor / 256 AES) ? – salocinx Aug 24 '15 at 11:19
  • 1
    Is this Android? @Duncan knows that system much better, but as I recall, JNCryptor is surprisingly slow on Android. Note that you should expect RNCryptor to be fairly slow on any platform if you use a password, no matter how short the message. This is a security feature. You can make it dramatically faster by using a random key rather than a password (if JNCryptor can handle keys). – Rob Napier Aug 24 '15 at 11:23
  • @salocinx this isn't a surprising result in any case. By design, RNCryptor takes ~160ms to compute the key from a password on an iPhone 4. So everything scales from that. Stuff faster than an iPhone 4 should be proportionally faster (but the computation can't be spread across CPUs). IIRC, Java's key derivation function is a bit slower than iOS's, so this is definitely in the right range even for a desktop. – Rob Napier Aug 24 '15 at 11:37
  • @Rob Napier: No, currently I am using the JNCryptor classes on my desktop Ubuntu server which is running Java. I plan to work out an Android version of my app which then would communicate with the server. I already read that the lib is still in a pre-mature state regarding Android... – salocinx Aug 24 '15 at 14:06
0

I had the same problem, my version was 65 and JNCryptor expects 2 or 3.

Try not using baos.toByteArray().

byte[] decrypted = cryptor.decryptData(baos.toByteArray(), password.toCharArray());

Instead use:

byte[] decrypted = cryptor.decryptData(Base64.decodeBase64(baos), password.toCharArray());

Hope it helps, Diego