Question

I have to decrypt data coming from a java application that uses PBKDF2WithHmacSHA1. I do not have any control over the java code but I have a copy of the source.

The C# part i have based on this example: http://steelmon.wordpress.com/2013/07/01/simple-interoperable-encryption-in-java-and-net/ It works fine if I encrypt/decrypt from c#, but when I try to decrypt in c#, then I get the "Padding is invalid and cannot be removed." exception..

I have tried all the relevant examples I could find. Made sure the salt and key are the same in java and c#, checked that I am using PBKDF2 on both ends and anything else I could think of.

salt: cdWSu23E9BLbNXWUTnrznFgc

key: gygp6yevKWKBUwFy4GXpuFwT

Below is the java code:

public class Cryption {
private static int KEYLEN_BITS = 256;
private static int ITERATIONS = 128;

static private  Log log = LogFactory.getLog(Cryption.class);    

private static SecretKeyFactory secretKeyFactory = null;
private static  Map<String, SecretKey> secretKeyMap = new HashMap<String, SecretKey>(); 

private String password = null;
private SecretKey secretKey = null;
private Cipher cipher = null;

private synchronized static SecretKeyFactory createSecretKeyFactory() {
    try {
        if(secretKeyFactory == null) {                       
            secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");          
        }       
    } catch(Exception e) { 
        log.error("createSecretKeyFactory", e);
    }
    return secretKeyFactory;
}

private static SecretKey createSecretKey(String password, byte []salt) {
    SecretKey secretKey = secretKeyMap.get(password);
    if(secretKey == null) {
        try {
            KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEYLEN_BITS);
            SecretKey tmpSecretKey = createSecretKeyFactory().generateSecret(spec);
            secretKey = new SecretKeySpec(tmpSecretKey.getEncoded(), "AES");
            secretKeyMap.put(password, secretKey);
        } catch(Exception e) { 
            log.error("getSecretKey", e);
        }
    }
    return secretKey;
}

private boolean createCipher(String password, byte []salt, byte[] iv, int mode) {
    try {
        this.password = password;
        secretKey = createSecretKey(password, salt);    
        cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        if(iv != null) {
            cipher.init(mode, secretKey, new IvParameterSpec(iv));
        } else {
            cipher.init(mode, secretKey);               
        }
    } catch(Exception e) { 
        log.error("getCipher", e);
        return false;
    }
    return true;
}

public String getPassword() {
    return password;
}

public byte[] getCipherIV() {
    byte [] iv = null;

    try {       
        AlgorithmParameters params = cipher.getParameters();
        iv = params.getParameterSpec(IvParameterSpec.class).getIV();
    } catch(Exception e) {
        log.error("getCipherIV", e);
    }
    return iv;
}

public byte[] execute(byte []in) {
    byte [] out = null;

    try {       
        out = cipher.doFinal(in); 
    } catch(Exception e) { 
        log.error("execute", e);
    }
    return out;
}

static Cryption create(String password, byte[] salt, byte[] iv, int mode) {
    Cryption cryption = new Cryption();

    cryption.createCipher(password, salt, iv, mode);
    return cryption;
}

static void init(Cryption cryption, byte[] iv, int mode) {
    try {       
        if(iv != null) {
            cryption.cipher.init(mode, cryption.secretKey, new IvParameterSpec(iv));
        } else {
            cryption.cipher.init(mode, cryption.secretKey);             
        }
    } catch(Exception e) { 
        log.error("init", e);
    }
}   
}

And here is the c# code i use for decrypting

        private void btnDecrypt_Click(object sender, EventArgs e)
    {
        byte[] salt = Encoding.UTF8.GetBytes("cdWSu23E9BLbNXWUTnrznFgc");
        int iterations = 128;
        Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes("gygp6yevKWKBUwFy4GXpuFwT", salt, iterations);
        byte[] key = rfc2898.GetBytes(24);


        AesManaged aesCipher = new AesManaged();
        aesCipher.KeySize = 256;
        aesCipher.BlockSize = 128;
        aesCipher.Mode = CipherMode.CBC;
        aesCipher.Padding = PaddingMode.PKCS7;
        aesCipher.Key = key;

        String cipherB64 = "cFSGQv/mEnzaU9fAKIyFTvlIMxKnN8yRgzXm/E7MLoBF3g8iE6tloBcI84po+cT3r2Yz+wothDhYoM02yppPLHcv8Mj0FLF3frtAlOGq3TormcbwmSzx3JdB+GtFtliZkxNCyeTiGWi3jqnHsTRo7G3lQPEEUXEt03kFjErZfqEf8IwcD+PNqwtsU1fCn0gNgVvcvJck795U304QfDCOfkVEjNomGhbz4hTy4HPgokXUSWOEQEihTjz3j70+JZvLhsYGRnIxmRad8gsn7sVBr8vfG9KnL8i1CUh9vKuGLjiZCIhz7r00j4bmQYrjoj9yoKJmPVQsxW7FfTnOJFwlYw==";
        String ivB64 = "wWJLcuahcy4ZLLkW6CIqaA==";
        byte[] cipherText = Convert.FromBase64String(cipherB64);
        aesCipher.IV = Convert.FromBase64String(ivB64);

        ICryptoTransform decryptTransform = aesCipher.CreateDecryptor();
        byte[] plainText = decryptTransform.TransformFinalBlock(cipherText, 0, cipherText.Length);
        txtOutput.Text = System.Text.Encoding.UTF8.GetString(plainText);
    }
Was it helpful?

Solution

I doubt you use the exact same input to the ciphers in both java and c# - for starters the key you generate in c# is 24 bytes (196 bits), but you specify a keysize of 256. That can't be right.

     Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes("gygp6yevKWKBUwFy4GXpuFwT", salt, iterations);
>>>  byte[] key = rfc2898.GetBytes(24);      

     AesManaged aesCipher = new AesManaged();
>>>  aesCipher.KeySize = 256;
     aesCipher.BlockSize = 128;
     aesCipher.Mode = CipherMode.CBC;
     aesCipher.Padding = PaddingMode.PKCS7;
>>>  aesCipher.Key = key;

Do a raw print out of the generated keys in both java (the bytes from tmpSecretKey.getEncoded()) and in c# (the value of byte[] key) and compare. If/when those match but it still don´t work do the same with the iv and the byte value of the data to encrypt.

OTHER TIPS

Crypto is designed to fail if any parameters do not match. You need to check all inputs to both encryption and decryption methods to ensure that they are the same. This is not just checking the Base64 characters, but checking the byte[] outputs from the two key generation methods and the IV conversions. The byte arrays need to match exactly, bit for bit, since even a single bit error will wreck decryption.

Start by simplifying parts of your code. Using an explicitly coded key:

byte[] myKey = new byte[] {0x00, 0x01, 0x02 ... 0x0F};

will remove any differences in the two key generation methods. You are moving between systems so you cannot rely on default settings for parameters. You need to explicitly set every parameter on both sides.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top