refactor keycrypters

This commit is contained in:
Craig Raw 2020-05-17 12:51:56 +02:00
parent 312143cb61
commit e20501d954
24 changed files with 360 additions and 188 deletions

View file

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

View file

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

View file

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

View file

@ -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.
* *

View file

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

View file

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

View file

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

View file

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

View file

@ -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. */

View file

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

View file

@ -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 +
']';
}
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -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 {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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