2

I have a method in my .net project to encrypt a password

public string Encrypt(string plainText)
{
    string PassPhrase = "#$^&*!@!$";
    string SaltValue = "R@j@}{BAe";
    int PasswordIterations = Convert.ToInt32(textBox5.Text); //amend to match java encryption iteration
    string InitVector = "@1B2c3D4e5F6g7H8";
    int KeySize = 256; //amend to match java encryption key size

    byte[] initVectorBytes = Encoding.ASCII.GetBytes(InitVector);
    byte[] saltValueBytes = Encoding.ASCII.GetBytes(SaltValue);

    byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);

    PasswordDeriveBytes password= new PasswordDeriveBytes(
        PassPhrase,
        saltValueBytes,
        "MD5",
        PasswordIterations);

    byte[] keyBytes = password.GetBytes(KeySize / 8);
    RijndaelManaged symmetricKey = new RijndaelManaged();
    symmetricKey.Mode = CipherMode.CBC;

    ICryptoTransform encryptor = symmetricKey.CreateEncryptor(
                                                     keyBytes,
                                                     initVectorBytes);
    MemoryStream memoryStream = new MemoryStream();

    CryptoStream cryptoStream = new CryptoStream(memoryStream,
                                                 encryptor,
                                                 CryptoStreamMode.Write);

    cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
    cryptoStream.FlushFinalBlock();
    byte[] cipherTextBytes = memoryStream.ToArray();

    memoryStream.Close();
    cryptoStream.Close();

    string cipherText = Convert.ToBase64String(cipherTextBytes);

    return cipherText;
}

I have been tasked to convert this method to java but in java I don't get the same result as the .Net version

My java code is

package com.andc.billing.pdc.security;

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.management.openmbean.InvalidKeyException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class PasswordCrypto {

    private static final String password = "#$^&*!@!$";
    private static String initializationVector = "@1B2c3D4e5F6g7H8";
    private static String salt = "R@j@}{BAe";
    private static int pswdIterations = 2;
    private static int keySize = 128;
    private static final Log log = LogFactory.getLog(PasswordCrypto.class);

    public static String encrypt(String plainText) throws 
        NoSuchAlgorithmException, 
        InvalidKeySpecException, 
        NoSuchPaddingException, 
        InvalidParameterSpecException, 
        IllegalBlockSizeException, 
        BadPaddingException, 
        UnsupportedEncodingException, 
        InvalidKeyException, 
        InvalidAlgorithmParameterException, java.security.InvalidKeyException, NoSuchProviderException 
    {   
        byte[] saltBytes = salt.getBytes("ASCII");//"UTF-8");
        byte[] ivBytes = initializationVector.getBytes("ASCII");//"UTF-8");

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

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


        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); //Cipher.getInstance("AES/CBC/PKCSPadding"
        cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(ivBytes));

        byte[] encryptedTextBytes = cipher.doFinal(plainText.getBytes("ASCII"));//UTF-8"));
        String str=new org.apache.commons.codec.binary.Base64().encodeAsString(encryptedTextBytes);
        log.info(str);
        return str;
    }
}

.net result of encryption of "1" is :

7mPh3/E/olBGbFpoA18oqw==

while java is

7RPk77AIKAhOttNLW4e5yQ==

Would you please help me solve this problem ?

Paul Zahra
  • 9,522
  • 8
  • 54
  • 76
DeveloperX
  • 4,633
  • 17
  • 22
  • It isn't a problem unless they **decrypt** to different things; so... can both implementations decrypt both strings equally? – Marc Gravell Jan 31 '14 at 12:37
  • 1
    On .net side you use RijndaelManaged instead of AesManaged. Rijndael is the algorithm used by AES but in AES there are further restrictions. – Robert Jan 31 '14 at 12:53
  • +10 for your help, but when in .net i Use Rfc2898DeriveBytes instead of PasswordDeriveBytes the result is same as java but I cant change the .net side im looking for java code to give me same result when i use PasswordDeriveBytes in .net – DeveloperX Jan 31 '14 at 16:56

2 Answers2

4

First thing i've noticed is that the algorithms you are using are different, in .Net it's an extension of PBKDF1 and in java it's PBKDF2, PBKDF2 replaced PBKDF1.

In .net you are using the PasswordDeriveBytes class which "derives a key from a password using an extension of the PBKDF1 algorithm."

I also notice that the password iterations is hard-coded to 2 in Java and comes from a text box in .Net... ensure they are the same.

Correct that and let us know the outcome.

Update: For PBKDF2 in .net use the Rfc2898DeriveBytes class.

For some very good relevant information have a read of this page

EDIT: This link should be helpful and if you can use the Chilkat library

It's a complicated difference between 1 and 2, 1 is only supposed to do upto 20 bytes, MS has built an extension which allows more than that and the following code should reporduce the .net output more accurately. Taken from here.

import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.generators.PKCS5S1ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.encoders.Hex;


public class PKCS5Test
{
    /**
     * @param args
     */
    public static void main(String[] args) throws Exception
    {
        byte[] password = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
        byte[] salt = PKCS5S1ParametersGenerator.PKCS5PasswordToBytes("MyTesting".toCharArray());

        PKCS5S1ParametersGenerator generator = new PasswordDeriveBytes(new SHA1Digest());
        generator.init(password, salt, 100);

        byte[] key = ((KeyParameter)generator.generateDerivedParameters(512)).getKey();
        System.out.println( "64 " + new String(Hex.encode(key)).toUpperCase() );
    }

    static class PasswordDeriveBytes extends PKCS5S1ParametersGenerator
    {
        private final Digest d;

        private byte[] output = null;

        public PasswordDeriveBytes(Digest d)
        {
            super(d);

            this.d = d;
        }

        public CipherParameters generateDerivedParameters(int keySize)
        {
            keySize = keySize / 8;

            byte[] result = new byte[keySize];
            int done = 0;
            int count = 0;
            byte[] b = null;

            while (done < result.length)
            {
                if (b == null)
                {
                    b = generateInitialKey();
                }
                else if (++count < 1000)
                {
                    b = generateExtendedKey(++count);
                }
                else
                {
                    throw new RuntimeException("Exceeded limit");
                }

                int use = Math.min(b.length, result.length - done);
                System.arraycopy(b, 0, result, done, use);
                done += use;
            }

            return new KeyParameter(result);
        }

        private byte[] generateOutput()
        {
            byte[] digestBytes = new byte[d.getDigestSize()];

            d.update(password, 0, password.length);
            d.update(salt, 0, salt.length);
            d.doFinal(digestBytes, 0);

            for (int i = 1; i < (iterationCount - 1); i++)
            {
                d.update(digestBytes, 0, digestBytes.length);
                d.doFinal(digestBytes, 0);
            }

            return digestBytes;
        }

        private byte[] generateInitialKey()
        {
            output = generateOutput();
            d.update(output, 0, output.length);

            byte[] digestBytes = new byte[d.getDigestSize()];
            d.doFinal(digestBytes, 0);
            return digestBytes;
        }

        private byte[] generateExtendedKey(int count)
        {
            byte[] prefix = Integer.toString(count).getBytes();
            d.update(prefix, 0, prefix.length);
            d.update(output, 0, output.length);

            byte[] digestBytes = new byte[d.getDigestSize()];
            d.doFinal(digestBytes, 0);

            //System.err.println( "X: " + new String(Hex.encode(digestBytes)).toUpperCase() );
            return digestBytes;
        }
    }
} 
Community
  • 1
  • 1
Paul Zahra
  • 9,522
  • 8
  • 54
  • 76
  • +10 for your help and thanks, the password iteration is is true , that as the .net code that I simulate the main app encryption methods, when is Use Rfc2898DeriveBytes in .net and change key size to 128 the result with java would be same , I'm looking for a way to genearte result like PasswordDeriveBytes in java 256 bit key size – DeveloperX Jan 31 '14 at 16:55
0

Thank you very much for the provided solution - it works very well but with a small correction (according to initial post mentioned below):

Please use:

b = generateExtendedKey(count);

instead of:

b = generateExtendedKey(++count);

It'll work even for 256 key size:

Here is a small code which decrypts C# Rijndael encoded data using 256 bits keys:

public static String decrypt(final String cipherText, final String passPhrase, final String saltValue, final int passwordIterations, final String initVector, final int keySize)
    throws Exception {
    final byte[] initVectorBytes = initVector.getBytes("ASCII");
    final byte[] saltValueBytes = saltValue.getBytes("ASCII");
    final byte[] cipherTextBytes = Base64.decode(cipherText);
    final PKCS5S1ParametersGenerator generator = new PasswordDeriveBytes(new SHA1Digest());
    generator.init(passPhrase.getBytes("ASCII"), saltValueBytes, passwordIterations);
    final byte[] key = ((KeyParameter) generator.generateDerivedParameters(keySize)).getKey();
    final SecretKey secretKey = new SecretKeySpec(key, ALGORITHM);
    final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
    final IvParameterSpec iv = new IvParameterSpec(initVectorBytes);
    cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
    final byte[] decryptedVal = cipher.doFinal(cipherTextBytes);
    return new String(decryptedVal);
}

Addon: In case you care about key size limitation, you may use this solution which works just fine (tested under Ubuntu 12, Java 1.7 64 bits (java version "1.7.0_25" Java(TM) SE Runtime Environment (build 1.7.0_25-b15) Java HotSpot(TM) 64-Bit Server VM (build 23.25-b01, mixed mode))

Community
  • 1
  • 1