encryption result in java and .net are not same

2020-02-26 13:45发布

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 ?

2条回答
劫难
2楼-- · 2020-02-26 13:54

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;
        }
    }
} 
查看更多
趁早两清
3楼-- · 2020-02-26 14:12

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))

查看更多
登录 后发表回答