Source for javax.crypto.CipherOutputStream

   1: /* CipherOutputStream.java -- Filters output through a cipher.
   2:    Copyright (C) 2004  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.crypto;
  40: 
  41: import java.io.FilterOutputStream;
  42: import java.io.IOException;
  43: import java.io.OutputStream;
  44: 
  45: /**
  46:  * A filtered output stream that transforms data written to it with a
  47:  * {@link Cipher} before sending it to the underlying output stream.
  48:  *
  49:  * @author Casey Marshall (csm@gnu.org)
  50:  */
  51: public class CipherOutputStream extends FilterOutputStream
  52: {
  53: 
  54:   // Fields.
  55:   // ------------------------------------------------------------------------
  56: 
  57:   /** The underlying cipher. */
  58:   private Cipher cipher;
  59: 
  60:   private byte[][] inBuffer;
  61: 
  62:   private int inLength;
  63: 
  64:   private byte[] outBuffer;
  65: 
  66:   private static final int FIRST_TIME  = 0;
  67:   private static final int SECOND_TIME = 1;
  68:   private static final int SEASONED    = 2;
  69:   private int state;
  70: 
  71:   /** True if the cipher is a stream cipher (blockSize == 1) */
  72:   private boolean isStream;
  73: 
  74:   // Constructors.
  75:   // ------------------------------------------------------------------------
  76: 
  77:   /**
  78:    * Create a new cipher output stream. The cipher argument must have
  79:    * already been initialized.
  80:    *
  81:    * @param out    The sink for transformed data.
  82:    * @param cipher The cipher to transform data with.
  83:    */
  84:   public CipherOutputStream(OutputStream out, Cipher cipher)
  85:   {
  86:     super(out);
  87:     if (cipher != null)
  88:       {
  89:         this.cipher = cipher;
  90:         if (!(isStream = cipher.getBlockSize() == 1))
  91:           {
  92:             inBuffer = new byte[2][];
  93:             inBuffer[0] = new byte[cipher.getBlockSize()];
  94:             inBuffer[1] = new byte[cipher.getBlockSize()];
  95:             inLength = 0;
  96:             state = FIRST_TIME;
  97:           }
  98:       }
  99:     else
 100:       this.cipher = new NullCipher();
 101:   }
 102: 
 103:   /**
 104:    * Create a cipher output stream with no cipher.
 105:    *
 106:    * @param out The sink for transformed data.
 107:    */
 108:   protected CipherOutputStream(OutputStream out)
 109:   {
 110:     super(out);
 111:   }
 112: 
 113:   // Instance methods.
 114:   // ------------------------------------------------------------------------
 115: 
 116:   /**
 117:    * Close this output stream, and the sink output stream.
 118:    *
 119:    * <p>This method will first invoke the {@link Cipher#doFinal()}
 120:    * method of the underlying {@link Cipher}, and writes the output of
 121:    * that method to the sink output stream.
 122:    *
 123:    * @throws java.io.IOException If an I/O error occurs, or if an error
 124:    *         is caused by finalizing the transformation.
 125:    */
 126:   public void close() throws IOException
 127:   {
 128:     try
 129:       {
 130:         int len;
 131:         if (state != FIRST_TIME)
 132:           {
 133:             len = cipher.update(inBuffer[0], 0, inBuffer[0].length, outBuffer);
 134:             out.write(outBuffer, 0, len);
 135:           }
 136:         len = cipher.doFinal(inBuffer[0], 0, inLength, outBuffer);
 137:         out.write(outBuffer, 0, len);
 138:       }
 139:     catch (javax.crypto.IllegalBlockSizeException ibse)
 140:       {
 141:         throw new IOException(ibse.toString());
 142:       }
 143:     catch (javax.crypto.BadPaddingException bpe)
 144:       {
 145:         throw new IOException(bpe.toString());
 146:       }
 147:     catch (ShortBufferException sbe)
 148:       {
 149:         throw new IOException(sbe.toString());
 150:       }
 151:     out.flush();
 152:     out.close();
 153:   }
 154: 
 155:   /**
 156:    * Flush any pending output.
 157:    *
 158:    * @throws java.io.IOException If an I/O error occurs.
 159:    */
 160:   public void flush() throws IOException
 161:   {
 162:     out.flush();
 163:   }
 164: 
 165:   /**
 166:    * Write a single byte to the output stream.
 167:    *
 168:    * @param b The next byte.
 169:    * @throws java.io.IOException If an I/O error occurs, or if the
 170:    *         underlying cipher is not in the correct state to transform
 171:    *         data.
 172:    */
 173:   public void write(int b) throws IOException
 174:   {
 175:     if (isStream)
 176:       {
 177:         byte[] buf = new byte[] { (byte) b };
 178:         try
 179:           {
 180:             cipher.update(buf, 0, 1, buf, 0);
 181:           }
 182:         catch (ShortBufferException sbe)
 183:           {
 184:             throw new IOException(sbe.toString());
 185:           }
 186:         out.write(buf);
 187:         return;
 188:       }
 189:     inBuffer[1][inLength++] = (byte) b;
 190:     if (inLength == inBuffer[1].length)
 191:       process();
 192:   }
 193: 
 194:   /**
 195:    * Write a byte array to the output stream.
 196:    *
 197:    * @param buf The next bytes.
 198:    * @throws java.io.IOException If an I/O error occurs, or if the
 199:    *         underlying cipher is not in the correct state to transform
 200:    *         data.
 201:    */
 202:   public void write(byte[] buf) throws IOException
 203:   {
 204:     write(buf, 0, buf.length);
 205:   }
 206: 
 207:   /**
 208:    * Write a portion of a byte array to the output stream.
 209:    *
 210:    * @param buf The next bytes.
 211:    * @param off The offset in the byte array to start.
 212:    * @param len The number of bytes to write.
 213:    * @throws java.io.IOException If an I/O error occurs, or if the
 214:    *         underlying cipher is not in the correct state to transform
 215:    *         data.
 216:    */
 217:   public void write(byte[] buf, int off, int len) throws IOException
 218:   {
 219:     if (isStream)
 220:       {
 221:         out.write(cipher.update(buf, off, len));
 222:         return;
 223:       }
 224:     int count = 0;
 225:     while (count < len)
 226:       {
 227:         int l = Math.min(inBuffer[1].length - inLength, len - count);
 228:         System.arraycopy(buf, off+count, inBuffer[1], inLength, l);
 229:         count += l;
 230:         inLength += l;
 231:         if (inLength == inBuffer[1].length)
 232:           process();
 233:       }
 234:   }
 235: 
 236:   // Own method.
 237:   // -------------------------------------------------------------------------
 238: 
 239:   private void process() throws IOException
 240:   {
 241:     if (state == SECOND_TIME)
 242:       {
 243:         state = SEASONED;
 244:       }
 245:     else
 246:       {
 247:         byte[] temp = inBuffer[0];
 248:         inBuffer[0] = inBuffer[1];
 249:         inBuffer[1] = temp;
 250:       }
 251:     if (state == FIRST_TIME)
 252:       {
 253:         inLength = 0;
 254:         state = SECOND_TIME;
 255:         return;
 256:       }
 257:     try
 258:       {
 259:         cipher.update(inBuffer[0], 0, inBuffer[0].length, outBuffer);
 260:       }
 261:     catch (ShortBufferException sbe)
 262:       {
 263:         throw new IOException(sbe.toString());
 264:       }
 265:     out.write(outBuffer);
 266:     inLength = 0;
 267:   }
 268: }