AES-CTR is a mode of operation that turns AES into a stream cipher by encrypting a running counter and XORing the result with the data. CTR stands for counter mode and is defined in NIST SP 800-38A. It needs no padding, so ciphertext is exactly as long as the plaintext, and it provides no integrity check on its own. AES-GCM is built on top of it.
The encryptor builds a sequence of 16-byte counter blocks T1, T2, T3 and so on. Each one is encrypted with AES to give a keystream block, and the keystream is XORed with the plaintext. Decryption is the same operation. The last block can be partial, since unused keystream bytes are simply dropped. Only the AES encrypt direction is ever used, and blocks do not depend on one another, so work can run in parallel.
NIST SP 800-38A has one hard rule: across all messages encrypted under a key, every counter block must be distinct. Layout is up to you. Common choices:
The example uses Python's cryptography package. It checks that the keystream equals AES encryption of consecutive counter values, and that OpenSSL agrees on the output.
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
key = bytes(range(16))
nonce = bytes.fromhex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")
e = Cipher(algorithms.AES(key), modes.CTR(nonce)).encryptor()
print(e.update(b"hello").hex())
e = Cipher(algorithms.AES(key), modes.CTR(nonce)).encryptor()
ks = e.update(bytes(32))
ecb = Cipher(algorithms.AES(key), modes.ECB()).encryptor()
print(ks[:16].hex(), ecb.update(nonce).hex())
print(ks[16:].hex(), ecb.update((int.from_bytes(nonce, "big") + 1).to_bytes(16, "big")).hex())
0ec2ab845b
66a7c7e8345231489751de073316adad 66a7c7e8345231489751de073316adad
b281d700b79e3cada4ad73bb6e9c1fea b281d700b79e3cada4ad73bb6e9c1fea
The five-byte message "hello" gives five ciphertext bytes, 0ec2ab845b, and the command openssl enc -aes-128-ctr printed the same value.
No. AES-CTR needs no padding because it XORs a keystream and works on any length, including a single byte. The ciphertext length equals the plaintext length, which is also a small leak, since observers learn the exact message size. CBC, in comparison, pads to a multiple of 16 bytes.
The same keystream is applied twice, and an attacker who XORs the two ciphertexts gets the XOR of the two plaintexts. With one key and one counter start, attack at dawn and retreat at six produced 13110013060a5441155444121e16, identical to the XOR of the plaintexts. RFC 3686 calls any reuse of the IV with the same nonce and key catastrophic, and says fresh keys are needed.