Introduction
The cfb cipher feedback mode java tutorial introduces a feedback mode that turns a block cipher into a self-synchronizing stream cipher. CFB (Cipher Feedback) 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 of any length (no padding required), and produces a ciphertext by XORing each plaintext segment with a keystream segment derived from the previous ciphertext segment. CFB is the mode used by SSH for the Cipher Block Chaining variant known as CFB-128 in the SSH-2 protocol.
For a Java learner the cfb cipher feedback mode java program below is a useful exercise because the mode demonstrates how a block cipher can be operated as a stream cipher. We implement CFB-128 (full-block feedback) manually using AES/ECB/NoPadding as the block primitive and compare the output with the JDK's built-in AES/CFB/NoPadding implementation.
What is CFB Mode?
The cfb cipher feedback mode java code in this tutorial implements a feedback stream cipher mode. CFB takes a 128, 192, or 256-bit AES key, a 16-byte initialization vector, and a plaintext of any length. It outputs a ciphertext of the same length as the plaintext.
CFB is similar to CBC but the roles of encryption and XOR are swapped. In CBC, the plaintext is XORed with the previous ciphertext and then encrypted. In CFB, the previous ciphertext is encrypted and then XORed with the plaintext to produce the new ciphertext. The block cipher is used only for encryption, never for decryption, which is one of the advantages of CFB over CBC.
The full-block CFB-128 variant operates on 16-byte segments. There are also CFB-8 and CFB-1 variants that operate on 8-bit and 1-bit segments; these are used in some legacy protocols where byte-level or bit-level synchronization is required.
CFB is not an authenticated mode. An attacker can flip bits in the ciphertext and the corresponding plaintext bits will flip in the next block; the mode provides no integrity. For real security you must combine CFB with a MAC.
How CFB Mode Works Step by Step
Step 1: Initialization. Set the feedback register equal to the IV. The IV must be unique per message under the same key; a random IV from a CSPRNG or a counter-based IV are both acceptable.
Step 2: For each plaintext block i from 1 to N. Compute keystream = E_K(feedback). Compute ciphertext block C_i = P_i XOR keystream. Set feedback = C_i.
Step 3: 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 4: Decryption. For each ciphertext block i from 1 to N. Compute keystream = E_K(feedback). Compute P_i = C_i XOR keystream. Set feedback = C_i. Note that the block cipher decryption is never used; only the encryption primitive is needed.
Step 5: Partial block. If the plaintext length is not a multiple of 16 bytes, the last keystream block is generated by encrypting the previous ciphertext block (or the IV for the first block if the plaintext is shorter than one block) and only the needed prefix of the keystream is XORed with the plaintext.
Step 6: Self-synchronization. If a ciphertext bit is corrupted, the corresponding plaintext bit is corrupted, and the next block's plaintext is corrupted because the feedback register is wrong. After one block, the cipher resynchronizes.
Java Program for CFB 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 CfbModeDemo {
private static final int BLOCK = 16;
private final SecretKeySpec keySpec;
public CfbModeDemo(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 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;
}
public byte[] encrypt(byte[] iv, byte[] plaintext) throws Exception {
byte[] out = new byte[plaintext.length];
byte[] feedback = iv;
int off = 0;
while (off < plaintext.length) {
byte[] ks = aesEcbEncrypt(feedback);
int len = Math.min(BLOCK, plaintext.length - off);
byte[] pBlock = Arrays.copyOfRange(plaintext, off, off + len);
byte[] cBlock = xor(pBlock, Arrays.copyOf(ks, len));
System.arraycopy(cBlock, 0, out, off, len);
// Update feedback with the full ciphertext block (padded if partial)
feedback = Arrays.copyOf(cBlock, BLOCK);
off += len;
}
return out;
}
public byte[] decrypt(byte[] iv, byte[] ciphertext) throws Exception {
byte[] out = new byte[ciphertext.length];
byte[] feedback = iv;
int off = 0;
while (off < ciphertext.length) {
byte[] ks = aesEcbEncrypt(feedback);
int len = Math.min(BLOCK, ciphertext.length - off);
byte[] cBlock = Arrays.copyOfRange(ciphertext, off, off + len);
byte[] pBlock = xor(cBlock, Arrays.copyOf(ks, len));
System.arraycopy(pBlock, 0, out, off, len);
// Update feedback with the full ciphertext block (padded if partial)
feedback = Arrays.copyOf(cBlock, BLOCK);
off += len;
}
return 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 = "CFB turns a block cipher into a stream cipher";
CfbModeDemo cfb = new CfbModeDemo(key);
byte[] ct = cfb.encrypt(iv, pt.getBytes("UTF-8"));
byte[] recovered = cfb.decrypt(iv, ct);
// Cross-check against the JDK built-in AES/CFB/NoPadding
Cipher ref = Cipher.getInstance("AES/CFB/NoPadding");
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 CFB): " + Base64.getEncoder().encodeToString(ct));
System.out.println("Ciphertext (JDK CFB) : " + 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 implementation is identical in structure to the CBC implementation from the previous tutorial, with the order of XOR and encrypt swapped.
aesEcbEncrypt() wraps the JDK's "AES/ECB/NoPadding" cipher. We use this algorithm because we want the raw AES block encryption without chaining or padding. CFB only ever uses the encryption primitive, never the decryption primitive.
xor() is the helper that XORs two equal-length byte arrays. The XOR is applied between the keystream (the encrypted feedback) and the plaintext to produce the ciphertext.
encrypt() iterates over the plaintext in 16-byte blocks. For each block, it encrypts the feedback register (initially the IV) to produce a keystream block, XORs the keystream with the plaintext block to produce the ciphertext block, and updates the feedback register with the ciphertext block. The feedback update is the heart of CFB: each keystream block depends on the previous ciphertext block, which prevents the ECB failure mode.
decrypt() is structurally identical to encrypt() because XOR is its own inverse. The same encrypt routine handles both directions; only the input (plaintext vs ciphertext) differs. This is one of the advantages of CFB over CBC: you do not need to implement the block cipher decryption primitive.
The partial-block handling copies the ciphertext block (padded with zeros if shorter than 16 bytes) into the feedback register. This is a simplification; in a strict CFB-128 implementation the feedback register would not be updated for a partial block because the next block would not be processed.
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/CFB/NoPadding". 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: CFB turns a block cipher into a stream cipher Output: Ciphertext (manual CFB): <base64 of 45 bytes> Ciphertext (JDK CFB) : <same base64 of 45 bytes> Match : true Recovered : CFB turns a block cipher into a stream cipher
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 CFB 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, swapping the order of encrypt and XOR is a silent bug. CFB encrypts the previous ciphertext to produce the keystream, then XORs with the plaintext. Reversing the order (XOR then encrypt) produces CBC, not CFB.
Third, using the decryption primitive in the encrypt routine is a bug. CFB only ever uses the encryption primitive. If you call D_K instead of E_K, the output is a different cipher that may not be secure.
Fourth, using CFB without a MAC is insecure. An attacker can flip bits in the ciphertext and the corresponding plaintext bits will flip in the next block; the mode provides no integrity. For real security you must combine CFB with a MAC.
Real-World Applications
CFB-128 was the default mode for the SSH-2 protocol in its early versions. The OpenSSH client shipped "aes128-cfb" and "aes256-cfb" as standard ciphers before ChaCha20-Poly1305 became the default in 2014.
CFB-8 was used in some early IPsec ESP profiles for bit-synchronized streaming. The mode is rarely seen today because CTR and GCM are simpler and faster.
CFB is a building block inside some modern AEAD constructions. The OCB family of AEADs (RFC 7253 for OCB2, RFC 8452 for OCB3) uses a CFB-like feedback to encrypt the plaintext in a single pass plus a polynomial MAC for authentication.
On the JVM specifically, the JDK SunJCE provider ships "AES/CFB/NoPadding" and "AES/CFB/PKCS5Padding". The NoPadding variant is the standard CFB mode; the PKCS5Padding variant adds padding that is rarely needed because CFB does not require padding.
The SSH-2 RFC 4344 specifies CFB-128 as one of the required ciphers. The OpenSSH client still ships "aes128-cfb" and "aes256-cfb" for compatibility with old servers, but the modern default is "chacha20-poly1305@openssh.com".
Conclusion
You learned what CFB mode is, how the order of encrypt and XOR differs from CBC, and how to implement the full mode without padding using only the JDK AES block primitive. You also saw why the IV must be unpredictable, why the encryption primitive is sufficient for both directions, and why CFB must be combined with a MAC for security. Next, study OFB for the output-feedback variant that produces a pure keystream, CTR for the modern counter mode, and GCM for the modern AEAD that replaced CFB in TLS 1.3.
Comments
Comments
Post a Comment