Likai
Likai

Reputation: 1

JAVA AES256 ENCRYPTION/DECRYPTION java.lang.NullPointerException + javax.crypto.BadPaddingExceptions

So i've been stuck on a very annoying problem with a chat program I've been designing. Clients encrypt messages in AES256 format, send them as strings to a server, which then redistributes them to the clients in the form of:

<encrypted username> encrypted message

It's the client's job to both encrypt messages, and decrypt them. The strings coming back from the server match what is sent.

The ENCRYPTOR and DECRYPTOR work properly when using an encrypted String defined LOCALLY. Putting encrypted messages that were not encrypted in the same instance into the DECRYPTOR gives null pointer exceptions, BadPadding exceptions or other strange errors.

The chat system works flawlessly, but the issues arise when the messages need to be decrypted

SO, I messed around with the code and tried a lot of workarounds to see if the data would be decrypted again. I ended up trying multiple decryption methods to see if others would work, to no avail. Same issues.

 int port=1234;
                           String host="localhost";
                    try
                    {
                                socket=new Socket(host,port);
                                userip=new BufferedReader(new InputStreamReader(System.in));
                                output=new PrintStream(socket.getOutputStream());
                                input=new BufferedReader(new InputStreamReader(socket.getInputStream()));
                           //     AES256 encryptedmessage = new AES256();
                    }
                    catch(Exception e)
                    {
                                System.err.println("Couldn't connect. "+host);
                    }
                    if(socket!=null)
                    {
                                try
                                {
                                            //AES256 message = new AES256();
                                            String encrypted = "";
                                            new Thread(new Client()).start();
                                            while(!flag)
                                            {
                                            encrypted = Global.encryptor.encrypt(userip.readLine());
                                            //encrypted = message.encrypt(userip.readLine());
                                            output.println(encrypted);
                                            System.out.println(encrypted);
                                            //NOT NECESSARY ^^^
                                            }
                                output.close();
                                input.close();
                                socket.close();
                    }

The part that sends input to the server is

output.println(encrypted);

where encrypted is a string that has been put through the encryption method in AES256.java (I tried declaring an AES256 object in its own file as an attempt to avoid the null pointer errors/other errors that have been recurring. It works exactly the same and causes the same errors.)

The client properly takes in input from the user, encrypts it, and sends it to the server.

The SERVER receives the encrypted username and messages of the user through this code:

            public void run()
        {
              //AES256 X = new AES256();
              String msg;
              String username;
              try
              {
                    input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    output = new PrintStream(socket.getOutputStream());
                    output.println("Enter your alias: ");
                    username = input.readLine();
                    //THIS IS IMPORTANT.
                    output.println("CONNECTION ESTABLISHED");
                    output.println(username + ": CONNECTED.");
                    output.println("To disconnect, enter $$");
                    output.println("END UNENC");
                    for (int i = 0; i <= 9; i++)
                          if (th[i] != null && th[i] != this)
                                th[i].output.println("------------A new user connected: " + username);
                          while (true)
                          {
                                msg = input.readLine();
                                if (msg.startsWith("$$"))
                                      break;
                                for (int i = 0; i <= 9; i++)
                                      if (th[i] != null){
                                            th[i].output.println("<" + username + "> " + msg);
                                      }
                                      }

the relevant code here is

for (int i = 0; i <= 9; i++)
                                      if (th[i] != null){
                                            th[i].output.println("<" + username + "> " + msg);
                                      }
                                      }

The above code takes in the username and message given from the client, and sends it to all that are connected. Both username and messages are still encrypted.

At this point, the client receives something of this nature:

<nf8q3nfqlidcnalkm2> djiami2j389danfill23nlfuinvavv34

The username and message are exactly the same, character-wise, as what was originally sent to the server. However, when I try to decrypt these two things, errors come into play (mostly null pointer errors).

This information gets back to the client through this code, in the client file:

public void run()
{
String msg;
String unencrypt = "";
String line = "";
String username = "";
String message = "";
int index1 = 0;
int index2 = 0;

try
{
while((msg=input.readLine())!=null)
{
//  d.SetMessage(msg);
//  String original = msg;
//  byte[] utf8Bytes = original.getBytes("UTF-8");
    if(!msg.contains("<")){
    System.out.println(msg);

    }
    else if(msg.contains("<"))
    {
        username = msg.substring(1, msg.indexOf(">"));
        System.out.println("USERNAME:"+username);
        message = msg.substring(msg.indexOf(">")+2,msg.length());
        System.out.println("MESSAGE:"+message);
        index1 = line.indexOf('<');
        index2 = line.indexOf('>');
        unencrypt = Global.encryptor.decrypt(username);
        //System.out.println("<" + d.decrypt(d.GetMessage() + ">"));
    }
}
flag=true;
}
catch(IOException e)
{
System.err.println("IOException" + e);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

You'll notice a lot of unused, redundant or unfinished code for doing stuff, but that's simply due to the fact I have retried a thousand different ways of doing it. The error in this particular instance, as with all of them, comes when the client tries to decrypt the string it gets back from the server.

Keep in mind that the encryptor and decryptor work perfectly when a string comes straight from the encryptor, back into the decryptor within the same instance.

The main culprit, the cause of ALL problems is when this string that comes from the server, is attempted to be decrypted in ANY way. This method was just one of those ways:

unencrypt = Global.encryptor.decrypt(username);

Let's look at the errors it gives, whenever this decryptor is ran with a string from the server:

javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:966)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at Messenger.AES256.decrypt(AES256.java:84)
at Messenger.Client.run(Client.java:106)
at java.lang.Thread.run(Unknown Source)
java.lang.NullPointerException
at java.lang.String.<init>(Unknown Source)
at Messenger.AES256.decrypt(AES256.java:91)
at Messenger.Client.run(Client.java:106)
at java.lang.Thread.run(Unknown Source)

I've been clueless as to how to fix this. Here is the AES256 stuff that's coming into play:

ENCRYPTOR (what is sent to the server)

  public String encrypt(String plainText) throws Exception {   

    //get salt
    salt = generateSalt();      
    byte[] saltBytes = salt.getBytes("UTF-8");

    // Derive the key
    SecretKeyFactory factory =        SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    PBEKeySpec spec = new PBEKeySpec(
            password.toCharArray(), 
            saltBytes, 
            pswdIterations, 
            keySize
            );

    SecretKey secretKey = factory.generateSecret(spec);
    SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");

    //encrypt the message
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, secret);
    AlgorithmParameters params = cipher.getParameters();
    ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV();
    byte[] encryptedTextBytes = cipher.doFinal(plainText.getBytes("UTF-8"));
    return new Base64().encodeAsString(encryptedTextBytes);
  }

DECRYPTOR (what is ran on strings that are sent to the client)

 @SuppressWarnings("static-access")
public String decrypt(String encryptedText) throws Exception {

    byte[] saltBytes = salt.getBytes("UTF-8");
    byte[] encryptedTextBytes = new Base64().decodeBase64(encryptedText);

    // Derive the key
    SecretKeyFactory factory =     SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    PBEKeySpec spec = new PBEKeySpec(
            password.toCharArray(), 
            saltBytes, 
            pswdIterations, 
            keySize
            );

    SecretKey secretKey = factory.generateSecret(spec);
    SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");

    // Decrypt the message
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(ivBytes));


    byte[] decryptedTextBytes = null;
    try {
        decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    }

    return new String(decryptedTextBytes);
  }

My belief is that something about the strings changes when they are sent to the server and come back. Or, the AES256 class needs to remember something about the string after it's encrypted. I'm not sure.

Could someone give me some insight on the matter? I feel so close to getting it to work, but I just can't decrypt these strings once they're sent back to the client.

EDIT:

            final protected static char[] hexArray =     "0123456789ABCDEF".toCharArray();
        public static String bytesToHex(byte[] bytes) {
            char[] hexChars = new char[bytes.length * 2];
            for ( int j = 0; j < bytes.length; j++ ) {
                int v = bytes[j] & 0xFF;
                hexChars[j * 2] = hexArray[v >>> 4];
                hexChars[j * 2 + 1] = hexArray[v & 0x0F];
            }
            return new String(hexChars);}

I'm attempting to convert the ivBytes and salt into hexadecimals so I can send them to the server and back and get what I need to decrypt again.

When I try to print this new hexadecimal, I get:

HEXADECIMAL IV BYTES: ?T???/0??q"?H

EDIT #2:

I've successfully converted ivBytes[] and saltBytes[] to and from Hexadecimal format. I am sending input to the server in the form of...

HexIvBytes + " " + HexSaltBytes + " " + EncryptedMessage

Here's an example of the output:

testing
HEXADECIMAL IV BYTES: 70FDE9D7D1A0E2AAD92A95E113186067
IV BYTES: [B@2aafb23c
HEXADECIMAL SALT BYTES:     71C385C387C2ACC2BDC3B2CB9C1BC3926F63370749C3B4C2BD04E280B9C2A90E
SALT BYTES: [B@2b80d80f
ENCRYPTED MESSAGE:eYgv/GxKP3oG3SbbnflTyw==
IVBYTES:[B@2aafb23c
<6C5D1C71F8C775C363C914982C617B11 C386C3A5C3A4C3AE1942C5BE565268175C5A7354C39F6F15C5A1C2A7 NtDi9/P696DgTezN7T0ZVg==> 70FDE9D7D1A0E2AAD92A95E113186067 71C385C387C2ACC2BDC3B2CB9C1BC3926F63370749C3B4C2BD04E280B9C2A90E eYgv/GxKP3oG3SbbnflTyw==

So I'm trying to decrypt these things on the client side, now. This is what I'm working with:

if(msg.contains("<"))
    {
        username = msg.substring(1, msg.indexOf(">"));
        usernameivHex = username.substring(0, username.indexOf(" "));
        saltHex = username.substring(username.indexOf(" "), username.indexOf("|"));
        encryptedUsername = username.substring(username.indexOf("|"),username.indexOf(">"));

        d.setivBytes(hexStringToByteArray(usernameivHex));
        d.setSaltBytes(hexStringToByteArray(saltHex));
        System.out.println("DECRYPYTED USERNAME: " + d.decrypt(encryptedUsername));

    }
}

I'm getting huge out of bounds errors for this line though:

            encryptedUsername = username.substring(username.indexOf("|"),username.indexOf(">"));

java.lang.StringIndexOutOfBoundsException: String index out of range: -100
at java.lang.String.substring(Unknown Source)
at Messenger.Client.run(Client.java:155)
at java.lang.Thread.run(Unknown Source)

EDIT #3:

Success! Using the transferred IvBytes and SaltBytes, i was able to succesfully decrypt strings on the client side.

Upvotes: 0

Views: 2785

Answers (1)

Maarten Bodewes
Maarten Bodewes

Reputation: 94098

You need the same salt to be used, not just the same password. Password and salt determine the value of the key seed - the output of PBKDF2. This in turn is input material for the key (sometimes simply by using the right number of bytes from the key seed).

If you have an invalid key then you most likely will end up with padding errors. If you want to be sure that you get errors for the wrong salt/password you may want to include an authentication tag (generated e.g. using HMAC).

The easiest way of sending the salt is by simply prefixing it to the ciphertext before encoding. If you want to separate the salt and ciphertext you could send them as JSON string : value pairs

"salt"=<base64>
"ciphertext"=<base64>

The same goes for the IV.

Upvotes: 1

Related Questions