0

I need help figuring out why Java encryption fails on my machine running Linux Mint 17.2 Rafaela. My application is not able to decrypt previously encrypted values using the RC4 algorithm.

I am testing with Java 8 u112 and I installed the JCE, but that did not help.

Here is the minimum sample I created, which works on my Windows machine:

import javax.xml.bind.DatatypeConverter;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

public class CryptoTest
{

  private static final String ADMIN_PASSWORD = "admin";
  private static final String ADMIN_ENCRYPTED_PASSWORD = "532C05C5B5";                             // RC4 encrypted password using KEY
  private static final String ADMIN_AUTH_KEY = "1391a8a860b7d6e2e86df513700e490c16dae47cdae227ca"; // PBKDF2(username,password,salt)
  private static final String CRYPTO_ALGORITHM = "RC4";

  protected static String encryptPassword(String passwordDataToEncrypt, String userskey) throws Exception 
  {
    SecureRandom sr = new SecureRandom(userskey.getBytes());
    KeyGenerator kg = KeyGenerator.getInstance(CRYPTO_ALGORITHM);
    kg.init(sr);
    SecretKey sk = kg.generateKey();
    Cipher cipher = Cipher.getInstance(CRYPTO_ALGORITHM);
    cipher.init(Cipher.ENCRYPT_MODE, sk);
    return bytesToHex(cipher.doFinal(passwordDataToEncrypt.getBytes()));
  }

  private static String bytesToHex(byte[] in) 
  {
    return DatatypeConverter.printHexBinary(in);
  }

  private static byte[] hexStringToByteArray(String s) 
  {
    return DatatypeConverter.parseHexBinary(s);
  }

  protected static String decryptPassword(byte[] toDecryptPassword, String key) throws Exception 
  {
    SecureRandom sr = new SecureRandom(key.getBytes());
    KeyGenerator kg = KeyGenerator.getInstance(CRYPTO_ALGORITHM);
    kg.init(sr);
    SecretKey sk = kg.generateKey();
    Cipher cipher = Cipher.getInstance(CRYPTO_ALGORITHM);
    cipher.init(Cipher.DECRYPT_MODE, sk);
    return new String(cipher.doFinal(toDecryptPassword));
  }

  public static void assertEquals(String arg1, String arg2)
  {
    if (! arg1.equals(arg2))
    {
      System.out.println(String.format("%s does not equal %s", arg1, arg2));
    }
  }

  public static void testGetDecryptedPassword() throws Exception
  {
    String decryptedPassword = decryptPassword(hexStringToByteArray(ADMIN_ENCRYPTED_PASSWORD), ADMIN_AUTH_KEY);
    assertEquals(ADMIN_PASSWORD, decryptedPassword);
  }

  public static void testGetEncryptedPassword() throws Exception
  {
    String encryptedPassword = encryptPassword(ADMIN_PASSWORD, ADMIN_AUTH_KEY);
    assertEquals(ADMIN_ENCRYPTED_PASSWORD, encryptedPassword);
  }

  public static void testEncryptAndDecryptPasswords() throws Exception
  {
    String originalPassword = "password";
    String encryptedPassword = encryptPassword(originalPassword, ADMIN_AUTH_KEY);
    String decryptedPassword = decryptPassword(hexStringToByteArray(encryptedPassword), ADMIN_AUTH_KEY);
    assertEquals(originalPassword, decryptedPassword);

    originalPassword = "This is a STRONG password 4 me!!!@#$^";
    encryptedPassword = encryptPassword(originalPassword, ADMIN_AUTH_KEY);
    decryptedPassword = decryptPassword(hexStringToByteArray(encryptedPassword), ADMIN_AUTH_KEY);
    assertEquals(originalPassword, decryptedPassword);
  }

  public static void main(final String[] args)
  {
    try
    {
      int strength =  Cipher.getMaxAllowedKeyLength("AES");
      if ( strength > 128 ){
        System.out.printf("isUnlimitedSupported=TRUE,strength: %d%n",strength);
      } else {
        System.out.printf("isUnlimitedSupported=FALSE,strength: %d%n",strength);
      }

      testGetDecryptedPassword();
      testGetEncryptedPassword();
      testEncryptAndDecryptPasswords();
    }
    catch (Exception e)
    {
      System.out.printf("Caught exception: %s\n", e.getMessage());
      e.printStackTrace(System.out);
    }
  }
}

The output on my linux box is:

isUnlimitedSupported=TRUE,strength: 2147483647
admin does not equal <junk>
532C05C5B5 does not equal 5D16D89D2F
password does not equal <junk>
This is a STRONG password 4 me!!!@#$^ does not equal <junk>

Where <junk> is a bunch of unprintable chars.

user207421
  • 305,947
  • 44
  • 307
  • 483
Dusty Campbell
  • 3,146
  • 31
  • 34
  • 1
    Lots of bad stuff here, though I suspect the ones that are currently biting you are the the `String(byte[])` constructor and the related `String.getBytes()` method. Those use the platform default character set and thus are *not* portable. Always explicitly specify a character set. I see you're also using SecureRandom as a PBKDF. That's also a bad idea. – President James K. Polk Jan 21 '17 at 21:16
  • In addition, passwords should be hashed (and salted!) , not encrypted. Then as long as you are using the same salt, and hash method, your resulting hash should match what is stored. – ivanivan Jan 21 '17 at 23:48

1 Answers1

3

Your code assumes that you get the same SecretKey each time you initialize your SecureRandom() with the same password in the following code:

SecureRandom sr = new SecureRandom(userskey.getBytes());
KeyGenerator kg = KeyGenerator.getInstance(CRYPTO_ALGORITHM);
kg.init(sr);
SecretKey sk = kg.generateKey();

You really can't make that assumption, and you should not use that approach. The SecureRandom is part of the JCA architecture where the actual implementation you get when you instantiates a new SecureRandom(..) depends on which security providers are available on your system, and which priority each provider have.

If you need to make encryption keys from passwords, you should use a key derivation function invented for that like PBKDF2.

Ebbe M. Pedersen
  • 7,250
  • 3
  • 27
  • 47
  • I am not trying to use the password text as PBKDF, instead I just want to encrypt some a secret, store it, and then be able to decrypt it later. So, I've omitted the code to generate the PBDKF (shown here in ADMIN_AUTH_KEY). It's not being used for authentication just as a way to store some secrets that I don't want in plain text. – Dusty Campbell Jan 22 '17 at 01:20
  • You are generating a Key using a SecureRandom(..) initialized with the bytes from a password. Java do not define how SecureRandom should generate new random numbers .. this is up to the implementation of the provider to decide. Basing you encryption on how your current provider generates random numbers is just going to give trouble .. – Ebbe M. Pedersen Jan 22 '17 at 01:38
  • OK, at first I didn't understand your answer or comment, but after going back and reading more of the Java documentation I understand and you are right. I found a way to get what I want from this answer: http://stackoverflow.com/a/1133815/2174 – Dusty Campbell Feb 05 '17 at 17:33