File Source: CipherUtils.java
/*
P/P * Method: com.dmdirc.util.CipherUtils__static_init
*/
1 /*
2 * Copyright (c) 2006-2009 Chris Smith, Shane Mc Cormack, Gregory Holmes
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23 package com.dmdirc.util;
24
25 import com.dmdirc.Main;
26 import com.dmdirc.config.IdentityManager;
27 import com.dmdirc.logger.ErrorLevel;
28 import com.dmdirc.logger.Logger;
29
30 import java.io.IOException;
31 import java.io.UnsupportedEncodingException;
32 import java.nio.charset.Charset;
33 import java.security.InvalidAlgorithmParameterException;
34 import java.security.InvalidKeyException;
35 import java.security.MessageDigest;
36 import java.security.NoSuchAlgorithmException;
37 import java.security.spec.AlgorithmParameterSpec;
38 import java.security.spec.InvalidKeySpecException;
39 import java.security.spec.KeySpec;
40
41 import javax.crypto.BadPaddingException;
42 import javax.crypto.Cipher;
43 import javax.crypto.IllegalBlockSizeException;
44 import javax.crypto.NoSuchPaddingException;
45 import javax.crypto.SecretKey;
46 import javax.crypto.SecretKeyFactory;
47 import javax.crypto.spec.PBEKeySpec;
48 import javax.crypto.spec.PBEParameterSpec;
49 import net.miginfocom.Base64;
50
51 /**
52 * Helper class to encrypt and decrypt strings, requests passwords if needed.
53 */
54 public class CipherUtils {
55
56 /** Singleton instance. */
57 private static CipherUtils me;
58
59 /** Encryption cipher. */
60 private Cipher ecipher;
61
62 /** Decryption cipher. */
63 private Cipher dcipher;
64
65 /** Salt. */
66 private final byte[] SALT = {
67 (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32,
68 (byte) 0x56, (byte) 0x35, (byte) 0xE3, (byte) 0x03,
69 };
70
71 /** Iteration count. */
72 private static final int ITERATIONS = 19;
73
74 /** Number of auth attemps before failing the attempt. */
75 private static final int AUTH_TRIES = 4;
76
77 /** User password. */
78 private String password;
79
80 /**
81 * Prevents creation of a new instance of Encipher.
82 */
/*
P/P * Method: void com.dmdirc.util.CipherUtils()
*
* Postconditions:
* this.SALT == &new byte[](CipherUtils#1)
* new byte[](CipherUtils#1) num objects == 1
* this.SALT.length == 8
* this.SALT[0] == -87
* this.SALT[1] == -101
* this.SALT[2] == -56
* this.SALT[3] == 50
* this.SALT[4] == 86
* this.SALT[5] == 53
* this.SALT[6] == -29
* ...
*/
83 protected CipherUtils() {
84 // Do nothing
85 }
86
87 /**
88 * Retrieves a singleton instance of CipherUtils.
89 *
90 * @return A singleton cipher utils instance.
91 */
92 public static CipherUtils getCipherUtils() {
/*
P/P * Method: CipherUtils getCipherUtils()
*
* Preconditions:
* init'ed(me)
*
* Postconditions:
* me == One-of{old me, &new CipherUtils(getCipherUtils#1)}
* me != null
* return_value == One-of{old me, &new CipherUtils(getCipherUtils#1)}
* return_value != null
* new CipherUtils(getCipherUtils#1) num objects <= 1
* new CipherUtils(getCipherUtils#1).SALT == &new byte[](CipherUtils#1)
* new byte[](CipherUtils#1) num objects <= 1
* new byte[](CipherUtils#1).length == 8
* new byte[](CipherUtils#1)[0] == -87
* new byte[](CipherUtils#1)[1] == -101
* ...
*
* Test Vectors:
* me: Inverse{null}, Addr_Set{null}
*/
93 synchronized(CipherUtils.class) {
94 if (me == null) {
95 me = new CipherUtils();
96 }
97
98 return me;
99 }
100 }
101
102 /**
103 * Encrypts a string using the stored settings. Will return null if the
104 * automatic user authentication fails - use checkauth and auth.
105 * @param str String to encrypt
106 * @return Encrypted string
107 */
108 public String encrypt(final String str) {
/*
P/P * Method: String encrypt(String)
*
* Preconditions:
* init'ed(this.dcipher)
* (soft) init'ed(this.ecipher)
* (soft) init'ed(this.password)
* (soft) str != null
*
* Presumptions:
* init'ed(com.dmdirc.logger.ErrorLevel.LOW)
* javax.crypto.Cipher:doFinal(...).length@117 in {0, 3..169_538_366}
*
* Postconditions:
* return_value in Addr_Set{null,&new String(encodeToString#1)}
* init'ed(this.dcipher)
* init'ed(this.ecipher)
* init'ed(this.password)
* new String(encodeToString#1) num objects <= 1
*
* Test Vectors:
* !(this.dcipher == null) & !(this.ecipher == null): {1}, {0}
*/
109 if (!checkAuthed()) {
110 if (auth()) {
111 createCiphers();
112 } else {
113 return null;
114 }
115 }
116 try {
117 return Base64.encodeToString(ecipher.doFinal(str.getBytes("UTF8")), false);
118 } catch (BadPaddingException e) {
119 Logger.userError(ErrorLevel.LOW, "Unable to decrypt string: " + e.getMessage());
120 } catch (IllegalBlockSizeException e) {
121 Logger.userError(ErrorLevel.LOW, "Unable to decrypt string: " + e.getMessage());
122 } catch (UnsupportedEncodingException e) {
123 Logger.userError(ErrorLevel.LOW, "Unable to decrypt string: " + e.getMessage());
124 }
125
126 return null;
127 }
128
129 /**
130 * Encrypts a string using the stored settings. Will return null if the
131 * automatic user authentication fails - use checkauth and auth.
132 * @param str String to decrypt
133 * @return Decrypted string
134 */
135 public String decrypt(final String str) {
/*
P/P * Method: String decrypt(String)
*
* Preconditions:
* init'ed(this.dcipher)
* (soft) init'ed(net.miginfocom.Base64__static_init.new int[](Base64__static_init#1)[...])
* (soft) init'ed(this.ecipher)
* (soft) init'ed(this.password)
*
* Presumptions:
* init'ed(com.dmdirc.logger.ErrorLevel.LOW)
*
* Postconditions:
* return_value in Addr_Set{null,&new String(decrypt#1)}
* init'ed(this.dcipher)
* init'ed(this.ecipher)
* init'ed(this.password)
* new String(decrypt#1) num objects <= 1
*
* Test Vectors:
* !(this.dcipher == null) & !(this.ecipher == null): {1}, {0}
*/
136 if (!checkAuthed()) {
137 if (auth()) {
138 createCiphers();
139 } else {
140 return null;
141 }
142 }
143 try {
144 return new String(dcipher.doFinal(Base64.decode(str)));
145 } catch (BadPaddingException e) {
146 Logger.userError(ErrorLevel.LOW, "Unable to decrypt string: " + e.getMessage());
147 } catch (IllegalBlockSizeException e) {
148 Logger.userError(ErrorLevel.LOW, "Unable to decrypt string: " + e.getMessage());
149 }
150 return null;
151 }
152
153 /**
154 * Performs a SHA-512 hash.
155 * @param data String to hashed
156 * @return hashed string
157 */
158 public String hash(final String data) {
159 try {
/*
P/P * Method: String hash(String)
*
* Preconditions:
* (soft) data != null
*
* Presumptions:
* init'ed(com.dmdirc.logger.ErrorLevel.LOW)
* java.security.MessageDigest:getInstance(...)@160 != null
*
* Postconditions:
* return_value in Addr_Set{null,&new String(hash#1)}
* new String(hash#1) num objects <= 1
*/
160 return new String(MessageDigest.getInstance("SHA-512")
161 .digest(data.getBytes("UTF8")), Charset.forName("UTF-8"));
162 } catch (NoSuchAlgorithmException e) {
163 Logger.userError(ErrorLevel.LOW, "Unable to hash string");
164 } catch (IOException e) {
165 Logger.userError(ErrorLevel.LOW, "Unable to hash string");
166 }
167 return null;
168 }
169
170 /**
171 * Checks if a user is authed.
172 *
173 * @return true if authed, false otherwise
174 */
175 public boolean checkAuthed() {
/*
P/P * Method: bool checkAuthed()
*
* Preconditions:
* init'ed(this.dcipher)
* (soft) init'ed(this.ecipher)
*
* Postconditions:
* init'ed(return_value)
*
* Test Vectors:
* this.dcipher: Addr_Set{null}, Inverse{null}
* this.ecipher: Addr_Set{null}, Inverse{null}
*/
176 if (dcipher != null && ecipher != null) {
177 return true;
178 }
179 return false;
180 }
181
182 /**
183 * creates ciphers.
184 */
185 protected void createCiphers() {
186 try {
/*
P/P * Method: void createCiphers()
*
* Preconditions:
* (soft) this.password != null
*
* Presumptions:
* init'ed(com.dmdirc.logger.ErrorLevel.LOW)
* javax.crypto.Cipher:getInstance(...)@191 != null
* javax.crypto.Cipher:getInstance(...)@192 != null
* javax.crypto.SecretKeyFactory:generateSecret(...)@189 != null
* javax.crypto.SecretKeyFactory:getInstance(...)@189 != null
*
* Postconditions:
* init'ed(this.dcipher)
* init'ed(this.ecipher)
*/
187 final KeySpec keySpec = new PBEKeySpec(
188 password.toCharArray(), SALT, ITERATIONS);
189 final SecretKey key = SecretKeyFactory.
190 getInstance("PBEWithMD5AndDES").generateSecret(keySpec);
191 ecipher = Cipher.getInstance(key.getAlgorithm());
192 dcipher = Cipher.getInstance(key.getAlgorithm());
193 final AlgorithmParameterSpec paramSpec =
194 new PBEParameterSpec(SALT, ITERATIONS);
195 ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
196 dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
197 } catch (InvalidAlgorithmParameterException e) {
198 Logger.userError(ErrorLevel.LOW, "Unable to create ciphers");
199 ecipher = null;
200 dcipher = null;
201 } catch (InvalidKeySpecException e) {
202 Logger.userError(ErrorLevel.LOW, "Unable to create ciphers");
203 ecipher = null;
204 dcipher = null;
205 } catch (NoSuchPaddingException e) {
206 Logger.userError(ErrorLevel.LOW, "Unable to create ciphers");
207 ecipher = null;
208 dcipher = null;
209 } catch (NoSuchAlgorithmException e) {
210 Logger.userError(ErrorLevel.LOW, "Unable to create ciphers");
211 ecipher = null;
212 dcipher = null;
213 } catch (InvalidKeyException e) {
214 Logger.userError(ErrorLevel.LOW, "Unable to create ciphers");
215 ecipher = null;
216 dcipher = null;
217 }
218 }
219
220 /**
221 * Auths a user and sets the password.
222 *
223 * @return true if auth was successful, false otherwise.
224 */
225 public boolean auth() {
/*
P/P * Method: bool auth()
*
* Preconditions:
* (soft) init'ed(this.password)
*
* Presumptions:
* com.dmdirc.config.IdentityManager:getConfigIdentity(...)@242 != null
* com.dmdirc.config.IdentityManager:getGlobalConfig(...)@229 != null
* com.dmdirc.config.IdentityManager:getGlobalConfig(...)@230 != null
* com.dmdirc.config.IdentityManager:getGlobalConfig(...)@232 != null
* com.dmdirc.config.IdentityManager:getGlobalConfig(...)@234 != null
* ...
*
* Postconditions:
* init'ed(return_value)
* init'ed(this.password)
*
* Test Vectors:
* com.dmdirc.config.ConfigManager:hasOptionString(...)@229: {0}, {1}
* com.dmdirc.config.ConfigManager:hasOptionString(...)@232: {0}, {1}
* java.lang.String:equals(...)@245: {1}, {0}
* java.lang.String:isEmpty(...)@238: {0}, {1}
*/
226 String passwordHash = null;
227 String prompt = "Please enter your password";
228 int tries = 1;
229 if (IdentityManager.getGlobalConfig().hasOptionString("encryption", "password")) {
230 password = IdentityManager.getGlobalConfig().getOption("encryption", "password");
231 } else {
232 if (IdentityManager.getGlobalConfig().hasOptionString("encryption",
233 "passwordHash")) {
234 passwordHash = IdentityManager.getGlobalConfig().getOption("encryption",
235 "passwordHash");
236 }
237
238 while ((password == null || password.isEmpty()) && tries < AUTH_TRIES) {
239 password = getPassword(prompt);
240 if (passwordHash == null) {
241 passwordHash = hash(password);
242 IdentityManager.getConfigIdentity().setOption("encryption",
243 "passwordHash", passwordHash);
244 }
245 if (!hash(password).equals(passwordHash)) {
246 prompt = "<html>Password mis-match<br>Please re-enter "
247 + "your password</html>";
248 tries++;
249 password = null;
250 }
251 }
252 }
253 if (tries == AUTH_TRIES) {
254 return false;
255 }
256 return true;
257 }
258
259 /**
260 * Requests the encryption password from the user.
261 *
262 * @param prompt The prompt to show
263 * @return The user-specified password
264 */
265 protected String getPassword(final String prompt) {
/*
P/P * Method: String getPassword(String)
*
* Presumptions:
* com.dmdirc.Main:getUI(...)@266 != null
*
* Postconditions:
* init'ed(return_value)
*/
266 return Main.getUI().getUserInput(prompt);
267 }
268 }
SofCheck Inspector Build Version : 2.17854
| CipherUtils.java |
2009-Jun-25 01:54:24 |
| CipherUtils.class |
2009-Sep-02 17:04:17 |