diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/Argon2KeyDeriver.java b/src/main/java/com/sparrowwallet/drongo/crypto/Argon2KeyDeriver.java index 297d064..e8c5229 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/Argon2KeyDeriver.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/Argon2KeyDeriver.java @@ -6,34 +6,74 @@ import de.mkammerer.argon2.Argon2Factory; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; -public class Argon2KeyDeriver implements KeyDeriver { - private static final int SALT_LENGTH = 16; - private static final int HASH_LENGTH = 32; - private static final int ITERATIONS = 10; - private static final int MEMORY = 256 * 1024; - private static final int PARALLELISM = 4; +public class Argon2KeyDeriver implements KeyDeriver, AsymmetricKeyDeriver { + public static final Argon2Parameters TEST_PARAMETERS = new Argon2Parameters(16, 32, 1, 1024, 1); + public static final Argon2Parameters SPRW1_PARAMETERS = new Argon2Parameters(16, 32, 10, 256 * 1024, 4); + private final Argon2Parameters argon2Parameters; private final byte[] salt; public Argon2KeyDeriver() { + this(isTest() ? TEST_PARAMETERS : SPRW1_PARAMETERS); + } + + public Argon2KeyDeriver(Argon2Parameters argon2Parameters) { + this.argon2Parameters = argon2Parameters; + SecureRandom secureRandom = new SecureRandom(); - salt = new byte[SALT_LENGTH]; + salt = new byte[argon2Parameters.saltLength]; secureRandom.nextBytes(salt); } public Argon2KeyDeriver(byte[] salt) { + this(isTest() ? TEST_PARAMETERS : SPRW1_PARAMETERS, salt); + } + + public Argon2KeyDeriver(Argon2Parameters argon2Parameters, byte[] salt) { + this.argon2Parameters = argon2Parameters; this.salt = salt; } + @Override + public byte[] getSalt() { + return salt; + } + @Override public Key deriveKey(String password) throws KeyCrypterException { - Argon2Advanced argon2 = Argon2Factory.createAdvanced(Argon2Factory.Argon2Types.ARGON2id, SALT_LENGTH, HASH_LENGTH); - byte[] hash = argon2.rawHash(ITERATIONS, MEMORY, PARALLELISM, password.getBytes(StandardCharsets.UTF_8), salt); + Argon2Advanced argon2 = Argon2Factory.createAdvanced(Argon2Factory.Argon2Types.ARGON2id, argon2Parameters.saltLength, argon2Parameters.hashLength); + byte[] hash = argon2.rawHash(argon2Parameters.iterations, argon2Parameters.memory, argon2Parameters.parallelism, password.getBytes(StandardCharsets.UTF_8), salt); return new Key(hash, salt, getDeriverType()); } + @Override + public ECKey deriveECKey(String password) throws KeyCrypterException { + Key key = deriveKey(password); + return ECKey.fromPrivate(key.getKeyBytes()); + } + @Override public EncryptionType.Deriver getDeriverType() { return EncryptionType.Deriver.ARGON2; } + + private static boolean isTest() { + return System.getProperty("org.gradle.test.worker") != null; + } + + public static class Argon2Parameters { + public final int saltLength; + public final int hashLength; + public final int iterations; + public final int memory; + public final int parallelism; + + public Argon2Parameters(int saltLength, int hashLength, int iterations, int memory, int parallelism) { + this.saltLength = saltLength; + this.hashLength = hashLength; + this.iterations = iterations; + this.memory = memory; + this.parallelism = parallelism; + } + } } diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/AsymmetricKeyDeriver.java b/src/main/java/com/sparrowwallet/drongo/crypto/AsymmetricKeyDeriver.java index 5a5815b..4a9c8a7 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/AsymmetricKeyDeriver.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/AsymmetricKeyDeriver.java @@ -8,4 +8,6 @@ public interface AsymmetricKeyDeriver { * @throws KeyCrypterException */ ECKey deriveECKey(String password) throws KeyCrypterException; + + byte[] getSalt(); } diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/EncryptionType.java b/src/main/java/com/sparrowwallet/drongo/crypto/EncryptionType.java index 01540c1..9e6a0eb 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/EncryptionType.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/EncryptionType.java @@ -49,6 +49,10 @@ public class EncryptionType { public KeyDeriver getKeyDeriver() { return new Argon2KeyDeriver(); } + + public KeyDeriver getKeyDeriver(byte[] salt) { + return new Argon2KeyDeriver(salt); + } }; public abstract KeyDeriver getKeyDeriver(); diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/Pbkdf2KeyDeriver.java b/src/main/java/com/sparrowwallet/drongo/crypto/Pbkdf2KeyDeriver.java index 5afba91..497757f 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/Pbkdf2KeyDeriver.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/Pbkdf2KeyDeriver.java @@ -29,6 +29,11 @@ public class Pbkdf2KeyDeriver implements KeyDeriver, AsymmetricKeyDeriver { this.iterationCount = iterationCount; } + @Override + public byte[] getSalt() { + return salt; + } + @Override public Key deriveKey(String password) throws KeyCrypterException { PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA512Digest()); diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/DeterministicSeed.java b/src/main/java/com/sparrowwallet/drongo/wallet/DeterministicSeed.java index 1422c16..948246f 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/DeterministicSeed.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/DeterministicSeed.java @@ -158,7 +158,7 @@ public class DeterministicSeed implements EncryptableItem { @Override public EncryptionType getEncryptionType() { - return new EncryptionType(EncryptionType.Deriver.SCRYPT, EncryptionType.Crypter.AES_CBC_PKCS7); + return new EncryptionType(EncryptionType.Deriver.ARGON2, EncryptionType.Crypter.AES_CBC_PKCS7); } @Override @@ -174,15 +174,13 @@ public class DeterministicSeed implements EncryptableItem { return type; } - public DeterministicSeed encrypt(String password) { + public DeterministicSeed encrypt(Key key) { if(encryptedMnemonicCode != null) { throw new IllegalArgumentException("Trying to encrypt twice"); } if(mnemonicCode == null) { throw new IllegalArgumentException("Mnemonic missing so cannot encrypt"); } - KeyDeriver keyDeriver = getEncryptionType().getDeriver().getKeyDeriver(); - Key key = keyDeriver.deriveKey(password); KeyCrypter keyCrypter = getEncryptionType().getCrypter().getKeyCrypter(); EncryptedData encryptedMnemonic = keyCrypter.encrypt(getMnemonicAsBytes(), null, key); @@ -193,16 +191,30 @@ public class DeterministicSeed implements EncryptableItem { } private byte[] getMnemonicAsBytes() { - return getMnemonicString().getBytes(StandardCharsets.UTF_8); + String mnemonicString = getMnemonicString(); + if(mnemonicString == null) { + return null; + } + + return mnemonicString.getBytes(StandardCharsets.UTF_8); } public DeterministicSeed decrypt(String password) { if(!isEncrypted()) { throw new IllegalStateException("Cannot decrypt unencrypted seed"); } + KeyDeriver keyDeriver = getEncryptionType().getDeriver().getKeyDeriver(encryptedMnemonicCode.getKeySalt()); Key key = keyDeriver.deriveKey(password); + return decrypt(key); + } + + public DeterministicSeed decrypt(Key key) { + if(!isEncrypted()) { + throw new IllegalStateException("Cannot decrypt unencrypted seed"); + } + KeyCrypter keyCrypter = getEncryptionType().getCrypter().getKeyCrypter(); List mnemonic = decodeMnemonicCode(keyCrypter.decrypt(encryptedMnemonicCode, key)); DeterministicSeed seed = new DeterministicSeed(mnemonic, needsPassphrase, creationTimeSeconds, type); diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java b/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java index bf4d690..f4ccad0 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java @@ -185,15 +185,21 @@ public class Keystore { return seed != null && seed.isEncrypted(); } - public void encrypt(String password) { - if(seed != null && !seed.isEncrypted()) { - seed = seed.encrypt(password); + public void encrypt(Key key) { + if(hasSeed() && !seed.isEncrypted()) { + seed = seed.encrypt(key); } } public void decrypt(String password) { - if(seed != null && seed.isEncrypted()) { + if(hasSeed() && seed.isEncrypted()) { seed = seed.decrypt(password); } } + + public void decrypt(Key key) { + if(hasSeed() && seed.isEncrypted()) { + seed = seed.decrypt(key); + } + } } diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java b/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java index dc13a1f..feb0709 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java @@ -1,5 +1,6 @@ package com.sparrowwallet.drongo.wallet; +import com.sparrowwallet.drongo.crypto.Key; import com.sparrowwallet.drongo.policy.Policy; import com.sparrowwallet.drongo.policy.PolicyType; import com.sparrowwallet.drongo.protocol.ScriptType; @@ -156,9 +157,9 @@ public class Wallet { return false; } - public void encrypt(String password) { + public void encrypt(Key key) { for(Keystore keystore : keystores) { - keystore.encrypt(password); + keystore.encrypt(key); } } @@ -167,4 +168,10 @@ public class Wallet { keystore.decrypt(password); } } + + public void decrypt(Key key) { + for(Keystore keystore : keystores) { + keystore.decrypt(key); + } + } } diff --git a/src/test/java/com/sparrowwallet/drongo/crypto/Argon2KeyDeriverTest.java b/src/test/java/com/sparrowwallet/drongo/crypto/Argon2KeyDeriverTest.java index ac1b724..e88029b 100644 --- a/src/test/java/com/sparrowwallet/drongo/crypto/Argon2KeyDeriverTest.java +++ b/src/test/java/com/sparrowwallet/drongo/crypto/Argon2KeyDeriverTest.java @@ -1,8 +1,6 @@ package com.sparrowwallet.drongo.crypto; -import de.mkammerer.argon2.Argon2; -import de.mkammerer.argon2.Argon2Factory; -import de.mkammerer.argon2.Argon2Helper; +import com.sparrowwallet.drongo.Utils; import org.junit.Assert; import org.junit.Test; @@ -10,11 +8,24 @@ import java.nio.charset.StandardCharsets; import java.security.SecureRandom; public class Argon2KeyDeriverTest { + @Test + public void noPasswordTest() { + String password = ""; + + Argon2KeyDeriver.Argon2Parameters testParams = Argon2KeyDeriver.TEST_PARAMETERS; + byte[] salt = new byte[testParams.saltLength]; + Argon2KeyDeriver keyDeriver = new Argon2KeyDeriver(salt); + Key key = keyDeriver.deriveKey(password); + + String hex = Utils.bytesToHex(key.getKeyBytes()); + Assert.assertEquals("6f6600a054c0271b96788906f62dfb1323c37b761715a0ae95ac524e4e1f2811", hex); + } + @Test public void testArgon2() { String password = "thisisapassword"; - Argon2KeyDeriver keyDeriver = new Argon2KeyDeriver(); + Argon2KeyDeriver keyDeriver = new Argon2KeyDeriver(Argon2KeyDeriver.TEST_PARAMETERS); Key key = keyDeriver.deriveKey(password); KeyCrypter keyCrypter = new AESKeyCrypter(); @@ -30,7 +41,7 @@ public class Argon2KeyDeriverTest { //Decrypt - Argon2KeyDeriver keyDeriver2 = new Argon2KeyDeriver(encrypted.getKeySalt()); + Argon2KeyDeriver keyDeriver2 = new Argon2KeyDeriver(Argon2KeyDeriver.TEST_PARAMETERS, encrypted.getKeySalt()); Key key2 = keyDeriver2.deriveKey(password); byte[] decrypted = keyCrypter.decrypt(encrypted, key2); diff --git a/src/test/java/com/sparrowwallet/drongo/wallet/DeterministicSeedTest.java b/src/test/java/com/sparrowwallet/drongo/wallet/DeterministicSeedTest.java index 6dea0ab..b83d395 100644 --- a/src/test/java/com/sparrowwallet/drongo/wallet/DeterministicSeedTest.java +++ b/src/test/java/com/sparrowwallet/drongo/wallet/DeterministicSeedTest.java @@ -1,6 +1,6 @@ package com.sparrowwallet.drongo.wallet; -import com.sparrowwallet.drongo.Utils; +import com.sparrowwallet.drongo.crypto.KeyDeriver; import org.junit.Assert; import org.junit.Test; @@ -10,7 +10,8 @@ public class DeterministicSeedTest { String words = "absent essay fox snake vast pumpkin height crouch silent bulb excuse razor"; DeterministicSeed seed = new DeterministicSeed(words, "pp", 0, DeterministicSeed.Type.BIP39); - DeterministicSeed encryptedSeed = seed.encrypt("pass"); + KeyDeriver keyDeriver = seed.getEncryptionType().getDeriver().getKeyDeriver(); + DeterministicSeed encryptedSeed = seed.encrypt(keyDeriver.deriveKey("pass")); DeterministicSeed decryptedSeed = encryptedSeed.decrypt("pass"); Assert.assertEquals(words, decryptedSeed.getMnemonicString()); diff --git a/src/test/java/com/sparrowwallet/drongo/wallet/WalletTest.java b/src/test/java/com/sparrowwallet/drongo/wallet/WalletTest.java index ab895fc..2379f0f 100644 --- a/src/test/java/com/sparrowwallet/drongo/wallet/WalletTest.java +++ b/src/test/java/com/sparrowwallet/drongo/wallet/WalletTest.java @@ -1,5 +1,8 @@ package com.sparrowwallet.drongo.wallet; +import com.sparrowwallet.drongo.crypto.Argon2KeyDeriver; +import com.sparrowwallet.drongo.crypto.Key; +import com.sparrowwallet.drongo.crypto.KeyDeriver; import com.sparrowwallet.drongo.policy.Policy; import com.sparrowwallet.drongo.policy.PolicyType; import com.sparrowwallet.drongo.protocol.ScriptType; @@ -17,7 +20,10 @@ public class WalletTest { wallet.getKeystores().add(keystore); wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2PKH, wallet.getKeystores(), 1)); - wallet.encrypt("pass"); + KeyDeriver keyDeriver = new Argon2KeyDeriver(); + Key key = keyDeriver.deriveKey("pass"); + wallet.encrypt(key); + wallet.decrypt("pass"); } }