mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 10:06:45 +00:00
refactor keycrypters
This commit is contained in:
parent
312143cb61
commit
e20501d954
24 changed files with 360 additions and 188 deletions
|
@ -245,20 +245,6 @@ public class Utils {
|
||||||
return Ripemd160.getHash(sha256);
|
return Ripemd160.getHash(sha256);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates RIPEMD160(SHA256(input)). This is used in Address calculations.
|
|
||||||
*/
|
|
||||||
public static byte[] sha256sha256(byte[] input) {
|
|
||||||
byte[] sha256 = Sha256Hash.hash(input);
|
|
||||||
return Sha256Hash.hash(sha256);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] decryptAesCbcPkcs7(byte[] initializationVector, byte[] encryptedBytes, byte[] keyBytes) {
|
|
||||||
KeyCrypter keyCrypter = new AESKeyCrypter();
|
|
||||||
EncryptedData data = new EncryptedData(initializationVector, encryptedBytes, null);
|
|
||||||
return keyCrypter.decrypt(data, new Key(keyBytes, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Convert to a string path, starting with "M/" */
|
/** Convert to a string path, starting with "M/" */
|
||||||
public static String formatHDPath(List<ChildNumber> path) {
|
public static String formatHDPath(List<ChildNumber> path) {
|
||||||
StringJoiner joiner = new StringJoiner("/");
|
StringJoiner joiner = new StringJoiner("/");
|
||||||
|
@ -294,10 +280,4 @@ public class Utils {
|
||||||
hmacSha512.doFinal(out, 0);
|
hmacSha512.doFinal(out, 0);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] getPbkdf2HmacSha512Hash(byte[] preimage, byte[] salt, int iterationCount) {
|
|
||||||
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA512Digest());
|
|
||||||
gen.init(preimage, salt, iterationCount);
|
|
||||||
return ((KeyParameter) gen.generateDerivedParameters(512)).getKey();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,16 +23,6 @@ public class AESKeyCrypter implements KeyCrypter {
|
||||||
|
|
||||||
private static final SecureRandom secureRandom = new SecureRandom();
|
private static final SecureRandom secureRandom = new SecureRandom();
|
||||||
|
|
||||||
@Override
|
|
||||||
public EncryptionType getUnderstoodEncryptionType() {
|
|
||||||
return EncryptionType.ENCRYPTED_AES;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Key deriveKey(CharSequence password) throws KeyCrypterException {
|
|
||||||
throw new UnsupportedOperationException("AESKeyCrypter does not define a key derivation function, but keys must be either 128, 192 or 256 bits long");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt bytes previously encrypted with this class.
|
* Decrypt bytes previously encrypted with this class.
|
||||||
*
|
*
|
||||||
|
@ -93,9 +83,14 @@ public class AESKeyCrypter implements KeyCrypter {
|
||||||
final int length1 = cipher.processBytes(plainBytes, 0, plainBytes.length, encryptedBytes, 0);
|
final int length1 = cipher.processBytes(plainBytes, 0, plainBytes.length, encryptedBytes, 0);
|
||||||
final int length2 = cipher.doFinal(encryptedBytes, length1);
|
final int length2 = cipher.doFinal(encryptedBytes, length1);
|
||||||
|
|
||||||
return new EncryptedData(iv, Arrays.copyOf(encryptedBytes, length1 + length2), aesKey.getSalt());
|
return new EncryptedData(iv, Arrays.copyOf(encryptedBytes, length1 + length2), aesKey.getSalt(), aesKey.getDeriver(), getCrypterType());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new KeyCrypterException("Could not encrypt bytes.", e);
|
throw new KeyCrypterException("Could not encrypt bytes.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EncryptionType.Crypter getCrypterType() {
|
||||||
|
return EncryptionType.Crypter.AES_CBC_PKCS7;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
|
public class Argon2KeyDeriver implements KeyDeriver {
|
||||||
|
@Override
|
||||||
|
public Key deriveKey(String password) throws KeyCrypterException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EncryptionType.Deriver getDeriverType() {
|
||||||
|
return EncryptionType.Deriver.ARGON2;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,6 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
public interface AsymmetricKeyCrypter {
|
public interface AsymmetricKeyCrypter {
|
||||||
/**
|
|
||||||
* Return the EncryptionType enum value which denotes the type of encryption/ decryption that this KeyCrypter
|
|
||||||
* can understand.
|
|
||||||
*/
|
|
||||||
EncryptionType getUnderstoodEncryptionType();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a ECKey based on the provided password
|
|
||||||
* @param password
|
|
||||||
* @return ECKey The ECKey to use for encrypting and decrypting
|
|
||||||
* @throws KeyCrypterException
|
|
||||||
*/
|
|
||||||
ECKey deriveECKey(CharSequence password) throws KeyCrypterException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt the provided encrypted bytes, converting them into unencrypted bytes.
|
* Decrypt the provided encrypted bytes, converting them into unencrypted bytes.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
|
public interface AsymmetricKeyDeriver {
|
||||||
|
/**
|
||||||
|
* Create a ECKey based on the provided password
|
||||||
|
* @param password
|
||||||
|
* @return ECKey The ECKey to use for encrypting and decrypting
|
||||||
|
* @throws KeyCrypterException
|
||||||
|
*/
|
||||||
|
ECKey deriveECKey(String password) throws KeyCrypterException;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class DoubleSha256KeyDeriver implements KeyDeriver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key deriveKey(String password) throws KeyCrypterException {
|
||||||
|
byte[] sha256 = Sha256Hash.hash(password.getBytes(StandardCharsets.UTF_8));
|
||||||
|
byte[] doubleSha256 = Sha256Hash.hash(sha256);
|
||||||
|
return new Key(doubleSha256, null, getDeriverType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EncryptionType.Deriver getDeriverType() {
|
||||||
|
return EncryptionType.Deriver.DOUBLE_SHA256;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.Utils;
|
|
||||||
import org.bouncycastle.crypto.digests.SHA256Digest;
|
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||||
import org.bouncycastle.crypto.digests.SHA512Digest;
|
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||||
import org.bouncycastle.crypto.macs.HMac;
|
import org.bouncycastle.crypto.macs.HMac;
|
||||||
|
@ -8,7 +7,6 @@ import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
|
||||||
|
@ -18,21 +16,6 @@ import java.util.Base64;
|
||||||
public class ECIESKeyCrypter implements AsymmetricKeyCrypter {
|
public class ECIESKeyCrypter implements AsymmetricKeyCrypter {
|
||||||
private final KeyCrypter aesKeyCrypter = new AESKeyCrypter();
|
private final KeyCrypter aesKeyCrypter = new AESKeyCrypter();
|
||||||
|
|
||||||
@Override
|
|
||||||
public EncryptionType getUnderstoodEncryptionType() {
|
|
||||||
return EncryptionType.ENCRYPTED_ECIES_AES;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ECKey deriveECKey(CharSequence password) throws KeyCrypterException {
|
|
||||||
return deriveECKey(password.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ECKey deriveECKey(String password) throws KeyCrypterException {
|
|
||||||
byte[] secret = Utils.getPbkdf2HmacSha512Hash(password.toString().getBytes(StandardCharsets.UTF_8), new byte[0], 1024);
|
|
||||||
return ECKey.fromPrivate(secret);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] decrypt(EncryptedData encryptedBytesToDecode, ECKey key) throws KeyCrypterException {
|
public byte[] decrypt(EncryptedData encryptedBytesToDecode, ECKey key) throws KeyCrypterException {
|
||||||
return decryptEcies(encryptedBytesToDecode.getEncryptedBytes(), encryptedBytesToDecode.getInitialisationVector(), key);
|
return decryptEcies(encryptedBytesToDecode.getEncryptedBytes(), encryptedBytesToDecode.getInitialisationVector(), key);
|
||||||
|
@ -65,13 +48,13 @@ public class ECIESKeyCrypter implements AsymmetricKeyCrypter {
|
||||||
throw new InvalidPasswordException();
|
throw new InvalidPasswordException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return aesKeyCrypter.decrypt(new EncryptedData(iv, ciphertext, null), new Key(key_e, null));
|
return aesKeyCrypter.decrypt(new EncryptedData(iv, ciphertext, null, null), new Key(key_e, null, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, ECKey key) throws KeyCrypterException {
|
public EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, ECKey key) throws KeyCrypterException {
|
||||||
byte[] encryptedBytes = encryptEcies(key, plainBytes, initializationVector);
|
byte[] encryptedBytes = encryptEcies(key, plainBytes, initializationVector);
|
||||||
return new EncryptedData(initializationVector, encryptedBytes, null);
|
return new EncryptedData(initializationVector, encryptedBytes, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] encryptEcies(ECKey key, byte[] message, byte[] magic) {
|
public byte[] encryptEcies(ECKey key, byte[] message, byte[] magic) {
|
||||||
|
@ -83,7 +66,7 @@ public class ECIESKeyCrypter implements AsymmetricKeyCrypter {
|
||||||
byte[] key_e = Arrays.copyOfRange(hash, 16, 32);
|
byte[] key_e = Arrays.copyOfRange(hash, 16, 32);
|
||||||
byte[] key_m = Arrays.copyOfRange(hash, 32, 64);
|
byte[] key_m = Arrays.copyOfRange(hash, 32, 64);
|
||||||
|
|
||||||
byte[] ciphertext = aesKeyCrypter.encrypt(message, iv, new Key(key_e, null)).getEncryptedBytes();
|
byte[] ciphertext = aesKeyCrypter.encrypt(message, iv, new Key(key_e, null, null)).getEncryptedBytes();
|
||||||
byte[] encrypted = concat(magic, ephemeral.getPubKey(), ciphertext);
|
byte[] encrypted = concat(magic, ephemeral.getPubKey(), ciphertext);
|
||||||
byte[] result = hmac256(key_m, encrypted);
|
byte[] result = hmac256(key_m, encrypted);
|
||||||
return Base64.getEncoder().encode(concat(encrypted, result));
|
return Base64.getEncoder().encode(concat(encrypted, result));
|
||||||
|
|
|
@ -831,7 +831,7 @@ public class ECKey implements EncryptableItem {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EncryptionType getEncryptionType() {
|
public EncryptionType getEncryptionType() {
|
||||||
return keyCrypter != null ? keyCrypter.getUnderstoodEncryptionType() : EncryptionType.UNENCRYPTED;
|
return new EncryptionType(EncryptionType.Deriver.SCRYPT, keyCrypter != null ? keyCrypter.getCrypterType() : EncryptionType.Crypter.NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,7 +15,7 @@ public interface EncryptableItem {
|
||||||
/** Returns the initialization vector and encrypted secret bytes, or null if not encrypted. */
|
/** Returns the initialization vector and encrypted secret bytes, or null if not encrypted. */
|
||||||
EncryptedData getEncryptedData();
|
EncryptedData getEncryptedData();
|
||||||
|
|
||||||
/** Returns an enum constant describing what algorithm was used to encrypt the key or UNENCRYPTED. */
|
/** Returns an object containing enums describing which algorithms are used to derive the key and encrypt the data. */
|
||||||
EncryptionType getEncryptionType();
|
EncryptionType getEncryptionType();
|
||||||
|
|
||||||
/** Returns the time in seconds since the UNIX epoch at which this encryptable item was first created/derived. */
|
/** Returns the time in seconds since the UNIX epoch at which this encryptable item was first created/derived. */
|
||||||
|
|
|
@ -14,11 +14,17 @@ public final class EncryptedData {
|
||||||
private final byte[] initialisationVector;
|
private final byte[] initialisationVector;
|
||||||
private final byte[] encryptedBytes;
|
private final byte[] encryptedBytes;
|
||||||
private final byte[] keySalt;
|
private final byte[] keySalt;
|
||||||
|
private final EncryptionType encryptionType;
|
||||||
|
|
||||||
public EncryptedData(byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt) {
|
public EncryptedData(byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt, EncryptionType.Deriver deriver, EncryptionType.Crypter crypter) {
|
||||||
|
this(initialisationVector, encryptedBytes, keySalt, new EncryptionType(deriver, crypter));
|
||||||
|
}
|
||||||
|
|
||||||
|
public EncryptedData(byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt, EncryptionType encryptionType) {
|
||||||
this.initialisationVector = Arrays.copyOf(initialisationVector, initialisationVector.length);
|
this.initialisationVector = Arrays.copyOf(initialisationVector, initialisationVector.length);
|
||||||
this.encryptedBytes = Arrays.copyOf(encryptedBytes, encryptedBytes.length);
|
this.encryptedBytes = Arrays.copyOf(encryptedBytes, encryptedBytes.length);
|
||||||
this.keySalt = keySalt == null ? null : Arrays.copyOf(keySalt, keySalt.length);
|
this.keySalt = keySalt == null ? null : Arrays.copyOf(keySalt, keySalt.length);
|
||||||
|
this.encryptionType = encryptionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getInitialisationVector() {
|
public byte[] getInitialisationVector() {
|
||||||
|
@ -33,29 +39,38 @@ public final class EncryptedData {
|
||||||
return keySalt;
|
return keySalt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EncryptionType getEncryptionType() {
|
||||||
|
return encryptionType;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
EncryptedData other = (EncryptedData) o;
|
EncryptedData other = (EncryptedData) o;
|
||||||
return Arrays.equals(encryptedBytes, other.encryptedBytes) && Arrays.equals(initialisationVector, other.initialisationVector) && Arrays.equals(keySalt, other.keySalt);
|
return Arrays.equals(encryptedBytes, other.encryptedBytes) &&
|
||||||
|
Arrays.equals(initialisationVector, other.initialisationVector) &&
|
||||||
|
Arrays.equals(keySalt, other.keySalt) &&
|
||||||
|
encryptionType.equals(other.encryptionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(Arrays.hashCode(encryptedBytes), Arrays.hashCode(initialisationVector), Arrays.hashCode(keySalt));
|
return Objects.hash(Arrays.hashCode(encryptedBytes), Arrays.hashCode(initialisationVector), Arrays.hashCode(keySalt), encryptionType.hashCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "EncryptedData [initialisationVector=" + Arrays.toString(initialisationVector)
|
return "EncryptedData [initialisationVector=" + Arrays.toString(initialisationVector)
|
||||||
+ ", encryptedPrivateKey=" + Arrays.toString(encryptedBytes)
|
+ ", encryptedPrivateKey=" + Arrays.toString(encryptedBytes)
|
||||||
+ ", keySalt=" + Arrays.toString(keySalt) + "]";
|
+ ", keySalt=" + Arrays.toString(keySalt)
|
||||||
|
+ ", type=" + encryptionType + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public EncryptedData copy() {
|
public EncryptedData copy() {
|
||||||
return new EncryptedData(Arrays.copyOf(initialisationVector, initialisationVector.length),
|
return new EncryptedData(Arrays.copyOf(initialisationVector, initialisationVector.length),
|
||||||
Arrays.copyOf(encryptedBytes, encryptedBytes.length),
|
Arrays.copyOf(encryptedBytes, encryptedBytes.length),
|
||||||
Arrays.copyOf(keySalt, keySalt.length));
|
Arrays.copyOf(keySalt, keySalt.length),
|
||||||
|
encryptionType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,130 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
public enum EncryptionType {
|
import java.nio.charset.StandardCharsets;
|
||||||
UNENCRYPTED, ENCRYPTED_AES, ENCRYPTED_SCRYPT_AES, ENCRYPTED_ECIES_AES;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class EncryptionType {
|
||||||
|
public enum Deriver {
|
||||||
|
NONE() {
|
||||||
|
public KeyDeriver getKeyDeriver() {
|
||||||
|
return new KeyDeriver() {
|
||||||
|
@Override
|
||||||
|
public Key deriveKey(String password) throws KeyCrypterException {
|
||||||
|
return new Key(password.getBytes(StandardCharsets.UTF_8), null, NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Deriver getDeriverType() {
|
||||||
|
return NONE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DOUBLE_SHA256() {
|
||||||
|
public KeyDeriver getKeyDeriver() {
|
||||||
|
return new DoubleSha256KeyDeriver();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PBKDF2() {
|
||||||
|
public KeyDeriver getKeyDeriver() {
|
||||||
|
return new Pbkdf2KeyDeriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyDeriver getKeyDeriver(byte[] salt) {
|
||||||
|
return new Pbkdf2KeyDeriver(salt);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SCRYPT() {
|
||||||
|
public KeyDeriver getKeyDeriver() {
|
||||||
|
return new ScryptKeyDeriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyDeriver getKeyDeriver(byte[] salt) {
|
||||||
|
return new ScryptKeyDeriver(salt);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ARGON2() {
|
||||||
|
public KeyDeriver getKeyDeriver() {
|
||||||
|
return new Argon2KeyDeriver();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public abstract KeyDeriver getKeyDeriver();
|
||||||
|
|
||||||
|
public KeyDeriver getKeyDeriver(byte[] salt) {
|
||||||
|
return getKeyDeriver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Crypter {
|
||||||
|
NONE() {
|
||||||
|
@Override
|
||||||
|
public KeyCrypter getKeyCrypter() {
|
||||||
|
return new KeyCrypter() {
|
||||||
|
@Override
|
||||||
|
public byte[] decrypt(EncryptedData encryptedBytesToDecode, Key key) throws KeyCrypterException {
|
||||||
|
return encryptedBytesToDecode.getEncryptedBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, Key key) throws KeyCrypterException {
|
||||||
|
return new EncryptedData(plainBytes, initializationVector, key.getSalt(), key.getDeriver(), NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Crypter getCrypterType() {
|
||||||
|
return NONE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AES_CBC_PKCS7() {
|
||||||
|
@Override
|
||||||
|
public KeyCrypter getKeyCrypter() {
|
||||||
|
return new AESKeyCrypter();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public abstract KeyCrypter getKeyCrypter();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Deriver deriver;
|
||||||
|
private final Crypter crypter;
|
||||||
|
|
||||||
|
public EncryptionType(Deriver deriver, Crypter crypter) {
|
||||||
|
this.deriver = deriver;
|
||||||
|
this.crypter = crypter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Deriver getDeriver() {
|
||||||
|
return deriver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Crypter getCrypter() {
|
||||||
|
return crypter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
EncryptionType that = (EncryptionType) o;
|
||||||
|
return deriver == that.deriver &&
|
||||||
|
crypter == that.crypter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(deriver, crypter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "EncryptionType[" +
|
||||||
|
"deriver=" + deriver +
|
||||||
|
", crypter=" + crypter +
|
||||||
|
']';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,12 @@ package com.sparrowwallet.drongo.crypto;
|
||||||
public class Key {
|
public class Key {
|
||||||
private final byte[] keyBytes;
|
private final byte[] keyBytes;
|
||||||
private final byte[] salt;
|
private final byte[] salt;
|
||||||
|
private final EncryptionType.Deriver deriver;
|
||||||
|
|
||||||
public Key(byte[] keyBytes, byte[] salt) {
|
public Key(byte[] keyBytes, byte[] salt, EncryptionType.Deriver deriver) {
|
||||||
this.keyBytes = keyBytes;
|
this.keyBytes = keyBytes;
|
||||||
this.salt = salt;
|
this.salt = salt;
|
||||||
|
this.deriver = deriver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getKeyBytes() {
|
public byte[] getKeyBytes() {
|
||||||
|
@ -16,4 +18,8 @@ public class Key {
|
||||||
public byte[] getSalt() {
|
public byte[] getSalt() {
|
||||||
return salt;
|
return salt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EncryptionType.Deriver getDeriver() {
|
||||||
|
return deriver;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,6 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>A KeyCrypter can be used to encrypt and decrypt a message. The sequence of events to encrypt and then decrypt
|
|
||||||
* a message are as follows:</p>
|
|
||||||
*
|
|
||||||
* <p>(1) Ask the user for a password. deriveKey() is then called to create an Key. This contains the AES
|
|
||||||
* key that will be used for encryption.</p>
|
|
||||||
* <p>(2) Encrypt the message using encrypt(), providing the message bytes and the Key from (1). This returns
|
|
||||||
* an EncryptedData which contains the encryptedPrivateKey bytes and an initialisation vector.</p>
|
|
||||||
* <p>(3) To decrypt an EncryptedData, repeat step (1) to get a Key, then call decrypt().</p>
|
|
||||||
*
|
|
||||||
* <p>There can be different algorithms used for encryption/ decryption so the getUnderstoodEncryptionType is used
|
|
||||||
* to determine whether any given KeyCrypter can understand the type of encrypted data you have.</p>
|
|
||||||
*/
|
|
||||||
public interface KeyCrypter {
|
public interface KeyCrypter {
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the EncryptionType enum value which denotes the type of encryption/ decryption that this KeyCrypter
|
|
||||||
* can understand.
|
|
||||||
*/
|
|
||||||
EncryptionType getUnderstoodEncryptionType();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a Key (which typically contains an AES key)
|
|
||||||
* @param password
|
|
||||||
* @return Key The Key which typically contains the AES key to use for encrypting and decrypting
|
|
||||||
* @throws KeyCrypterException
|
|
||||||
*/
|
|
||||||
Key deriveKey(CharSequence password) throws KeyCrypterException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt the provided encrypted bytes, converting them into unencrypted bytes.
|
* Decrypt the provided encrypted bytes, converting them into unencrypted bytes.
|
||||||
*
|
*
|
||||||
|
@ -39,8 +11,10 @@ public interface KeyCrypter {
|
||||||
/**
|
/**
|
||||||
* Encrypt the supplied bytes, converting them into ciphertext.
|
* Encrypt the supplied bytes, converting them into ciphertext.
|
||||||
*
|
*
|
||||||
* @return encryptedPrivateKey An encryptedPrivateKey containing the encrypted bytes and an initialisation vector.
|
* @return EncryptedData An EncryptedData object containing the encrypted bytes and an initialisation vector and key salt.
|
||||||
* @throws KeyCrypterException if encryption was unsuccessful
|
* @throws KeyCrypterException if encryption was unsuccessful
|
||||||
*/
|
*/
|
||||||
EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, Key key) throws KeyCrypterException;
|
EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, Key key) throws KeyCrypterException;
|
||||||
|
|
||||||
|
EncryptionType.Crypter getCrypterType();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
|
public interface KeyDeriver {
|
||||||
|
/**
|
||||||
|
* Create a Key (which typically contains an AES key)
|
||||||
|
* @param password
|
||||||
|
* @return Key The Key which typically contains the AES key to use for encrypting and decrypting
|
||||||
|
* @throws KeyCrypterException
|
||||||
|
*/
|
||||||
|
Key deriveKey(String password) throws KeyCrypterException;
|
||||||
|
|
||||||
|
EncryptionType.Deriver getDeriverType();
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||||
|
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
|
||||||
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class Pbkdf2KeyDeriver implements KeyDeriver, AsymmetricKeyDeriver {
|
||||||
|
public static final int DEFAULT_ITERATION_COUNT = 1024;
|
||||||
|
|
||||||
|
private final byte[] salt;
|
||||||
|
private final int iterationCount;
|
||||||
|
|
||||||
|
public static final Pbkdf2KeyDeriver DEFAULT_INSTANCE = new Pbkdf2KeyDeriver();
|
||||||
|
|
||||||
|
public Pbkdf2KeyDeriver() {
|
||||||
|
this.salt = new byte[0];
|
||||||
|
this.iterationCount = DEFAULT_ITERATION_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pbkdf2KeyDeriver(byte[] salt) {
|
||||||
|
this.salt = salt;
|
||||||
|
this.iterationCount = DEFAULT_ITERATION_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pbkdf2KeyDeriver(byte[] salt, int iterationCount) {
|
||||||
|
this.salt = salt;
|
||||||
|
this.iterationCount = iterationCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key deriveKey(String password) throws KeyCrypterException {
|
||||||
|
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA512Digest());
|
||||||
|
gen.init(password.getBytes(StandardCharsets.UTF_8), salt, iterationCount);
|
||||||
|
byte[] keyBytes = ((KeyParameter)gen.generateDerivedParameters(512)).getKey();
|
||||||
|
return new Key(keyBytes, 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.PBKDF2;
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,8 +21,8 @@ import java.util.Objects;
|
||||||
* <p>2) Using the AES Key generated above, you then can encrypt and decrypt any bytes using
|
* <p>2) Using the AES Key generated above, you then can encrypt and decrypt any bytes using
|
||||||
* the AES symmetric cipher. Eight bytes of salt is used to prevent dictionary attacks.</p>
|
* the AES symmetric cipher. Eight bytes of salt is used to prevent dictionary attacks.</p>
|
||||||
*/
|
*/
|
||||||
public class ScryptKeyCrypter extends AESKeyCrypter {
|
public class ScryptKeyDeriver implements KeyDeriver {
|
||||||
private static final Logger log = LoggerFactory.getLogger(ScryptKeyCrypter.class);
|
private static final Logger log = LoggerFactory.getLogger(ScryptKeyDeriver.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key length in bytes.
|
* Key length in bytes.
|
||||||
|
@ -49,14 +49,14 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
|
||||||
/**
|
/**
|
||||||
* Encryption/Decryption using default parameters and a random salt.
|
* Encryption/Decryption using default parameters and a random salt.
|
||||||
*/
|
*/
|
||||||
public ScryptKeyCrypter() {
|
public ScryptKeyDeriver() {
|
||||||
this.scryptParameters = new ScryptParameters(randomSalt());
|
this.scryptParameters = new ScryptParameters(randomSalt());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encryption/Decryption using default parameters and provided salt.
|
* Encryption/Decryption using default parameters and provided salt.
|
||||||
*/
|
*/
|
||||||
public ScryptKeyCrypter(byte[] salt) {
|
public ScryptKeyDeriver(byte[] salt) {
|
||||||
this.scryptParameters = new ScryptParameters(salt);
|
this.scryptParameters = new ScryptParameters(salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
|
||||||
* @param iterations
|
* @param iterations
|
||||||
* number of scrypt iterations
|
* number of scrypt iterations
|
||||||
*/
|
*/
|
||||||
public ScryptKeyCrypter(int iterations) {
|
public ScryptKeyDeriver(int iterations) {
|
||||||
this.scryptParameters = new ScryptParameters(randomSalt(), iterations);
|
this.scryptParameters = new ScryptParameters(randomSalt(), iterations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,13 +77,18 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
|
||||||
* @param scryptParameters ScryptParameters to use
|
* @param scryptParameters ScryptParameters to use
|
||||||
* @throws NullPointerException if the scryptParameters or any of its N, R or P is null.
|
* @throws NullPointerException if the scryptParameters or any of its N, R or P is null.
|
||||||
*/
|
*/
|
||||||
public ScryptKeyCrypter(ScryptParameters scryptParameters) {
|
public ScryptKeyDeriver(ScryptParameters scryptParameters) {
|
||||||
this.scryptParameters = scryptParameters;
|
this.scryptParameters = scryptParameters;
|
||||||
if (scryptParameters.getSalt() == null || scryptParameters.getSalt() == null || scryptParameters.getSalt().length == 0) {
|
if (scryptParameters.getSalt() == null || scryptParameters.getSalt() == null || scryptParameters.getSalt().length == 0) {
|
||||||
log.warn("You are using a ScryptParameters with no salt. Your encryption may be vulnerable to a dictionary attack.");
|
log.warn("You are using a ScryptParameters with no salt. Your encryption may be vulnerable to a dictionary attack.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EncryptionType.Deriver getDeriverType() {
|
||||||
|
return EncryptionType.Deriver.SCRYPT;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate AES key.
|
* Generate AES key.
|
||||||
*
|
*
|
||||||
|
@ -94,7 +99,7 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
|
||||||
* @throws KeyCrypterException
|
* @throws KeyCrypterException
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Key deriveKey(CharSequence password) throws KeyCrypterException {
|
public Key deriveKey(String password) throws KeyCrypterException {
|
||||||
byte[] passwordBytes = null;
|
byte[] passwordBytes = null;
|
||||||
try {
|
try {
|
||||||
passwordBytes = convertToByteArray(password);
|
passwordBytes = convertToByteArray(password);
|
||||||
|
@ -106,7 +111,7 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] keyBytes = SCrypt.generate(passwordBytes, salt, (int) scryptParameters.getN(), scryptParameters.getR(), scryptParameters.getP(), KEY_LENGTH);
|
byte[] keyBytes = SCrypt.generate(passwordBytes, salt, (int) scryptParameters.getN(), scryptParameters.getR(), scryptParameters.getP(), KEY_LENGTH);
|
||||||
return new Key(keyBytes, scryptParameters.getSalt());
|
return new Key(keyBytes, scryptParameters.getSalt(), getDeriverType());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new KeyCrypterException("Could not generate key from password and salt.", e);
|
throw new KeyCrypterException("Could not generate key from password and salt.", e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -136,15 +141,6 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
|
||||||
return scryptParameters;
|
return scryptParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the EncryptionType enum value which denotes the type of encryption/ decryption that this KeyCrypter
|
|
||||||
* can understand.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public EncryptionType getUnderstoodEncryptionType() {
|
|
||||||
return EncryptionType.ENCRYPTED_SCRYPT_AES;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "AES-" + KEY_LENGTH * 8 + "-CBC, Scrypt (" + scryptParametersString() + ")";
|
return "AES-" + KEY_LENGTH * 8 + "-CBC, Scrypt (" + scryptParametersString() + ")";
|
||||||
|
@ -163,7 +159,7 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
return Objects.equals(scryptParameters, ((ScryptKeyCrypter)o).scryptParameters);
|
return Objects.equals(scryptParameters, ((ScryptKeyDeriver)o).scryptParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ScryptParameters {
|
public static class ScryptParameters {
|
|
@ -1,6 +1,7 @@
|
||||||
package com.sparrowwallet.drongo.wallet;
|
package com.sparrowwallet.drongo.wallet;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
|
import com.sparrowwallet.drongo.crypto.Pbkdf2KeyDeriver;
|
||||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -108,7 +109,8 @@ public class Bip39MnemonicCode {
|
||||||
String mnemonic = String.join(" ", words);
|
String mnemonic = String.join(" ", words);
|
||||||
String salt = "mnemonic" + Normalizer.normalize(passphrase, Normalizer.Form.NFKD);
|
String salt = "mnemonic" + Normalizer.normalize(passphrase, Normalizer.Form.NFKD);
|
||||||
|
|
||||||
return Utils.getPbkdf2HmacSha512Hash(mnemonic.getBytes(StandardCharsets.UTF_8), salt.getBytes(StandardCharsets.UTF_8), PBKDF2_ROUNDS);
|
Pbkdf2KeyDeriver keyDeriver = new Pbkdf2KeyDeriver(salt.getBytes(StandardCharsets.UTF_8), PBKDF2_ROUNDS);
|
||||||
|
return keyDeriver.deriveKey(mnemonic).getKeyBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -158,7 +158,7 @@ public class DeterministicSeed implements EncryptableItem {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EncryptionType getEncryptionType() {
|
public EncryptionType getEncryptionType() {
|
||||||
return EncryptionType.ENCRYPTED_SCRYPT_AES;
|
return new EncryptionType(EncryptionType.Deriver.SCRYPT, EncryptionType.Crypter.AES_CBC_PKCS7);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -174,27 +174,41 @@ public class DeterministicSeed implements EncryptableItem {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeterministicSeed encrypt(KeyCrypter keyCrypter, Key aesKey) {
|
public DeterministicSeed encrypt(String password) {
|
||||||
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");
|
||||||
}
|
}
|
||||||
EncryptedData encryptedMnemonic = keyCrypter.encrypt(getMnemonicAsBytes(), null, aesKey);
|
KeyDeriver keyDeriver = getEncryptionType().getDeriver().getKeyDeriver();
|
||||||
return new DeterministicSeed(encryptedMnemonic, needsPassphrase, creationTimeSeconds, type);
|
Key key = keyDeriver.deriveKey(password);
|
||||||
|
|
||||||
|
KeyCrypter keyCrypter = getEncryptionType().getCrypter().getKeyCrypter();
|
||||||
|
EncryptedData encryptedMnemonic = keyCrypter.encrypt(getMnemonicAsBytes(), null, key);
|
||||||
|
DeterministicSeed seed = new DeterministicSeed(encryptedMnemonic, needsPassphrase, creationTimeSeconds, type);
|
||||||
|
seed.setPassphrase(passphrase);
|
||||||
|
|
||||||
|
return seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getMnemonicAsBytes() {
|
private byte[] getMnemonicAsBytes() {
|
||||||
return getMnemonicString().getBytes(StandardCharsets.UTF_8);
|
return getMnemonicString().getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeterministicSeed decrypt(KeyCrypter crypter, Key aesKey) {
|
public DeterministicSeed decrypt(String password) {
|
||||||
if(!isEncrypted()) {
|
if(!isEncrypted()) {
|
||||||
throw new IllegalStateException("Cannot decrypt unencrypted seed");
|
throw new IllegalStateException("Cannot decrypt unencrypted seed");
|
||||||
}
|
}
|
||||||
List<String> mnemonic = decodeMnemonicCode(crypter.decrypt(encryptedMnemonicCode, aesKey));
|
KeyDeriver keyDeriver = getEncryptionType().getDeriver().getKeyDeriver(encryptedMnemonicCode.getKeySalt());
|
||||||
return new DeterministicSeed(mnemonic, needsPassphrase, creationTimeSeconds, type);
|
Key key = keyDeriver.deriveKey(password);
|
||||||
|
|
||||||
|
KeyCrypter keyCrypter = getEncryptionType().getCrypter().getKeyCrypter();
|
||||||
|
List<String> mnemonic = decodeMnemonicCode(keyCrypter.decrypt(encryptedMnemonicCode, key));
|
||||||
|
DeterministicSeed seed = new DeterministicSeed(mnemonic, needsPassphrase, creationTimeSeconds, type);
|
||||||
|
seed.setPassphrase(passphrase);
|
||||||
|
|
||||||
|
return seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.sparrowwallet.drongo.wallet;
|
package com.sparrowwallet.drongo.wallet;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
|
import com.sparrowwallet.drongo.crypto.Pbkdf2KeyDeriver;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.text.Normalizer;
|
import java.text.Normalizer;
|
||||||
|
@ -33,7 +34,8 @@ public class ElectrumMnemonicCode {
|
||||||
String mnemonic = Normalizer.normalize(mnemonicWords, Normalizer.Form.NFKD);
|
String mnemonic = Normalizer.normalize(mnemonicWords, Normalizer.Form.NFKD);
|
||||||
String salt = "electrum" + Normalizer.normalize(passphrase, Normalizer.Form.NFKD);
|
String salt = "electrum" + Normalizer.normalize(passphrase, Normalizer.Form.NFKD);
|
||||||
|
|
||||||
return Utils.getPbkdf2HmacSha512Hash(mnemonic.getBytes(StandardCharsets.UTF_8), salt.getBytes(StandardCharsets.UTF_8), PBKDF2_ROUNDS);
|
Pbkdf2KeyDeriver keyDeriver = new Pbkdf2KeyDeriver(salt.getBytes(StandardCharsets.UTF_8), PBKDF2_ROUNDS);
|
||||||
|
return keyDeriver.deriveKey(mnemonic).getKeyBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -187,15 +187,13 @@ public class Keystore {
|
||||||
|
|
||||||
public void encrypt(String password) {
|
public void encrypt(String password) {
|
||||||
if(seed != null && !seed.isEncrypted()) {
|
if(seed != null && !seed.isEncrypted()) {
|
||||||
KeyCrypter keyCrypter = new ScryptKeyCrypter();
|
seed = seed.encrypt(password);
|
||||||
seed = seed.encrypt(keyCrypter, keyCrypter.deriveKey(password));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void decrypt(String password) {
|
public void decrypt(String password) {
|
||||||
if(seed != null && seed.isEncrypted()) {
|
if(seed != null && seed.isEncrypted()) {
|
||||||
KeyCrypter keyCrypter = new ScryptKeyCrypter(seed.getEncryptedData().getKeySalt());
|
seed = seed.decrypt(password);
|
||||||
seed = seed.decrypt(keyCrypter, keyCrypter.deriveKey(password));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,10 @@ public class ECIESKeyCrypterTest {
|
||||||
byte[] testMessageBytes = testMessage.getBytes(StandardCharsets.UTF_8);
|
byte[] testMessageBytes = testMessage.getBytes(StandardCharsets.UTF_8);
|
||||||
byte[] initializationVector = "BIE1".getBytes(StandardCharsets.UTF_8);
|
byte[] initializationVector = "BIE1".getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
AsymmetricKeyCrypter keyCrypter = new ECIESKeyCrypter();
|
AsymmetricKeyDeriver keyDeriver = new Pbkdf2KeyDeriver();
|
||||||
|
ECKey key = keyDeriver.deriveECKey("iampassword");
|
||||||
|
|
||||||
ECKey key = keyCrypter.deriveECKey("iampassword");
|
AsymmetricKeyCrypter keyCrypter = new ECIESKeyCrypter();
|
||||||
EncryptedData encryptedData = keyCrypter.encrypt(testMessageBytes, initializationVector, key);
|
EncryptedData encryptedData = keyCrypter.encrypt(testMessageBytes, initializationVector, key);
|
||||||
byte[] crypterDecrypted = keyCrypter.decrypt(encryptedData, key);
|
byte[] crypterDecrypted = keyCrypter.decrypt(encryptedData, key);
|
||||||
|
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
public class ScryptKeyCrypterTest {
|
|
||||||
@Test
|
|
||||||
public void testScrypt() {
|
|
||||||
ScryptKeyCrypter scryptKeyCrypter = new ScryptKeyCrypter();
|
|
||||||
Key key = scryptKeyCrypter.deriveKey("pass");
|
|
||||||
|
|
||||||
String message = "absent essay fox snake vast pumpkin height crouch silent bulb excuse razor";
|
|
||||||
byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
byte[] iv = new byte[16];
|
|
||||||
SecureRandom secureRandom = new SecureRandom();
|
|
||||||
secureRandom.nextBytes(iv);
|
|
||||||
|
|
||||||
EncryptedData scrypted = scryptKeyCrypter.encrypt(messageBytes, iv, key);
|
|
||||||
|
|
||||||
AESKeyCrypter aesKeyCrypter = new AESKeyCrypter();
|
|
||||||
EncryptedData aescrypted = aesKeyCrypter.encrypt(messageBytes, iv, key);
|
|
||||||
|
|
||||||
Assert.assertArrayEquals(scrypted.getEncryptedBytes(), aescrypted.getEncryptedBytes());
|
|
||||||
|
|
||||||
byte[] sdecrypted = scryptKeyCrypter.decrypt(scrypted, key);
|
|
||||||
byte[] aesdecrypted = aesKeyCrypter.decrypt(aescrypted, key);
|
|
||||||
|
|
||||||
Assert.assertArrayEquals(sdecrypted, aesdecrypted);
|
|
||||||
|
|
||||||
String decryptedMessage = new String(sdecrypted, StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
Assert.assertEquals(message, decryptedMessage);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
public class ScryptKeyDeriverTest {
|
||||||
|
@Test
|
||||||
|
public void testScrypt() {
|
||||||
|
ScryptKeyDeriver scryptKeyDeriver = new ScryptKeyDeriver();
|
||||||
|
Key key = scryptKeyDeriver.deriveKey("pass");
|
||||||
|
|
||||||
|
KeyCrypter keyCrypter = new AESKeyCrypter();
|
||||||
|
|
||||||
|
String message = "absent essay fox snake vast pumpkin height crouch silent bulb excuse razor";
|
||||||
|
byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
byte[] iv = new byte[16];
|
||||||
|
SecureRandom secureRandom = new SecureRandom();
|
||||||
|
secureRandom.nextBytes(iv);
|
||||||
|
|
||||||
|
EncryptedData scrypted = keyCrypter.encrypt(messageBytes, iv, key);
|
||||||
|
|
||||||
|
//Decrypt
|
||||||
|
|
||||||
|
ScryptKeyDeriver scryptKeyDeriver2 = new ScryptKeyDeriver(scrypted.getKeySalt());
|
||||||
|
Key key2 = scryptKeyDeriver2.deriveKey("pass");
|
||||||
|
|
||||||
|
byte[] sdecrypted = keyCrypter.decrypt(scrypted, key2);
|
||||||
|
String decryptedMessage = new String(sdecrypted, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
Assert.assertEquals(message, decryptedMessage);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,6 @@
|
||||||
package com.sparrowwallet.drongo.wallet;
|
package com.sparrowwallet.drongo.wallet;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.crypto.Key;
|
|
||||||
import com.sparrowwallet.drongo.crypto.KeyCrypter;
|
|
||||||
import com.sparrowwallet.drongo.crypto.ScryptKeyCrypter;
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -12,17 +9,10 @@ public class DeterministicSeedTest {
|
||||||
public void testEncryption() {
|
public void testEncryption() {
|
||||||
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";
|
||||||
|
|
||||||
KeyCrypter keyCrypter = new ScryptKeyCrypter();
|
|
||||||
Key key = keyCrypter.deriveKey("pass");
|
|
||||||
DeterministicSeed seed = new DeterministicSeed(words, "pp", 0, DeterministicSeed.Type.BIP39);
|
DeterministicSeed seed = new DeterministicSeed(words, "pp", 0, DeterministicSeed.Type.BIP39);
|
||||||
DeterministicSeed encryptedSeed = seed.encrypt(keyCrypter, key);
|
DeterministicSeed encryptedSeed = seed.encrypt("pass");
|
||||||
|
|
||||||
System.out.println(Utils.bytesToHex(encryptedSeed.getEncryptedData().getInitialisationVector()));
|
DeterministicSeed decryptedSeed = encryptedSeed.decrypt("pass");
|
||||||
System.out.println(Utils.bytesToHex(encryptedSeed.getEncryptedData().getEncryptedBytes()));
|
Assert.assertEquals(words, decryptedSeed.getMnemonicString());
|
||||||
|
|
||||||
KeyCrypter keyCrypter2 = new ScryptKeyCrypter();
|
|
||||||
Key key2 = keyCrypter2.deriveKey("pass");
|
|
||||||
seed = encryptedSeed.decrypt(keyCrypter2, key2);
|
|
||||||
Assert.assertEquals(words, seed.getMnemonicString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue