/* * Copyright 2004 Mass Dosage * * Licensed 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 com.ant; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.CipherOutputStream; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; /** * Ant task for generating symmetric encryption keys and encrypting and * decrypting files. * * @author mass */ public class Crypter extends Task { /** * The default cipher transformation used for encryption or decryption. */ public static final String DEFAULT_CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding"; /** * The default algorithm used if generating a key. */ public static final String DEFAULT_KEY_ALGORITHM = "AES/CBC"; private String keyFile; private File inputFile; private File outputFile; private boolean encrypt = true; private boolean generateKey = false; private String salt = "EYnJl9GPHi44mt"; private String cipherTransformation = DEFAULT_CIPHER_TRANSFORMATION; private String keyAlgorithm = DEFAULT_KEY_ALGORITHM; private IvParameterSpec ivSpec; /** * Reads the contents of the key file and converts this into a * <code>Key</code>. * * @return The <code>Key</code> object. * @throws BuildException * If the contents of the key file cannot be read. */ private SecretKey readKey() throws BuildException { if (this.keyFile == null) { throw new BuildException("No 'keyFile' specified, cannot continue."); } try { this.logInfo(" key: " + keyFile + " " + keyFile.length()); this.logInfo(" salt: " + salt ); /*KeyGenerator kgen = KeyGenerator.getInstance("AES"); SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); sr.setSeed(keyFile.getBytes()); kgen.init(128, sr); // 192 and 256 bits may not be available SecretKey skey = kgen.generateKey();*/ //String salt="sale"; MessageDigest digest = MessageDigest.getInstance("SHA-1"); for(int i=0; i<128;i++){ digest.update(salt.getBytes()); digest.update(keyFile.getBytes()); digest.update(digest.digest()); } byte[] sha1 = digest.digest(); byte[] aes_key = new byte[16]; System.arraycopy(sha1, 0, aes_key, 0, aes_key.length); SecretKey secret = new SecretKeySpec(aes_key, "AES"); return secret; } catch (Exception e){ this.logInfo("readKey error: " + e); return null; } } /** * Initialises a <code>Cipher</code> in the mode set in the ant task * (encrypt or decrypt) with the passed <code>Key</code>. * * @param key * The <code>Key</code> which the <code>Cipher</code> will use * for encryption or decryption. * @return The initialised <code>Cipher</code>. * @throws BuildException * If an error occurs initialising the cipher. */ private Cipher initialiseCipher(Key key) throws BuildException { Cipher cipher = null; try { cipher = Cipher.getInstance(this.cipherTransformation); final byte[] iv = new byte[16]; Arrays.fill(iv, (byte) 0); IvParameterSpec ivSpec = new IvParameterSpec(iv); if (encrypt) { cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec); this.logInfo("Initialised cipher to perform encryption using " + this.cipherTransformation); this.logInfo("key: " + hex(key.getEncoded())); } else { cipher.init(Cipher.DECRYPT_MODE, key, ivSpec); this.logInfo("Initialised cipher to perform decryption using " + this.cipherTransformation); } } catch (NoSuchAlgorithmException e){ throw new BuildException("Cipher transformation algorithm [" + this.cipherTransformation + "] not supported", e); } catch (NoSuchPaddingException e){ throw new BuildException("Cipher padding scheme not supported", e); } catch (InvalidKeyException e){ this.logInfo("Error: " + e); throw new BuildException("Invalid key for cipher", e); } catch (InvalidAlgorithmParameterException e){ this.logInfo("Error: " + e); throw new BuildException("Invalid AlgorithmParameter for cipher", e); } return cipher; } private String hex(byte[] data) { int offset = 0; int length = data.length; final StringBuffer buf = new StringBuffer(); for (int i = offset; i < offset + length; i++) { int halfbyte = (data[i] >>> 4) & 0x0F; int twohalfs = 0; do { if ((0 <= halfbyte) && (halfbyte <= 9)) { buf.append((char) ('0' + halfbyte)); } else { buf.append((char) ('a' + (halfbyte - 10))); } halfbyte = data[i] & 0x0F; } while (twohalfs++ < 1); } return buf.toString(); } /** * Performs the encryption/decryption according to the state of the passed * <code>Cipher</code>, using the input and output files set in the ant * task. * * @param cipher * An initialised <code>Cipher</code> to use for the * encryption/decryption. * @throws BuildException * If the input or output files cannot be found, read, or * written to; or if an error occurs performing the * encryption/decryption. */ private void crypt(Cipher cipher) throws BuildException { FileInputStream in = null; try { in = new FileInputStream(this.inputFile); } catch (FileNotFoundException e){ throw new BuildException("Could not find input file " + this.inputFile, e); } FileOutputStream fileout = null; try { fileout = new FileOutputStream(this.outputFile); } catch (FileNotFoundException e){ throw new BuildException("Invalid output file " + this.outputFile, e); } CipherOutputStream out = new CipherOutputStream(fileout, cipher); byte[] buffer = new byte[8192]; int length; try { while ((length = in.read(buffer)) != -1) { out.write(buffer, 0, length); } in.close(); out.close(); } catch (IOException e){ throw new BuildException("Error writing output file " + this.outputFile, e); } this.logInfo("Performed cryptographic transformation on " + this.inputFile.getAbsolutePath() + " to " + this.outputFile.getAbsolutePath()); } /** * Called by the project to perform the encryption or decryption using the * parameters set in the task. * * @throws BuildException * If something goes wrong executing this task. */ public void execute() throws BuildException { if (!(this.inputFile == null && this.outputFile == null)) { // if input // or output // files // specified, // need to // attmept // enc/dec Key key = this.readKey(); this.logInfo("key read " + (key != null)); Cipher cipher = this.initialiseCipher(key); this.crypt(cipher); } } /** * Sets the algorithm used to generate a <code>Key</code>. If this is not * set, then the default value specified by * <code>DEFAULT_KEY_ALGORITHM</code> will be used. * * @param keyAlgorithm * The standard name of the requested key algorithm. */ public void setKeyAlgorithm(String keyAlgorithm) { this.keyAlgorithm = keyAlgorithm; } /** * Sets the location of the <code>File</code> containing the * <code>Key</code> to be used for encryption/decryption. * * @param keyFile * The location of the key file. */ public void setKeyFile(String key) { this.keyFile = key; } /** * Sets the location of the input <code>File</code> that is to be * encrypted/decrypted. * * @param inputFile * The location of the input file. */ public void setInputFile(File inputFile) { this.inputFile = inputFile; } /** * Sets the location of the output <code>File</code> that is the results of * the encryption/decryption. * * @param outputFile * The location of the output file. */ public void setOutputFile(File outputFile) { this.outputFile = outputFile; } public void setSalt(String salt) { this.salt = salt; } /** * Sets the mode that is used to determine whether encryption or decryption * will be performed. If the value "true" is passed to this method then * encryption will be performed, if the value "false" is passed then * decryption will be performed. If this value is not set, encryption will * be performed by default. * * @param encrypt * Whether to perform encryption or decryption. */ public void setEncrypt(boolean encrypt) { this.encrypt = encrypt; } /** * Sets the cipher transformation that will be used to perform the * encryption/decryption. If this is not set, then the default value * specified by <code>DEFAULT_CIPHER_TRANSFORMATION</code> will be used. * * @param transformation * The name of the transformation, for example * <i>Blowfish/ECB/PKCS5Padding</i>. */ public void setCipherTransformation(String transformation) { this.cipherTransformation = transformation; } /** * Calcola il SHA1 del messaggio, usando la crypto api. * * @param message * the message * @param offset * the offset * @param length * the length * @return the byte[] */ public static byte[] SHA1(final byte[] message, final int offset, final int length) { MessageDigest digest; try { digest = MessageDigest.getInstance("SHA-1"); //$NON-NLS-1$ digest.update(message, offset, length); final byte[] sha1 = digest.digest(); return sha1; } catch (final NoSuchAlgorithmException e){ } return null; } /** * SH a1. * * @param message * the message * @return the byte[] */ public static byte[] SHA1(final byte[] message) { return SHA1(message, 0, message.length); } /** * Logs an informational message. This method is required so that this task * can be used outside of ant. * * @param message * Message to log. */ private void logInfo(String message) { if (this.getProject() != null) { // we are running in ant, so use ant // log this.log(message, Project.MSG_INFO); } else { // we are running outside of ant, log to System.out System.out.println(message); } } }