Understand AES best practices, block padding, and modes

Introduction

AES (Advanced Encryption Standard) is the most widely used symmetric encryption; HTTPS/TLS, Wi‑Fi (WPA2/3), full-disk encryption (BitLocker, FileVault), ZIP/7z archive encryption, and messaging apps (Signal, WhatsApp) all rely on it. Understanding how AES works helps explain how modern secure encryption is implemented.

Understanding encryption and AES

Why use AES

A “symmetric encryption algorithm” means the same key is used to transform plaintext into ciphertext and to reverse that operation. Considering practicality, AES is one of the symmetric encryption standards that best balances security, performance, and hardware support.

The recipe for perfect secrecy

The simplest classic example of perfect secrecy is the One-Time Pad (OTP). Any data in a computer can be seen as a series of 0s and 1s; by XORing the plaintext with a truly random key of the same length that is used only once, the ciphertext reveals no information about the plaintext even to an attacker with unlimited computing power.

Achieving perfect secrecy is very strict (OTP is nearly impractical). Claude Shannon’s concepts let us balance “theoretical security” and “practical feasibility”:

  • Confusion: hide the relationship between the key and the ciphertext so attackers cannot derive the key
  • Diffusion: ensure each bit of plaintext affects many bits of ciphertext to avoid preserving patterns

How AES works

AES is a block cipher that processes 128 bits of data at a time, with key sizes of 128 / 192 / 256 bits.

  • Processes a fixed-size block each time (128 bits = 16 bytes)
  • Uses the same Key for encryption and decryption
  • Applies multiple rounds of transformations to make recovery difficult

AES treats 16 bytes of data as a 4x4 matrix:

[ a0 a4 a8 a12 ]
[ a1 a5 a9 a13 ]
[ a2 a6 a10 a14 ]
[ a3 a7 a11 a15 ]
  • SubBytes
  • ShiftRows
  • MixColumns
  • AddRoundKey

AES modes of operation

AES itself only encrypts a single block, but real-world data is usually much larger, so modes of operation are used to handle many blocks:

ECB (Electronic Codebook)

Each block is encrypted independently; identical plaintext blocks produce identical ciphertext blocks. This is the biggest security flaw—encrypted images reveal the original outlines. Do not use in real development.

CBC (Cipher Block Chaining)

Each plaintext block is XORed with the previous ciphertext block before encryption. The first block uses a randomly generated IV (initialization vector). Same plaintext + different IV → completely different ciphertext. The downside is encryption must be done serially and cannot be parallelized. It was historically one of the most common symmetric modes.

CTR (Counter)

Turns a block cipher into a stream cipher: encrypt an incrementing counter (Nonce + counter) to generate a keystream that is XORed with the plaintext. Each block can be computed independently, supporting full parallelism, and no padding is required. Note: never reuse the same Nonce.

GCM (Galois/Counter Mode)

Adds GHASH authentication on top of CTR to produce an Auth Tag. Decryption verifies the Auth Tag to detect tampering. Provides both confidentiality and integrity; currently the recommended mode and widely used in HTTPS/TLS.

CFB / OFB

Both are variants that turn a block cipher into a stream cipher. CFB depends on the previous ciphertext block (similar to CBC), while OFB is independent of ciphertext and is more suitable for low-latency scenarios.

PKCS7 padding

Fill the missing bytes with the value equal to the number of missing bytes

Block ciphers operate on fixed block sizes. PKCS7 padding is a method to make plaintext length align with the block size. Supported block sizes: 1–255 bytes, most commonly used with AES (16-byte blocks).

Block size = 8 bytes, Plaintext = HELLO (5 bytes)
Original: H E L L O
Missing: 3 bytes
Padded: H E L L O 03 03 03
Another example, Plaintext = HELLO123 (exactly 8 bytes):
Even if it exactly aligns, add a full block, otherwise decryption cannot determine the end:
H E L L O 1 2 3 | 08 08 08 08 08 08 08 08
Missing 1 byte → fill 01
Missing 2 bytes → fill 02 02
Missing 5 bytes → fill 05 05 05 05 05
Missing 16 bytes→ fill 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10

How to remove padding after decryption?

Read the value of the last byte → remove that many bytes

length := len(plaintext)
unpadding := int(plaintext[length-1])
plaintext = plaintext[:(length - unpadding)]

Common vulnerability: Padding Oracle Attack

If a system returns different error messages on decryption failure, an attacker can exploit those differences to gradually recover the plaintext—this is the Padding Oracle Attack.

  • Use AES-GCM (an AEAD mode that does not require padding)
  • Do not leak whether padding was correct in error messages

Initialization Vector (IV)

IV / Nonce must be unique; some modes (like CBC) additionally require randomness

The initialization vector🔗 (IV) is a piece of random data introduced during encryption to ensure that the same plaintext produces different ciphertexts each time.

  1. Mode leakage: avoid letting attackers observe repeated ciphertext blocks and infer corresponding plaintext content (Fixed IV Penguin)
  2. Reuse: using the same IV with the same key causes a Many-Time Pad problem

IVs are typically prepended in plaintext to the encrypted data because the decryption side needs the same IV to recover the first block.

Conclusion

Correct algorithm + correct usage = real security
  • Use proven practices (standard libraries, mature packages)
  • IVs can be public but must be random (at least cryptographically secure pseudorandom)
  • Provide keys via environment variables
  • For new projects prefer AES-256-GCM; when compatibility is needed use AES-128-CBC + PKCS7; never use ECB.

Further reading