001/**
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
005 *
006 * The contents of this file are subject to the terms
007 * of the Common Development and Distribution License
008 * (the License). You may not use this file except in
009 * compliance with the License.
010 *
011 * You can obtain a copy of the License at
012 * https://opensso.dev.java.net/public/CDDLv1.0.html or
013 * opensso/legal/CDDLv1.0.txt
014 * See the License for the specific language governing
015 * permission and limitations under the License.
016 *
017 * When distributing Covered Code, include this CDDL
018 * Header Notice in each file and include the License file
019 * at opensso/legal/CDDLv1.0.txt.
020 * If applicable, add the following below the CDDL Header,
021 * with the fields enclosed by brackets [] replaced by
022 * your own identifying information:
023 * "Portions Copyrighted [year] [name of copyright owner]"
024 *
025 * $Id: DataEncryptor.java,v 1.1 2009/02/26 23:58:41 exu Exp $
026 *
027 */
028
029package com.sun.identity.security;
030
031import java.util.Map;
032import java.util.HashMap;
033import javax.crypto.Cipher;
034import javax.crypto.KeyGenerator;
035import javax.crypto.SecretKey;
036import java.security.NoSuchAlgorithmException;
037import javax.crypto.NoSuchPaddingException;
038import java.security.Key;
039import java.security.InvalidKeyException;
040import java.io.UnsupportedEncodingException;
041import javax.crypto.SecretKeyFactory;
042import javax.crypto.spec.PBEKeySpec;
043import javax.crypto.spec.PBEParameterSpec;
044
045import com.sun.identity.shared.encode.Base64;
046
047
048/**
049 * This class <code>DataEncryptor</code> is used to encrypt the data
050 * with symmetric and asymmetric keys.
051 * @supported.all.api
052 */
053public class DataEncryptor {
054
055    private static final String ENCRYPTED_DATA = "EncryptedData";
056    
057    private static final String ENCRYPTED_KEY = "EncryptedKey";
058    private static final byte[] ___y = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
059            0x01, 0x01 };
060    private static final int ITERATION_COUNT = 5;
061    private static PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(
062            ___y, ITERATION_COUNT);
063
064    /**
065     * Encrypts the given data with an asymmetric key. The asymmetric 
066     * encryption uses symmetric secret key for data encryption and sends
067     * the secret key to the recipient by encrypting the same with given
068     * transport key (publick key). 
069     * @param data the data to be encrypted.
070     * @param encryptionAlgorithm the encryption algorithm to be used.
071     *        The encryption algorithm must be one of the supported
072     *        algorithm by the underlying JCE encryption provider.
073     *        Examples of encryption algorithms are "DES", "AES" etc. 
074     * @param encryptionStrength the encryption strength for a given
075     *                           encryption algorithm.
076     * @param encKey the encryption key to be used. For PKI, this
077     *               key should be public key of the intended recipient.  
078     * @return the encrypted data in Base64 encoded format.
079     */
080    public static String encryptWithAsymmetricKey(String data,
081                   String encryptionAlgorithm,
082                   int encryptionStrength,
083                   Key encKey) throws Exception {
084        try {
085            KeyGenerator keygen = KeyGenerator.getInstance(encryptionAlgorithm);
086            if(encryptionStrength != 0) {
087               keygen.init(encryptionStrength);
088            }
089            SecretKey sKey = keygen.generateKey();
090            Cipher cipher = Cipher.getInstance(encryptionAlgorithm); 
091            cipher.init(Cipher.ENCRYPT_MODE, sKey);
092            byte[] encData = cipher.doFinal(data.getBytes("UTF-8")); 
093            cipher = Cipher.getInstance(encKey.getAlgorithm());
094            cipher.init(Cipher.WRAP_MODE, encKey);
095            byte[] keyWrap = cipher.wrap(sKey);
096            byte[] encDataPad = wrapKeyWithEncryptedData(encData, keyWrap);
097            return Base64.encode(encDataPad);
098        } catch (NoSuchAlgorithmException nse) {
099            throw new Exception(nse.getMessage());
100        } catch (NoSuchPaddingException npe) {
101            throw new Exception(npe.getMessage());
102        } catch (InvalidKeyException ike) {
103            throw new Exception(ike.getMessage());
104        } catch (UnsupportedEncodingException uae) {
105            throw new Exception(uae.getMessage());
106        } 
107    }
108
109    /**
110     * Decrypts the given data with asymmetric key.
111     * @param data the data to be decrypted.
112     * @param encAlgorithm the encryption algorithm was used for encrypted
113     *                     data. 
114     * @param encKey the private key for decrypting the data.
115     * @return the decrypted data.
116     */
117    public static String decryptWithAsymmetricKey(
118              String data, String encAlgorithm, Key encKey) throws Exception {
119
120        try {
121            byte[] tmp = Base64.decode(data);
122            Map map = unwrapKeyWithEncodedData(tmp);
123            byte[] encData = (byte[])map.get(ENCRYPTED_DATA);
124            byte[] keyData = (byte[])map.get(ENCRYPTED_KEY);
125            Cipher cipher = Cipher.getInstance(encKey.getAlgorithm());
126            cipher.init(Cipher.UNWRAP_MODE, encKey); 
127            Key secretKey = cipher.unwrap(keyData, encAlgorithm, 
128                   Cipher.SECRET_KEY);
129            cipher = Cipher.getInstance(encAlgorithm);
130            cipher.init(Cipher.DECRYPT_MODE, secretKey);
131            byte[] decryptedData =  cipher.doFinal(encData);
132            return Base64.encode(decryptedData);
133        } catch (NoSuchAlgorithmException nse) {
134            throw new Exception(nse.getMessage());
135        } catch (InvalidKeyException ike) {
136            throw new Exception(ike.getMessage());
137        }
138    }
139
140    /**
141     * Encrypts the given data with a symmetric key that was generated
142     * using given shared secret. 
143     * @param data the data to be encrypted.
144     * @param encAlgorithm the encryption algorithm to be used.
145     *        The encryption algorithm must be one of the supported
146     *        algorithm by the underlying JCE encryption provider.
147     *        For password based encryptions, the encryption algorithm
148     *        PBEWithMD5AndDES is commonly used. 
149     * @param secret the shared secret to be used for symmetric encryption.
150     * @return the encrypted data in Base64 encoded format. 
151     */
152    public static String encryptWithSymmetricKey(String data, 
153               String encAlgorithm, String secret) throws Exception {
154        try {
155            String algorithm = encAlgorithm;
156            if(!algorithm.startsWith("PBEWith")) {
157               algorithm = "PBEWithMD5And" + encAlgorithm;
158            }
159            SecretKeyFactory skFactory = 
160                     SecretKeyFactory.getInstance(algorithm);
161            PBEKeySpec pbeKeySpec = new PBEKeySpec(secret.toCharArray());
162            SecretKey sKey = skFactory.generateSecret(pbeKeySpec);
163            Cipher cipher = Cipher.getInstance(algorithm);
164            cipher.init(Cipher.ENCRYPT_MODE, sKey, pbeParameterSpec );
165            byte[] encData = cipher.doFinal(data.getBytes("UTF-8")); 
166            encData = addPrefix(encData); 
167            return Base64.encode(encData);
168        } catch (NoSuchAlgorithmException nse) {
169            throw new Exception(nse.getMessage());
170        } 
171    }
172
173    /**
174     * Decrypts the given data with a symmetric key generated using shared
175     * secret.
176     * @param data the data to be decrypted with symmetric key.
177     * @param encAlgorithm the encryption algorithm was used for
178     *        encrypting the data.
179     * @param secret the shared secret to be used for decrypting the data.
180     * @return the decrypted data. 
181     */
182    public static String decryptWithSymmetricKey(String data, 
183             String encAlgorithm, String secret) throws Exception {
184        try {
185            String algorithm = encAlgorithm;
186            if(!algorithm.startsWith("PBEWith")) {
187               algorithm = "PBEWithMD5And" + encAlgorithm;
188            }
189            SecretKeyFactory skFactory = 
190                     SecretKeyFactory.getInstance(algorithm);
191            PBEKeySpec pbeKeySpec = new PBEKeySpec(secret.toCharArray());
192            SecretKey sKey = skFactory.generateSecret(pbeKeySpec);
193            Cipher cipher = Cipher.getInstance(algorithm);
194            cipher.init(Cipher.DECRYPT_MODE, sKey, pbeParameterSpec);
195            byte[] tmp = Base64.decode(data);
196            byte[] encData = removePrefix(tmp);
197            byte[] decData = cipher.doFinal(encData);
198            return Base64.encode(decData);
199         } catch (NoSuchAlgorithmException nse) {
200            throw new Exception(nse.getMessage());
201         }
202    }
203
204    private static byte[] addPrefix(byte[] encData) {
205        int length = encData.length;
206        byte[] result = new byte[9 + length]; 
207        byte[] encrypted = new String("ENCRYPTED").getBytes();
208        for (int i=0; i < 9; i++) {
209             result[i] = encrypted[i];
210        }
211        for (int i=0; i <  length; i++) {
212            result[9 + i] = encData[i];
213        }
214        return result;
215    }
216
217    private static byte[] removePrefix(byte[] data) {
218        int length = data.length - 9;
219        byte[] result = new byte[length];
220        for (int i=0; i < length; i++) {
221            result[i] = data[9 + i];
222        }
223        return result;
224    }
225
226    private static byte[] wrapKeyWithEncryptedData(byte[] data, 
227                         byte[] key) {
228
229        int dataLength = data.length;
230        int keyLength = key.length;
231        byte[] result = new byte[17 + data.length + key.length];
232        byte[] encrypted = new String("ENCRYPTED").getBytes();
233        for (int i=0; i < 9; i++) {
234             result[i] = encrypted[i];
235        }
236        byte[] datasize = intToByteArray(dataLength);
237        for (int i =0; i < 4; i++) {
238            result[i+9] = datasize[i];
239        }
240
241        for (int i=0; i < dataLength; i++) {
242             result[i+13] = data[i];
243        }
244
245        byte[] keysize = intToByteArray(keyLength);
246        for (int i =0; i < 4; i++) {
247            result[i+13+dataLength] = keysize[i];
248        }
249
250        for (int i=0; i < keyLength; i++) {
251             result[i+17+dataLength] = key[i];
252        }
253
254        return result;
255    }
256
257    private static Map unwrapKeyWithEncodedData(byte[] decodeData) {
258        Map map = new HashMap();
259        
260        byte[] dataLength = new byte[4]; 
261        int j = 0;
262        for (int i = 9; i < 13; i++) {
263             dataLength[j] = decodeData[i];
264             j++;
265        }
266
267        int encDataLength = byteArrayToInt(dataLength); 
268        byte[] encData = new byte[encDataLength];
269        j = 0;
270        for (int i=13; i < encDataLength + 13; i++) {
271             encData[j] = decodeData[i];
272             j++;
273        }
274
275        map.put(ENCRYPTED_DATA, encData);
276        byte[] keyLen = new byte[4];
277        int startIndex = 13 + encDataLength;
278        int endIndex = startIndex + 4;
279        j = 0;
280        for (int i=startIndex; i < endIndex; i++) {
281             keyLen[j] = decodeData[i]; 
282             j++;
283        }
284
285        int keyDataLength = byteArrayToInt(keyLen);
286        startIndex = startIndex + 4; 
287        endIndex = startIndex + keyDataLength; 
288        byte[] keyData = new byte[keyDataLength];
289        j = 0;
290        for (int i=startIndex; i < endIndex; i++) {
291             keyData[j] = decodeData[i]; 
292             j++;
293        }
294        map.put(ENCRYPTED_KEY, keyData);
295        return map;
296    }
297
298
299    private static final byte[] intToByteArray(int value) {
300        return new byte[] {
301                (byte)(value >>> 24),
302                (byte)(value >>> 16),
303                (byte)(value >>> 8),
304                (byte)value
305        };
306    }
307
308    private static final int byteArrayToInt(byte [] b) {
309        return (b[0] << 24)
310                + ((b[1] & 0xFF) << 16)
311                + ((b[2] & 0xFF) << 8)
312                + (b[3] & 0xFF);
313    }
314
315
316
317}