diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java index 8474b400..aee9debe 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java @@ -122,16 +122,12 @@ public class FileWalletExportPane extends TitledDescriptionPane { Optional password = dlg.showAndWait(); if(password.isPresent()) { final String walletId = AppServices.get().getOpenWallets().get(wallet).getWalletId(wallet); + String walletPassword = password.get().asString(); Storage.DecryptWalletService decryptWalletService = new Storage.DecryptWalletService(copy, password.get()); decryptWalletService.setOnSucceeded(workerStateEvent -> { EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done")); Wallet decryptedWallet = decryptWalletService.getValue(); - - try { - exportWallet(file, decryptedWallet); - } finally { - decryptedWallet.clearPrivate(); - } + exportWallet(file, decryptedWallet, walletPassword); }); decryptWalletService.setOnFailed(workerStateEvent -> { EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed")); @@ -141,14 +137,14 @@ public class FileWalletExportPane extends TitledDescriptionPane { decryptWalletService.start(); } } else { - exportWallet(file, wallet); + exportWallet(file, wallet, null); } } - private void exportWallet(File file, Wallet exportWallet) { + private void exportWallet(File file, Wallet exportWallet, String password) { try { if(file != null) { - FileWalletExportService fileWalletExportService = new FileWalletExportService(exporter, file, exportWallet); + FileWalletExportService fileWalletExportService = new FileWalletExportService(exporter, file, exportWallet, password); fileWalletExportService.setOnSucceeded(event -> { EventManager.get().post(new WalletExportEvent(exportWallet)); }); @@ -163,7 +159,7 @@ public class FileWalletExportPane extends TitledDescriptionPane { fileWalletExportService.start(); } else { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - exporter.exportWallet(exportWallet, outputStream); + exporter.exportWallet(exportWallet, outputStream, password); QRDisplayDialog qrDisplayDialog; if(exporter instanceof CoboVaultMultisig) { qrDisplayDialog = new QRDisplayDialog(RegistryType.BYTES.toString(), outputStream.toByteArray(), true); @@ -185,6 +181,10 @@ public class FileWalletExportPane extends TitledDescriptionPane { errorMessage = e.getCause().getMessage(); } setError("Export Error", errorMessage); + } finally { + if(file == null && password != null) { + exportWallet.clearPrivate(); + } } } @@ -192,11 +192,13 @@ public class FileWalletExportPane extends TitledDescriptionPane { private final WalletExport exporter; private final File file; private final Wallet wallet; + private final String password; - public FileWalletExportService(WalletExport exporter, File file, Wallet wallet) { + public FileWalletExportService(WalletExport exporter, File file, Wallet wallet, String password) { this.exporter = exporter; this.file = file; this.wallet = wallet; + this.password = password; } @Override @@ -205,7 +207,11 @@ public class FileWalletExportPane extends TitledDescriptionPane { @Override protected Void call() throws Exception { try(OutputStream outputStream = new FileOutputStream(file)) { - exporter.exportWallet(wallet, outputStream); + exporter.exportWallet(wallet, outputStream, password); + } finally { + if(password != null) { + wallet.clearPrivate(); + } } return null; diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Bip129.java b/src/main/java/com/sparrowwallet/sparrow/io/Bip129.java index c8fc9ee8..ce35e654 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Bip129.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Bip129.java @@ -189,7 +189,7 @@ public class Bip129 implements KeystoreFileExport, KeystoreFileImport, WalletExp } @Override - public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException { try { String record = "BSMS 1.0\n" + OutputDescriptor.getOutputDescriptor(wallet) + diff --git a/src/main/java/com/sparrowwallet/sparrow/io/CaravanMultisig.java b/src/main/java/com/sparrowwallet/sparrow/io/CaravanMultisig.java index 4209d3a8..8c84b667 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/CaravanMultisig.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/CaravanMultisig.java @@ -94,7 +94,7 @@ public class CaravanMultisig implements WalletImport, WalletExport { } @Override - public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException { if(!wallet.isValid()) { throw new ExportException("Cannot export an incomplete wallet"); } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ColdcardMultisig.java b/src/main/java/com/sparrowwallet/sparrow/io/ColdcardMultisig.java index ea5f8a4b..bf02af29 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/ColdcardMultisig.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/ColdcardMultisig.java @@ -165,7 +165,7 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle } @Override - public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException { if(!wallet.isValid()) { throw new ExportException("Cannot export an incomplete wallet"); } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Descriptor.java b/src/main/java/com/sparrowwallet/sparrow/io/Descriptor.java index 0bbb699a..655e57be 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Descriptor.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Descriptor.java @@ -23,7 +23,7 @@ public class Descriptor implements WalletImport, WalletExport { } @Override - public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException { try { BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); bufferedWriter.write("# Receive and change descriptor (BIP389):"); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java b/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java index d86c06f5..13eb0694 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java @@ -2,10 +2,7 @@ package com.sparrowwallet.sparrow.io; import com.google.gson.*; import com.google.gson.reflect.TypeToken; -import com.sparrowwallet.drongo.ExtendedKey; -import com.sparrowwallet.drongo.KeyDerivation; -import com.sparrowwallet.drongo.KeyPurpose; -import com.sparrowwallet.drongo.Utils; +import com.sparrowwallet.drongo.*; import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.crypto.*; import com.sparrowwallet.drongo.policy.Policy; @@ -18,7 +15,10 @@ import org.slf4j.LoggerFactory; import java.io.*; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.*; +import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; public class Electrum implements KeystoreFileImport, WalletImport, WalletExport { @@ -301,11 +301,11 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport @Override public String getExportFileExtension(Wallet wallet) { - return "json"; + return wallet.isEncrypted() ? "" : "json"; } @Override - public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException { try { ElectrumJsonWallet ew = new ElectrumJsonWallet(); if(wallet.getPolicyType().equals(PolicyType.SINGLE)) { @@ -342,7 +342,18 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport ek.seed = keystore.getSeed().getMnemonicString().asString(); ek.passphrase = keystore.getSeed().getPassphrase() == null ? null : keystore.getSeed().getPassphrase().asString(); } - ew.use_encryption = false; + if(password != null) { + ek.xprv = encrypt(ek.xprv, password); + if(ek.seed != null) { + ek.seed = encrypt(ek.seed, password); + } + if(ek.passphrase != null) { + ek.passphrase = encrypt(ek.passphrase, password); + } + ew.use_encryption = true; + } else { + ew.use_encryption = false; + } } else if(keystore.getSource() == KeystoreSource.SW_WATCH) { ek.type = "bip32"; ek.xpub = keystore.getExtendedPublicKey().toString(xpubHeader); @@ -373,14 +384,41 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); String json = gson.toJson(eJson); + if(password != null) { + ECKey encryptionKey = Pbkdf2KeyDeriver.DEFAULT_INSTANCE.deriveECKey(password); + outputStream = new DeflaterOutputStream(new ECIESOutputStream(outputStream, encryptionKey)); + } + outputStream.write(json.getBytes(StandardCharsets.UTF_8)); outputStream.flush(); + outputStream.close(); } catch (Exception e) { log.error("Error exporting Electrum Wallet", e); throw new ExportException("Error exporting Electrum Wallet", e); } } + private String encrypt(String plain, String password) throws NoSuchAlgorithmException { + if(plain == null) { + return null; + } + + byte[] plainBytes = plain.getBytes(StandardCharsets.UTF_8); + + KeyDeriver keyDeriver = new DoubleSha256KeyDeriver(); + Key key = keyDeriver.deriveKey(password); + + KeyCrypter keyCrypter = new AESKeyCrypter(); + byte[] initializationVector = new byte[16]; + SecureRandom.getInstanceStrong().nextBytes(initializationVector); + EncryptedData encryptedData = keyCrypter.encrypt(plainBytes, initializationVector, key); + byte[] encrypted = new byte[initializationVector.length + encryptedData.getEncryptedBytes().length]; + System.arraycopy(initializationVector, 0, encrypted, 0, 16); + System.arraycopy(encryptedData.getEncryptedBytes(), 0, encrypted, 16, encryptedData.getEncryptedBytes().length); + byte[] encryptedBase64 = Base64.getEncoder().encode(encrypted); + return new String(encryptedBase64, StandardCharsets.UTF_8); + } + @Override public boolean isEncrypted(File file) { return (FileType.BINARY.equals(IOUtils.getFileType(file))); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java b/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java index 3e1b8684..6f1e4d42 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java @@ -29,7 +29,7 @@ public class ElectrumPersonalServer implements WalletExport { } @Override - public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException { if(wallet.getScriptType() == ScriptType.P2TR) { throw new ExportException(getName() + " does not support Taproot wallets."); } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Sparrow.java b/src/main/java/com/sparrowwallet/sparrow/io/Sparrow.java index 4eac7586..dcd1c863 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Sparrow.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Sparrow.java @@ -28,7 +28,7 @@ public class Sparrow implements WalletImport, WalletExport { } @Override - public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException { try { Wallet exportedWallet = !wallet.isMasterWallet() ? wallet.getMasterWallet() : wallet; PersistenceType persistenceType = PersistenceType.DB; diff --git a/src/main/java/com/sparrowwallet/sparrow/io/SpecterDIY.java b/src/main/java/com/sparrowwallet/sparrow/io/SpecterDIY.java index 24925acc..782513d1 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/SpecterDIY.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/SpecterDIY.java @@ -65,7 +65,7 @@ public class SpecterDIY implements KeystoreFileImport, WalletExport { } @Override - public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException { try { BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)); writer.append("addwallet ").append(wallet.getFullName()).append("&").append(OutputDescriptor.getOutputDescriptor(wallet).toString().replace('\'', 'h')).append("\n"); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/SpecterDesktop.java b/src/main/java/com/sparrowwallet/sparrow/io/SpecterDesktop.java index 44b83cc3..64bc01d2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/SpecterDesktop.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/SpecterDesktop.java @@ -18,7 +18,7 @@ public class SpecterDesktop implements WalletImport, WalletExport { private static final Logger log = LoggerFactory.getLogger(SpecterDesktop.class); @Override - public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException { try { SpecterWallet specterWallet = new SpecterWallet(); specterWallet.label = wallet.getFullName(); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java index 2bc9c015..89363744 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java @@ -752,7 +752,7 @@ public class Storage { protected Void call() throws IOException, ExportException { Sparrow export = new Sparrow(); try(BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(newWalletFile))) { - export.exportWallet(wallet, outputStream); + export.exportWallet(wallet, outputStream, null); } return null; diff --git a/src/main/java/com/sparrowwallet/sparrow/io/WalletExport.java b/src/main/java/com/sparrowwallet/sparrow/io/WalletExport.java index a949a1d6..d8f25f75 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/WalletExport.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/WalletExport.java @@ -5,7 +5,7 @@ import com.sparrowwallet.drongo.wallet.Wallet; import java.io.OutputStream; public interface WalletExport extends ImportExport { - void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException; + void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException; String getWalletExportDescription(); String getExportFileExtension(Wallet wallet); boolean isWalletExportScannable(); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/WalletLabels.java b/src/main/java/com/sparrowwallet/sparrow/io/WalletLabels.java index e771b5ae..6cc2571c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/WalletLabels.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/WalletLabels.java @@ -46,7 +46,7 @@ public class WalletLabels implements WalletImport, WalletExport { } @Override - public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException { List