/* * Copyright 2014 http://Bither.net * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 net.bither.bitherj.utils; import net.bither.bitherj.BitherjSettings; import net.bither.bitherj.core.Address; import net.bither.bitherj.core.AddressManager; import net.bither.bitherj.core.HDAccount; import net.bither.bitherj.core.HDAccountCold; import net.bither.bitherj.core.HDMKeychain; import net.bither.bitherj.crypto.DumpedPrivateKey; import net.bither.bitherj.crypto.ECKey; import net.bither.bitherj.crypto.EncryptedData; import net.bither.bitherj.crypto.EncryptedPrivateKey; import net.bither.bitherj.crypto.KeyCrypter; import net.bither.bitherj.crypto.KeyCrypterException; import net.bither.bitherj.crypto.KeyCrypterScrypt; import net.bither.bitherj.crypto.PasswordSeed; import net.bither.bitherj.crypto.SecureCharSequence; import net.bither.bitherj.crypto.bip38.Bip38; import net.bither.bitherj.exception.AddressFormatException; import net.bither.bitherj.qrcode.QRCodeUtil; import net.bither.bitherj.qrcode.SaltForQRCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.crypto.params.KeyParameter; import java.math.BigInteger; import java.security.SignatureException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class PrivateKeyUtil { private static final Logger log = LoggerFactory.getLogger(PrivateKeyUtil.class); public static String BACKUP_KEY_SPLIT_MUTILKEY_STRING = "\n"; public static String getEncryptedString(ECKey ecKey) { String salt = "1"; if (ecKey.getKeyCrypter() instanceof KeyCrypterScrypt) { KeyCrypterScrypt scrypt = (KeyCrypterScrypt) ecKey.getKeyCrypter(); salt = Utils.bytesToHexString(scrypt.getSalt()); } EncryptedPrivateKey key = ecKey.getEncryptedPrivateKey(); return Utils.bytesToHexString(key.getEncryptedBytes()) + QRCodeUtil.QR_CODE_SPLIT + Utils .bytesToHexString(key.getInitialisationVector()) + QRCodeUtil.QR_CODE_SPLIT + salt; } public static ECKey getECKeyFromSingleString(String str, CharSequence password) { try { DecryptedECKey decryptedECKey = decryptionECKey(str, password, false); if (decryptedECKey != null && decryptedECKey.ecKey != null) { return decryptedECKey.ecKey; } else { return null; } } catch (Exception e) { e.printStackTrace(); return null; } } private static DecryptedECKey decryptionECKey(String str, CharSequence password, boolean needPrivteKeyText) throws Exception { String[] strs = QRCodeUtil.splitOfPasswordSeed(str); if (strs.length != 3) { log.error("decryption: PrivateKeyFromString format error"); return null; } byte[] temp = Utils.hexStringToByteArray(strs[2]); if (temp.length != KeyCrypterScrypt.SALT_LENGTH + 1 && temp.length != KeyCrypterScrypt.SALT_LENGTH) { log.error("decryption: salt lenth is {} not {}", temp.length, KeyCrypterScrypt.SALT_LENGTH + 1); return null; } SaltForQRCode saltForQRCode = new SaltForQRCode(temp); byte[] salt = saltForQRCode.getSalt(); boolean isCompressed = saltForQRCode.isCompressed(); boolean isFromXRandom = saltForQRCode.isFromXRandom(); KeyCrypterScrypt crypter = new KeyCrypterScrypt(salt); EncryptedPrivateKey epk = new EncryptedPrivateKey(Utils.hexStringToByteArray (strs[1]), Utils.hexStringToByteArray(strs[0])); byte[] decrypted = crypter.decrypt(epk, crypter.deriveKey(password)); ECKey ecKey = null; SecureCharSequence privateKeyText = null; if (needPrivteKeyText) { DumpedPrivateKey dumpedPrivateKey = new DumpedPrivateKey(decrypted, isCompressed); privateKeyText = dumpedPrivateKey.toSecureCharSequence(); dumpedPrivateKey.clearPrivateKey(); } else { BigInteger bigInteger = new BigInteger(1, decrypted); byte[] pub = ECKey.publicKeyFromPrivate(bigInteger, isCompressed); ecKey = new ECKey(epk, pub, crypter); ecKey.setFromXRandom(isFromXRandom); } Utils.wipeBytes(decrypted); return new DecryptedECKey(ecKey, privateKeyText); } public static String getBIP38PrivateKeyString(Address address, CharSequence password) throws AddressFormatException, InterruptedException { SecureCharSequence decrypted = getDecryptPrivateKeyString(address.getFullEncryptPrivKey() , password); String bip38 = Bip38.encryptNoEcMultiply(password, decrypted.toString()); if (BitherjSettings.DEV_DEBUG) { SecureCharSequence d = Bip38.decrypt(bip38, password); if (d.equals(decrypted)) { log.info("BIP38 right"); } else { throw new RuntimeException("BIP38 wrong " + d.toString() + " , " + "" + decrypted.toString()); } } decrypted.wipe(); return bip38; } public static SecureCharSequence getDecryptPrivateKeyString(String str, CharSequence password) { try { DecryptedECKey decryptedECKey = decryptionECKey(str, password, true); if (decryptedECKey != null && decryptedECKey.privateKeyText != null) { return decryptedECKey.privateKeyText; } else { return null; } } catch (Exception e) { e.printStackTrace(); return null; } } public static String changePassword(String str, CharSequence oldpassword, CharSequence newPassword) { String[] strs = QRCodeUtil.splitOfPasswordSeed(str); if (strs.length != 3) { log.error("change Password: PrivateKeyFromString format error"); return null; } byte[] temp = Utils.hexStringToByteArray(strs[2]); if (temp.length != KeyCrypterScrypt.SALT_LENGTH + 1 && temp.length != KeyCrypterScrypt.SALT_LENGTH) { log.error("decryption: salt lenth is {} not {}", temp.length, KeyCrypterScrypt.SALT_LENGTH + 1); return null; } byte[] salt = new byte[KeyCrypterScrypt.SALT_LENGTH]; if (temp.length == KeyCrypterScrypt.SALT_LENGTH) { salt = temp; } else { System.arraycopy(temp, 1, salt, 0, salt.length); } KeyCrypterScrypt crypter = new KeyCrypterScrypt(salt); EncryptedPrivateKey epk = new EncryptedPrivateKey(Utils.hexStringToByteArray (strs[1]), Utils.hexStringToByteArray(strs[0])); byte[] decrypted = crypter.decrypt(epk, crypter.deriveKey(oldpassword)); EncryptedPrivateKey encryptedPrivateKey = crypter.encrypt(decrypted, crypter.deriveKey(newPassword)); byte[] newDecrypted = crypter.decrypt(encryptedPrivateKey, crypter.deriveKey(newPassword)); if (!Arrays.equals(decrypted, newDecrypted)) { throw new KeyCrypterException("change Password, cannot be successfully decrypted after encryption so aborting wallet encryption."); } Utils.wipeBytes(decrypted); Utils.wipeBytes(newDecrypted); return Utils.bytesToHexString(encryptedPrivateKey.getEncryptedBytes()) + QRCodeUtil.QR_CODE_SPLIT + Utils.bytesToHexString(encryptedPrivateKey.getInitialisationVector()) + QRCodeUtil.QR_CODE_SPLIT + strs[2]; } public static String getEncryptPrivateKeyStringFromAllAddresses() { String content = ""; List<Address> privates = AddressManager.getInstance().getPrivKeyAddresses(); for (int i = 0; i < privates.size(); i++) { Address address = privates.get(i); content += address.getFullEncryptPrivKey(); if (i < privates.size() - 1) { content += QRCodeUtil.QR_CODE_SPLIT; } } HDMKeychain keychain = AddressManager.getInstance().getHdmKeychain(); if (keychain != null) { if (Utils.isEmpty(content)) { content += keychain.getQRCodeFullEncryptPrivKey(); } else { content += QRCodeUtil.QR_CODE_SPLIT + keychain.getQRCodeFullEncryptPrivKey(); } } HDAccount hdAccount = AddressManager.getInstance().getHDAccountHot(); if (hdAccount != null) { if (Utils.isEmpty(content)) { content += hdAccount.getQRCodeFullEncryptPrivKey(); } else { content += QRCodeUtil.QR_CODE_SPLIT + hdAccount.getQRCodeFullEncryptPrivKey(); } } HDAccountCold hdAccountCold = AddressManager.getInstance().getHDAccountCold(); if (hdAccountCold != null) { if (Utils.isEmpty(content)) { content += hdAccountCold.getQRCodeFullEncryptPrivKey(); } else { content += QRCodeUtil.QR_CODE_SPLIT + hdAccountCold.getQRCodeFullEncryptPrivKey(); } } return content; } public static HDMKeychain getHDMKeychain(String str, CharSequence password) { HDMKeychain hdmKeychain = null; String[] strs = QRCodeUtil.splitOfPasswordSeed(str); if (strs.length % 3 != 0) { log.error("Backup: PrivateKeyFromString format error"); return null; } for (int i = 0; i < strs.length; i += 3) { if (strs[i].indexOf(QRCodeUtil.HDM_QR_CODE_FLAG) == 0) { try { String encryptedString = strs[i].substring(1) + QRCodeUtil.QR_CODE_SPLIT + strs[i + 1] + QRCodeUtil.QR_CODE_SPLIT + strs[i + 2]; hdmKeychain = new HDMKeychain(new EncryptedData(encryptedString) , password, null); } catch (Exception e) { e.printStackTrace(); } } } return hdmKeychain; } public static HDAccountCold getHDAccountCold(String str, CharSequence password) { HDAccountCold hdAccountCold = null; String[] strs = QRCodeUtil.splitOfPasswordSeed(str); if (strs.length % 3 != 0) { log.error("Backup: PrivateKeyFromString format error"); return null; } for (int i = 0; i < strs.length; i += 3) { if (strs[i].indexOf(QRCodeUtil.HD_QR_CODE_FLAG) == 0) { try { String encryptedString = strs[i].substring(1) + QRCodeUtil.QR_CODE_SPLIT + strs[i + 1] + QRCodeUtil.QR_CODE_SPLIT + strs[i + 2]; hdAccountCold = new HDAccountCold(new EncryptedData(encryptedString), password); } catch (Exception e) { e.printStackTrace(); } } } return hdAccountCold; } public static List<Address> getECKeysFromBackupString(String str, CharSequence password) { String[] strs = QRCodeUtil.splitOfPasswordSeed(str); if (strs.length % 3 != 0) { log.error("Backup: PrivateKeyFromString format error"); return null; } ArrayList<Address> list = new ArrayList<Address>(); for (int i = 0; i < strs.length; i += 3) { if (strs[i].indexOf(QRCodeUtil.HDM_QR_CODE_FLAG) == 0) { continue; } if (strs[i].indexOf(QRCodeUtil.HD_QR_CODE_FLAG) == 0){ continue; } String encryptedString = strs[i] + QRCodeUtil.QR_CODE_SPLIT + strs[i + 1] + QRCodeUtil.QR_CODE_SPLIT + strs[i + 2]; ECKey key = getECKeyFromSingleString(encryptedString, password); if (key == null) { return null; } else { Address address = new Address(key.toAddress(), key.getPubKey(), encryptedString, false, key.isFromXRandom()); key.clearPrivateKey(); list.add(address); } } return list; } /** * will release key * * @param key * @param password * @return */ public static ECKey encrypt(ECKey key, CharSequence password) { KeyCrypter scrypt = new KeyCrypterScrypt(); KeyParameter derivedKey = scrypt.deriveKey(password); ECKey encryptedKey = key.encrypt(scrypt, derivedKey); // Check that the encrypted key can be successfully decrypted. // This is done as it is a critical failure if the private key cannot be decrypted successfully // (all bitcoin controlled by that private key is lost forever). // For a correctly constructed keyCrypter the encryption should always be reversible so it is just being as cautious as possible. if (!ECKey.encryptionIsReversible(key, encryptedKey, scrypt, derivedKey)) { // Abort encryption throw new KeyCrypterException("The key " + key.toString() + " cannot be successfully decrypted after encryption so aborting wallet encryption."); } key.clearPrivateKey(); return encryptedKey; } private static class DecryptedECKey { public DecryptedECKey(ECKey ecKey, SecureCharSequence privateKeyText) { this.ecKey = ecKey; this.privateKeyText = privateKeyText; } public ECKey ecKey; public SecureCharSequence privateKeyText; } public static boolean verifyMessage(String address, String messageText, String signatureText) { // Strip CRLF from signature text try { signatureText = signatureText.replaceAll("\n", "").replaceAll("\r", ""); ECKey key = ECKey.signedMessageToKey(messageText, signatureText); String signAddress = key.toAddress(); return Utils.compareString(address, signAddress); } catch (SignatureException e) { e.printStackTrace(); return false; } } public static String formatEncryptPrivateKeyForDb(String encryptPrivateKey) { if (Utils.isEmpty(encryptPrivateKey)) { return encryptPrivateKey; } String[] strs = QRCodeUtil.splitOfPasswordSeed(encryptPrivateKey); byte[] temp = Utils.hexStringToByteArray(strs[2]); byte[] salt = new byte[KeyCrypterScrypt.SALT_LENGTH]; if (temp.length == KeyCrypterScrypt.SALT_LENGTH + 1) { System.arraycopy(temp, 1, salt, 0, salt.length); } else { salt = temp; } strs[2] = Utils.bytesToHexString(salt); return Utils.joinString(strs, QRCodeUtil.QR_CODE_SPLIT); } public static String getFullencryptPrivateKey(Address address, String encryptPrivKey) { String[] strings = QRCodeUtil.splitString(encryptPrivKey); byte[] salt = Utils.hexStringToByteArray(strings[2]); if (salt.length == KeyCrypterScrypt.SALT_LENGTH) { SaltForQRCode saltForQRCode = new SaltForQRCode(salt, address.isCompressed(), address.isFromXRandom()); strings[2] = Utils.bytesToHexString(saltForQRCode.getQrCodeSalt()); } return Utils.joinString(strings, QRCodeUtil.QR_CODE_SPLIT); } public static String getFullencryptHDMKeyChain(boolean isFromXRandom, String encryptPrivKey) { String[] strings = QRCodeUtil.splitString(encryptPrivKey); byte[] salt = Utils.hexStringToByteArray(strings[2]); if (salt.length == KeyCrypterScrypt.SALT_LENGTH) { SaltForQRCode saltForQRCode = new SaltForQRCode(salt, true, isFromXRandom); strings[2] = Utils.bytesToHexString(saltForQRCode.getQrCodeSalt()).toUpperCase(); } return Utils.joinString(strings, QRCodeUtil.QR_CODE_SPLIT); } public static String getBackupPrivateKeyStr() { String backupString = ""; for (Address address : AddressManager.getInstance().getPrivKeyAddresses()) { if (address != null) { PasswordSeed passwordSeed = new PasswordSeed(address.getAddress(), address.getFullEncryptPrivKey()); backupString = backupString + passwordSeed.toPasswordSeedString() + BACKUP_KEY_SPLIT_MUTILKEY_STRING; } } HDMKeychain keychain = AddressManager.getInstance().getHdmKeychain(); if (keychain != null) { try { if (!keychain.isInRecovery()) { String address = keychain.getFirstAddressFromDb(); backupString += QRCodeUtil.HDM_QR_CODE_FLAG + Base58.bas58ToHexWithAddress(address) + QRCodeUtil.QR_CODE_SPLIT + keychain.getFullEncryptPrivKey() + BACKUP_KEY_SPLIT_MUTILKEY_STRING; } } catch (AddressFormatException e) { e.printStackTrace(); } } HDAccount hdAccount = AddressManager.getInstance().getHDAccountHot(); if (hdAccount != null) { try { String address = hdAccount.getFirstAddressFromDb(); backupString += QRCodeUtil.HD_QR_CODE_FLAG + Base58.bas58ToHexWithAddress(address) + QRCodeUtil.QR_CODE_SPLIT + hdAccount.getFullEncryptPrivKey() + BACKUP_KEY_SPLIT_MUTILKEY_STRING; } catch (AddressFormatException e) { e.printStackTrace(); } } HDAccountCold hdAccountCold = AddressManager.getInstance().getHDAccountCold(); if (hdAccountCold != null) { try { String address = hdAccountCold.getFirstAddressFromDb(); backupString += QRCodeUtil.HD_QR_CODE_FLAG + Base58.bas58ToHexWithAddress (address) + QRCodeUtil.QR_CODE_SPLIT + hdAccountCold .getFullEncryptPrivKey() + BACKUP_KEY_SPLIT_MUTILKEY_STRING; } catch (AddressFormatException e) { e.printStackTrace(); } } return backupString; } }