wallet encryption with argon2 key derivation

This commit is contained in:
Craig Raw 2020-05-18 15:50:25 +02:00
parent 7cb2f043a1
commit 8ffd225007
10 changed files with 122 additions and 28 deletions

View file

@ -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;
}
}
}

View file

@ -8,4 +8,6 @@ public interface AsymmetricKeyDeriver {
* @throws KeyCrypterException
*/
ECKey deriveECKey(String password) throws KeyCrypterException;
byte[] getSalt();
}

View file

@ -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();

View file

@ -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());

View file

@ -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<String> mnemonic = decodeMnemonicCode(keyCrypter.decrypt(encryptedMnemonicCode, key));
DeterministicSeed seed = new DeterministicSeed(mnemonic, needsPassphrase, creationTimeSeconds, type);

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);

View file

@ -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());

View file

@ -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");
}
}