Introduction
The cbc mode java explained tutorial introduces the most commonly taught block cipher mode of operation. CBC (Cipher Block Chaining) was specified by the National Bureau of Standards in FIPS 81 in 1980 and standardized internationally as ISO 10116. The mode takes a key, an initialization vector, and a plaintext, and produces a ciphertext by XORing each plaintext block with the previous ciphertext block before encrypting under the block cipher. CBC was the default mode for SSL 3.0, TLS 1.0, and TLS 1.1, and remains a building block inside AES-CCM and AES-CBC-MAC.
For a Java learner the cbc mode java explained program below is a clear way to see how chaining eliminates the ECB failure mode. We implement CBC manually using AES/ECB/NoPadding as the block primitive and compare the output with the JDK's built-in AES/CBC/PKCS5Padding implementation.
What is CBC Mode?
The cbc mode java explained code in this tutorial implements a feedback block cipher mode. CBC takes a 128, 192, or 256-bit AES key, a 16-byte initialization vector, and a plaintext whose length is a multiple of 16 bytes (or which has been padded to a multiple of 16 bytes). It outputs a ciphertext of the same length as the padded plaintext.
CBC is the simplest chaining mode. Each plaintext block P_i is XORed with the previous ciphertext block C_(i-1) (or the IV for the first block) before being encrypted: C_i = E_K(P_i XOR C_(i-1)). The XOR ensures that even if two plaintext blocks are equal, the two ciphertext blocks are different.
Decryption is the inverse: P_i = D_K(C_i) XOR C_(i-1). Note that the decryption requires only the block cipher decryption primitive, not the encryption primitive. This is one of the advantages of CBC over ECB: you can decrypt a single block without decrypting the previous block (although you still need the previous ciphertext block for the XOR).
CBC is not an authenticated mode. An attacker can reorder, swap, or repeat ciphertext blocks and the decryption will not detect the change. To detect tampering you need to add a MAC; AES-CBC plus HMAC-SHA256 was the standard construction in TLS 1.0 to 1.1 before AEAD ciphers replaced it in TLS 1.2 and 1.3.
How CBC Mode Works Step by Step
Step 1: Pad the plaintext. The standard padding is PKCS5 (or PKCS7, which is the same for 16-byte blocks): append N bytes of value N where N is the number of padding bytes needed to make the length a multiple of 16. If the plaintext is already a multiple of 16, append a full 16-byte block of value 16.
Step 2: Initialization. Set the previous ciphertext block equal to the IV. This IV must be unpredictable to the attacker; a random IV or a counter-based IV both work, but a static IV is insecure because it leaks equality of the first blocks of two messages.
Step 3: For each plaintext block i from 1 to N. Compute intermediate = P_i XOR previous. Compute C_i = E_K(intermediate). Set previous = C_i.
Step 4: Output. The ciphertext is the concatenation of C_1, C_2, ..., C_N. The IV is transmitted alongside the ciphertext but is not secret.
Step 5: Decryption. For each ciphertext block i from 1 to N. Compute intermediate = D_K(C_i). Compute P_i = intermediate XOR previous. Set previous = C_i. Strip the padding by reading the last byte N and removing the last N bytes.
Step 6: Verify padding. The last byte N must be in the range 1 to 16, and the last N bytes must all be equal to N. If not, the decryption fails with a padding error. Note that the padding oracle attack exploits this verification step; modern implementations use constant-time verification and return generic errors.
Java Program for CBC Mode
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
public class CbcModeDemo {
private static final int BLOCK = 16;
private final SecretKeySpec keySpec;
public CbcModeDemo(byte[] key) {
this.keySpec = new SecretKeySpec(key, "AES");
}
private byte[] aesEcbEncrypt(byte[] block) throws Exception {
Cipher c = Cipher.getInstance("AES/ECB/NoPadding");
c.init(Cipher.ENCRYPT_MODE, keySpec);
return c.doFinal(block);
}
private byte[] aesEcbDecrypt(byte[] block) throws Exception {
Cipher c = Cipher.getInstance("AES/ECB/NoPadding");
c.init(Cipher.DECRYPT_MODE, keySpec);
return c.doFinal(block);
}
private static byte[] xor(byte[] a, byte[] b) {
byte[] out = new byte[a.length];
for (int i = 0; i < a.length; i++) out[i] = (byte) (a[i] ^ b[i]);
return out;
}
private static byte[] pkcs5Pad(byte[] data) {
int n = BLOCK - (data.length % BLOCK);
byte[] out = new byte[data.length + n];
System.arraycopy(data, 0, out, 0, data.length);
for (int i = data.length; i < out.length; i++) out[i] = (byte) n;
return out;
}
private static byte[] pkcs5Unpad(byte[] data) {
if (data.length == 0 || data.length % BLOCK != 0) {
throw new IllegalArgumentException("Invalid PKCS5 length");
}
int n = data[data.length - 1] & 0xFF;
if (n < 1 || n > BLOCK) {
throw new IllegalArgumentException("Invalid PKCS5 padding");
}
for (int i = data.length - n; i < data.length; i++) {
if ((data[i] & 0xFF) != n) {
throw new IllegalArgumentException("Invalid PKCS5 padding");
}
}
return Arrays.copyOfRange(data, 0, data.length - n);
}
public byte[] encrypt(byte[] iv, byte[] plaintext) throws Exception {
byte[] padded = pkcs5Pad(plaintext);
byte[] prev = iv;
byte[] out = new byte[padded.length];
for (int i = 0; i < padded.length; i += BLOCK) {
byte[] block = Arrays.copyOfRange(padded, i, i + BLOCK);
byte[] x = xor(block, prev);
byte[] c = aesEcbEncrypt(x);
System.arraycopy(c, 0, out, i, BLOCK);
prev = c;
}
return out;
}
public byte[] decrypt(byte[] iv, byte[] ciphertext) throws Exception {
byte[] prev = iv;
byte[] out = new byte[ciphertext.length];
for (int i = 0; i < ciphertext.length; i += BLOCK) {
byte[] block = Arrays.copyOfRange(ciphertext, i, i + BLOCK);
byte[] d = aesEcbDecrypt(block);
byte[] p = xor(d, prev);
System.arraycopy(p, 0, out, i, BLOCK);
prev = block;
}
return pkcs5Unpad(out);
}
public static void main(String[] args) throws Exception {
byte[] key = new byte[16];
new SecureRandom().nextBytes(key);
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
String pt = "CBC mode chains blocks with XOR";
CbcModeDemo cbc = new CbcModeDemo(key);
byte[] ct = cbc.encrypt(iv, pt.getBytes("UTF-8"));
byte[] recovered = cbc.decrypt(iv, ct);
// Cross-check against the JDK built-in AES/CBC/PKCS5Padding
Cipher ref = Cipher.getInstance("AES/CBC/PKCS5Padding");
ref.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
byte[] refCt = ref.doFinal(pt.getBytes("UTF-8"));
System.out.println("Key : " + Base64.getEncoder().encodeToString(key));
System.out.println("IV : " + Base64.getEncoder().encodeToString(iv));
System.out.println("Plaintext: " + pt);
System.out.println("Ciphertext (manual CBC): " + Base64.getEncoder().encodeToString(ct));
System.out.println("Ciphertext (JDK CBC) : " + Base64.getEncoder().encodeToString(refCt));
System.out.println("Match : " + Arrays.equals(ct, refCt));
System.out.println("Recovered : " + new String(recovered, "UTF-8"));
}
}
Code Explanation Line by Line
The class stores a SecretKeySpec and declares the constant BLOCK equal to 16 (the AES block size in bytes).
aesEcbEncrypt() and aesEcbDecrypt() wrap the JDK's "AES/ECB/NoPadding" cipher. We use this algorithm because it gives us the raw AES block encryption and decryption without any chaining or padding. ECB on a single 16-byte block is exactly what CBC needs to chain.
xor() is the helper that XORs two equal-length byte arrays. The XOR is the heart of CBC: it mixes each plaintext block with the previous ciphertext block before the AES call.
pkcs5Pad() appends the standard PKCS5/PKCS7 padding. The padding value N is the number of bytes appended, in the range 1 to 16. If the plaintext is already a multiple of 16, a full 16-byte block of value 16 is appended.
pkcs5Unpad() strips the padding. The last byte N is read as the padding length, then the last N bytes are verified to all equal N. Any deviation raises an IllegalArgumentException. In production code the verification must be constant time to avoid padding oracle attacks.
encrypt() pads the plaintext, then iterates over the padded data in 16-byte blocks. For each block, it XORs with the previous ciphertext block (or the IV for the first block), encrypts the result, and appends the ciphertext to the output. The previous ciphertext block is updated after each iteration.
decrypt() iterates over the ciphertext in 16-byte blocks. For each block, it decrypts with AES, XORs with the previous ciphertext block (or the IV for the first block), and appends the result to the output. The previous ciphertext block is the original ciphertext, not the decrypted plaintext, because CBC uses the ciphertext as the chaining value.
main() demonstrates the round trip with a 16-byte key and a 16-byte IV. It encrypts the plaintext, decrypts the ciphertext, and then cross-checks the manual implementation against the JDK's built-in "AES/CBC/PKCS5Padding". The "Match" line proves that the manual implementation produces the same ciphertext as the JDK.
Sample Input and Output
Input: Key : <16 random bytes, base64> IV : <16 random bytes, base64> Plaintext: CBC mode chains blocks with XOR Output: Ciphertext (manual CBC): <base64 of 48 bytes> Ciphertext (JDK CBC) : <same base64 of 48 bytes> Match : true Recovered : CBC mode chains blocks with XOR
The exact ciphertext bytes are random because the key and IV are random. The two invariant lines are the Match: true line, which proves the manual implementation matches the JDK, and the Recovered: plaintext line, which proves the round trip works.
Common Mistakes to Avoid
First, using a static IV is the classic CBC bug. If the IV is the same for two messages under the same key, the first blocks leak equality. The IV must be unpredictable to the attacker; a random IV from a CSPRNG or a counter-based IV are both acceptable.
Second, using ECB-style padding verification that returns early on the first mismatching byte opens a padding oracle. The verification must be constant time and the error returned must be generic, not distinguishing between a padding failure and a MAC failure.
Third, using CBC without a MAC is insecure. An attacker can swap, repeat, or reorder ciphertext blocks and the decryption will not detect the change. The standard construction is Encrypt-then-MAC: encrypt with CBC, then HMAC the IV plus ciphertext, and verify the HMAC before decrypting.
Fourth, using CBC with a 64-bit block cipher (e.g., DES, 3DES, Blowfish) leaks the birthday bound after roughly 2 to the power 32 blocks. The CVE-2016-2187 "Sweet32" attack exploited this to break HTTPS sessions that still used 3DES. AES with its 128-bit block has a birthday bound of 2 to the power 64, which is safe for ordinary traffic.
Real-World Applications
CBC was the default mode for SSL 3.0 (1996) and TLS 1.0 (1999). The padding oracle attacks of the 2010s (POODLE for SSL 3.0, Lucky 13 for TLS 1.0) exploited the combination of CBC plus MAC-then-Encrypt, which is why TLS 1.2 deprecated CBC plus HMAC in favor of AES-GCM, and TLS 1.3 removed CBC entirely.
AES-CBC remains a building block inside AES-CCM (used in Bluetooth Low Energy and IEEE 802.15.4) and AES-CBC-MAC (used in ANSI X9.9 banking and in IPsec AH). The CBC primitive is correct; the failure mode in TLS was the way the padding and MAC were composed.
OpenSSL still ships the "enc -aes-128-cbc" command for compatibility with old scripts and key derivation. The PKCS7 padding is applied by default; the "-nosalt" and "-K" options are needed to use the raw key without the OpenSSL key derivation function.
On the JVM specifically, the JDK SunJCE provider ships "AES/CBC/PKCS5Padding" and "AES/CBC/NoPadding". The NoPadding variant requires the caller to ensure the input length is a multiple of 16 bytes; the PKCS5Padding variant handles the padding automatically.
The Padding Oracle attack by Vaudenay in 2002 was the first published demonstration that CBC plus a padding oracle leaks the plaintext byte by byte. If you study modern applied cryptography you will see the Vaudenay paper as the foundation of every later padding oracle attack including POODLE and Lucky 13.
Conclusion
You learned what CBC mode is, how the XOR chaining eliminates the ECB failure mode, and how to implement the full mode with PKCS5 padding using only the JDK AES block primitive. You also saw why the IV must be unpredictable, why the padding verification must be constant time, and why CBC must be combined with a MAC for security. Next, study CFB and OFB for the stream cipher variants of CBC, CTR for the modern counter mode, and GCM for the modern AEAD that replaced CBC in TLS 1.3.
Comments
Comments
Post a Comment