简体   繁体   中英

Curious behavior by C# to Java encryption/decryption using AES

I have very curious problem by string decryption using the AES algorithm. My C# application sends encrypted data (strings) to the Java application. Even I use the same key string, the decryption leads into the Exception:

javax.crypto.BadPaddingException: Given final block not properly padded at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:810)

however only when length of the plain input text to encrypt (at C# side) exceeds 1393 characters... But if the length equals to 1393 characters, or is lesser than 1393 characters, it works fine.

Here is the C# codefor encryption:

    private static string Encrypt(string textToEncrypt, string key)
    {
        try
        {
            RijndaelManaged rijndaelCipher = new RijndaelManaged();
            rijndaelCipher.Mode = CipherMode.CBC;
            rijndaelCipher.Padding = PaddingMode.PKCS7;

            rijndaelCipher.KeySize = 0x80; // 256bit key
            rijndaelCipher.BlockSize = 0x80;
            byte[] pwdBytes = Encoding.UTF8.GetBytes(key);
            byte[] keyBytes = new byte[0x10];
            int len = pwdBytes.Length;
            if (len > keyBytes.Length)
            {
                len = keyBytes.Length;
            }
            Array.Copy(pwdBytes, keyBytes, len);
            rijndaelCipher.Key = keyBytes;
            rijndaelCipher.IV = keyBytes;
            ICryptoTransform transform = rijndaelCipher.CreateEncryptor();
            byte[] plainText = Encoding.UTF8.GetBytes(textToEncrypt);
            return Convert.ToBase64String(transform.TransformFinalBlock(plainText, 0, plainText.Length));
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

And a Java code for decryption:

public static String Decrypt(String text, String key) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    byte[] keyBytes = new byte[16];
    byte[] b = key.getBytes("UTF-8");
    int len = b.length;
    if (len > keyBytes.length) {
        len = keyBytes.length;
    }
    System.arraycopy(b, 0, keyBytes, 0, len);

    SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
    IvParameterSpec ivSpec = new IvParameterSpec(keyBytes);
    cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

    BASE64Decoder decoder = new BASE64Decoder();
    byte[] results = cipher.doFinal(decoder.decodeBuffer(text));
    return new String(results, "UTF-8");
}

I've tried replace the BASE64Decoder to Base64 codec from apache, but the result was the same... I will be grateful for any advice or idea. Thanks.

If your transmitting ciphertext across a network, you should ensure the integrity of it with a message authentication code . The MAC should be validated before attempting to decrypt the ciphertext. This will defend against both unintended corrupting and malicious tampering of the message on the wire. In this case, it should help you ensure that the ciphertext you are transmitting is exactly the same as what your receiving.

I rewrote the java decrypt side to do a MAC validation. It assumes that the MAC is generated using the HMACSHA1 algorithm initialzed with the same key material used for the AES cipher (NOTE: this is an example only, the encryption key and the HMAC key should be different in a real system) and prepended to the ciphertext. The first 20 bytes of the message should be the MAC, followed by the ciphertext immediately afterwards.

public static String Decrypt(String message, String key) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    byte[] keyBytes = new byte[16];
    byte[] b = key.getBytes("UTF-8");
    int len = b.length;
    if (len > keyBytes.length) {
        len = keyBytes.length;
    }
    System.arraycopy(b, 0, keyBytes, 0, len);

    SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
    IvParameterSpec ivSpec = new IvParameterSpec(keyBytes);

    byte[] messageBytes =  DatatypeConverter.parseBase64Binary(message);
    byte[] macBytes = new byte[20];
    byte[] ciphertext = new byte[messageBytes.length - 20];

    System.arraycopy(messageBytes, 0, macBytes, 0, macBytes.length);
    System.arraycopy(messageBytes, 20, ciphertext, 0, ciphertext.length);

    Mac mac = Mac.getInstance("HMACSHA1");
    mac.init(keySpec);

    verifyMac(mac.doFinal(ciphertext), macBytes);

    cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

    byte[] results = cipher.doFinal(ciphertext);
    return new String(results, "UTF-8");
}

private static void verifyMac(byte[] mac1, byte[] mac2) throws Exception {
    MessageDigest sha = MessageDigest.getInstance("SHA1");
    byte[] mac1_hash = sha.digest(mac1);
    sha.reset();
    byte[] mac2_hash = sha.digest(mac2);

    if(!Arrays.equals(mac1_hash, mac2_hash)){
        throw new RuntimeException("Invalid MAC");
    }

}

Just some note on security practices. Using the UTF-8 bytes of a typed password does not give you enough entropy in your secret key. If your going to use a password as a crypto key you should process it using a key stretching algorithm. PBKDF2 is a popular choice for this. The IV for the AES cipher should be randomly generated (using a cryptographically secure source of randomness) and transmitted in the clear with the ciphertext. The key used to initialize AES cipher should be different than the key used for the MAC. You can use PBKDF2 to create a key twice as long as you need from your supplied password and use the first half for AES and the second half for HMAC-SHA1. As a final note, when verifying MACs, you should verify a hash of the MAC instead of the MAC directly (as demonstrated in the verifyMac method above). Verifying the MAs directly exposes a possible timing attack vector.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM