Skip to content
Cryptography Reviewed 2026-09-12

Weak Cipher Mode

What does this mean ?

A cipher mode determines how a block cipher protects messages. Choosing “AES” without specifying the construction, nonce handling and integrity check is incomplete. Encryption that hides content but does not authenticate it may permit undetected modification.

What can happen ?

ECB reveals repeated-block patterns. Reusing a nonce with AES-GCM can undermine confidentiality and authentication. Ignoring an authentication failure or omitting the tag can cause the application to accept altered data. Older algorithms with inadequate security should not be selected for new designs.

Recommendation

Prefer a reviewed authenticated-encryption API such as AES-GCM or ChaCha20-Poly1305 in a suitable protocol. Use a cryptographically generated key, a fresh nonce according to the algorithm's requirements and its usage limits, and the full recommended authentication tag. Store or transmit the nonce and tag with the ciphertext; they are not secret.

Authenticate relevant metadata as associated data, including a protocol/version identifier where appropriate. Never release decrypted content before authentication succeeds. Protect keys separately and design rotation/version handling. CBC is not automatically broken in every reviewed encrypt-then-MAC protocol, but adding ad hoc integrity to an encryption-only sample is not a good starting point for a new application.

Sample Code

These examples encrypt one bounded byte string with a managed key. The constant associated data identifies a fixture record format. Real protocols must specify metadata, serialization, key IDs, replay protection and maximum per-key usage. Random nonces reduce collision probability; they still require sensible message limits and key rotation.

cryptography AESGCM appends a 16-byte tag to the ciphertext:

import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def seal_record(key: bytes, plaintext: bytes) -> tuple[bytes, bytes]:
    if len(key) != 32 or len(plaintext) > 65536:
        raise ValueError("Invalid key or record size")
    nonce = os.urandom(12)
    sealed = AESGCM(key).encrypt(nonce, plaintext, b"record-v1")
    return nonce, sealed

def open_record(key: bytes, nonce: bytes, sealed: bytes) -> bytes:
    if len(key) != 32 or len(nonce) != 12 or not 16 <= len(sealed) <= 65552:
        raise ValueError("Invalid encrypted record")
    return AESGCM(key).decrypt(nonce, sealed, b"record-v1")

Treat InvalidTag as a failure. Do not return guessed or partial plaintext after an exception.

Node's API handles the GCM tag separately:

import { randomBytes, createCipheriv, createDecipheriv } from 'node:crypto';
const recordContext = Buffer.from('record-v1');

function sealRecord(key, plaintext) {
  if (key.length !== 32 || plaintext.length > 65536) throw new Error('Invalid record');
  const nonce = randomBytes(12);
  const cipher = createCipheriv('aes-256-gcm', key, nonce, { authTagLength: 16 });
  cipher.setAAD(recordContext);
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
  return { nonce, ciphertext, tag: cipher.getAuthTag() };
}

function openRecord(key, record) {
  if (key.length !== 32 || record.nonce.length !== 12 || record.tag.length !== 16 ||
      record.ciphertext.length > 65536) throw new Error('Invalid encrypted record');
  const decipher = createDecipheriv('aes-256-gcm', key, record.nonce, { authTagLength: 16 });
  decipher.setAAD(recordContext);
  decipher.setAuthTag(record.tag);
  return Buffer.concat([decipher.update(record.ciphertext), decipher.final()]);
}

Inputs here are Buffers, not arbitrary decoded JSON. Validate your record serialization before calling these helpers. decipher.final() must succeed before the returned bytes are used.

.NET8+ AES-GCM with a 32-byte managed key and bounded plaintext:

byte[] nonce = System.Security.Cryptography.RandomNumberGenerator.GetBytes(12);
byte[] ciphertext = new byte[plaintext.Length];
byte[] tag = new byte[16];
byte[] context = System.Text.Encoding.UTF8.GetBytes("record-v1");
using var aes = new System.Security.Cryptography.AesGcm(key, tagSizeInBytes: 16);
aes.Encrypt(nonce, plaintext, ciphertext, tag, context);

byte[] recovered = new byte[ciphertext.Length];
aes.Decrypt(nonce, ciphertext, tag, recovered, context);
// Use recovered only if Decrypt returned successfully.

Validate record lengths before allocating buffers. Do not reuse the nonce for another encryption under this key.

Java's GCM doFinal output includes the tag:

byte[] nonce = new byte[12];
new java.security.SecureRandom().nextBytes(nonce);
var cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, key,
    new javax.crypto.spec.GCMParameterSpec(128, nonce));
cipher.updateAAD("record-v1".getBytes(java.nio.charset.StandardCharsets.UTF_8));
byte[] sealed = cipher.doFinal(plaintext);

key is a managed AES key. For decryption, initialize with the stored nonce, apply the same associated data and call doFinal(sealed). Reject AEADBadTagException; do not consume unauthenticated output from streaming update calls.

OpenSSL's GCM API returns the authentication tag through an output parameter:

if (strlen($key) !== 32 || strlen($plaintext) > 65536) {
    throw new InvalidArgumentException('Invalid key or record size');
}
$nonce = random_bytes(12);
$tag = '';
$ciphertext = openssl_encrypt($plaintext, 'aes-256-gcm', $key,
                             OPENSSL_RAW_DATA, $nonce, $tag, 'record-v1', 16);
if ($ciphertext === false || strlen($tag) !== 16) {
    throw new RuntimeException('Encryption failed');
}
// Store nonce, ciphertext and tag together; keep key separate.
$recovered = openssl_decrypt($ciphertext, 'aes-256-gcm', $key,
                            OPENSSL_RAW_DATA, $nonce, $tag, 'record-v1');
if ($recovered === false) throw new RuntimeException('Authentication failed');

For records loaded from storage, require a 12-byte nonce, 16-byte tag and bounded ciphertext before decrypting. Omitting the tag is not a complete GCM example.

Regression checks

With an ephemeral test key, check that encrypt/decrypt returns the original bytes. Encrypt the same value twice and verify different nonces/ciphertexts. Reject a changed tag, changed ciphertext, wrong key and wrong associated data. Confirm failure returns no plaintext. Test size boundaries and serialized record validation without printing keys or confidential inputs.

References