aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/impl/credentialsstorage/AESCrypto.java
blob: 63e6ef3cdc4452c1003c53ff89508455fa31a992 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
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);
        }
    }
}