2 * Copyright (C) 2007-2012 Argeo GmbH
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org
.argeo
.security
.crypto
;
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 import java
.security
.Security
;
28 import javax
.crypto
.Cipher
;
29 import javax
.crypto
.CipherInputStream
;
30 import javax
.crypto
.CipherOutputStream
;
31 import javax
.crypto
.SecretKey
;
32 import javax
.crypto
.SecretKeyFactory
;
33 import javax
.crypto
.spec
.IvParameterSpec
;
34 import javax
.crypto
.spec
.PBEKeySpec
;
35 import javax
.crypto
.spec
.SecretKeySpec
;
37 import org
.apache
.commons
.logging
.Log
;
38 import org
.apache
.commons
.logging
.LogFactory
;
39 import org
.argeo
.ArgeoException
;
40 import org
.argeo
.StreamUtils
;
41 import org
.bouncycastle
.jce
.provider
.BouncyCastleProvider
;
43 /** Simple password based encryption / decryption */
44 public class PasswordBasedEncryption
{
45 private final static Log log
= LogFactory
46 .getLog(PasswordBasedEncryption
.class);
49 Security
.addProvider(new BouncyCastleProvider());
52 public final static Integer DEFAULT_ITERATION_COUNT
= 1024;
53 /** Stronger with 256, but causes problem with Oracle JVM */
54 public final static Integer DEFAULT_SECRETE_KEY_LENGTH
= 256;
55 public final static Integer DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED
= 128;
56 public final static String DEFAULT_SECRETE_KEY_FACTORY
= "PBKDF2WithHmacSHA1";
57 public final static String DEFAULT_SECRETE_KEY_ENCRYPTION
= "AES";
58 public final static String DEFAULT_CIPHER_NAME
= "AES/CBC/PKCS5Padding";
59 public final static String DEFAULT_CHARSET
= "UTF-8";
61 private Integer iterationCount
= DEFAULT_ITERATION_COUNT
;
62 private Integer secreteKeyLength
= DEFAULT_SECRETE_KEY_LENGTH
;
63 private String secreteKeyFactoryName
= DEFAULT_SECRETE_KEY_FACTORY
;
64 private String secreteKeyEncryption
= DEFAULT_SECRETE_KEY_ENCRYPTION
;
65 private String cipherName
= DEFAULT_CIPHER_NAME
;
67 private static byte[] DEFAULT_SALT_8
= { (byte) 0xA9, (byte) 0x9B,
68 (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x35, (byte) 0xE3,
70 private static byte[] DEFAULT_IV_16
= { (byte) 0xA9, (byte) 0x9B,
71 (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x35, (byte) 0xE3,
72 (byte) 0x03, (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32,
73 (byte) 0x56, (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
76 private Cipher ecipher
;
77 private Cipher dcipher
;
80 * Default provider is bouncy castle, in order to have consistent behaviour
81 * across implementations
83 private String securityProviderName
= "BC";
86 * This is up to the caller to clear the passed array. Neither copy of nor
87 * reference to the passed array is kept
89 public PasswordBasedEncryption(char[] password
) {
90 this(password
, DEFAULT_SALT_8
, DEFAULT_IV_16
);
94 * This is up to the caller to clear the passed array. Neither copies of nor
95 * references to the passed arrays are kept
97 public PasswordBasedEncryption(char[] password
, byte[] passwordSalt
,
98 byte[] initializationVector
) {
100 initKeyAndCiphers(password
, passwordSalt
, initializationVector
);
101 } catch (InvalidKeyException e
) {
102 Integer previousSecreteKeyLength
= secreteKeyLength
;
103 secreteKeyLength
= DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED
;
104 log
.warn("'" + e
.getMessage() + "', will use " + secreteKeyLength
105 + " secrete key length instead of "
106 + previousSecreteKeyLength
);
108 initKeyAndCiphers(password
, passwordSalt
, initializationVector
);
109 } catch (Exception e1
) {
110 throw new ArgeoException(
111 "Cannot get secret key (with restricted length)", e1
);
113 } catch (Exception e
) {
114 throw new ArgeoException("Cannot get secret key", e
);
118 protected void initKeyAndCiphers(char[] password
, byte[] passwordSalt
,
119 byte[] initializationVector
) throws GeneralSecurityException
{
120 byte[] salt
= new byte[8];
121 System
.arraycopy(passwordSalt
, 0, salt
, 0, salt
.length
);
122 // for (int i = 0; i < password.length && i < salt.length; i++)
123 // salt[i] = (byte) password[i];
124 byte[] iv
= new byte[16];
125 System
.arraycopy(initializationVector
, 0, iv
, 0, iv
.length
);
127 SecretKeyFactory keyFac
= SecretKeyFactory
128 .getInstance(getSecretKeyFactoryName());
129 PBEKeySpec keySpec
= new PBEKeySpec(password
, salt
,
130 getIterationCount(), getKeyLength());
131 String secKeyEncryption
= getSecretKeyEncryption();
132 if (secKeyEncryption
!= null) {
133 SecretKey tmp
= keyFac
.generateSecret(keySpec
);
134 key
= new SecretKeySpec(tmp
.getEncoded(), getSecretKeyEncryption());
136 key
= keyFac
.generateSecret(keySpec
);
138 ecipher
= Cipher
.getInstance(getCipherName(), securityProviderName
);
139 ecipher
.init(Cipher
.ENCRYPT_MODE
, key
, new IvParameterSpec(iv
));
140 dcipher
= Cipher
.getInstance(getCipherName());
141 dcipher
.init(Cipher
.DECRYPT_MODE
, key
, new IvParameterSpec(iv
));
144 public void encrypt(InputStream decryptedIn
, OutputStream encryptedOut
)
147 CipherOutputStream out
= new CipherOutputStream(encryptedOut
,
149 StreamUtils
.copy(decryptedIn
, out
);
150 StreamUtils
.closeQuietly(out
);
151 } catch (IOException e
) {
153 } catch (Exception e
) {
154 throw new ArgeoException("Cannot encrypt", e
);
156 StreamUtils
.closeQuietly(decryptedIn
);
160 public void decrypt(InputStream encryptedIn
, OutputStream decryptedOut
)
163 CipherInputStream decryptedIn
= new CipherInputStream(encryptedIn
,
165 StreamUtils
.copy(decryptedIn
, decryptedOut
);
166 } catch (IOException e
) {
168 } catch (Exception e
) {
169 throw new ArgeoException("Cannot decrypt", e
);
171 StreamUtils
.closeQuietly(encryptedIn
);
175 public byte[] encryptString(String str
) {
176 ByteArrayOutputStream out
= null;
177 ByteArrayInputStream in
= null;
179 out
= new ByteArrayOutputStream();
180 in
= new ByteArrayInputStream(str
.getBytes(DEFAULT_CHARSET
));
182 return out
.toByteArray();
183 } catch (Exception e
) {
184 throw new ArgeoException("Cannot encrypt", e
);
186 StreamUtils
.closeQuietly(out
);
190 /** Closes the input stream */
191 public String
decryptAsString(InputStream in
) {
192 ByteArrayOutputStream out
= null;
194 out
= new ByteArrayOutputStream();
196 return new String(out
.toByteArray(), DEFAULT_CHARSET
);
197 } catch (Exception e
) {
198 throw new ArgeoException("Cannot decrypt", e
);
200 StreamUtils
.closeQuietly(out
);
204 protected Key
getKey() {
208 protected Cipher
getEcipher() {
212 protected Cipher
getDcipher() {
216 protected Integer
getIterationCount() {
217 return iterationCount
;
220 protected Integer
getKeyLength() {
221 return secreteKeyLength
;
224 protected String
getSecretKeyFactoryName() {
225 return secreteKeyFactoryName
;
228 protected String
getSecretKeyEncryption() {
229 return secreteKeyEncryption
;
232 protected String
getCipherName() {
236 public void setIterationCount(Integer iterationCount
) {
237 this.iterationCount
= iterationCount
;
240 public void setSecreteKeyLength(Integer keyLength
) {
241 this.secreteKeyLength
= keyLength
;
244 public void setSecreteKeyFactoryName(String secreteKeyFactoryName
) {
245 this.secreteKeyFactoryName
= secreteKeyFactoryName
;
248 public void setSecreteKeyEncryption(String secreteKeyEncryption
) {
249 this.secreteKeyEncryption
= secreteKeyEncryption
;
252 public void setCipherName(String cipherName
) {
253 this.cipherName
= cipherName
;
256 public void setSecurityProviderName(String securityProviderName
) {
257 this.securityProviderName
= securityProviderName
;