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());
}
  • 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 – Sam I am says Reinstate Monica Aug 31 '16 at 19:47
  • Care to tell us what `new T()` is? – Stefan Zobel Aug 31 '16 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. – Artjom B. Aug 31 '16 at 19:50
  • Possible duplicate of [Encryption Diff Between Java and C#](http://stackoverflow.com/questions/27791726/encryption-diff-between-java-and-c-sharp) – Artjom B. Aug 31 '16 at 19:54
  • Stefan Zobel, I am not sure, as I am weak in C#, I asked some other programmers I know already, and they could not deduce that either. – Chris Franklin Aug 31 '16 at 20:00
  • Artjom J, this seems close but not quite, I have seen a bunch that deal with passwords, but that is not necessary here. – Chris Franklin Aug 31 '16 at 20:01
  • It looks that way, but I asked the source and got a response of no to that, but they could not provide me any Java samples. – Chris Franklin Aug 31 '16 at 20:04
  • "I am not sure, ..." Well, you should see that at the call site of the method. Could be anything (AES, DES, TripleDES, Rijndael, RC2, ...). – Stefan Zobel Aug 31 '16 at 20:04
  • If only it would tell me that. That would make life a little easier. I have already spent way too many frustrating hours researching this. – Chris Franklin Aug 31 '16 at 20:07
  • 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. – Stefan Zobel Aug 31 '16 at 20:11
  • And now you are starting to understand my frustration. I have been at this for weeks. – Chris Franklin Aug 31 '16 at 20:12
  • According to the [doc](https://msdn.microsoft.com/en-us/library/system.security.cryptography.symmetricalgorithm(v=vs.110).aspx) there exist only 5 subclasses (I don't believe that someone who has written this code got into the business of implementing its own). So that's not hopeless. – Stefan Zobel Aug 31 '16 at 20:17
  • Each time I try AES, I keep getting exceptions having to do with the fact that either ` initialisation vector must be the same length as block size` or `Wrong IV length: must be 16 bytes long`. – Chris Franklin Aug 31 '16 at 20:18
  • I've tried dozens of methods to derive it. One such attempt is `IvParameterSpec ivSpec = new IvParameterSpec(salt.getBytes());`. I can't find the right resource to tell me what I am doing wrong. No "ah ha" moments yet. – Chris Franklin Aug 31 '16 at 20:24
  • I have added one of my many failed attempts. – Chris Franklin Aug 31 '16 at 20:28
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/122343/discussion-between-chris-franklin-and-zaph). – Chris Franklin Aug 31 '16 at 20:33
  • I did correct the DESKeySpec issue in another attempt , but ended up with the same results. I updated my code above to reflect this. Do you mean I should substitute `PBKDF2` for the `AES` when I use `SecretKeySpec`? – Chris Franklin Aug 31 '16 at 20:48
  • @erickson The sample that was given to him is correct in the sense that the C# code yields exactly that result. I checked. The algorithm used is 256bit [AesManaged](https://msdn.microsoft.com/en-us/library/system.security.cryptography.aesmanaged(v=vs.110).aspx). – Stefan Zobel Sep 01 '16 at 16:13
  • @erickson I'm just writing down my findings. Would it be ok to post this as a (partial) answer? I'm unable to encrypt to the same result in Java, but I have some information that might help to get him going. – Stefan Zobel Sep 01 '16 at 16:28
  • @StefanZobel Yes, that would be good. – erickson Sep 01 '16 at 16:40
  • @erickson As I see it, the input is 32 bytes (16 characters interpreted as 16bit Windows Unicode characters) – Stefan Zobel Sep 01 '16 at 16:59
  • @StefanZobel Okay, that's what I was missing. I glossed over that and was using UTF-8. – erickson Sep 01 '16 at 17:45

2 Answers2

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]
Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
  • @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. – Stefan Zobel Sep 01 '16 at 17:16
  • This whole question is a horrible mess, why not. – zaph Sep 01 '16 at 22:48
  • @zaph Not sure what you are talking about? – Stefan Zobel Sep 01 '16 at 22:57
  • 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 Sep 01 '16 at 23:18
  • @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 :) – Stefan Zobel Sep 01 '16 at 23:51
  • Wrong, to say it is an integer is to assign an encoding, it is 8-bits and one day you may know that. Oh, and I guess you are thinking an of a twos compliment integer, not ones complement? Or perhaps it is part of a floating point number. Or a [8-bit color](https://en.wikipedia.org/wiki/8-bit_color) (RRRGGGBB)? Or a 8-bit [floating point number](https://en.wikipedia.org/wiki/Minifloat). And that a [byte](https://en.wikipedia.org/wiki/Byte) has always been 8-bits? An integer is one way to look at the bits, an applied encoding. Ah grasshopper, you have much to learn. – zaph Sep 02 '16 at 00:40
  • Other ways bytes are used: UTF-8 where the most significant bits determine how many bytes make up a code point. The machine instructions your computer is running generally have bit patterns to indicate the type of instruction. Historically, my first computer I had an 8-bit CPU where the bytes again were in patterns where generally the most significant bits determine the instruction type and other bits were modifiers. – zaph Sep 02 '16 at 02:32
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);
  }

}
Community
  • 1
  • 1
erickson
  • 265,237
  • 58
  • 395
  • 493