You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CipherUtils.java 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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. package com.dmdirc.util;
  23. import com.dmdirc.Main;
  24. import com.dmdirc.config.IdentityManager;
  25. import com.dmdirc.logger.ErrorLevel;
  26. import com.dmdirc.logger.Logger;
  27. import java.io.IOException;
  28. import java.io.UnsupportedEncodingException;
  29. import java.nio.charset.Charset;
  30. import java.security.InvalidAlgorithmParameterException;
  31. import java.security.InvalidKeyException;
  32. import java.security.MessageDigest;
  33. import java.security.NoSuchAlgorithmException;
  34. import java.security.spec.AlgorithmParameterSpec;
  35. import java.security.spec.InvalidKeySpecException;
  36. import java.security.spec.KeySpec;
  37. import javax.crypto.BadPaddingException;
  38. import javax.crypto.Cipher;
  39. import javax.crypto.IllegalBlockSizeException;
  40. import javax.crypto.NoSuchPaddingException;
  41. import javax.crypto.SecretKey;
  42. import javax.crypto.SecretKeyFactory;
  43. import javax.crypto.spec.PBEKeySpec;
  44. import javax.crypto.spec.PBEParameterSpec;
  45. import net.miginfocom.Base64;
  46. /**
  47. * Helper class to encrypt and decrypt strings, requests passwords if needed.
  48. */
  49. public class CipherUtils {
  50. /** Singleton instance. */
  51. private static CipherUtils me;
  52. /** Encryption cipher. */
  53. private Cipher ecipher;
  54. /** Decryption cipher. */
  55. private Cipher dcipher;
  56. /** Salt. */
  57. private final byte[] SALT = {
  58. (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32,
  59. (byte) 0x56, (byte) 0x35, (byte) 0xE3, (byte) 0x03,
  60. };
  61. /** Iteration count. */
  62. private static final int ITERATIONS = 19;
  63. /** Number of auth attemps before failing the attempt. */
  64. private static final int AUTH_TRIES = 4;
  65. /** User password. */
  66. private String password;
  67. /**
  68. * Prevents creation of a new instance of Encipher.
  69. */
  70. protected CipherUtils() {
  71. // Do nothing
  72. }
  73. /**
  74. * Retrieves a singleton instance of CipherUtils.
  75. *
  76. * @return A singleton cipher utils instance.
  77. */
  78. public static CipherUtils getCipherUtils() {
  79. synchronized(CipherUtils.class) {
  80. if (me == null) {
  81. me = new CipherUtils();
  82. }
  83. return me;
  84. }
  85. }
  86. /**
  87. * Encrypts a string using the stored settings. Will return null if the
  88. * automatic user authentication fails - use checkauth and auth.
  89. * @param str String to encrypt
  90. * @return Encrypted string
  91. */
  92. public String encrypt(final String str) {
  93. if (!checkAuthed()) {
  94. if (auth()) {
  95. createCiphers();
  96. } else {
  97. return null;
  98. }
  99. }
  100. try {
  101. return Base64.encodeToString(ecipher.doFinal(str.getBytes("UTF8")), false);
  102. } catch (BadPaddingException e) {
  103. Logger.userError(ErrorLevel.LOW, "Unable to decrypt string: " + e.getMessage());
  104. } catch (IllegalBlockSizeException e) {
  105. Logger.userError(ErrorLevel.LOW, "Unable to decrypt string: " + e.getMessage());
  106. } catch (UnsupportedEncodingException e) {
  107. Logger.userError(ErrorLevel.LOW, "Unable to decrypt string: " + e.getMessage());
  108. }
  109. return null;
  110. }
  111. /**
  112. * Encrypts a string using the stored settings. Will return null if the
  113. * automatic user authentication fails - use checkauth and auth.
  114. * @param str String to decrypt
  115. * @return Decrypted string
  116. */
  117. public String decrypt(final String str) {
  118. if (!checkAuthed()) {
  119. if (auth()) {
  120. createCiphers();
  121. } else {
  122. return null;
  123. }
  124. }
  125. try {
  126. return new String(dcipher.doFinal(Base64.decode(str)));
  127. } catch (BadPaddingException e) {
  128. Logger.userError(ErrorLevel.LOW, "Unable to decrypt string: " + e.getMessage());
  129. } catch (IllegalBlockSizeException e) {
  130. Logger.userError(ErrorLevel.LOW, "Unable to decrypt string: " + e.getMessage());
  131. }
  132. return null;
  133. }
  134. /**
  135. * Performs a SHA-512 hash.
  136. * @param data String to hashed
  137. * @return hashed string
  138. */
  139. public String hash(final String data) {
  140. try {
  141. return new String(MessageDigest.getInstance("SHA-512")
  142. .digest(data.getBytes("UTF8")), Charset.forName("UTF-8"));
  143. } catch (NoSuchAlgorithmException e) {
  144. Logger.userError(ErrorLevel.LOW, "Unable to hash string");
  145. } catch (IOException e) {
  146. Logger.userError(ErrorLevel.LOW, "Unable to hash string");
  147. }
  148. return null;
  149. }
  150. /**
  151. * Checks if a user is authed.
  152. *
  153. * @return true if authed, false otherwise
  154. */
  155. public boolean checkAuthed() {
  156. if (dcipher != null && ecipher != null) {
  157. return true;
  158. }
  159. return false;
  160. }
  161. /**
  162. * creates ciphers.
  163. */
  164. protected void createCiphers() {
  165. try {
  166. final KeySpec keySpec = new PBEKeySpec(
  167. password.toCharArray(), SALT, ITERATIONS);
  168. final SecretKey key = SecretKeyFactory.
  169. getInstance("PBEWithMD5AndDES").generateSecret(keySpec);
  170. ecipher = Cipher.getInstance(key.getAlgorithm());
  171. dcipher = Cipher.getInstance(key.getAlgorithm());
  172. final AlgorithmParameterSpec paramSpec =
  173. new PBEParameterSpec(SALT, ITERATIONS);
  174. ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
  175. dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
  176. } catch (InvalidAlgorithmParameterException e) {
  177. Logger.userError(ErrorLevel.LOW, "Unable to create ciphers");
  178. ecipher = null;
  179. dcipher = null;
  180. } catch (InvalidKeySpecException e) {
  181. Logger.userError(ErrorLevel.LOW, "Unable to create ciphers");
  182. ecipher = null;
  183. dcipher = null;
  184. } catch (NoSuchPaddingException e) {
  185. Logger.userError(ErrorLevel.LOW, "Unable to create ciphers");
  186. ecipher = null;
  187. dcipher = null;
  188. } catch (NoSuchAlgorithmException e) {
  189. Logger.userError(ErrorLevel.LOW, "Unable to create ciphers");
  190. ecipher = null;
  191. dcipher = null;
  192. } catch (InvalidKeyException e) {
  193. Logger.userError(ErrorLevel.LOW, "Unable to create ciphers");
  194. ecipher = null;
  195. dcipher = null;
  196. }
  197. }
  198. /**
  199. * Auths a user and sets the password.
  200. *
  201. * @return true if auth was successful, false otherwise.
  202. */
  203. public boolean auth() {
  204. String passwordHash = null;
  205. String prompt = "Please enter your password";
  206. int tries = 1;
  207. if (IdentityManager.getGlobalConfig().hasOption("encryption", "password")) {
  208. password = IdentityManager.getGlobalConfig().getOption("encryption", "password");
  209. } else {
  210. if (IdentityManager.getGlobalConfig().hasOption("encryption", "passwordHash")) {
  211. passwordHash = IdentityManager.getGlobalConfig().getOption("encryption",
  212. "passwordHash");
  213. }
  214. while ((password == null || password.length() == 0) && tries < AUTH_TRIES) {
  215. password = getPassword(prompt);
  216. if (passwordHash == null) {
  217. passwordHash = hash(password);
  218. IdentityManager.getConfigIdentity().setOption("encryption",
  219. "passwordHash", passwordHash);
  220. }
  221. if (!hash(password).equals(passwordHash)) {
  222. prompt = "<html>Password mis-match<br>Please re-enter "
  223. + "your password</html>";
  224. tries++;
  225. password = null;
  226. }
  227. }
  228. }
  229. if (tries == AUTH_TRIES) {
  230. return false;
  231. }
  232. return true;
  233. }
  234. /**
  235. * Requests the encryption password from the user.
  236. *
  237. * @param prompt The prompt to show
  238. * @return The user-specified password
  239. */
  240. protected String getPassword(final String prompt) {
  241. return Main.getUI().getUserInput(prompt);
  242. }
  243. }