mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 10:06:45 +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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,4 +8,6 @@ public interface AsymmetricKeyDeriver {
|
|||
* @throws KeyCrypterException
|
||||
*/
|
||||
ECKey deriveECKey(String password) throws KeyCrypterException;
|
||||
|
||||
byte[] getSalt();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue