/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* 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 net.java.sip.communicator.impl.credentialsstorage;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import net.java.sip.communicator.service.credentialsstorage.*;
import net.java.sip.communicator.util.*;
/**
* Performs encryption and decryption of text using AES algorithm.
*
* @author Dmitri Melnikov
*/
public class AESCrypto
implements Crypto
{
/**
* The algorithm associated with the key.
*/
private static final String KEY_ALGORITHM = "AES";
/**
* AES in ECB mode with padding.
*/
private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5PADDING";
/**
* Salt used when creating the key.
*/
private static byte[] SALT =
{ 0x0C, 0x0A, 0x0F, 0x0E, 0x0B, 0x0E, 0x0E, 0x0F };
/**
* Possible length of the keys in bits.
*/
private static int[] KEY_LENGTHS = new int[]{256, 128};
/**
* Number of iterations to use when creating the key.
*/
private static int ITERATION_COUNT = 1024;
/**
* Key derived from the master password to use for encryption/decryption.
*/
private Key key;
/**
* Decryption object.
*/
private Cipher decryptCipher;
/**
* Encryption object.
*/
private Cipher encryptCipher;
/**
* Creates the encryption and decryption objects and the key.
*
* @param masterPassword used to derive the key. Can be null.
*/
public AESCrypto(String masterPassword)
{
try
{
// we try init of key with suupplied lengths
// we stop after the first successful attempt
for (int i = 0; i < KEY_LENGTHS.length; i++)
{
decryptCipher = Cipher.getInstance(CIPHER_ALGORITHM);
encryptCipher = Cipher.getInstance(CIPHER_ALGORITHM);
try
{
initKey(masterPassword, KEY_LENGTHS[i]);
// its ok stop trying
break;
}
catch (InvalidKeyException e)
{
if(i == KEY_LENGTHS.length - 1)
throw e;
}
}
}
catch (InvalidKeyException e)
{
throw new RuntimeException("Invalid key", e);
}
catch (InvalidKeySpecException e)
{
throw new RuntimeException("Invalid key specification", e);
}
catch (NoSuchAlgorithmException e)
{
throw new RuntimeException("Algorithm not found", e);
}
catch (NoSuchPaddingException e)
{
throw new RuntimeException("Padding not found", e);
}
}
/**
* Initialize key with specified length.
*
* @param masterPassword used to derive the key. Can be null.
* @param keyLength Length of the key in bits.
* @throws InvalidKeyException if the key is invalid (bad encoding,
* wrong length, uninitialized, etc).
* @throws NoSuchAlgorithmException if the algorithm chosen does not exist
* @throws InvalidKeySpecException if the key specifications are invalid
*/
private void initKey(String masterPassword, int keyLength)
throws InvalidKeyException,
NoSuchAlgorithmException,
InvalidKeySpecException
{
// if the password is empty, we get an exception constructing the key
if (masterPassword == null)
{
// here a default password can be set,
// cannot be an empty string
masterPassword = " ";
}
// Password-Based Key Derivation Function found in PKCS5 v2.0.
// This is only available with java 6.
SecretKeyFactory factory =
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
// Make a key from the master password
KeySpec spec =
new PBEKeySpec(masterPassword.toCharArray(), SALT,
ITERATION_COUNT, keyLength);
SecretKey tmp = factory.generateSecret(spec);
// Make an algorithm specific key
key = new SecretKeySpec(tmp.getEncoded(), KEY_ALGORITHM);
// just a check whether the key size is wrong
encryptCipher.init(Cipher.ENCRYPT_MODE, key);
decryptCipher.init(Cipher.DECRYPT_MODE, key);
}
/**
* Decrypts the cyphertext using the key.
*
* @param ciphertext base64 encoded encrypted data
* @return decrypted data
* @throws CryptoException when the ciphertext cannot be decrypted with the
* key or on decryption error.
*/
public String decrypt(String ciphertext) throws CryptoException
{
try
{
decryptCipher.init(Cipher.DECRYPT_MODE, key);
return new String(decryptCipher.doFinal(Base64.decode(ciphertext)),
"UTF-8");
}
catch (BadPaddingException e)
{
throw new CryptoException(CryptoException.WRONG_KEY, e);
}
catch (Exception e)
{
throw new CryptoException(CryptoException.DECRYPTION_ERROR, e);
}
}
/**
* Encrypts the plaintext using the key.
*
* @param plaintext data to be encrypted
* @return base64 encoded encrypted data
* @throws CryptoException on encryption error
*/
public String encrypt(String plaintext) throws CryptoException
{
try
{
encryptCipher.init(Cipher.ENCRYPT_MODE, key);
return new String(Base64.encode(encryptCipher.doFinal(plaintext
.getBytes("UTF-8"))));
}
catch (Exception e)
{
throw new CryptoException(CryptoException.ENCRYPTION_ERROR, e);
}
}
}