1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package javax.crypto;
19
20import java.io.FilterOutputStream;
21import java.io.IOException;
22import java.io.OutputStream;
23import javax.crypto.NullCipher;
24
25/**
26 * This class wraps an output stream and a cipher so that {@code write} methods
27 * send the data through the cipher before writing them to the underlying output
28 * stream.
29 * <p>
30 * The cipher must be initialized for the requested operation before being used
31 * by a {@code CipherOutputStream}. For example, if a cipher initialized for
32 * encryption is used with a {@code CipherOutputStream}, the {@code
33 * CipherOutputStream} tries to encrypt the data writing it out.
34 */
35public class CipherOutputStream extends FilterOutputStream {
36
37    private final Cipher cipher;
38    private final byte[] arr = new byte[1];
39
40    /**
41     * Creates a new {@code CipherOutputStream} instance for an {@code
42     * OutputStream} and a {@code Cipher}.
43     *
44     * @param os
45     *            the output stream to write data to.
46     * @param c
47     *            the cipher to process the data with.
48     */
49    public CipherOutputStream(OutputStream os, Cipher c) {
50        super(os);
51        cipher = c;
52    }
53
54    /**
55     * Creates a new {@code CipherOutputStream} instance for an {@code
56     * OutputStream} without a cipher.
57     * <p>
58     * A {@code NullCipher} is created to process the data.
59     *
60     * @param os
61     *            the output stream to write the data to.
62     */
63    protected CipherOutputStream(OutputStream os) {
64        this(os, new NullCipher());
65    }
66
67    /**
68     * Writes the single byte to this cipher output stream.
69     *
70     * @param b
71     *            the byte to write.
72     * @throws IOException
73     *             if an error occurs.
74     */
75    @Override
76    public void write(int b) throws IOException {
77        byte[] result;
78        arr[0] = (byte) b;
79        result = cipher.update(arr);
80        if (result != null) {
81            out.write(result);
82        }
83    }
84
85    /**
86     * Writes the buffer of bytes to this cipher output stream.
87     *
88     * @param b
89     *            the buffer of bytes.
90     * @throws IOException
91     *             if an error occurs.
92     */
93    @Override
94    public void write(byte[] b) throws IOException {
95        write(b, 0, b.length);
96    }
97
98    /**
99     * Writes the {@code len} bytes from buffer {@code b} starting at offset
100     * {@code off} to this cipher output stream.
101     *
102     * @param b
103     *            the buffer.
104     * @param off
105     *            the offset to start at.
106     * @param len
107     *            the number of bytes.
108     * @throws IOException
109     *             if an error occurs.
110     */
111    @Override
112    public void write(byte[] b, int off, int len) throws IOException {
113        if (len == 0) {
114            return;
115        }
116        byte[] result = cipher.update(b, off, len);
117        if (result != null) {
118            out.write(result);
119        }
120    }
121
122    /**
123     * Flushes this cipher output stream.
124     *
125     * @throws IOException
126     *             if an error occurs
127     */
128    @Override
129    public void flush() throws IOException {
130        out.flush();
131    }
132
133    /**
134     * Close this cipher output stream.
135     * <p>
136     * On the underlying cipher {@code doFinal} will be invoked, and any
137     * buffered bytes from the cipher are also written out, and the cipher is
138     * reset to its initial state. The underlying output stream is also closed.
139     *
140     * @throws IOException
141     *             if an error occurs.
142     */
143    @Override
144    public void close() throws IOException {
145        byte[] result;
146        try {
147            if (cipher != null) {
148                result = cipher.doFinal();
149                if (result != null) {
150                    out.write(result);
151                }
152            }
153            if (out != null) {
154                out.flush();
155            }
156        } catch (BadPaddingException e) {
157            throw new IOException(e.getMessage());
158        } catch (IllegalBlockSizeException e) {
159            throw new IOException(e.getMessage());
160        } finally {
161            if (out != null) {
162                out.close();
163            }
164        }
165    }
166}
167
168