Glossary

AES-OFB

AES-OFB is the Output Feedback mode of AES, which turns the block cipher into a stream cipher by encrypting an IV over and over and XORing the results with the data. OFB stands for Output Feedback and is defined in NIST SP 800-38A. It needs no padding, so ciphertext is exactly as long as the plaintext, and it gives no integrity protection.

How it works

OFB feeds the cipher's own output back into its input. The first input block is the IV. Each output block is the AES encryption of the previous output block, and every output block is XORed with one block of plaintext. A short final block simply uses the leftmost bytes of the last output block. Decryption runs the identical steps, because XOR undoes itself. Only the AES encrypt direction is ever used.

  • IV: 16 bytes for AES. SP 800-38A requires it to be a nonce, meaning unique for every message under one key. It does not need to be unpredictable.
  • Key: 128, 192 or 256 bits.
  • Keystream: depends only on the key and IV, never on the data. If the IV is known, the output blocks can be generated before the data arrives.
  • Parallelism: none, in either direction, because each block needs the previous one. This is the main speed difference from AES-CTR.

The first output block is plain AES of the IV, which is also what AES-CFB uses for its first block. The script below confirms the keystream chain, then flips one ciphertext bit.

import warnings; warnings.simplefilter("ignore")
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
key = bytes(range(16))
iv = bytes(range(16))
ofb = lambda: Cipher(algorithms.AES(key), modes.OFB(iv))
ecb = Cipher(algorithms.AES(key), modes.ECB()).encryptor()
o1 = ecb.update(iv)
o2 = ecb.update(o1)
ks = ofb().encryptor().update(bytes(32))
print("O1", ks[:16].hex(), ks[:16] == o1)
print("O2", ks[16:].hex(), ks[16:] == o2)
msg = b"pay 100 to bob. pay 100 to eve. end"
c = bytearray(ofb().encryptor().update(msg))
c[4] ^= ord("1") ^ ord("9")
print(ofb().decryptor().update(bytes(c)).decode())
O1 0a940bb5416ef045f1c39458c653ea5a True
O2 aee71ea541d7ae4beb60becc593fb663 True
pay 900 to bob. pay 100 to eve. end

Encrypting the five bytes "hello" with this key and IV gave 62f167d92e, and openssl enc -aes-128-ofb and Node's aes-128-ofb printed the same bytes.

What happens if an OFB IV is reused?

Two messages encrypted with the same key and IV share one keystream, so XORing the two ciphertexts yields the XOR of the plaintexts. With an all-zero IV, "attack at dawn" and "retreat at six" gave 13110013060a5441155444121e16, exactly the XOR of the two plaintexts. SP 800-38A says every message under a key needs its own IV.

Do bit errors spread in AES-OFB?

No. A flipped ciphertext bit flips only the same plaintext bit, and later blocks are unaffected, according to SP 800-38A Appendix D. The flip side is that an attacker can change chosen bits, as the "bob" example shows. A bit error in the IV, however, corrupts every block.

Common pitfalls

  • Reusing an IV: the keystream repeats and plaintext XORs leak. Generate a fresh unique IV per message, for example 16 random bytes.
  • No authentication: the demo turned 100 into 900 with no error. Add HMAC over the IV and ciphertext, or switch to AES-GCM.
  • Expecting speed from parallelism: OFB cannot encrypt or decrypt blocks in parallel. AES-CTR can.
  • Library removal warnings: Python cryptography 48 prints "OFB has been moved to cryptography.hazmat.decrepit.ciphers.modes.OFB", and the old import path is removed in 49.0.0. Expect to import it from the decrepit module and treat that as a hint to migrate.
  • Choosing OFB for new designs: nothing it does is better than CTR or GCM. Keep it for legacy interoperability.

Related terms

  • AES — the block cipher OFB wraps.
  • AES-CTR — the counter-based stream mode that can run in parallel.
  • AES-CBC — the chained mode that needs padding.
  • AES-GCM — authenticated encryption that replaces OFB in new protocols.
  • HMAC — adds the integrity check OFB lacks.

See also