AES-128 encryption with keys longer than 16 bytes

I know this is beginner question, but can someone please explain how can I encode a text using AES-128 with PKCS7 padding (I am using the crates aes and block-modes) in such a way that I can decrypt it on Java (using AES/CBC/PKCS5Padding) when the key is longer than 16 bytes?

In Java, using the standard Cipher class, for example, I can specify a key that has 32 bytes.

Thank you.

In AES, the number in its name is the key length in bits. For AES/CBC/PKCS5Padding with a 32 byte key, you should be able to use Aes256 from the aes crate, with the same Cbc block mode.

I haven’t actually tested this myself to see if the output is compatible.

When given a 256 bit key to use with AES-128 for the above cipher, Java generates a 128 bit key. I am simply unable to find a similar implementation for Rust.

This was a mistake on my side as I was using the wrong key size.

You shouldn't use raw AES.

Instead you should use higher level crypto constructions like provided by libsodium. It provides crypto constructions for various purposes.

For example when you do not re-use the key, you can use secretbox. This handles things like checking that the encrypted value wasn't tampered with and will give you an error if it was. Otherwise an attacker may be able to slowly leak information an encrypted value if they are able to for example give the encrypted value to a system that will behave differently (giving error, taking a different amount of time, ...) depending on if the decryped value was valid or not. It also ensures that the encrypted value is always unique so that an attacker can't learn if the same value was encrypted twice. I believe secretbox will add a nonce to the value before encrypting it and then uses Poly1305 MAC to add a tag to verify if the encrypted value was not tampered with. When decrypting it will check this tag against the encrypted value using the secret key and only if it wasn't tampered with will it decrypt the value.

When you do re-use keys you could use something like crypto_aead_xchacha20poly1305_ietf_encrypt.

There are also apis for key derivation to generate a key from a password.

1 Like

This is applicable primarily only to libsodium. The RustCrypto project provides a safer implementation of the AES algorithm that you can use encryption and decryption with. They also provide crypto_box, however, which also works. But I disagree with the claim that you shouldn't use raw AES for the sole reason that that's equivalent to saying you shouldn't use raw XChaCha20 Poly1305. That's especially true if you want to use an AES cipher mode that secretbox doesn't provide (e.g. AES-256-GCM-SIV for example (I don't think it provides this)). I'm not a crypto expert, mind, but I would say that "don't use raw AES" is a controversial claim at best that is hard to uphold with actual evidence, particularly since code like monocypher doesn't say anything about "not using raw AES". AEAD is nice but sometimes you can't use it (e.g. FDE where you are strongly encouraged to use AES-XTS, which is not an AEAD).
I might be wrong in some of this -- and if I am please feel free to correct me. But I just feel like the claim of "don't use raw AES" is a difficult claim to make, particularly since it heavily depends on what your using AES for. If this was about implementing AES, I'd agree -- never implement AES without knowing what your doing. But this topic is about using an already (hopefully audited) AES implementation.

XChaCha20 Poly1305 provides authentication, raw AES does not.

Secretbox doesn't use AES. It uses XSalsa20 for encryption and Poly1305 MAC for authentication. AES-GCM is provided by libsodium as it is an AEAD: AES256-GCM - libsodium

An implementation can't change the fundamental fact that raw AES is not authenticated. AES-GCM is a crypto construct that is authenticated, but that is not raw AES. In addition OP was talking about AES/CBC/PKCS5Padding, which is not authenticated.

libsodium makes it easier to use a safe method. That doesn't mean that if you don't use libsodium, whatever method is provided is as safe as libsodium.

Monocypher doesn't provide raw AES at all. It only provides authenticated encryption.

3 Likes

Again, though, my point holds. There are lots of reasons why an AEAD has to be used alongside a non-AEAD cipher, such as TLS. AES-CBC with PKCS5 doesn't provide the same level of security as an AEAD, as you noted, and I agree with that. But AES-cBC isn't an AEAD so using it on anything is not as secure as AES-GCM or ChaCha20 Poly1305. But people still have to use it, or just do for other reasons, so I'm still not sure how requesting people not to use raw AES holds here. Its very situation-dependent, is it not?

The only reason not to use an AEAD is when you are forced to use a specific crypto system. If you can choose which one to use, like OP seems to be able to, you should always use an AEAD. There is no reason not to in that case and it improves security.

2 Likes

I should have known that I needed to warn that CBC isn’t particularly good without some kind of message authentication, and that using PKCS padding with it is downright dangerous in an online cryptosystem, as it can often be turned into a padding oracle.

That we can agree on.

Thank you for the detailed replies. In my case, the reason I am using AES-CBS with PKCS5 is simply because the application I am talking to requires it for historical reasons and it is not possible for me to update it. The concerns raised regarding the security of the solution have already been raised though.

I wanted to confirm this worked for myself, so for the benefit of anyone else that comes across this thread, here’s some Rust code to do AES-256 encryption with a fixed key:

use aes::Aes256;
use base64ct::{Base64, Encoding};
use block_modes::{BlockMode, Cbc};
use block_modes::block_padding::Pkcs7;
use hmac::Hmac;
use pbkdf2::pbkdf2;
use rand::RngCore;
use sha2::Sha256;

fn main() {
    let mut key = [0u8; 32];
    pbkdf2::<Hmac<Sha256>>(b"changeme", b"salt", 10, &mut key);
    let mut iv = [0u8; 16];
    rand::thread_rng().fill_bytes(&mut iv);
    let cipher = Cbc::<Aes256, Pkcs7>::new_from_slices(&key, &iv).unwrap();
    let enc = cipher.encrypt_vec(b"Hello, world!");
    let mut msg = Vec::with_capacity(iv.len() + enc.len());
    msg.extend_from_slice(&iv);
    msg.extend_from_slice(&enc);
    println!("{}", Base64::encode_string(&msg));
}

and the corresponding Java code to decrypt it:

package test;

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;
import java.util.Arrays;
import java.util.Base64;

public class DecryptAES {
    public static void main(String[] args) throws Exception {
        byte[] msg = Base64.getDecoder().decode(args[0]);
        PBEKeySpec pbeKeySpec = new PBEKeySpec("changeme".toCharArray(), "salt".getBytes(), 10, 256);
        SecretKey secretKey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(pbeKeySpec);
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
        IvParameterSpec iv = new IvParameterSpec(Arrays.copyOf(msg, 16));
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
        byte[] dec = cipher.doFinal(msg, 16, msg.length - 16);
        System.out.println(new String(dec));
    }
}

For example:

> cargo run --release
   Compiling rustaes v0.1.0
    Finished release [optimized] target(s) in 1.03s
     Running `target\release\rustaes.exe`
ugWZd3YFNqk/DuhSPoyHSxAv4+40JLnWh/kxK/SfXcw=

and passing that to the Java code:

> .\gradlew run --args ugWZd3YFNqkiCfdCPoyHSxAv4+40JLnWh/kxK/SfXcw=

> Task :run
Hello, wrust!

Well, it looks like the encrypted message got corrupted at some point without any errors, but I’m sure it’ll be fine, right?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.