Java tutorial
/* * Copyright (c) 2008-2011, Martijn Brinkers, Djigzo. * * This file is part of Djigzo email encryption. * * Djigzo is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License * version 3, 19 November 2007 as published by the Free Software * Foundation. * * Djigzo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public * License along with Djigzo. If not, see <http://www.gnu.org/licenses/> * * Additional permission under GNU AGPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, * wsdl4j-1.6.1.jar (or modified versions of these libraries), * containing parts covered by the terms of Eclipse Public License, * tyrex license, freemarker license, dom4j license, mx4j license, * Spice Software License, Common Development and Distribution License * (CDDL), Common Public License (CPL) the licensors of this Program grant * you additional permission to convey the resulting work. */ package mitm.common.security.crypto; import java.io.IOException; import java.io.InputStream; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import org.apache.commons.lang.ArrayUtils; import mitm.common.security.NoSuchProviderRuntimeException; import mitm.common.security.SecurityFactory; import mitm.common.security.SecurityFactoryFactory; import mitm.common.util.Check; /** * InputStream that must be wrapped around another InputStream so the data can be decrypted. * * This class is not thread safe. * * @author Martijn Brinkers * */ public class PBDecryptionInputStream extends InputStream { private final static int BUFFER_SIZE = 1024; /* * The input stream to delegate to */ private final InputStream delegate; /* * The password which will be used to decrypt the data with. */ private final char[] password; /* * The cipher used to decrypt the data (the cipher is based on algorithm). */ private Cipher cipher; /* * If true the encryption parameters are read from the input stream */ private boolean parametersRead; /* * True if input stream is closed */ private boolean closed; /* * True if the eof of the delegate has been reached */ private boolean eof; /* * Internal buffer for temporary storage of decrypted data */ private final byte[] decryptedBuffer = new byte[BUFFER_SIZE]; /* * The index into the buffer */ private int currentIndex; /* * Number of valid items in the buffer */ private int decryptedSize; public PBDecryptionInputStream(InputStream delegate, char[] password) { Check.notNull(delegate, "delegate"); this.delegate = delegate; this.password = ArrayUtils.clone(password); } private void init(String algorithm, byte[] salt, int iterationCount) throws CryptoException { try { SecurityFactory securityFactory = SecurityFactoryFactory.getSecurityFactory(); SecretKeyFactory keyFactory = securityFactory.createSecretKeyFactory(algorithm); PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterationCount); /* * Clear out the password */ Arrays.fill(password, '#'); Key secretKey = keyFactory.generateSecret(keySpec); cipher = securityFactory.createCipher(algorithm); cipher.init(Cipher.DECRYPT_MODE, secretKey); } catch (NoSuchProviderException e) { throw new NoSuchProviderRuntimeException(e); } catch (NoSuchAlgorithmException e) { throw new CryptoException(e); } catch (InvalidKeySpecException e) { throw new CryptoException(e); } catch (NoSuchPaddingException e) { throw new CryptoException(e); } catch (InvalidKeyException e) { throw new CryptoException(e); } } private void readParameters() throws IOException { if (!parametersRead) { PBEncryptedStreamParameters parameters = new PBEncryptedStreamParameters(delegate); parametersRead = true; try { init(parameters.getAlgorithm(), parameters.getSalt(), parameters.getIterationCount()); } catch (CryptoException e) { throw new IOException(e); } } } private void checkClosed() throws IOException { if (closed) { throw new IOException("InputStream is closed."); } } @Override public int read() throws IOException { byte[] buffer = new byte[1]; return read(buffer); } @Override public int read(byte buffer[]) throws IOException { return read(buffer, 0, buffer.length); } private void readInternalBuffer() throws IOException { assert (decryptedSize == 0); byte[] encrypted = new byte[BUFFER_SIZE]; byte[] decrypted; int read; do { read = delegate.read(encrypted); if (read == -1) { try { decrypted = cipher.doFinal(); } catch (IllegalBlockSizeException e) { throw new IOException(e); } catch (BadPaddingException e) { throw new IOException(e); } } else { decrypted = cipher.update(encrypted, 0, read); } } while (decrypted == null && read != -1); if (read == -1) { eof = true; } decryptedSize = decrypted != null ? decrypted.length : 0; currentIndex = 0; System.arraycopy(decrypted, 0, decryptedBuffer, currentIndex, decryptedSize); } @Override public int read(byte buffer[], int offset, int length) throws IOException { checkClosed(); readParameters(); if (decryptedSize == 0 && !eof) { readInternalBuffer(); } int read = -1; if (decryptedSize > 0) { read = 0; int count = Math.min(length, decryptedSize); for (; currentIndex < count; currentIndex++, decryptedSize--, read++) { buffer[offset + currentIndex] = decryptedBuffer[currentIndex]; } } return read; } /** * It is not possible to return the amount of available data in a reliable way for every algorithm. The reason is that * you only know how many data is available after decryption but that requires actually reading the data which can result * in a blocking call. */ @Override public int available() throws IOException { checkClosed(); return 0; } @Override public void close() throws IOException { if (!closed) { delegate.close(); closed = true; } } }