PageRenderTime 44ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/wallet/src/de/schildbach/wallet/util/Crypto.java

https://gitlab.com/braydon/litecoin-wallet
Java | 317 lines | 164 code | 45 blank | 108 comment | 6 complexity | 9b1bec87c464e97eea70e2fded68b59b MD5 | raw file
Possible License(s): Apache-2.0, GPL-3.0
  1. /*
  2. * Copyright 2012-2014 the original author or authors.
  3. *
  4. * Licensed under the MIT license (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://opensource.org/licenses/mit-license.php
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package de.schildbach.wallet.util;
  17. import java.io.File;
  18. import java.io.FileFilter;
  19. import java.io.FileInputStream;
  20. import java.io.IOException;
  21. import java.io.InputStreamReader;
  22. import java.io.Reader;
  23. import java.security.SecureRandom;
  24. import javax.annotation.Nonnull;
  25. import org.spongycastle.crypto.BufferedBlockCipher;
  26. import org.spongycastle.crypto.CipherParameters;
  27. import org.spongycastle.crypto.DataLengthException;
  28. import org.spongycastle.crypto.InvalidCipherTextException;
  29. import org.spongycastle.crypto.PBEParametersGenerator;
  30. import org.spongycastle.crypto.engines.AESFastEngine;
  31. import org.spongycastle.crypto.generators.OpenSSLPBEParametersGenerator;
  32. import org.spongycastle.crypto.modes.CBCBlockCipher;
  33. import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
  34. import org.spongycastle.crypto.params.ParametersWithIV;
  35. import android.util.Base64;
  36. import de.schildbach.wallet.Constants;
  37. /**
  38. * This class encrypts and decrypts a string in a manner that is compatible with OpenSSL.
  39. *
  40. * If you encrypt a string with this class you can decrypt it with the OpenSSL command: openssl enc -d -aes-256-cbc -a
  41. * -in cipher.txt -out plain.txt -pass pass:aTestPassword
  42. *
  43. * where: cipher.txt = file containing the cipher text plain.txt - where you want the plaintext to be saved
  44. *
  45. * substitute your password for "aTestPassword" or remove the "-pass" parameter to be prompted.
  46. *
  47. * @author jim
  48. * @author Andreas Schildbach, Litecoin Dev Team
  49. */
  50. public class Crypto
  51. {
  52. /**
  53. * number of times the password & salt are hashed during key creation.
  54. */
  55. private static final int NUMBER_OF_ITERATIONS = 1024;
  56. /**
  57. * Key length.
  58. */
  59. private static final int KEY_LENGTH = 256;
  60. /**
  61. * Initialization vector length.
  62. */
  63. private static final int IV_LENGTH = 128;
  64. /**
  65. * The length of the salt.
  66. */
  67. private static final int SALT_LENGTH = 8;
  68. /**
  69. * OpenSSL salted prefix text.
  70. */
  71. private static final String OPENSSL_SALTED_TEXT = "Salted__";
  72. /**
  73. * OpenSSL salted prefix bytes - also used as magic number for encrypted key file.
  74. */
  75. private static final byte[] OPENSSL_SALTED_BYTES = OPENSSL_SALTED_TEXT.getBytes(Constants.UTF_8);
  76. /**
  77. * Magic text that appears at the beginning of every OpenSSL encrypted file. Used in identifying encrypted key
  78. * files.
  79. */
  80. private static final String OPENSSL_MAGIC_TEXT = new String(encodeBase64(Crypto.OPENSSL_SALTED_BYTES), Constants.UTF_8).substring(0,
  81. Crypto.NUMBER_OF_CHARACTERS_TO_MATCH_IN_OPENSSL_MAGIC_TEXT);
  82. private static final int NUMBER_OF_CHARACTERS_TO_MATCH_IN_OPENSSL_MAGIC_TEXT = 10;
  83. private static final SecureRandom secureRandom = new SecureRandom();
  84. /**
  85. * Get password and generate key and iv.
  86. *
  87. * @param password
  88. * The password to use in key generation
  89. * @param salt
  90. * The salt to use in key generation
  91. * @return The CipherParameters containing the created key
  92. */
  93. private static CipherParameters getAESPasswordKey(final char[] password, final byte[] salt)
  94. {
  95. final PBEParametersGenerator generator = new OpenSSLPBEParametersGenerator();
  96. generator.init(PBEParametersGenerator.PKCS5PasswordToBytes(password), salt, NUMBER_OF_ITERATIONS);
  97. final ParametersWithIV key = (ParametersWithIV) generator.generateDerivedParameters(KEY_LENGTH, IV_LENGTH);
  98. return key;
  99. }
  100. /**
  101. * Password based encryption using AES - CBC 256 bits.
  102. *
  103. * @param plainText
  104. * The text to encrypt
  105. * @param password
  106. * The password to use for encryption
  107. * @return The encrypted string
  108. * @throws IOException
  109. */
  110. public static String encrypt(@Nonnull final String plainText, @Nonnull final char[] password) throws IOException
  111. {
  112. final byte[] plainTextAsBytes = plainText.getBytes(Constants.UTF_8);
  113. final byte[] encryptedBytes = encrypt(plainTextAsBytes, password);
  114. // OpenSSL prefixes the salt bytes + encryptedBytes with Salted___ and then base64 encodes it
  115. final byte[] encryptedBytesPlusSaltedText = concat(OPENSSL_SALTED_BYTES, encryptedBytes);
  116. return new String(encodeBase64(encryptedBytesPlusSaltedText), Constants.UTF_8);
  117. }
  118. /**
  119. * Password based encryption using AES - CBC 256 bits.
  120. *
  121. * @param plainBytes
  122. * The bytes to encrypt
  123. * @param password
  124. * The password to use for encryption
  125. * @return SALT_LENGTH bytes of salt followed by the encrypted bytes.
  126. * @throws IOException
  127. */
  128. private static byte[] encrypt(final byte[] plainTextAsBytes, final char[] password) throws IOException
  129. {
  130. try
  131. {
  132. // Generate salt - each encryption call has a different salt.
  133. final byte[] salt = new byte[SALT_LENGTH];
  134. secureRandom.nextBytes(salt);
  135. final ParametersWithIV key = (ParametersWithIV) getAESPasswordKey(password, salt);
  136. // The following code uses an AES cipher to encrypt the message.
  137. final BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
  138. cipher.init(true, key);
  139. final byte[] encryptedBytes = new byte[cipher.getOutputSize(plainTextAsBytes.length)];
  140. final int length = cipher.processBytes(plainTextAsBytes, 0, plainTextAsBytes.length, encryptedBytes, 0);
  141. cipher.doFinal(encryptedBytes, length);
  142. // The result bytes are the SALT_LENGTH bytes followed by the encrypted bytes.
  143. return concat(salt, encryptedBytes);
  144. }
  145. catch (final InvalidCipherTextException x)
  146. {
  147. throw new IOException("Could not encrypt bytes", x);
  148. }
  149. catch (final DataLengthException x)
  150. {
  151. throw new IOException("Could not encrypt bytes", x);
  152. }
  153. }
  154. /**
  155. * Decrypt text previously encrypted with this class.
  156. *
  157. * @param textToDecode
  158. * The code to decrypt
  159. * @param password
  160. * password to use for decryption
  161. * @return The decrypted text
  162. * @throws IOException
  163. */
  164. public static String decrypt(@Nonnull final String textToDecode, @Nonnull final char[] password) throws IOException
  165. {
  166. final byte[] decodeTextAsBytes = decodeBase64(textToDecode.getBytes(Constants.UTF_8));
  167. if (decodeTextAsBytes.length < OPENSSL_SALTED_BYTES.length)
  168. throw new IOException("out of salt");
  169. final byte[] cipherBytes = new byte[decodeTextAsBytes.length - OPENSSL_SALTED_BYTES.length];
  170. System.arraycopy(decodeTextAsBytes, OPENSSL_SALTED_BYTES.length, cipherBytes, 0, decodeTextAsBytes.length - OPENSSL_SALTED_BYTES.length);
  171. final byte[] decryptedBytes = decrypt(cipherBytes, password);
  172. return new String(decryptedBytes, Constants.UTF_8).trim();
  173. }
  174. /**
  175. * Decrypt bytes previously encrypted with this class.
  176. *
  177. * @param bytesToDecode
  178. * The bytes to decrypt
  179. * @param passwordbThe
  180. * password to use for decryption
  181. * @return The decrypted bytes
  182. * @throws IOException
  183. */
  184. private static byte[] decrypt(final byte[] bytesToDecode, final char[] password) throws IOException
  185. {
  186. try
  187. {
  188. // separate the salt and bytes to decrypt
  189. final byte[] salt = new byte[SALT_LENGTH];
  190. System.arraycopy(bytesToDecode, 0, salt, 0, SALT_LENGTH);
  191. final byte[] cipherBytes = new byte[bytesToDecode.length - SALT_LENGTH];
  192. System.arraycopy(bytesToDecode, SALT_LENGTH, cipherBytes, 0, bytesToDecode.length - SALT_LENGTH);
  193. final ParametersWithIV key = (ParametersWithIV) getAESPasswordKey(password, salt);
  194. // decrypt the message
  195. final BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
  196. cipher.init(false, key);
  197. final byte[] decryptedBytes = new byte[cipher.getOutputSize(cipherBytes.length)];
  198. final int length = cipher.processBytes(cipherBytes, 0, cipherBytes.length, decryptedBytes, 0);
  199. cipher.doFinal(decryptedBytes, length);
  200. return decryptedBytes;
  201. }
  202. catch (final InvalidCipherTextException x)
  203. {
  204. throw new IOException("Could not decrypt input string", x);
  205. }
  206. catch (final DataLengthException x)
  207. {
  208. throw new IOException("Could not decrypt input string", x);
  209. }
  210. }
  211. private static byte[] encodeBase64(byte[] decoded)
  212. {
  213. return Base64.encode(decoded, Base64.DEFAULT);
  214. }
  215. private static byte[] decodeBase64(byte[] encoded) throws IOException
  216. {
  217. try
  218. {
  219. return Base64.decode(encoded, Base64.DEFAULT);
  220. }
  221. catch (final IllegalArgumentException x)
  222. {
  223. throw new IOException("illegal base64 padding", x);
  224. }
  225. }
  226. /**
  227. * Concatenate two byte arrays.
  228. */
  229. private static byte[] concat(final byte[] arrayA, final byte[] arrayB)
  230. {
  231. final byte[] result = new byte[arrayA.length + arrayB.length];
  232. System.arraycopy(arrayA, 0, result, 0, arrayA.length);
  233. System.arraycopy(arrayB, 0, result, arrayA.length, arrayB.length);
  234. return result;
  235. }
  236. public final static FileFilter OPENSSL_FILE_FILTER = new FileFilter()
  237. {
  238. private final char[] buf = new char[OPENSSL_MAGIC_TEXT.length()];
  239. @Override
  240. public boolean accept(final File file)
  241. {
  242. Reader in = null;
  243. try
  244. {
  245. in = new InputStreamReader(new FileInputStream(file), Constants.UTF_8);
  246. if (in.read(buf) == -1)
  247. return false;
  248. final String str = new String(buf);
  249. if (!str.toString().equals(OPENSSL_MAGIC_TEXT))
  250. return false;
  251. return true;
  252. }
  253. catch (final IOException x)
  254. {
  255. return false;
  256. }
  257. finally
  258. {
  259. if (in != null)
  260. {
  261. try
  262. {
  263. in.close();
  264. }
  265. catch (final IOException x2)
  266. {
  267. }
  268. }
  269. }
  270. }
  271. };
  272. }