package com.greenaddress.greenapi; import com.blockstream.libwally.Wally; import com.greenaddress.greenbits.ui.MnemonicHelper; import org.bitcoin.NativeSecp256k1; import org.bitcoin.NativeSecp256k1Util; import org.bitcoin.Secp256k1Context; import org.codehaus.jackson.map.MappingJsonFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.SecureRandom; import java.text.Normalizer; import java.util.Arrays; import java.util.Map; public class CryptoHelper { private final static int BL = Wally.AES_BLOCK_LEN; private final static Object WL = Wally.bip39_get_wordlist("en"); public static byte[] randomBytes(final int len) { final byte[] b = new byte[len]; new SecureRandom().nextBytes(b); return b; } public static boolean initialize() { try { return Secp256k1Context.isEnabled() && NativeSecp256k1.randomize(randomBytes(32)); } catch (final NativeSecp256k1Util.AssertFailException e) { e.printStackTrace(); return false; } } public static byte[] mnemonic_to_seed(final String mnemonic) { final byte[] seed = new byte[Wally.BIP39_SEED_LEN_512]; final int written = Wally.bip39_mnemonic_to_seed(mnemonic, /*password*/null, seed); if (written != Wally.BIP39_SEED_LEN_512) throw new IllegalArgumentException(); return seed; } public static byte[] mnemonic_to_bytes(final String mnemonic) { return mnemonic_to_bytes(mnemonic, Wally.BIP39_ENTROPY_LEN_256); } private static byte[] mnemonic_to_bytes(final String mnemonic, final int size) { final byte[] buf = new byte[size]; final int len = Wally.bip39_mnemonic_to_bytes(WL, mnemonic, buf); if (len > buf.length) throw new IllegalArgumentException(); return len == size ? buf: Arrays.copyOf(buf, len); } public static String encrypted_mnemonic_to_mnemonic(final String mnemonics, final String pass) { return mnemonic_from_bytes(MnemonicHelper.decryptMnemonic( mnemonic_to_bytes(mnemonics, Wally.BIP39_ENTROPY_LEN_288), Normalizer.normalize(pass, Normalizer.Form.NFC))); } public static String encrypted_mnemonic_to_mnemonic(final byte[] data, final String pass) { return CryptoHelper.mnemonic_from_bytes(MnemonicHelper.decryptMnemonic(data, pass)); } public static String mnemonic_from_bytes(final byte[] data) { return Wally.bip39_mnemonic_from_bytes(WL, data); } private static byte[] getKey(final byte[] password, final byte[] salt) { return Arrays.copyOf(pbkdf2_hmac_sha512(password, salt), 32); } private static byte[] encrypt_aes_cbc(final byte[] data, final byte[] password, final byte[] salt) { final byte[] key = getKey(password, salt); final byte[] iv = randomBytes(BL); final byte[] encrypted = new byte[((data.length / BL) + 1) * BL]; final int written = Wally.aes_cbc(key, iv, data, Wally.AES_FLAG_ENCRYPT, encrypted); if (written != encrypted.length) throw new IllegalArgumentException("Encrypt failed"); final byte[] ivAndEncrypted = new byte[BL + written]; System.arraycopy(iv, 0, ivAndEncrypted, 0, BL); System.arraycopy(encrypted, 0, ivAndEncrypted, BL, written); return ivAndEncrypted; } private static byte[] decrypt_aes_cbc(final byte[] ivAndData, final byte[] password, final byte[] salt) { final byte[] key = getKey(password, salt); final byte[] iv = new byte[BL]; System.arraycopy(ivAndData, 0, iv, 0, BL); final byte[] dataNoIv = new byte[ivAndData.length - BL]; System.arraycopy(ivAndData, BL, dataNoIv, 0, ivAndData.length - BL); final byte[] tmpDecrypted = new byte[dataNoIv.length]; final int written = Wally.aes_cbc(key, iv, dataNoIv, Wally.AES_FLAG_DECRYPT, tmpDecrypted); if (written > tmpDecrypted.length || tmpDecrypted.length - written > BL) throw new IllegalArgumentException("Decrypt failed"); final byte[] plaintext = new byte[written]; System.arraycopy(tmpDecrypted, 0, plaintext, 0, written); return plaintext; } public static byte[] pbkdf2_hmac_sha512(final byte[] password, final byte[] salt) { return Wally.pbkdf2_hmac_sha512(password, salt, 0, 2048); } public static byte[] encryptJSON(final JSONMap json, final byte[] password, final byte[] salt) { final ByteArrayOutputStream b = new ByteArrayOutputStream(); try { new MappingJsonFactory().getCodec().writeValue(b, json.mData); } catch (final IOException e) { throw new RuntimeException(e.getMessage());// coding error } return encrypt_aes_cbc(b.toByteArray(), password, salt); } public static JSONMap decryptJSON(final byte[] encryptedData, final byte[] password, final byte[] salt) { final byte[] decrypted = decrypt_aes_cbc(encryptedData, password, salt); final Map<String, Object> json; try { json = new MappingJsonFactory().getCodec().readValue(new String(decrypted), Map.class); } catch (final IOException e) { throw new RuntimeException(e.getMessage()); // Coding error } return new JSONMap(json); } }