Java tutorial
/******************************************************************************* * Copyright (c) 2013 "Ivo van Kamp" * * jEncrypt is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package net.sourceforge.jencrypt.lib; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; import java.io.*; import java.security.*; import java.security.spec.InvalidKeySpecException; /** * CryptoWrapper is a wrapper for the Java Cryptographic Extension (JCE) * framework. */ public class CryptoWrapper { /** * Files are read part by part into a buffer the size of readBufferSize. * The read buffer is a byte array and can not be larger than Integer.MAX_SIZE. */ private int readBufferSize; /** * The size of the salt in bytes. */ private int saltSize; /** * Configurable jencrypt.ini parameter indicating the number of iterations * the PBKDF2 functions takes to derive a cryptographic key from a password. */ private int keyDerivationIterationCount; /** * Core JCE class javax.crypto.Cipher */ private final Cipher cipher; /** * The cryptographic key which determines the transformation of plaintext * data into ciphertext data (NIST IR 7298 Rev. 1) */ private final Key cipherKey; /** * Section 3.1 of NIST SP 800-132 Recommendation for Password-Based Key * Derivation: * * Salt A non-secret binary value that is used as an input to the key * derivation function PBKDF specified in this Recommendation to * allow the generation of a large set of keys for a given password. */ private byte[] salt; /** * The IV is used in an initial step in the encryption of a message and in * the corresponding decryption of the message (NIST SP800-38a). * As long as the initial vector is random for each message encrypted under * the same key replay and swap attacks are infeasible (LibTomCrypt * Developer Manual by Tom St Denis). */ private final Key initializationVector; /** * AES block size in bits (N.B. the Rijndael algorithm itself allows for * other sizes). */ public static final short AES_BLOCK_SIZE_IN_BITS = 128; /** * AES block size in bytes (N.B. the Rijndael algorithm itself allows for * other sizes). */ public static final int AES_BLOCK_SIZE_IN_BYTES = (AES_BLOCK_SIZE_IN_BITS >>> 3); private boolean isInitialized = false; /** * CryptoWrapper builder */ public static class CryptoWrapperBuilder { private String password; private String cipherName; private String transformationString; private short keySize; private int readBufferSize; private Key initializationVector; private Key cipherKey; private byte[] salt; private int saltSize; private int keyDerivationIterationCount; /** * The encryption algorithm (e.g. DES/RSA/AES/etc). */ public CryptoWrapperBuilder cipherName(String s) { cipherName = s; return this; } public CryptoWrapperBuilder salt(byte[] salt) { this.salt = salt; return this; } public CryptoWrapperBuilder saltSize(int saltSize) { this.saltSize = saltSize; return this; } public CryptoWrapperBuilder keyDerivationIterationCount(int keyDerivationIterationCount) { this.keyDerivationIterationCount = keyDerivationIterationCount; return this; } /** * A transformation always includes the name of a cryptographic * algorithm (e.g., DES), and may be followed by a feedback mode and * padding scheme. */ public CryptoWrapperBuilder transformationString(String s) { transformationString = s; return this; } /** * The en/decryption key. * * @throws DecoderException */ public CryptoWrapperBuilder password(String password) throws DecoderException { this.password = password; return this; } /** * The en/decryption key. * * @throws DecoderException */ public CryptoWrapperBuilder key(byte[] key) throws DecoderException { cipherKey = bytesToKey(key); return this; } /** * Size of the key in bits. */ public CryptoWrapperBuilder keySize(short s) { keySize = s; return this; } /** * The initialization vector. */ public CryptoWrapperBuilder initializationVector(byte[] initializationVector) { this.initializationVector = bytesToKey(initializationVector); return this; } /** * Files are read into a buffer the size of readBufferSize. */ public CryptoWrapperBuilder readBufferSize(int i) { readBufferSize = i; return this; } private Key bytesToKey(byte[] key) { if (this.cipherName != null) { return new SecretKeySpec(key, this.cipherName); } else { throw new IllegalStateException( "Encryption algorithm not specified with a call to CryptoWrapperBuilder.cipherName()"); } } /** * Calls the CryptoWrapper constructor with all parameters inside a * Builder instance. */ public CryptoWrapper build() throws IOException, GeneralSecurityException { return new CryptoWrapper(this); } } /** * CryptoWrapper constructor creates a Cipher instance with the given * parameters. * */ private CryptoWrapper(CryptoWrapperBuilder builder) throws IOException, GeneralSecurityException { readBufferSize = builder.readBufferSize; cipher = Cipher.getInstance(builder.transformationString); saltSize = builder.saltSize; keyDerivationIterationCount = builder.keyDerivationIterationCount; // Salt to prevent rainbow-table assisted dictionary attacks. // Used to derive a key from the password with PBKDF2WithHmacSHA1. if (builder.salt == null) { salt = new byte[saltSize]; SecureRandom secureRandom = new SecureRandom(); secureRandom.nextBytes(salt); } else { salt = builder.salt; } if (builder.cipherKey == null) { cipherKey = new SecretKeySpec(getHashedPassword(builder), builder.cipherName); } else { cipherKey = builder.cipherKey; } /* If no IV given then generate a new one */ if (builder.initializationVector == null) { initializationVector = getInitializationVector(builder); } else { initializationVector = builder.initializationVector; } } private Key getInitializationVector(CryptoWrapperBuilder builder) throws NoSuchAlgorithmException { KeyGenerator generator = KeyGenerator.getInstance(builder.cipherName); generator.init(CryptoWrapper.AES_BLOCK_SIZE_IN_BITS, new SecureRandom()); return generator.generateKey(); } private byte[] getHashedPassword(CryptoWrapperBuilder builder) throws NoSuchAlgorithmException, InvalidKeySpecException { /* Apply PBKDF2 (Password-Based Key Derivation Function 2) with * HMAC-SHA-1 to the password (for further details, see RFC-2898). */ SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); PBEKeySpec spec = new PBEKeySpec(builder.password.toCharArray(), salt, keyDerivationIterationCount, builder.keySize); SecretKey secret = factory.generateSecret(spec); return secret.getEncoded(); } /** * Return the encryption buffer size. E.g. To calculate the total number of * iterations of the encryption loop which is used to print the progress in * percentages of the encryption. */ public int getReadBufferSize() { return readBufferSize; } public byte[] getSalt() { return salt; } public int getSaltSize() { return saltSize; } public byte[] getEncryptionKeyBytes() { return cipherKey.getEncoded(); } public byte[] getInitializationVector() { return initializationVector.getEncoded(); } /** * Read the encryption key into a byte array. * * @param keyFile * the file with the en/decryption key * @return byte[] */ public byte[] readKeyFile(File keyFile) throws IOException { byte[] keyBytes = null; byte[] bytes = null; try { keyBytes = Utils.getBytesFromFile(keyFile); // convert hex to bytes bytes = new Hex().decode(keyBytes); } catch (Exception e) { throw new IOException("Error reading keyfile '" + keyFile.getName() + "': " + e.getMessage()); } return bytes; } /** * Save the generated en/decryption key. */ public void saveKeyFile(String file, Key k) throws IOException { BufferedWriter bw = null; try { FileWriter fw = new FileWriter(file); bw = new BufferedWriter(fw); bw.write(Hex.encodeHexString(k.getEncoded())); bw.flush(); } catch (IOException e) { throw new IOException("Error saving key file :'" + file + "': " + e.getMessage()); } finally { bw.close(); } } public byte[] cipherBytes(byte[] bytesToCipher, int cipherMode) throws Exception { if (isInitialized == false) { try { cipher.init(cipherMode, cipherKey, new IvParameterSpec(initializationVector.getEncoded())); } catch (InvalidKeyException e) { throw new InvalidKeyException("Error : " + e.getMessage() + "\nKey file corrupt or invalid key parameters." + "\nTo use key sizes above 128 bits please install the JCE Unlimited Strength Jurisdiction Policy Files."); } isInitialized = true; } return cipher.update(bytesToCipher); } /** * De- or encrypt a file. * * @param inputFileFullPath * file to en- or decrypt * @param outputFileFullPath * file with result of en-/decryption * @param cipherMode * Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE * @param progress * progress info class to print progress percentage * */ public void doCipherOperation(InputStream in, OutputStream out, long nrOfBytesToCipher, int cipherMode, ProgressInfo progress) throws Exception { if (nrOfBytesToCipher > 0) { try { // If the nr of bytes to read is smaller than readBufferSize // use the nr of bytes as the max buffer size. int maxBufferSize = (int) Math.min(readBufferSize, nrOfBytesToCipher); byte[] readBuffer = new byte[maxBufferSize]; if (isInitialized == false) { cipher.init(cipherMode, cipherKey, new IvParameterSpec(initializationVector.getEncoded())); isInitialized = true; } long nrOfBytesLeftToCipher = nrOfBytesToCipher; while (nrOfBytesLeftToCipher > 0) { in.read(readBuffer, 0, maxBufferSize); nrOfBytesLeftToCipher -= cipher.update(readBuffer, 0, maxBufferSize, readBuffer); // If a ProgressInfo was given, calculate the current // percentage and print it if (progress != null) progress.trackAndPrintProgress(); out.write(readBuffer, 0, maxBufferSize); if (nrOfBytesLeftToCipher > 0 && nrOfBytesLeftToCipher < maxBufferSize) { maxBufferSize = (int) nrOfBytesLeftToCipher; } } //cipher.doFinal(); } catch (IOException e) { throw new IOException("Error reading bytes to encrypt: " + e.getMessage()); } catch (InvalidKeyException e) { throw new InvalidKeyException("Error : " + e.getMessage() + "\nKey file corrupt or invalid key parameters." + "\nTo use key sizes above 128 bits please install the JCE Unlimited Strength Jurisdiction Policy Files."); } } else { // Print progress even if nrOfBytesToCipher == 0 if (progress != null) progress.trackAndPrintProgress(); } } }