View Javadoc
1   /*
2    * Copyright (C) 2007-2012 Argeo GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.argeo.util;
17  
18  import java.io.ByteArrayInputStream;
19  import java.io.ByteArrayOutputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.security.GeneralSecurityException;
24  import java.security.InvalidKeyException;
25  import java.security.Key;
26  
27  import javax.crypto.Cipher;
28  import javax.crypto.CipherInputStream;
29  import javax.crypto.CipherOutputStream;
30  import javax.crypto.SecretKey;
31  import javax.crypto.SecretKeyFactory;
32  import javax.crypto.spec.IvParameterSpec;
33  import javax.crypto.spec.PBEKeySpec;
34  import javax.crypto.spec.SecretKeySpec;
35  
36  public class PasswordEncryption {
37  	public final static Integer DEFAULT_ITERATION_COUNT = 1024;
38  	/** Stronger with 256, but causes problem with Oracle JVM */
39  	public final static Integer DEFAULT_SECRETE_KEY_LENGTH = 256;
40  	public final static Integer DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED = 128;
41  	public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1";
42  	public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES";
43  	public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding";
44  	public final static String DEFAULT_CHARSET = "UTF-8";
45  
46  	private Integer iterationCount = DEFAULT_ITERATION_COUNT;
47  	private Integer secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH;
48  	private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY;
49  	private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION;
50  	private String cipherName = DEFAULT_CIPHER_NAME;
51  
52  	private static byte[] DEFAULT_SALT_8 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
53  			(byte) 0x35, (byte) 0xE3, (byte) 0x03 };
54  	private static byte[] DEFAULT_IV_16 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
55  			(byte) 0x35, (byte) 0xE3, (byte) 0x03, (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
56  			(byte) 0x35, (byte) 0xE3, (byte) 0x03 };
57  
58  	private Key key;
59  	private Cipher ecipher;
60  	private Cipher dcipher;
61  
62  	private String securityProviderName = null;
63  
64  	/**
65  	 * This is up to the caller to clear the passed array. Neither copy of nor
66  	 * reference to the passed array is kept
67  	 */
68  	public PasswordEncryption(char[] password) {
69  		this(password, DEFAULT_SALT_8, DEFAULT_IV_16);
70  	}
71  
72  	/**
73  	 * This is up to the caller to clear the passed array. Neither copies of nor
74  	 * references to the passed arrays are kept
75  	 */
76  	public PasswordEncryption(char[] password, byte[] passwordSalt, byte[] initializationVector) {
77  		try {
78  			initKeyAndCiphers(password, passwordSalt, initializationVector);
79  		} catch (InvalidKeyException e) {
80  			Integer previousSecreteKeyLength = secreteKeyLength;
81  			secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED;
82  			System.err.println("'" + e.getMessage() + "', will use " + secreteKeyLength
83  					+ " secrete key length instead of " + previousSecreteKeyLength);
84  			try {
85  				initKeyAndCiphers(password, passwordSalt, initializationVector);
86  			} catch (Exception e1) {
87  				throw new UtilsException("Cannot get secret key (with restricted length)", e1);
88  			}
89  		} catch (Exception e) {
90  			throw new UtilsException("Cannot get secret key", e);
91  		}
92  	}
93  
94  	protected void initKeyAndCiphers(char[] password, byte[] passwordSalt, byte[] initializationVector)
95  			throws GeneralSecurityException {
96  		byte[] salt = new byte[8];
97  		System.arraycopy(passwordSalt, 0, salt, 0, salt.length);
98  		// for (int i = 0; i < password.length && i < salt.length; i++)
99  		// salt[i] = (byte) password[i];
100 		byte[] iv = new byte[16];
101 		System.arraycopy(initializationVector, 0, iv, 0, iv.length);
102 
103 		SecretKeyFactory keyFac = SecretKeyFactory.getInstance(getSecretKeyFactoryName());
104 		PBEKeySpec keySpec = new PBEKeySpec(password, salt, getIterationCount(), getKeyLength());
105 		String secKeyEncryption = getSecretKeyEncryption();
106 		if (secKeyEncryption != null) {
107 			SecretKey tmp = keyFac.generateSecret(keySpec);
108 			key = new SecretKeySpec(tmp.getEncoded(), getSecretKeyEncryption());
109 		} else {
110 			key = keyFac.generateSecret(keySpec);
111 		}
112 		if (securityProviderName != null)
113 			ecipher = Cipher.getInstance(getCipherName(), securityProviderName);
114 		else
115 			ecipher = Cipher.getInstance(getCipherName());
116 		ecipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
117 		dcipher = Cipher.getInstance(getCipherName());
118 		dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
119 	}
120 
121 	public void encrypt(InputStream decryptedIn, OutputStream encryptedOut) throws IOException {
122 		try {
123 			CipherOutputStream out = new CipherOutputStream(encryptedOut, ecipher);
124 			StreamUtils.copy(decryptedIn, out);
125 			StreamUtils.closeQuietly(out);
126 		} catch (IOException e) {
127 			throw e;
128 		} catch (Exception e) {
129 			throw new UtilsException("Cannot encrypt", e);
130 		} finally {
131 			StreamUtils.closeQuietly(decryptedIn);
132 		}
133 	}
134 
135 	public void decrypt(InputStream encryptedIn, OutputStream decryptedOut) throws IOException {
136 		try {
137 			CipherInputStream decryptedIn = new CipherInputStream(encryptedIn, dcipher);
138 			StreamUtils.copy(decryptedIn, decryptedOut);
139 		} catch (IOException e) {
140 			throw e;
141 		} catch (Exception e) {
142 			throw new UtilsException("Cannot decrypt", e);
143 		} finally {
144 			StreamUtils.closeQuietly(encryptedIn);
145 		}
146 	}
147 
148 	public byte[] encryptString(String str) {
149 		ByteArrayOutputStream out = null;
150 		ByteArrayInputStream in = null;
151 		try {
152 			out = new ByteArrayOutputStream();
153 			in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET));
154 			encrypt(in, out);
155 			return out.toByteArray();
156 		} catch (Exception e) {
157 			throw new UtilsException("Cannot encrypt", e);
158 		} finally {
159 			StreamUtils.closeQuietly(out);
160 		}
161 	}
162 
163 	/** Closes the input stream */
164 	public String decryptAsString(InputStream in) {
165 		ByteArrayOutputStream out = null;
166 		try {
167 			out = new ByteArrayOutputStream();
168 			decrypt(in, out);
169 			return new String(out.toByteArray(), DEFAULT_CHARSET);
170 		} catch (Exception e) {
171 			throw new UtilsException("Cannot decrypt", e);
172 		} finally {
173 			StreamUtils.closeQuietly(out);
174 		}
175 	}
176 
177 	protected Key getKey() {
178 		return key;
179 	}
180 
181 	protected Cipher getEcipher() {
182 		return ecipher;
183 	}
184 
185 	protected Cipher getDcipher() {
186 		return dcipher;
187 	}
188 
189 	protected Integer getIterationCount() {
190 		return iterationCount;
191 	}
192 
193 	protected Integer getKeyLength() {
194 		return secreteKeyLength;
195 	}
196 
197 	protected String getSecretKeyFactoryName() {
198 		return secreteKeyFactoryName;
199 	}
200 
201 	protected String getSecretKeyEncryption() {
202 		return secreteKeyEncryption;
203 	}
204 
205 	protected String getCipherName() {
206 		return cipherName;
207 	}
208 
209 	public void setIterationCount(Integer iterationCount) {
210 		this.iterationCount = iterationCount;
211 	}
212 
213 	public void setSecreteKeyLength(Integer keyLength) {
214 		this.secreteKeyLength = keyLength;
215 	}
216 
217 	public void setSecreteKeyFactoryName(String secreteKeyFactoryName) {
218 		this.secreteKeyFactoryName = secreteKeyFactoryName;
219 	}
220 
221 	public void setSecreteKeyEncryption(String secreteKeyEncryption) {
222 		this.secreteKeyEncryption = secreteKeyEncryption;
223 	}
224 
225 	public void setCipherName(String cipherName) {
226 		this.cipherName = cipherName;
227 	}
228 
229 	public void setSecurityProviderName(String securityProviderName) {
230 		this.securityProviderName = securityProviderName;
231 	}
232 }