All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.mibos.commons.crypt.Crypt Maven / Gradle / Ivy

/*
 * Copyright 2014 Michael Bock
 * 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.mibos.commons.crypt;

import org.apache.commons.io.output.CountingOutputStream;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.WillClose;
import javax.annotation.WillNotClose;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidObjectException;
import java.io.ObjectStreamException;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import static de.mibos.commons.crypt.BlockMode.CIPHER_BLOCK_CHAIN_MODE;
import static de.mibos.commons.crypt.BlockMode.CIPHER_FEEDBACK_MODE;
import static de.mibos.commons.crypt.BlockMode.OUTPUT_FEEDBACK_MODE;
import static de.mibos.commons.crypt.Padding.NO_PADDING;
import static de.mibos.commons.crypt.Padding.PKCS5_PADDING;
import static java.lang.Math.max;
import static java.lang.reflect.Modifier.isStatic;
import static org.apache.commons.io.IOUtils.closeQuietly;
import static org.apache.commons.io.IOUtils.copyLarge;
import static org.apache.commons.io.IOUtils.readFully;

/**
 * A collection of methods for easy to use symmetric encryption and decryption.
*

* You can create your own crypt instances but it is recommended to use the predefined ones. * * @author Michael Bock * @version 1.5 * @since 1.0 * $Id: Crypt.java 89 2014-12-05 00:22:30Z michaels-apps $ */ @ThreadSafe @Immutable public class Crypt implements Serializable { final private static long serialVersionUID = 2372422428972070135L; /** * The encryption algorithm AES with 256 Bits key size. Block mode is CBC, the * random initialization vector is putted in front of the cipher text. PKCS5 is * used for padding of the last cipher block. *
* This is the recommended Crypt for general purpose encryption! */ final public static Crypt AES256 = new Crypt("AES", 256, 128); /** * The encryption algorithm AES with 192 Bits key size. Block mode is CBC, the * random initialization vector is putted in front of the cipher text. PKCS5 is * used for padding of the last cipher block. */ final public static Crypt AES192 = new Crypt("AES", 192, 128); /** * The encryption algorithm AES with 128 Bits key size. Block mode is CBC, the * random initialization vector is putted in front of the cipher text. PKCS5 is * used for padding of the last cipher block. */ final public static Crypt AES128 = new Crypt("AES", 128, 128); /** * Bruce Schneiers encryption algorithm Blowfish with 256 Bits key size. Block mode is CBC, the * random initialization vector is putted in front of the cipher text. PKCS5 is * used for padding of the last cipher block. */ final public static Crypt Blowfish256 = new Crypt("Blowfish", 256, 64); /** * Bruce Schneiers encryption algorithm Blowfish with 192 Bits key size. Block mode is CBC, the * random initialization vector is putted in front of the cipher text. PKCS5 is * used for padding of the last cipher block. */ final public static Crypt Blowfish192 = new Crypt("Blowfish", 192, 64); /** * Bruce Schneiers encryption algorithm Blowfish with 128 Bits key size. Block mode is CBC, the * random initialization vector is putted in front of the cipher text. PKCS5 is * used for padding of the last cipher block. */ final public static Crypt Blowfish128 = new Crypt("Blowfish", 128, 64); /** * The encryption algorithm DES with 64 Bits key size (effective key size 56 bits!). * Block mode is CBC, the random initialization vector is putted in front of the * cipher text. PKCS5 is used for padding of the last cipher block. *
* This Crypt is generally not recommended! */ final public static Crypt DES64 = new Crypt("DES", 64, 64); /** * The encryption algorithm Triple-DES with 192 Bits key size (effective key size 168 bits, * the effective key size for brute force is smaller than 168 bits). * Block mode is CBC, the random initialization vector is putted in front of the * cipher text. PKCS5 is used for padding of the last cipher block. *
* This Crypt is not recommended! */ final public static Crypt DES192 = new Crypt("DESede", 192, 64); /** * The default algorithm for password hashing */ final public static String DEFAULT_PASSWORD_HASHING_ALGORITHM = "SHA-256"; /** * The default algorithm for block chain mode * * @deprecated use {@link de.mibos.commons.crypt.BlockMode#DEFAULT_BLOCK_CHAIN_MODE} instead */ @SuppressWarnings("UnusedDeclaration") final public static String DEFAULT_BLOCK_CHAIN_MODE = BlockMode.DEFAULT_BLOCK_CHAIN_MODE.getShortcut(); /** * The default algorithm for padding * * @deprecated use {@link de.mibos.commons.crypt.Padding#DEFAULT_PADDING_ALGORITHM} instead */ @SuppressWarnings("UnusedDeclaration") final public static String DEFAULT_PADDING_ALGORITHM = Padding.DEFAULT_PADDING_ALGORITHM.getShortcut(); /** * The default buffer size for IOUtils */ final private static int DEFAULT_BUFFER_SIZE = 4 * 1024; /** * the encryption algorithm (see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html) */ @Nonnull final private String encryptionAlgorithm; /** * the password hashing algorithm for String passwords */ @Nonnull final private String passwordHashingAlgorithm; /** * the block mode */ @Nonnull final private BlockMode blockMode; /** * the padding */ @Nonnull final private Padding padding; /** * the key size in bytes */ final private int keySizeInBytes; /** * the block size in bytes */ final private int blockSizeInBytes; /** * the buffer size in bytes for IOUtils operations */ final private int bufferSize; /** * Constructs a Crypt object with the specified parameters * * @param encryptionAlgorithm the encryption algorithm (see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html) * @param keySize the key size in bits. Compatibility to the encryption algorithm is not checked, so a * {@link de.mibos.commons.crypt.CryptoInitializationProblem} may be throw later * @param blockSize the block size in bits. Compatibility to the encryption algorithm is not checked! * @param passwordHashingAlgorithm the password hashing algorithm for String passwords * @param blockMode the block encoding mode. Modes other than CBC are not always supported. ECB is strongly discouraged * @param padding the padding mode (see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html) * @param bufferSize size of the buffer for io operations * @since 1.0 * @deprecated use {@link #Crypt(String, int, int, String, BlockMode, Padding, int)} instead */ public Crypt(@Nonnull final String encryptionAlgorithm, final int keySize, final int blockSize, @Nonnull final String passwordHashingAlgorithm, @Nonnull final String blockMode, @Nonnull final String padding, int bufferSize) { this(encryptionAlgorithm, keySize, blockSize, passwordHashingAlgorithm, BlockMode.forShortcut(blockMode), Padding.forShortcut(padding), bufferSize); } /** * Constructs a Crypt object with the specified parameters * * @param encryptionAlgorithm the encryption algorithm (see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html) * @param keySize the key size in bits. Compatibility to the encryption algorithm is not checked, so a * {@link de.mibos.commons.crypt.CryptoInitializationProblem} may be throw later * @param blockSize the block size in bits. Compatibility to the encryption algorithm is not checked! * @param passwordHashingAlgorithm the password hashing algorithm for String passwords * @param blockMode the block encoding mode. Modes other than CBC are not always supported. ECB is strongly discouraged * @param padding the padding mode (see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html) * @param bufferSize size of the buffer for io operations * @since 1.4 */ public Crypt(@Nonnull final String encryptionAlgorithm, final int keySize, final int blockSize, @Nonnull final String passwordHashingAlgorithm, @Nonnull final BlockMode blockMode, @Nonnull final Padding padding, int bufferSize) { //noinspection ConstantConditions assert encryptionAlgorithm != null : "encryptionAlgorithm must be not null"; assert keySize > 0 : "keySize must be greater than null"; assert blockSize > 0 : "blockSize must be greater than null"; //noinspection ConstantConditions assert passwordHashingAlgorithm != null : "passwordHashingAlgorithm must be not null"; //noinspection ConstantConditions assert blockMode != null : "blockMode must be not null"; //noinspection ConstantConditions assert padding != null : "padding must be not null"; assert bufferSize > 0 : "bufferSize must be greater than null"; // Check modes and padding for consistence if (blockMode == CIPHER_BLOCK_CHAIN_MODE) { if (padding != PKCS5_PADDING) throw new CryptoInitializationProblem("Block mode CBC is only supported in combination with PKCS5 padding"); } else if (blockMode == CIPHER_FEEDBACK_MODE || blockMode == OUTPUT_FEEDBACK_MODE) { if (padding != NO_PADDING) throw new CryptoInitializationProblem("Block modes CFB and OFB are only supported in combination with no padding"); } else { throw new CryptoInitializationProblem("Unsupported block mode " + blockMode); } this.encryptionAlgorithm = encryptionAlgorithm; this.keySizeInBytes = keySize / 8; this.blockSizeInBytes = blockSize / 8; this.passwordHashingAlgorithm = passwordHashingAlgorithm; this.blockMode = blockMode; this.padding = padding; this.bufferSize = bufferSize; } /** * Constructs a Crypt object with the specified parameters * * @param encryptionAlgorithm the encryption algorithm (see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html) * @param keySize the key size in bits. Compatibility to the encryption algorithm is not checked, so a * {@link de.mibos.commons.crypt.CryptoInitializationProblem} may be throw later * @param blockSize the block size in bits. Compatibility to the encryption algorithm is not checked! * @param passwordHashingAlgorithm the password hashing algorithm for String passwords * @param blockMode the block encoding mode. Modes other than CBC are not always supported. ECB is strongly discouraged * @param padding the padding mode (see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html) * @since 1.0 * @deprecated use {@link #Crypt(String, int, int, String, BlockMode, Padding)} instead */ public Crypt(@Nonnull final String encryptionAlgorithm, final int keySize, final int blockSize, @Nonnull final String passwordHashingAlgorithm, @Nonnull final String blockMode, @Nonnull final String padding) { this(encryptionAlgorithm, keySize, blockSize, passwordHashingAlgorithm, BlockMode.forShortcut(blockMode), Padding.forShortcut(padding), DEFAULT_BUFFER_SIZE); } /** * Constructs a Crypt object with the specified parameters * * @param encryptionAlgorithm the encryption algorithm (see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html) * @param keySize the key size in bits. Compatibility to the encryption algorithm is not checked, so a * {@link de.mibos.commons.crypt.CryptoInitializationProblem} may be throw later * @param blockSize the block size in bits. Compatibility to the encryption algorithm is not checked! * @param passwordHashingAlgorithm the password hashing algorithm for String passwords * @param blockMode the block encoding mode. Modes other than CBC are not always supported. ECB is strongly discouraged * @param padding the padding mode (see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html) * @since 1.4 */ public Crypt(@Nonnull final String encryptionAlgorithm, final int keySize, final int blockSize, @Nonnull final String passwordHashingAlgorithm, @Nonnull final BlockMode blockMode, @Nonnull final Padding padding) { this(encryptionAlgorithm, keySize, blockSize, passwordHashingAlgorithm, blockMode, padding, DEFAULT_BUFFER_SIZE); } /** * Constructs a Crypt object with the specified parameters * * @param encryptionAlgorithm the encryption algorithm (see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html) * @param keySize the key size in bits. Compatibility to the encryption algorithm is not checked, so a * {@link de.mibos.commons.crypt.CryptoInitializationProblem} may be throw later * @param blockSize the block size in bits. Compatibility to the encryption algorithm is not checked! * @param blockMode the block encoding mode. Modes other than CBC are not always supported. ECB is unsupported * @param padding the padding mode (see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html) * @since 1.0 * @deprecated use {@link #Crypt(String, int, int, BlockMode, Padding)} instead */ public Crypt(@Nonnull final String encryptionAlgorithm, final int keySize, final int blockSize, @Nonnull final String blockMode, @Nonnull final String padding) { this(encryptionAlgorithm, keySize, blockSize, DEFAULT_PASSWORD_HASHING_ALGORITHM, BlockMode.forShortcut(blockMode), Padding.forShortcut(padding), DEFAULT_BUFFER_SIZE); } /** * Constructs a Crypt object with the specified parameters * * @param encryptionAlgorithm the encryption algorithm (see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html) * @param keySize the key size in bits. Compatibility to the encryption algorithm is not checked, so a * {@link de.mibos.commons.crypt.CryptoInitializationProblem} may be throw later * @param blockSize the block size in bits. Compatibility to the encryption algorithm is not checked! * @param blockMode the block encoding mode. Modes other than CBC are not always supported. ECB is unsupported * @param padding the padding mode (see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html) * @since 1.4 */ public Crypt(@Nonnull final String encryptionAlgorithm, final int keySize, final int blockSize, @Nonnull final BlockMode blockMode, @Nonnull final Padding padding) { this(encryptionAlgorithm, keySize, blockSize, DEFAULT_PASSWORD_HASHING_ALGORITHM, blockMode, padding, DEFAULT_BUFFER_SIZE); } /** * Constructs a Crypt object with the specified parameters. For blockMode CBC is used. For padding PKCS5Padding is used * * @param encryptionAlgorithm the encryption algorithm (see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html) * @param keySize the key size in bits. Compatibility to the encryption algorithm is not checked! * @param blockSize the block size in bits. Compatibility to the encryption algorithm is not checked! * @param passwordHashingAlgorithm the password hashing algorithm for String passwords * @since 1.0 */ public Crypt(@Nonnull final String encryptionAlgorithm, final int keySize, final int blockSize, @Nonnull final String passwordHashingAlgorithm) { this(encryptionAlgorithm, keySize, blockSize, passwordHashingAlgorithm, BlockMode.DEFAULT_BLOCK_CHAIN_MODE, Padding.DEFAULT_PADDING_ALGORITHM); } /** * Constructs a Crypt object with the specified parameters. For blockMode CBC is used. For padding PKCS5Padding is used. * SHA-256 is used as password hashing algorithm. * * @param encryptionAlgorithm the encryption algorithm (see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html) * @param keySize the key size in bits. Compatibility to the encryption algorithm is not checked! * @param blockSize the block size in bits. Compatibility to the encryption algorithm is not checked! * @since 1.0 */ public Crypt(@Nonnull final String encryptionAlgorithm, final int keySize, final int blockSize) { this(encryptionAlgorithm, keySize, blockSize, DEFAULT_PASSWORD_HASHING_ALGORITHM); } /** * Returns the key size in bits * * @return the key size in bits * @since 1.0 */ public int getKeySize() { return keySizeInBytes * 8; } /** * Returns the block size in bits * * @return the block size in bits */ public int getBlockSize() { return blockSizeInBytes * 8; } /** * Returns the encryption method string * * @return the encryption method string */ @Nonnull public String getEncryptionMethod() { return encryptionAlgorithm + "/" + blockMode.getShortcut() + "/" + padding.getShortcut(); } /** * Encrypts all input from the given input stream to the given output stream. Both streams will be closed. * * @param input plain text input stream * @param output cipher text output stream * @param password password for encryption. The password used actually used will be the hash of this String using * the specified password hashing algorithm * @return the number of bytes written to the cipher text output stream * @throws IOException in case of an underlying {@link java.io.IOException} of the streams * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.0 */ public long encrypt(@Nonnull @WillClose final InputStream input, @Nonnull @WillClose final OutputStream output, @Nonnull final CharSequence password) throws IOException { //noinspection ConstantConditions assert password != null : "password must be not null"; byte[] passwordBytes; try { passwordBytes = getHashedEncryptionKey(password); } catch (RuntimeException e) { closeQuietly(input); closeQuietly(output); throw e; } try { return internalEncrypt(passwordBytes, input, output); } finally { resetPasswordBytes(passwordBytes); } } /** * Encrypts all input from the given input stream to the given output stream. Both streams will be closed. * * @param input plain text input stream * @param output cipher text output stream * @param passwordBytes the used password. The length must be compatible to the used encryption algorithm * @return the number of bytes written to the cipher text output stream * @throws IOException in case of an underlying {@link java.io.IOException} of the streams * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.0 */ public long encrypt(@Nonnull @WillClose final InputStream input, @Nonnull @WillClose final OutputStream output, @Nonnull final byte[] passwordBytes) throws IOException { //noinspection ConstantConditions assert input != null : "input must be not null"; //noinspection ConstantConditions assert output != null : "output must be not null"; //noinspection ConstantConditions assert passwordBytes != null : "passwordBytes must be not null"; return internalEncrypt(passwordBytes, input, output); } /** * Encrypts the input data and returns the cipher text * * @param input plain text * @param password password for encryption. The password used actually used will be the hash of this String using * the specified password hashing algorithm * @return cipher text * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.0 */ @Nonnull public byte[] encrypt(@Nonnull final byte[] input, @Nonnull final CharSequence password) { //noinspection ConstantConditions assert password != null : "password must be not null"; final byte[] passwordBytes = getHashedEncryptionKey(password); try { return encrypt(input, passwordBytes); } finally { resetPasswordBytes(passwordBytes); } } /** * Encrypts the input data and returns the cipher text. * * @param input plain text * @param passwordBytes the used password. The length must be compatible to the used encryption algorithm * @return cipher text * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.0 */ @Nonnull public byte[] encrypt(@Nonnull final byte[] input, @Nonnull final byte[] passwordBytes) { //noinspection ConstantConditions assert input != null : "input must be not null"; //noinspection ConstantConditions assert passwordBytes != null : "passwordBytes must be not null"; final byte[] output = new byte[(int) getMaximumCipherTextLength(input.length)]; int outputSize = internalEncrypt(passwordBytes, input, 0, input.length, output, 0); assert outputSize == output.length : "must not happen for standard algorithms"; return output; } /** * Encrypts all input from the given byte buffer to the given byte buffer.
* Note that the output {@link java.nio.ByteBuffer} must have a capacity left, which is as least as great * as a call to {@link #Crypt#getMaximumCipherTextLength(long)} for size of the cipher text returns, * which may be more than the size of the actual cipher text. * * @param input plain text byte buffer * @param output cipher text byte buffer * @param password password for encryption. The password used actually used will be the hash of this String using * the specified password hashing algorithm * @return cipher text * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.3 */ public long encrypt(@Nonnull final ByteBuffer input, @Nonnull final ByteBuffer output, @Nonnull final CharSequence password) { //noinspection ConstantConditions assert password != null : "password must be not null"; final byte[] passwordBytes = getHashedEncryptionKey(password); try { return encrypt(input, output, passwordBytes); } finally { resetPasswordBytes(passwordBytes); } } /** * Encrypts all input from the given byte buffer to the given byte buffer.
* Note that the output {@link java.nio.ByteBuffer} must have a capacity left, which is as least as great * as a call to {@link #Crypt#getMaximumCipherTextLength(long)} for size of the cipher text returns, * which may be more than the size of the actual cipher text. * * @param input plain text byte buffer * @param output cipher text byte buffer * @param passwordBytes the used password. The length must be compatible to the used encryption algorithm * @return the number of bytes written to the cipher text byte buffer * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.3 */ public long encrypt(@Nonnull final ByteBuffer input, @Nonnull final ByteBuffer output, @Nonnull final byte[] passwordBytes) { //noinspection ConstantConditions assert input != null : "input must be not null"; //noinspection ConstantConditions assert output != null : "output must be not null"; //noinspection ConstantConditions assert passwordBytes != null : "passwordBytes must be not null"; return internalEncrypt(passwordBytes, input, output); } /** * Decrypts all input from the given input stream to the given outputs stream. Both streams will be closed. * * @param input cipher text input stream * @param output plain text output stream * @param password password for decryption. The password used actually used will be the hash of this String using * the specified password hashing algorithm * @return the number of bytes written to the plain text output stream * @throws IOException in case of an underlying {@link java.io.IOException} of the streams * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.0 */ public long decrypt(@Nonnull @WillClose InputStream input, @Nonnull @WillClose OutputStream output, @Nonnull CharSequence password) throws IOException { //noinspection ConstantConditions assert password != null : "password must be not null"; final byte[] passwordBytes; try { passwordBytes = getHashedEncryptionKey(password); } catch (RuntimeException e) { closeQuietly(input); closeQuietly(output); throw e; } try { return internalDecrypt(passwordBytes, input, output); } finally { resetPasswordBytes(passwordBytes); } } /** * Decrypts all input from the given input stream to the given output stream. Both streams will be closed. * * @param input cipher text input stream * @param output plain text output stream * @param passwordBytes the used password. The length must be compatible to the used encryption algorithm * @return the number of bytes written to the plain text output stream * @throws IOException in case of an underlying {@link java.io.IOException} of the streams * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.0 */ public long decrypt(@Nonnull @WillClose InputStream input, @Nonnull @WillClose OutputStream output, @Nonnull final byte[] passwordBytes) throws IOException { //noinspection ConstantConditions assert input != null : "input must be not null"; //noinspection ConstantConditions assert output != null : "output must be not null"; //noinspection ConstantConditions assert passwordBytes != null : "passwordBytes must be not null"; return internalDecrypt(passwordBytes, input, output); } /** * Decrypts the input data and returns the plain text * * @param input cipher text * @param password password for decryption. The password used actually used will be the hash of this String using * the specified password hashing algorithm * @return plain text * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.0 */ @Nonnull public byte[] decrypt(@Nonnull final byte[] input, @Nonnull final CharSequence password) { //noinspection ConstantConditions assert password != null : "password must be not null"; final byte[] passwordBytes = getHashedEncryptionKey(password); try { return decrypt(input, passwordBytes); } finally { resetPasswordBytes(passwordBytes); } } /** * Decrypts the input data and returns the plain text * * @param input cipher text * @param passwordBytes the used password. The length must be compatible to the used encryption algorithm * @return plain text * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.0 */ @Nonnull public byte[] decrypt(@Nonnull final byte[] input, @Nonnull final byte[] passwordBytes) { //noinspection ConstantConditions assert input != null : "input must be not null"; //noinspection ConstantConditions assert passwordBytes != null : "passwordBytes must be not null"; final byte[] output = new byte[(int) getMaximumPlainTextLength(input.length)]; int outputSize = internalDecrypt(passwordBytes, input, 0, input.length, output, 0); if (outputSize < output.length) { final byte[] result = new byte[outputSize]; System.arraycopy(output, 0, result, 0, outputSize); return result; } else { return output; } } /** * Decrypts all input from the given byte buffer to the given byte buffer.
* Note that the output {@link java.nio.ByteBuffer} must have a capacity left, which is as least as great * as a call to {@link #Crypt#getMaximumPlainTextLength(long)} for size of the plain text returns, * which may be more than the size of the actual plain text. * * @param input cipher text byte buffer * @param output test text byte buffer * @param password password for decryption. The password used actually used will be the hash of this String using * the specified password hashing algorithm * @return cipher text * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @throws CryptoBufferOverflowException not enough bytes in output * @throws CryptoInvalidCipherTextException invalid cipher text * @since 1.3 */ public long decrypt(@Nonnull final ByteBuffer input, @Nonnull final ByteBuffer output, @Nonnull final CharSequence password) { //noinspection ConstantConditions assert password != null : "password must be not null"; final byte[] passwordBytes = getHashedEncryptionKey(password); try { return decrypt(input, output, passwordBytes); } finally { resetPasswordBytes(passwordBytes); } } /** * Decrypts all input from the given byte buffer to the given byte buffer.
* Note that the output {@link java.nio.ByteBuffer} must have a capacity left, which is as least as great * as a call to {@link #Crypt#getMaximumPlainTextLength(long)} for size of the plain text returns, * which may be more than the size of the actual plain text. * * @param input cipher text byte buffer * @param output test text byte buffer * @param passwordBytes the used password. The length must be compatible to the used encryption algorithm * @return the number of bytes written to the cipher text byte buffer * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @throws CryptoBufferOverflowException not enough bytes in output * @throws CryptoInvalidCipherTextException invalid cipher text * @since 1.3 */ public long decrypt(@Nonnull final ByteBuffer input, @Nonnull final ByteBuffer output, @Nonnull final byte[] passwordBytes) { //noinspection ConstantConditions assert input != null : "input must be not null"; //noinspection ConstantConditions assert output != null : "output must be not null"; //noinspection ConstantConditions assert passwordBytes != null : "passwordBytes must be not null"; return internalDecrypt(passwordBytes, input, output); } /** * Creates an input stream for this Crypt for decryption * This is simply an extension to the standard {@link javax.crypto.CipherInputStream}, which * additionally adds the initialization vector * * @param input the cipher input stream * @param password the password * @throws IOException {@link java.io.IOException} if failing to read the initialization vector * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.3 */ public CryptInputStream getInputStream(@Nonnull final InputStream input, @Nonnull final CharSequence password) throws IOException { return new CryptInputStream(this, input, password); } /** * Creates an input stream for this Crypt for decryption * This is simply an extension to the standard {@link javax.crypto.CipherInputStream}, which * additionally adds the initialization vector * * @param input the cipher input stream * @param passwordBytes the password bytes * @throws IOException {@link java.io.IOException} if failing to read the initialization vector * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.3 */ public CryptInputStream getInputStream(@Nonnull final InputStream input, @Nonnull final byte[] passwordBytes) throws IOException { return new CryptInputStream(this, input, passwordBytes); } /** * Creates an input stream for this Crypt for encryption * This is simply an extension to the standard {@link javax.crypto.CipherInputStream}, which * provides the initialization vector as input * * @param input the cipher input stream * @param password the password * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.5 */ public PlainInputStream getPlainInputStream(@Nonnull final InputStream input, @Nonnull final CharSequence password) { return new PlainInputStream(this, input, password); } /** * Creates an input stream for this Crypt for encryption * This is simply an extension to the standard {@link javax.crypto.CipherInputStream}, which * provides the initialization vector as input * * @param input the cipher input stream * @param passwordBytes the password bytes * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.5 */ public PlainInputStream getPlainInputStream(@Nonnull final InputStream input, @Nonnull final byte[] passwordBytes) { return new PlainInputStream(this, input, passwordBytes); } /** * Creates an output stream for this Crypt. * This is simply an extension to the standard {@link javax.crypto.CipherOutputStream}, which * writes the initialization vector in this method. * Please ensure that close is always called explicitly for this stream object. Failing to do so will result in * missing cipher text bytes. * * @param output the cipher output stream * @param password the password * @throws IOException {@link java.io.IOException} if failing to write the initialization vector * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.3 */ public CryptOutputStream getOutputStream(@Nonnull final OutputStream output, @Nonnull final CharSequence password) throws IOException { return new CryptOutputStream(this, output, password); } /** * Creates an output stream for this Crypt. * This is simply an extension to the standard {@link javax.crypto.CipherOutputStream}, which * writes the initialization vector in this method. * Please ensure that close is always called explicitly for this stream object. Failing to do so will result in * missing cipher text bytes. * * @param output the cipher output stream * @param passwordBytes the password bytes * @throws IOException {@link java.io.IOException} if failing to write the initialization vector * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @since 1.3 */ public CryptOutputStream getOutputStream(@Nonnull final OutputStream output, @Nonnull final byte[] passwordBytes) throws IOException { return new CryptOutputStream(this, output, passwordBytes); } /** * Returns the maximum bytes needed for the cipher text for a given plain text length * * @param plainTextLength plain text length * @return maximum bytes needed for the cipher text * @since 1.4 */ public long getMaximumCipherTextLength(final long plainTextLength) { if (padding == NO_PADDING) { return plainTextLength + blockSizeInBytes; } else { return plainTextLength + 2 * blockSizeInBytes - (plainTextLength % blockSizeInBytes); } } /** * Returns the maximum bytes needed for the plain text for a given cipher text length * * @param cipherTextLength cipher text length * @return maximum bytes needed for the plain text * @since 1.4 */ @SuppressWarnings("WeakerAccess") public long getMaximumPlainTextLength(final long cipherTextLength) { return max(cipherTextLength - blockSizeInBytes, 0); } /** * Encrypts all input from the given input stream to the given output stream. Both streams will be closed. * * @param passwordBytes the used password. The length must be compatible to the used encryption algorithm * @param input plain text input stream * @param output cipher text output stream * @return the number of bytes written to the cipher text output stream * @throws IOException in case of an underlying {@link java.io.IOException} of the streams * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem */ private long internalEncrypt(@Nonnull final byte[] passwordBytes, @Nonnull @WillClose final InputStream input, @Nonnull @WillClose final OutputStream output) throws IOException { final Cipher cipher; final byte[] iv; try { cipher = getEncryptionCipher(passwordBytes); iv = cipher.getIV(); } catch (RuntimeException e) { closeQuietly(input); closeQuietly(output); throw e; } final CountingOutputStream countingOutputStream = new CountingOutputStream(output); final CipherOutputStream cipherOutputStream = new CipherOutputStream(countingOutputStream, cipher); try { countingOutputStream.write(iv); copyLarge(input, cipherOutputStream, new byte[bufferSize]); } finally { closeQuietly(input); closeQuietly(cipherOutputStream); } return countingOutputStream.getByteCount(); } /** * Encrypts all input from the given byte array to the given byte array * * @param passwordBytes the used password. The length must be compatible to the used encryption algorithm * @param input plain text byte array * @param inputOffset offset in the plain text array * @param inputLength length of the plain text * @param output cipher text byte array * @param outputOffset offset in the cipher text array * @return the number of bytes written to the cipher text byte array * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem */ @SuppressWarnings("SameParameterValue") private int internalEncrypt(@Nonnull final byte[] passwordBytes, @Nonnull final byte[] input, final int inputOffset, final int inputLength, @Nonnull final byte[] output, final int outputOffset) { try { final Cipher cipher = getEncryptionCipher(passwordBytes); final byte[] iv = cipher.getIV(); System.arraycopy(iv, 0, output, outputOffset, iv.length); return iv.length + cipher.doFinal(input, inputOffset, inputLength, output, outputOffset + iv.length); } catch (ShortBufferException e) { throw new CryptoBufferOverflowException("output buffer too small", e); } catch (IllegalBlockSizeException | BadPaddingException e) { throw new CryptoInitializationProblem(getEncryptionMethod() + " encryption or padding not available on encrypt?", e); } } /** * Encrypts all input from the given byte buffer to the give byte buffer * * @param passwordBytes the used password. The length must be compatible to the used encryption algorithm * @param input plain text byte buffer * @param output cipher text byte buffer * @return the number of bytes written to the cipher text byte buffer * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem */ private long internalEncrypt(@Nonnull final byte[] passwordBytes, @Nonnull final ByteBuffer input, @Nonnull final ByteBuffer output) { try { final Cipher cipher = getEncryptionCipher(passwordBytes); final byte[] iv = cipher.getIV(); output.put(iv); return iv.length + cipher.doFinal(input, output); } catch (ShortBufferException e) { throw new CryptoBufferOverflowException("output buffer too small", e); } catch (IllegalBlockSizeException | BadPaddingException e) { throw new CryptoInitializationProblem(getEncryptionMethod() + " encryption or padding not available on encrypt?", e); } } /** * Decrypts all input from the given input stream to the given output stream. Both streams will be closed. * * @param passwordBytes the used password. The length must be compatible to the used encryption algorithm * @param input cipher text input stream * @param output plain text output stream * @return the number of bytes written to the plain text output stream * @throws IOException in case of an underlying {@link java.io.IOException} of the streams * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem */ private long internalDecrypt(@Nonnull final byte[] passwordBytes, @Nonnull @WillClose final InputStream input, @Nonnull @WillClose OutputStream output) throws IOException { final Cipher cipher; try { final byte[] iv = getIV(input); cipher = getDecryptionCipher(passwordBytes, iv); } catch (RuntimeException | IOException e) { closeQuietly(input); closeQuietly(output); throw e; } final CipherInputStream cipherInputStream = new CipherInputStream(input, cipher); final CountingOutputStream countingOutputStream = new CountingOutputStream(output); try { copyLarge(cipherInputStream, countingOutputStream, new byte[bufferSize]); } finally { closeQuietly(cipherInputStream); closeQuietly(countingOutputStream); } return countingOutputStream.getByteCount(); } /** * Decrypts all input from the given byte array to the given byte array * * @param passwordBytes the used password. The length must be compatible to the used encryption algorithm * @param input cipher text byte array * @param inputOffset offset in the cipher text array * @param inputLength length of the cipher text * @param output plain text byte array * @param outputOffset offset in the plain text array * @return the number of bytes written to the plain text byte array * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem */ @SuppressWarnings("SameParameterValue") private int internalDecrypt(@Nonnull final byte[] passwordBytes, @Nonnull final byte[] input, final int inputOffset, final int inputLength, @Nonnull final byte[] output, final int outputOffset) { try { final byte[] iv = getIV(input, inputOffset); final Cipher cipher = getDecryptionCipher(passwordBytes, iv); return cipher.doFinal(input, inputOffset + iv.length, inputLength - iv.length, output, outputOffset); } catch (ShortBufferException e) { throw new CryptoBufferOverflowException("output buffer too small", e); } catch (IllegalBlockSizeException e) { throw new CryptoInvalidCipherTextException("input must be a multiple of the block size", e); } catch (BadPaddingException e) { throw new CryptoInvalidCipherTextException("invalid padding", e); } } /** * Decrypts all input from the given byte buffer to the give byte buffer * * @param passwordBytes the used password. The length must be compatible to the used encryption algorithm * @param input cipher text byte buffer * @param output plain text byte buffer * @return the number of bytes written to the cipher text byte buffer * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem * @throws CryptoBufferOverflowException not enough bytes in output * @throws CryptoInvalidCipherTextException invalid h text */ private long internalDecrypt(@Nonnull final byte[] passwordBytes, @Nonnull final ByteBuffer input, @Nonnull final ByteBuffer output) { try { final byte[] iv = getIV(input); final Cipher cipher = getDecryptionCipher(passwordBytes, iv); return cipher.doFinal(input, output); } catch (ShortBufferException e) { throw new CryptoBufferOverflowException("output buffer too small", e); } catch (IllegalBlockSizeException e) { throw new CryptoInvalidCipherTextException("input must be a multiple of the block size", e); } catch (BadPaddingException e) { throw new CryptoInvalidCipherTextException("invalid padding", e); } } /** * Creates and initializes the Cipher object for encryption * * @param passwordBytes the used password. The length must be compatible to the used encryption algorithm * @return the cipher object for this crypt object * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem */ @Nonnull Cipher getEncryptionCipher(@Nonnull final byte[] passwordBytes) { final Cipher cipher = getCipher(); initCipher(cipher, passwordBytes, Cipher.ENCRYPT_MODE, null); return cipher; } /** * Creates and initializes the Cipher object for decryption * * @param passwordBytes the used password. The length must be compatible to the used encryption algorithm * @param iv the initialization vector * @return the cipher object for this crypt object * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem */ @Nonnull Cipher getDecryptionCipher(@Nonnull final byte[] passwordBytes, @Nonnull byte[] iv) { final Cipher cipher = getCipher(); initCipher(cipher, passwordBytes, Cipher.DECRYPT_MODE, iv); return cipher; } /** * Reads the initialization vector from an input stream * * @param input the stream * @return the initialization vector */ byte[] getIV(@Nonnull @WillNotClose final InputStream input) throws IOException { try { final byte[] iv = new byte[blockSizeInBytes]; readFully(input, iv); return iv; } catch (EOFException e) { throw new CryptoInvalidCipherTextException("not enough bytes for initialization vector", e); } } /** * Reads the initialization vector from a byte buffer * * @param input the byte buffer * @return the initialization vector */ private byte[] getIV(@Nonnull final ByteBuffer input) { try { final byte[] iv = new byte[blockSizeInBytes]; input.get(iv); return iv; } catch (BufferUnderflowException e) { throw new CryptoInvalidCipherTextException("not enough bytes for initialization vector", e); } } /** * Reads the initialization vector from a byte array * * @param input the byte array * @return the initialization vector */ private byte[] getIV(@Nonnull final byte[] input, final int offset) { try { final byte[] iv = new byte[blockSizeInBytes]; System.arraycopy(input, offset, iv, 0, blockSizeInBytes); return iv; } catch (IndexOutOfBoundsException e) { throw new CryptoInvalidCipherTextException("not enough bytes for initialization vector", e); } } /** * Creates the cipher object * * @return the cipher object for this crypt object * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem */ @Nonnull private Cipher getCipher() { try { return Cipher.getInstance(getEncryptionMethod()); } catch (NoSuchPaddingException | NoSuchAlgorithmException e) { throw new CryptoInitializationProblem(getEncryptionMethod() + " encryption or padding not available", e); } } /** * Initializes the cipher object * * @param cipher the cipher object * @param passwordBytes the password bytes * @param mode the cipher mode (encrypt or decrypt) * @param iv the initialization vector (only needed for decryption) * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem */ private void initCipher(@Nonnull final Cipher cipher, @Nonnull final byte[] passwordBytes, final int mode, @CheckForNull final byte[] iv) { try { final SecretKeySpec keySpec = new SecretKeySpec(passwordBytes, encryptionAlgorithm); if (iv != null) { cipher.init(mode, keySpec, new IvParameterSpec(iv)); } else { cipher.init(mode, keySpec); } if (cipher.getBlockSize() != blockSizeInBytes) { throw new CryptoInitializationProblem( getEncryptionMethod() + " calculated block size " + cipher.getBlockSize() + " does not match expected blocksize " + blockSizeInBytes); } } catch (InvalidAlgorithmParameterException e) { throw new CryptoInitializationProblem(getEncryptionMethod() + " initialization vector not allowed?", e); } catch (InvalidKeyException e) { throw new CryptoInitializationProblem( getEncryptionMethod() + " invalid key size, you probably need to replace the security policies in $JAVA_HOME/jre/lib/security, " + "see here http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html", e); } } /** * Hashes a password string * * @param password the string password * @return the hashed password bytes * @throws CryptoInitializationProblem in case of a {@link de.mibos.commons.crypt.CryptoInitializationProblem} problem */ @Nonnull public byte[] getHashedEncryptionKey(@Nonnull final CharSequence password) { try { final MessageDigest md = MessageDigest.getInstance(passwordHashingAlgorithm); if (password instanceof CharBuffer) { md.update(StandardCharsets.UTF_8.encode((CharBuffer) password)); } else { md.update(StandardCharsets.UTF_8.encode(password.toString())); } final byte[] digest = md.digest(); md.reset(); if (digest.length == keySizeInBytes) { return digest; } else if (digest.length > keySizeInBytes) { final byte[] passwordBytes = new byte[keySizeInBytes]; System.arraycopy(digest, 0, passwordBytes, 0, keySizeInBytes); resetPasswordBytes(digest); return passwordBytes; } else { throw new CryptoInitializationProblem("Hashing algorithm " + passwordHashingAlgorithm + " produces not enough bytes for key, required " + (keySizeInBytes * 8) + " bits, provided only " + (digest.length * 8) + " bits"); } } catch (NoSuchAlgorithmException e) { throw new CryptoInitializationProblem("Hashing algorithm " + passwordHashingAlgorithm + " not available", e); } } /** * Prevents password bytes from hanging around in memory by overwriting with zero bytes * * @param passwordBytes the password bytes to reset */ void resetPasswordBytes(@Nonnull final byte[] passwordBytes) { Arrays.fill(passwordBytes, (byte) 0); } /** * String description of this Crypt * * @return String description of this Crypt * @since 1.4 */ @Override public String toString() { return "Crypt{" + "encryptionAlgorithm='" + encryptionAlgorithm + '\'' + ", passwordHashingAlgorithm='" + passwordHashingAlgorithm + '\'' + ", blockMode=" + blockMode + ", padding=" + padding + ", keySize=" + (keySizeInBytes * 8) + ", blockSize=" + (blockSizeInBytes * 8) + ", bufferSize=" + bufferSize + '}'; } /** * Is this Crypt equal to the given object * * @param other the object to compare * @return true, if this Crypt is equal to the given object * @since 1.4 */ @Override public boolean equals(@CheckForNull final Object other) { if (this == other) { return true; } if (other == null || getClass() != other.getClass()) { return false; } final Crypt otherCrypt = (Crypt) other; return blockSizeInBytes == otherCrypt.blockSizeInBytes && bufferSize == otherCrypt.bufferSize && keySizeInBytes == otherCrypt.keySizeInBytes && blockMode == otherCrypt.blockMode && padding == otherCrypt.padding && encryptionAlgorithm.equalsIgnoreCase(otherCrypt.encryptionAlgorithm) && passwordHashingAlgorithm.equalsIgnoreCase(otherCrypt.passwordHashingAlgorithm); } /** * The hash code of this crypt * * @return the hash code of this crypt * @since 1.4 */ @Override public int hashCode() { int result = encryptionAlgorithm.hashCode(); result = 31 * result + passwordHashingAlgorithm.hashCode(); result = 31 * result + blockMode.hashCode(); result = 31 * result + padding.hashCode(); result = 31 * result + keySizeInBytes; result = 31 * result + blockSizeInBytes; result = 31 * result + bufferSize; return result; } /** * For the static instances, the same instance will be returned * * @return the static instance, if found * @throws ObjectStreamException error accessing static instances * @since 1.4 */ @Nonnull private Object readResolve() throws ObjectStreamException { final Field[] fields = getClass().getFields(); for (Field field : fields) { try { if (isStatic(field.getModifiers()) && this.equals(field.get(null))) { return field.get(null); } } catch (IllegalAccessException e) { throw new InvalidObjectException(e.getMessage()); } } return this; } /** * Returns the encryption algorithm * * @return the encryption algorithm * @since 1.4 */ @Nonnull public String getEncryptionAlgorithm() { return encryptionAlgorithm; } /** * Returns the password hashing algorithm * * @return the password hashing algorithm * @since 1.4 */ @Nonnull public String getPasswordHashingAlgorithm() { return passwordHashingAlgorithm; } /** * Returns the block mode * * @return the block mode * @since 1.4 */ @Nonnull public BlockMode getBlockMode() { return blockMode; } /** * Returns the padding * * @return the padding * @since 1.4 */ @Nonnull public Padding getPadding() { return padding; } /** * Returns the buffer size * * @return the buffer size * @since 1.4 */ public int getBufferSize() { return bufferSize; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy