2

I have a method used to convert a String into an encoded String with a key and a salt in C# that I am trying to create an equivalent for in Java.

The C# method is as follows:

    public static string Encrypt<T>(string Value, string Key, string Salt) where T : SymmetricAlgorithm, new()
    {
        DeriveBytes deriveBytes = new Rfc2898DeriveBytes(Key, Encoding.Unicode.GetBytes(Salt));

        SymmetricAlgorithm algorithm = new T();
        byte[] keyBytes = deriveBytes.GetBytes(algorithm.KeySize >> 3);
        byte[] ivBytes = deriveBytes.GetBytes(algorithm.BlockSize >> 3);

        ICryptoTransform transform = algorithm.CreateEncryptor(keyBytes, ivBytes);

        using (MemoryStream buffer = new MemoryStream())
        {
            using (CryptoStream stream = new CryptoStream(buffer, transform, CryptoStreamMode.Write))
            {
                using (StreamWriter writer = new StreamWriter(stream, Encoding.Unicode))
                {
                    writer.Write(Value);
                }
            }

            return Convert.ToBase64String(buffer.ToArray());
        }
    }

I have tried many different solutions throughout SO and all over the web with no avail. I even have a sample:

value("YourId|YourFacId"),
key("6JxI1HOSg7KQj4fJ1Xb3L1T6AVdLZLBAPFSqOjh2UoA="),
salt("FPSJxiSMpAavjKqyGvVe1A==")

These all get sent to the above method and come back with the return string of:
"Y5w4A3pDZwTcq+FGyqUMO/mZSr6hSst8qiac9zDbfso9FQQbdTDsKnkKDT7SHl4y".

I have yet to find anything in SO that matches my issue, so I am looking for help here. Any leads would be appreciated. Thanks.

The attempted link to the other question showed me nothing that I haven't already seen. There are no passwords to deal with in my example. Here is one of my many failed attempts at this:

private String encrypt(String user) throws Exception
{
    Cipher deCipher;
    Cipher enCipher;
    SecretKeySpec key;
    IvParameterSpec ivSpec;
    String plainKey = "6JxI1HOSg7KQj4fJ1Xb3L1T6AVdLZLBAPFSqOjh2UoA=";
    String salt = "FPSJxiSMpAavjKqyGvVe1A==";
    String result = "";
    ivSpec = new IvParameterSpec(salt.getBytes());
    key = new SecretKeySpec(plainkey.getBytes(), "AES");
    enCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    byte[] input = convertToByteArray(user);
    enCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);

    return new String(enCipher.doFinal(input).toString());
}
22
  • There seems to be a legitimate(but probably a duplicate) question here about how to do encryption in java, but currently, this looks like a code-translation question, and people reflexively downvote and close code translation questions Commented Aug 31, 2016 at 19:47
  • Care to tell us what new T() is? Commented Aug 31, 2016 at 19:48
  • This is simple CBC encryption with key and IV derived from PBKDF2 with the default number of iterations. It was discussed countless times. Commented Aug 31, 2016 at 19:50
  • Possible duplicate of Encryption Diff Between Java and C# Commented Aug 31, 2016 at 19:54
  • 1
    You don't have the caller code and nobody tells you which algo gets used? That's insane. Only thing you could do then is to try out all SymmetricAlgorithm subclasses on your exampe in C# first. Commented Aug 31, 2016 at 20:11

2 Answers 2

3

Thanks to Stefan and a little fiddling, I found that the C# code includes a byte-order mark when encoding the value of the message, but not when encoding the value of the salt. The equivalent Java code looks like this:

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

class SO39257791
{

  private static final int KEY_LEN = 256 / 8, BLOCK_LEN = 16, ITERATIONS = 1000;

  public static void main(String... argv)
    throws Exception
  {
    String value = "YourId|YourFacId";
    String key = "6JxI1HOSg7KQj4fJ1Xb3L1T6AVdLZLBAPFSqOjh2UoA=";
    String salt = "FPSJxiSMpAavjKqyGvVe1A==";
    String good = "Y5w4A3pDZwTcq+FGyqUMO/mZSr6hSst8qiac9zDbfso9FQQbdTDsKnkKDT7SHl4y";

    String output = encrypt(value, key, salt);
    if (output.equals(good))
      System.out.println("strings are equal");
    else
      System.out.println("strings are NOT equal!");
  }

  static final String encrypt(String value, String key, String salt)
    throws GeneralSecurityException, UnsupportedEncodingException
  {
    /* Derive the key, given password and salt. */
    byte[] s = salt.getBytes(StandardCharsets.UTF_16LE);
    int dkLen = (KEY_LEN + BLOCK_LEN) * 8;
    KeySpec spec = new PBEKeySpec(key.toCharArray(), s, ITERATIONS, dkLen);
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    byte[] dk = factory.generateSecret(spec).getEncoded();
    SecretKey secret = new SecretKeySpec(Arrays.copyOfRange(dk, 0, KEY_LEN), "AES");
    byte[] iv = Arrays.copyOfRange(dk, KEY_LEN, KEY_LEN + BLOCK_LEN);

    /* Encrypt the message. */
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(iv));
    byte[] plaintext = value.getBytes("UnicodeLittle"); /* Use Byte Order Mark */
    byte[] ciphertext = cipher.doFinal(plaintext);

    return Base64.getEncoder().encodeToString(ciphertext);
  }

}
Sign up to request clarification or add additional context in comments.

2 Comments

Damn! UnicodeLittle vs. UnicodeLittleUnmarked -- that's mean :)
It works great! Big thanks to you and @Stefan Zobel for all of the help. I would have never come up with this on my own.
3

I can only give you some partial information to get you going:

The algorithm used is 256bit AesManaged. The mode is CBC and the padding is PKCS7 and the keysize = 256.

The sample values given to you can be shown to be correct with this little test program

static void Main(string[] args)
{
    string key = "6JxI1HOSg7KQj4fJ1Xb3L1T6AVdLZLBAPFSqOjh2UoA=";
    string salt = "FPSJxiSMpAavjKqyGvVe1A==";
    string value = "YourId|YourFacId";

    string result = Encrypt<AesManaged>(value, key, salt);
    Console.WriteLine(result);
    string expected = "Y5w4A3pDZwTcq+FGyqUMO/mZSr6hSst8qiac9zDbfso9FQQbdTDsKnkKDT7SHl4y";
    if (expected.Equals(result))
    {
        Console.WriteLine("strings are equal");
    }
    else
    {
        Console.WriteLine("strings are NOT equal!");
    }
}

The key and the salt are plain strings (not base-64 encoded). For them, to get the bytes, in Java you have to use

  byte[] saltBytes = salt.getBytes("UnicodeLittleUnmarked");
  byte[] keyBytes = key.getBytes("UTF-8");

The PBKDF2 uses an SHA1 digest and 1000 iterations (I checked this with BouncyCastle's PKCS5S2ParametersGenerator)

  final int iterations = 1000;
  PKCS5S2ParametersGenerator pbkdf = new PKCS5S2ParametersGenerator(new SHA1Digest());
  pbkdf.init(keyBytes, saltBytes, iterations);
  final int keySize = 32 * 8;
  final int ivSize = 16 * 8;
  CipherParameters cp = pbkdf.generateDerivedParameters(keySize, ivSize);

That gives me the following ivBytes and keyBytes

// Java ivBytes
// [-33, 102, -108, 66, -46, 89, 122, 102, -63, -15, -92, 66, -88, -29, 67, -59]

// Java keyBytes:
// [-127, 125, -40, -123, 60, -70, 16, -6, -15, -116, 127, 93, 46, 80, 26, 31, -36, 47, -120, -37, 57, 21, -94, 44, 98, -119, -109, 48, -71, 15, -36, 80]

These are the signed equivalents for what I get in the C# code:

// C# ivBytes:
// [223, 102, 148, 66, 210, 89, 122, 102, 193, 241, 164, 66, 168, 227, 67, 197]

// C# keyBytes:
// [129, 125, 216, 133, 60, 186, 16, 250, 241, 140, 127, 93, 46, 80, 26, 31, 220, 47, 136, 219, 57, 21, 162, 44, 98, 137, 147, 48, 185, 15, 220, 80]

As I read the C# code, the value bytes must also be retrieved as UTF-16 little-endian

byte[] valueBytes = value.getBytes("UnicodeLittleUnmarked");

From here on, I can't proceed further. The last thing I can tell you is what the crypted byte array (last step before the base-64 encoding) looks like:

// C# crypted:
// [99, 156, 56, 3, 122, 67, 103, 4, 220, 171, 225, 70, 202, 165, 12, 59, 249, 153, 74, 190, 161, 74, 203, 124, 170, 38, 156, 247, 48, 219, 126, 202, 61, 21, 4, 27, 117, 48, 236, 42, 121, 10, 13, 62, 210, 30, 94, 50]

// or signed:
// [99, -100, 56, 3, 122, 67, 103, 4, -36, -85, -31, 70, -54, -91, 12, 59, -7, -103, 74, -66, -95, 74, -53, 124, -86, 38, -100, -9, 48, -37, 126, -54, 61, 21, 4, 27, 117, 48, -20, 42, 121, 10, 13, 62, -46, 30, 94, 50]

7 Comments

@zaph Well, the C# test program obviously produces the correct output without doing any Base64 decoding first. And the ivBytes / keyBytes byte[] arrays do have the correct length.
This whole question is a horrible mess, why not.
@zaph Not sure what you are talking about?
This comment by the OP: "I asked the source and got a response of no to that, but they could not provide me any Java samples". That is a mess. That is why there is so much guessing. Then there is the unsigned vs signed integers when encrypted data is not an array of integers at all, just 8-bit bytes and displayed much better in hex.
@zaph Agreed, I can commiserate with him. Bytes are fixed-size integers, it's just that programming languages can't agree whether they are signed or not. But you know that :)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.