/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.crypt;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Control mechanism for the Periodic Cipher Feed Back mode. This is
* a CFB variant used apparently by a number of programs, including PGP.
* Thanks to Hal for suggesting it.
*
* http://www.streamsec.com/pcfb1.pdf
*
* NOTE: This is identical to CFB if block size = key size. As of Freenet 0.7,
* we use it with block size = key size. Which is not recommended, but is as
* safe as CFB. We will get rid of this eventually, and move to 128-bit block
* size (i.e. standard AES) with a more standard mode (e.g. CTR or CBC).
*
* @author Scott
*/
public class PCFBMode {
/** The underlying block cipher. */
protected final BlockCipher c;
/** The register, with which data is XOR'ed */
protected final byte[] feedback_register;
/** When this reaches the end of the register, we refillBuffer() i.e. re-encrypt the
* register. */
protected int registerPointer;
/** Create the PCFB with no IV. The caller must either:
* a) Call reset() with a proper IV, or
* b) Accept the initial IV of all zero's. (We will still encrypt this before using it).
* If the key is random and never reused, for instance if it is a one time key or
* derived from a hash, b) may be acceptable. It is used in some parts of Freenet.
* However, it is very bad practice cryptographically, and we should get rid of it.
* NOTE THAT IV:KEY PAIRS *MUST* BE UNIQUE! If two instances use the same key with the
* same empty IV, the bad guys will be able to XOR the two ciphertexts to get the XOR
* of the plaintext. If they can deduce the register's value they may even be able to
* decrypt the next 32 bytes, however after that they should hopefully be stumped -
* but it will certainly make more sophisticated attacks easier.
* FIXME CRYPTO !!!
* @param c The underlying block cipher
* @deprecated
*/
@Deprecated
public static PCFBMode create(BlockCipher c) {
return new PCFBMode(c);
}
public static PCFBMode create(BlockCipher c, byte[] iv) {
return create(c, iv, 0);
}
/** Create the PCFB with an IV. The register pointer will be set to the end of the IV,
* so refillBuffer() will be called prior to any encryption. IV's *must* be unique for
* a given key. IT IS STRONGLY RECOMMENDED TO USE THIS CONSTRUCTOR, THE OTHER ONE WILL
* BE REMOVED EVENTUALLY.
* @param offset */
public static PCFBMode create(BlockCipher c, byte[] iv, int offset) {
return new PCFBMode(c, iv, offset);
}
protected PCFBMode(BlockCipher c) {
this.c = c;
feedback_register = new byte[c.getBlockSize() >> 3];
registerPointer = feedback_register.length;
}
protected PCFBMode(BlockCipher c, byte[] iv, int offset) {
this(c);
System.arraycopy(iv, offset, feedback_register, 0, feedback_register.length);
// registerPointer is already set to the end by this(c), so we will refillBuffer() immediately.
}
/**
* Resets the PCFBMode to an initial IV
*/
public final void reset(byte[] iv) {
System.arraycopy(iv, 0, feedback_register, 0, feedback_register.length);
registerPointer = feedback_register.length;
}
/**
* Resets the PCFBMode to an initial IV
* @param iv The buffer containing the IV.
* @param offset The offset to start reading the IV at.
*/
public final void reset(byte[] iv, int offset) {
System.arraycopy(iv, offset, feedback_register, 0, feedback_register.length);
registerPointer = feedback_register.length;
}
/**
* Writes the initialization vector to the stream. Though the IV
* is transmitted in the clear, this gives the attacker no additional
* information because the registerPointer is set so that the encrypted
* buffer is empty. This causes an immediate encryption of the IV,
* thus invalidating any information that the attacker had.
*/
public void writeIV(RandomSource rs, OutputStream out) throws IOException {
rs.nextBytes(feedback_register);
out.write(feedback_register);
}
/**
* Reads the initialization vector from the given stream.
*/
public void readIV(InputStream in) throws IOException {
//for (int i=0; i<feedback_register.length; i++) {
// feedback_register[i]=(byte)in.read();
//}
Util.readFully(in, feedback_register);
}
/**
* returns the length of the IV
*/
public int lengthIV() {
return feedback_register.length;
}
/**
* returns the length of the IV for a PCFB created with a specific cipher.
*/
public static int lengthIV(BlockCipher c) {
return c.getBlockSize() >> 3;
}
/**
* Deciphers one byte of data, by XOR'ing the ciphertext byte with
* one byte from the encrypted buffer. Then places the received
* byte in the feedback register. If no bytes are available in
* the encrypted buffer, the feedback register is encrypted, providing
* block_size/8 new bytes for decryption
*/
//public synchronized int decipher(int b) {
public int decipher(int b) {
if (registerPointer == feedback_register.length) refillBuffer();
int rv = (feedback_register[registerPointer] ^ (byte) b) & 0xff;
feedback_register[registerPointer++] = (byte) b;
return rv;
}
public void blockDecipher(byte[] buf, int off, int len) {
final int feedback_length = feedback_register.length;
if (registerPointer != 0) {
/* handle first incomplete feedback run */
int l = Math.min(feedback_length - registerPointer, len);
len -= l;
while(l-- > 0) {
byte b = buf[off];
buf[off++] ^= feedback_register[registerPointer];
feedback_register[registerPointer++] = b;
}
if (len == 0) return;
refillBuffer();
}
// assert(registerPointer == 0);
while (len > feedback_length) {
/* consume full blocks */
// note: we skip *last* full block to avoid extra refillBuffer()
len -= feedback_length;
while (registerPointer < feedback_length) {
byte b = buf[off];
buf[off++] ^= feedback_register[registerPointer];
feedback_register[registerPointer++] = b;
}
refillBuffer();
}
// assert(registerPointer == 0 && len <= feedback_length);
while (len-- > 0) {
/* handle final block */
byte b = buf[off];
buf[off++] ^= feedback_register[registerPointer];
feedback_register[registerPointer++] = b;
}
return;
}
/**
* Enciphers one byte of data, by XOR'ing the plaintext byte with
* one byte from the encrypted buffer. Then places the enciphered
* byte in the feedback register. If no bytes are available in
* the encrypted buffer, the feedback register is encrypted, providing
* block_size/8 new bytes for encryption
*/
//public synchronized int encipher(int b) {
public int encipher(int b) {
if (registerPointer == feedback_register.length) refillBuffer();
feedback_register[registerPointer] ^= (byte) b;
return feedback_register[registerPointer++] & 0xff;
}
public void blockEncipher(byte[] buf, int off, int len) {
final int feedback_length = feedback_register.length;
if (registerPointer != 0) {
/* handle first incomplete feedback run */
int l = Math.min(feedback_length - registerPointer, len);
for(len -= l; l-- > 0; off++)
buf[off] = (feedback_register[registerPointer++] ^= buf[off]);
if (len == 0) return;
refillBuffer();
}
// assert(registerPointer == 0);
while (len > feedback_length) {
/* consume full blocks */
// note: we skip *last* full block to avoid extra refillBuffer()
len -= feedback_length;
for (; registerPointer < feedback_length; off++)
buf[off] = (feedback_register[registerPointer++] ^= buf[off]);
refillBuffer();
}
// assert(registerPointer == 0 && len <= feedback_length);
for (; len-- > 0; off++) {
/* handle final partial block */
buf[off] = (feedback_register[registerPointer++] ^= buf[off]);
}
return;
}
// Refills the encrypted buffer with data.
//private synchronized void refillBuffer() {
protected void refillBuffer() {
// Encrypt feedback into result
c.encipher(feedback_register, feedback_register);
registerPointer=0;
}
}