/wallet/src/de/schildbach/wallet/util/Crypto.java
Java | 317 lines | 164 code | 45 blank | 108 comment | 6 complexity | 9b1bec87c464e97eea70e2fded68b59b MD5 | raw file
Possible License(s): Apache-2.0, GPL-3.0
- /*
- * Copyright 2012-2014 the original author or authors.
- *
- * Licensed under the MIT license (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://opensource.org/licenses/mit-license.php
- *
- * 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 de.schildbach.wallet.util;
- import java.io.File;
- import java.io.FileFilter;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.Reader;
- import java.security.SecureRandom;
- import javax.annotation.Nonnull;
- import org.spongycastle.crypto.BufferedBlockCipher;
- import org.spongycastle.crypto.CipherParameters;
- import org.spongycastle.crypto.DataLengthException;
- import org.spongycastle.crypto.InvalidCipherTextException;
- import org.spongycastle.crypto.PBEParametersGenerator;
- import org.spongycastle.crypto.engines.AESFastEngine;
- import org.spongycastle.crypto.generators.OpenSSLPBEParametersGenerator;
- import org.spongycastle.crypto.modes.CBCBlockCipher;
- import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
- import org.spongycastle.crypto.params.ParametersWithIV;
- import android.util.Base64;
- import de.schildbach.wallet.Constants;
- /**
- * This class encrypts and decrypts a string in a manner that is compatible with OpenSSL.
- *
- * If you encrypt a string with this class you can decrypt it with the OpenSSL command: openssl enc -d -aes-256-cbc -a
- * -in cipher.txt -out plain.txt -pass pass:aTestPassword
- *
- * where: cipher.txt = file containing the cipher text plain.txt - where you want the plaintext to be saved
- *
- * substitute your password for "aTestPassword" or remove the "-pass" parameter to be prompted.
- *
- * @author jim
- * @author Andreas Schildbach, Litecoin Dev Team
- */
- public class Crypto
- {
- /**
- * number of times the password & salt are hashed during key creation.
- */
- private static final int NUMBER_OF_ITERATIONS = 1024;
- /**
- * Key length.
- */
- private static final int KEY_LENGTH = 256;
- /**
- * Initialization vector length.
- */
- private static final int IV_LENGTH = 128;
- /**
- * The length of the salt.
- */
- private static final int SALT_LENGTH = 8;
- /**
- * OpenSSL salted prefix text.
- */
- private static final String OPENSSL_SALTED_TEXT = "Salted__";
- /**
- * OpenSSL salted prefix bytes - also used as magic number for encrypted key file.
- */
- private static final byte[] OPENSSL_SALTED_BYTES = OPENSSL_SALTED_TEXT.getBytes(Constants.UTF_8);
- /**
- * Magic text that appears at the beginning of every OpenSSL encrypted file. Used in identifying encrypted key
- * files.
- */
- private static final String OPENSSL_MAGIC_TEXT = new String(encodeBase64(Crypto.OPENSSL_SALTED_BYTES), Constants.UTF_8).substring(0,
- Crypto.NUMBER_OF_CHARACTERS_TO_MATCH_IN_OPENSSL_MAGIC_TEXT);
- private static final int NUMBER_OF_CHARACTERS_TO_MATCH_IN_OPENSSL_MAGIC_TEXT = 10;
- private static final SecureRandom secureRandom = new SecureRandom();
- /**
- * Get password and generate key and iv.
- *
- * @param password
- * The password to use in key generation
- * @param salt
- * The salt to use in key generation
- * @return The CipherParameters containing the created key
- */
- private static CipherParameters getAESPasswordKey(final char[] password, final byte[] salt)
- {
- final PBEParametersGenerator generator = new OpenSSLPBEParametersGenerator();
- generator.init(PBEParametersGenerator.PKCS5PasswordToBytes(password), salt, NUMBER_OF_ITERATIONS);
- final ParametersWithIV key = (ParametersWithIV) generator.generateDerivedParameters(KEY_LENGTH, IV_LENGTH);
- return key;
- }
- /**
- * Password based encryption using AES - CBC 256 bits.
- *
- * @param plainText
- * The text to encrypt
- * @param password
- * The password to use for encryption
- * @return The encrypted string
- * @throws IOException
- */
- public static String encrypt(@Nonnull final String plainText, @Nonnull final char[] password) throws IOException
- {
- final byte[] plainTextAsBytes = plainText.getBytes(Constants.UTF_8);
- final byte[] encryptedBytes = encrypt(plainTextAsBytes, password);
- // OpenSSL prefixes the salt bytes + encryptedBytes with Salted___ and then base64 encodes it
- final byte[] encryptedBytesPlusSaltedText = concat(OPENSSL_SALTED_BYTES, encryptedBytes);
- return new String(encodeBase64(encryptedBytesPlusSaltedText), Constants.UTF_8);
- }
- /**
- * Password based encryption using AES - CBC 256 bits.
- *
- * @param plainBytes
- * The bytes to encrypt
- * @param password
- * The password to use for encryption
- * @return SALT_LENGTH bytes of salt followed by the encrypted bytes.
- * @throws IOException
- */
- private static byte[] encrypt(final byte[] plainTextAsBytes, final char[] password) throws IOException
- {
- try
- {
- // Generate salt - each encryption call has a different salt.
- final byte[] salt = new byte[SALT_LENGTH];
- secureRandom.nextBytes(salt);
- final ParametersWithIV key = (ParametersWithIV) getAESPasswordKey(password, salt);
- // The following code uses an AES cipher to encrypt the message.
- final BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
- cipher.init(true, key);
- final byte[] encryptedBytes = new byte[cipher.getOutputSize(plainTextAsBytes.length)];
- final int length = cipher.processBytes(plainTextAsBytes, 0, plainTextAsBytes.length, encryptedBytes, 0);
- cipher.doFinal(encryptedBytes, length);
- // The result bytes are the SALT_LENGTH bytes followed by the encrypted bytes.
- return concat(salt, encryptedBytes);
- }
- catch (final InvalidCipherTextException x)
- {
- throw new IOException("Could not encrypt bytes", x);
- }
- catch (final DataLengthException x)
- {
- throw new IOException("Could not encrypt bytes", x);
- }
- }
- /**
- * Decrypt text previously encrypted with this class.
- *
- * @param textToDecode
- * The code to decrypt
- * @param password
- * password to use for decryption
- * @return The decrypted text
- * @throws IOException
- */
- public static String decrypt(@Nonnull final String textToDecode, @Nonnull final char[] password) throws IOException
- {
- final byte[] decodeTextAsBytes = decodeBase64(textToDecode.getBytes(Constants.UTF_8));
- if (decodeTextAsBytes.length < OPENSSL_SALTED_BYTES.length)
- throw new IOException("out of salt");
- final byte[] cipherBytes = new byte[decodeTextAsBytes.length - OPENSSL_SALTED_BYTES.length];
- System.arraycopy(decodeTextAsBytes, OPENSSL_SALTED_BYTES.length, cipherBytes, 0, decodeTextAsBytes.length - OPENSSL_SALTED_BYTES.length);
- final byte[] decryptedBytes = decrypt(cipherBytes, password);
- return new String(decryptedBytes, Constants.UTF_8).trim();
- }
- /**
- * Decrypt bytes previously encrypted with this class.
- *
- * @param bytesToDecode
- * The bytes to decrypt
- * @param passwordbThe
- * password to use for decryption
- * @return The decrypted bytes
- * @throws IOException
- */
- private static byte[] decrypt(final byte[] bytesToDecode, final char[] password) throws IOException
- {
- try
- {
- // separate the salt and bytes to decrypt
- final byte[] salt = new byte[SALT_LENGTH];
- System.arraycopy(bytesToDecode, 0, salt, 0, SALT_LENGTH);
- final byte[] cipherBytes = new byte[bytesToDecode.length - SALT_LENGTH];
- System.arraycopy(bytesToDecode, SALT_LENGTH, cipherBytes, 0, bytesToDecode.length - SALT_LENGTH);
- final ParametersWithIV key = (ParametersWithIV) getAESPasswordKey(password, salt);
- // decrypt the message
- final BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
- cipher.init(false, key);
- final byte[] decryptedBytes = new byte[cipher.getOutputSize(cipherBytes.length)];
- final int length = cipher.processBytes(cipherBytes, 0, cipherBytes.length, decryptedBytes, 0);
- cipher.doFinal(decryptedBytes, length);
- return decryptedBytes;
- }
- catch (final InvalidCipherTextException x)
- {
- throw new IOException("Could not decrypt input string", x);
- }
- catch (final DataLengthException x)
- {
- throw new IOException("Could not decrypt input string", x);
- }
- }
- private static byte[] encodeBase64(byte[] decoded)
- {
- return Base64.encode(decoded, Base64.DEFAULT);
- }
- private static byte[] decodeBase64(byte[] encoded) throws IOException
- {
- try
- {
- return Base64.decode(encoded, Base64.DEFAULT);
- }
- catch (final IllegalArgumentException x)
- {
- throw new IOException("illegal base64 padding", x);
- }
- }
- /**
- * Concatenate two byte arrays.
- */
- private static byte[] concat(final byte[] arrayA, final byte[] arrayB)
- {
- final byte[] result = new byte[arrayA.length + arrayB.length];
- System.arraycopy(arrayA, 0, result, 0, arrayA.length);
- System.arraycopy(arrayB, 0, result, arrayA.length, arrayB.length);
- return result;
- }
- public final static FileFilter OPENSSL_FILE_FILTER = new FileFilter()
- {
- private final char[] buf = new char[OPENSSL_MAGIC_TEXT.length()];
- @Override
- public boolean accept(final File file)
- {
- Reader in = null;
- try
- {
- in = new InputStreamReader(new FileInputStream(file), Constants.UTF_8);
- if (in.read(buf) == -1)
- return false;
- final String str = new String(buf);
- if (!str.toString().equals(OPENSSL_MAGIC_TEXT))
- return false;
- return true;
- }
- catch (final IOException x)
- {
- return false;
- }
- finally
- {
- if (in != null)
- {
- try
- {
- in.close();
- }
- catch (final IOException x2)
- {
- }
- }
- }
- }
- };
- }