mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-27 02:26:44 +00:00
wallet encryption with argon2 key derivation
This commit is contained in:
parent
7cb2f043a1
commit
8ffd225007
10 changed files with 122 additions and 28 deletions
|
@ -6,34 +6,74 @@ import de.mkammerer.argon2.Argon2Factory;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
public class Argon2KeyDeriver implements KeyDeriver {
|
public class Argon2KeyDeriver implements KeyDeriver, AsymmetricKeyDeriver {
|
||||||
private static final int SALT_LENGTH = 16;
|
public static final Argon2Parameters TEST_PARAMETERS = new Argon2Parameters(16, 32, 1, 1024, 1);
|
||||||
private static final int HASH_LENGTH = 32;
|
public static final Argon2Parameters SPRW1_PARAMETERS = new Argon2Parameters(16, 32, 10, 256 * 1024, 4);
|
||||||
private static final int ITERATIONS = 10;
|
|
||||||
private static final int MEMORY = 256 * 1024;
|
|
||||||
private static final int PARALLELISM = 4;
|
|
||||||
|
|
||||||
|
private final Argon2Parameters argon2Parameters;
|
||||||
private final byte[] salt;
|
private final byte[] salt;
|
||||||
|
|
||||||
public Argon2KeyDeriver() {
|
public Argon2KeyDeriver() {
|
||||||
|
this(isTest() ? TEST_PARAMETERS : SPRW1_PARAMETERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Argon2KeyDeriver(Argon2Parameters argon2Parameters) {
|
||||||
|
this.argon2Parameters = argon2Parameters;
|
||||||
|
|
||||||
SecureRandom secureRandom = new SecureRandom();
|
SecureRandom secureRandom = new SecureRandom();
|
||||||
salt = new byte[SALT_LENGTH];
|
salt = new byte[argon2Parameters.saltLength];
|
||||||
secureRandom.nextBytes(salt);
|
secureRandom.nextBytes(salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Argon2KeyDeriver(byte[] salt) {
|
public Argon2KeyDeriver(byte[] salt) {
|
||||||
|
this(isTest() ? TEST_PARAMETERS : SPRW1_PARAMETERS, salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Argon2KeyDeriver(Argon2Parameters argon2Parameters, byte[] salt) {
|
||||||
|
this.argon2Parameters = argon2Parameters;
|
||||||
this.salt = salt;
|
this.salt = salt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getSalt() {
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Key deriveKey(String password) throws KeyCrypterException {
|
public Key deriveKey(String password) throws KeyCrypterException {
|
||||||
Argon2Advanced argon2 = Argon2Factory.createAdvanced(Argon2Factory.Argon2Types.ARGON2id, SALT_LENGTH, HASH_LENGTH);
|
Argon2Advanced argon2 = Argon2Factory.createAdvanced(Argon2Factory.Argon2Types.ARGON2id, argon2Parameters.saltLength, argon2Parameters.hashLength);
|
||||||
byte[] hash = argon2.rawHash(ITERATIONS, MEMORY, PARALLELISM, password.getBytes(StandardCharsets.UTF_8), salt);
|
byte[] hash = argon2.rawHash(argon2Parameters.iterations, argon2Parameters.memory, argon2Parameters.parallelism, password.getBytes(StandardCharsets.UTF_8), salt);
|
||||||
return new Key(hash, salt, getDeriverType());
|
return new Key(hash, salt, getDeriverType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ECKey deriveECKey(String password) throws KeyCrypterException {
|
||||||
|
Key key = deriveKey(password);
|
||||||
|
return ECKey.fromPrivate(key.getKeyBytes());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EncryptionType.Deriver getDeriverType() {
|
public EncryptionType.Deriver getDeriverType() {
|
||||||
return EncryptionType.Deriver.ARGON2;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,4 +8,6 @@ public interface AsymmetricKeyDeriver {
|
||||||
* @throws KeyCrypterException
|
* @throws KeyCrypterException
|
||||||
*/
|
*/
|
||||||
ECKey deriveECKey(String password) throws KeyCrypterException;
|
ECKey deriveECKey(String password) throws KeyCrypterException;
|
||||||
|
|
||||||
|
byte[] getSalt();
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,10 @@ public class EncryptionType {
|
||||||
public KeyDeriver getKeyDeriver() {
|
public KeyDeriver getKeyDeriver() {
|
||||||
return new Argon2KeyDeriver();
|
return new Argon2KeyDeriver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public KeyDeriver getKeyDeriver(byte[] salt) {
|
||||||
|
return new Argon2KeyDeriver(salt);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public abstract KeyDeriver getKeyDeriver();
|
public abstract KeyDeriver getKeyDeriver();
|
||||||
|
|
|
@ -29,6 +29,11 @@ public class Pbkdf2KeyDeriver implements KeyDeriver, AsymmetricKeyDeriver {
|
||||||
this.iterationCount = iterationCount;
|
this.iterationCount = iterationCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getSalt() {
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Key deriveKey(String password) throws KeyCrypterException {
|
public Key deriveKey(String password) throws KeyCrypterException {
|
||||||
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA512Digest());
|
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA512Digest());
|
||||||
|
|
|
@ -158,7 +158,7 @@ public class DeterministicSeed implements EncryptableItem {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EncryptionType getEncryptionType() {
|
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
|
@Override
|
||||||
|
@ -174,15 +174,13 @@ public class DeterministicSeed implements EncryptableItem {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeterministicSeed encrypt(String password) {
|
public DeterministicSeed encrypt(Key key) {
|
||||||
if(encryptedMnemonicCode != null) {
|
if(encryptedMnemonicCode != null) {
|
||||||
throw new IllegalArgumentException("Trying to encrypt twice");
|
throw new IllegalArgumentException("Trying to encrypt twice");
|
||||||
}
|
}
|
||||||
if(mnemonicCode == null) {
|
if(mnemonicCode == null) {
|
||||||
throw new IllegalArgumentException("Mnemonic missing so cannot encrypt");
|
throw new IllegalArgumentException("Mnemonic missing so cannot encrypt");
|
||||||
}
|
}
|
||||||
KeyDeriver keyDeriver = getEncryptionType().getDeriver().getKeyDeriver();
|
|
||||||
Key key = keyDeriver.deriveKey(password);
|
|
||||||
|
|
||||||
KeyCrypter keyCrypter = getEncryptionType().getCrypter().getKeyCrypter();
|
KeyCrypter keyCrypter = getEncryptionType().getCrypter().getKeyCrypter();
|
||||||
EncryptedData encryptedMnemonic = keyCrypter.encrypt(getMnemonicAsBytes(), null, key);
|
EncryptedData encryptedMnemonic = keyCrypter.encrypt(getMnemonicAsBytes(), null, key);
|
||||||
|
@ -193,16 +191,30 @@ public class DeterministicSeed implements EncryptableItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getMnemonicAsBytes() {
|
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) {
|
public DeterministicSeed decrypt(String password) {
|
||||||
if(!isEncrypted()) {
|
if(!isEncrypted()) {
|
||||||
throw new IllegalStateException("Cannot decrypt unencrypted seed");
|
throw new IllegalStateException("Cannot decrypt unencrypted seed");
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyDeriver keyDeriver = getEncryptionType().getDeriver().getKeyDeriver(encryptedMnemonicCode.getKeySalt());
|
KeyDeriver keyDeriver = getEncryptionType().getDeriver().getKeyDeriver(encryptedMnemonicCode.getKeySalt());
|
||||||
Key key = keyDeriver.deriveKey(password);
|
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();
|
KeyCrypter keyCrypter = getEncryptionType().getCrypter().getKeyCrypter();
|
||||||
List<String> mnemonic = decodeMnemonicCode(keyCrypter.decrypt(encryptedMnemonicCode, key));
|
List<String> mnemonic = decodeMnemonicCode(keyCrypter.decrypt(encryptedMnemonicCode, key));
|
||||||
DeterministicSeed seed = new DeterministicSeed(mnemonic, needsPassphrase, creationTimeSeconds, type);
|
DeterministicSeed seed = new DeterministicSeed(mnemonic, needsPassphrase, creationTimeSeconds, type);
|
||||||
|
|
|
@ -185,15 +185,21 @@ public class Keystore {
|
||||||
return seed != null && seed.isEncrypted();
|
return seed != null && seed.isEncrypted();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void encrypt(String password) {
|
public void encrypt(Key key) {
|
||||||
if(seed != null && !seed.isEncrypted()) {
|
if(hasSeed() && !seed.isEncrypted()) {
|
||||||
seed = seed.encrypt(password);
|
seed = seed.encrypt(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void decrypt(String password) {
|
public void decrypt(String password) {
|
||||||
if(seed != null && seed.isEncrypted()) {
|
if(hasSeed() && seed.isEncrypted()) {
|
||||||
seed = seed.decrypt(password);
|
seed = seed.decrypt(password);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void decrypt(Key key) {
|
||||||
|
if(hasSeed() && seed.isEncrypted()) {
|
||||||
|
seed = seed.decrypt(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.sparrowwallet.drongo.wallet;
|
package com.sparrowwallet.drongo.wallet;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.crypto.Key;
|
||||||
import com.sparrowwallet.drongo.policy.Policy;
|
import com.sparrowwallet.drongo.policy.Policy;
|
||||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
|
@ -156,9 +157,9 @@ public class Wallet {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void encrypt(String password) {
|
public void encrypt(Key key) {
|
||||||
for(Keystore keystore : keystores) {
|
for(Keystore keystore : keystores) {
|
||||||
keystore.encrypt(password);
|
keystore.encrypt(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,4 +168,10 @@ public class Wallet {
|
||||||
keystore.decrypt(password);
|
keystore.decrypt(password);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void decrypt(Key key) {
|
||||||
|
for(Keystore keystore : keystores) {
|
||||||
|
keystore.decrypt(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
import de.mkammerer.argon2.Argon2;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import de.mkammerer.argon2.Argon2Factory;
|
|
||||||
import de.mkammerer.argon2.Argon2Helper;
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -10,11 +8,24 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
public class Argon2KeyDeriverTest {
|
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
|
@Test
|
||||||
public void testArgon2() {
|
public void testArgon2() {
|
||||||
String password = "thisisapassword";
|
String password = "thisisapassword";
|
||||||
|
|
||||||
Argon2KeyDeriver keyDeriver = new Argon2KeyDeriver();
|
Argon2KeyDeriver keyDeriver = new Argon2KeyDeriver(Argon2KeyDeriver.TEST_PARAMETERS);
|
||||||
Key key = keyDeriver.deriveKey(password);
|
Key key = keyDeriver.deriveKey(password);
|
||||||
|
|
||||||
KeyCrypter keyCrypter = new AESKeyCrypter();
|
KeyCrypter keyCrypter = new AESKeyCrypter();
|
||||||
|
@ -30,7 +41,7 @@ public class Argon2KeyDeriverTest {
|
||||||
|
|
||||||
//Decrypt
|
//Decrypt
|
||||||
|
|
||||||
Argon2KeyDeriver keyDeriver2 = new Argon2KeyDeriver(encrypted.getKeySalt());
|
Argon2KeyDeriver keyDeriver2 = new Argon2KeyDeriver(Argon2KeyDeriver.TEST_PARAMETERS, encrypted.getKeySalt());
|
||||||
Key key2 = keyDeriver2.deriveKey(password);
|
Key key2 = keyDeriver2.deriveKey(password);
|
||||||
|
|
||||||
byte[] decrypted = keyCrypter.decrypt(encrypted, key2);
|
byte[] decrypted = keyCrypter.decrypt(encrypted, key2);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package com.sparrowwallet.drongo.wallet;
|
package com.sparrowwallet.drongo.wallet;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.crypto.KeyDeriver;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
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";
|
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 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");
|
DeterministicSeed decryptedSeed = encryptedSeed.decrypt("pass");
|
||||||
Assert.assertEquals(words, decryptedSeed.getMnemonicString());
|
Assert.assertEquals(words, decryptedSeed.getMnemonicString());
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package com.sparrowwallet.drongo.wallet;
|
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.Policy;
|
||||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
|
@ -17,7 +20,10 @@ public class WalletTest {
|
||||||
wallet.getKeystores().add(keystore);
|
wallet.getKeystores().add(keystore);
|
||||||
wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2PKH, wallet.getKeystores(), 1));
|
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");
|
wallet.decrypt("pass");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue