Why does JNCryptor/RNCryptor library throws a InvalidHMACException?

1.3k views Asked by At

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 ...
    }
}
2

There are 2 answers

5
Rob Napier On BEST ANSWER

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.

0
Diego Iturriaga On

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