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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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/" */
|
||||
public static String formatHDPath(List<ChildNumber> path) {
|
||||
StringJoiner joiner = new StringJoiner("/");
|
||||
|
@ -294,10 +280,4 @@ public class Utils {
|
|||
hmacSha512.doFinal(out, 0);
|
||||
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();
|
||||
|
||||
@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.
|
||||
*
|
||||
|
@ -93,9 +83,14 @@ public class AESKeyCrypter implements KeyCrypter {
|
|||
final int length1 = cipher.processBytes(plainBytes, 0, plainBytes.length, encryptedBytes, 0);
|
||||
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) {
|
||||
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;
|
||||
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||
import org.bouncycastle.crypto.macs.HMac;
|
||||
|
@ -8,7 +7,6 @@ import org.bouncycastle.crypto.params.KeyParameter;
|
|||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
|
@ -18,21 +16,6 @@ import java.util.Base64;
|
|||
public class ECIESKeyCrypter implements AsymmetricKeyCrypter {
|
||||
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
|
||||
public byte[] decrypt(EncryptedData encryptedBytesToDecode, ECKey key) throws KeyCrypterException {
|
||||
return decryptEcies(encryptedBytesToDecode.getEncryptedBytes(), encryptedBytesToDecode.getInitialisationVector(), key);
|
||||
|
@ -65,13 +48,13 @@ public class ECIESKeyCrypter implements AsymmetricKeyCrypter {
|
|||
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
|
||||
public EncryptedData encrypt(byte[] plainBytes, byte[] initializationVector, ECKey key) throws KeyCrypterException {
|
||||
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) {
|
||||
|
@ -83,7 +66,7 @@ public class ECIESKeyCrypter implements AsymmetricKeyCrypter {
|
|||
byte[] key_e = Arrays.copyOfRange(hash, 16, 32);
|
||||
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[] result = hmac256(key_m, encrypted);
|
||||
return Base64.getEncoder().encode(concat(encrypted, result));
|
||||
|
|
|
@ -831,7 +831,7 @@ public class ECKey implements EncryptableItem {
|
|||
|
||||
@Override
|
||||
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. */
|
||||
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();
|
||||
|
||||
/** 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[] encryptedBytes;
|
||||
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.encryptedBytes = Arrays.copyOf(encryptedBytes, encryptedBytes.length);
|
||||
this.keySalt = keySalt == null ? null : Arrays.copyOf(keySalt, keySalt.length);
|
||||
this.encryptionType = encryptionType;
|
||||
}
|
||||
|
||||
public byte[] getInitialisationVector() {
|
||||
|
@ -33,29 +39,38 @@ public final class EncryptedData {
|
|||
return keySalt;
|
||||
}
|
||||
|
||||
public EncryptionType getEncryptionType() {
|
||||
return encryptionType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
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
|
||||
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
|
||||
public String toString() {
|
||||
return "EncryptedData [initialisationVector=" + Arrays.toString(initialisationVector)
|
||||
+ ", encryptedPrivateKey=" + Arrays.toString(encryptedBytes)
|
||||
+ ", keySalt=" + Arrays.toString(keySalt) + "]";
|
||||
+ ", keySalt=" + Arrays.toString(keySalt)
|
||||
+ ", type=" + encryptionType + "]";
|
||||
}
|
||||
|
||||
public EncryptedData copy() {
|
||||
return new EncryptedData(Arrays.copyOf(initialisationVector, initialisationVector.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;
|
||||
|
||||
public enum EncryptionType {
|
||||
UNENCRYPTED, ENCRYPTED_AES, ENCRYPTED_SCRYPT_AES, ENCRYPTED_ECIES_AES;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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 {
|
||||
private final byte[] keyBytes;
|
||||
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.salt = salt;
|
||||
this.deriver = deriver;
|
||||
}
|
||||
|
||||
public byte[] getKeyBytes() {
|
||||
|
@ -16,4 +18,8 @@ public class Key {
|
|||
public byte[] getSalt() {
|
||||
return salt;
|
||||
}
|
||||
|
||||
public EncryptionType.Deriver getDeriver() {
|
||||
return deriver;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +1,6 @@
|
|||
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 {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -39,8 +11,10 @@ public interface KeyCrypter {
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
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
|
||||
* the AES symmetric cipher. Eight bytes of salt is used to prevent dictionary attacks.</p>
|
||||
*/
|
||||
public class ScryptKeyCrypter extends AESKeyCrypter {
|
||||
private static final Logger log = LoggerFactory.getLogger(ScryptKeyCrypter.class);
|
||||
public class ScryptKeyDeriver implements KeyDeriver {
|
||||
private static final Logger log = LoggerFactory.getLogger(ScryptKeyDeriver.class);
|
||||
|
||||
/**
|
||||
* Key length in bytes.
|
||||
|
@ -49,14 +49,14 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
|
|||
/**
|
||||
* Encryption/Decryption using default parameters and a random salt.
|
||||
*/
|
||||
public ScryptKeyCrypter() {
|
||||
public ScryptKeyDeriver() {
|
||||
this.scryptParameters = new ScryptParameters(randomSalt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encryption/Decryption using default parameters and provided salt.
|
||||
*/
|
||||
public ScryptKeyCrypter(byte[] salt) {
|
||||
public ScryptKeyDeriver(byte[] salt) {
|
||||
this.scryptParameters = new ScryptParameters(salt);
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
|
|||
* @param iterations
|
||||
* number of scrypt iterations
|
||||
*/
|
||||
public ScryptKeyCrypter(int iterations) {
|
||||
public ScryptKeyDeriver(int iterations) {
|
||||
this.scryptParameters = new ScryptParameters(randomSalt(), iterations);
|
||||
}
|
||||
|
||||
|
@ -77,13 +77,18 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
|
|||
* @param scryptParameters ScryptParameters to use
|
||||
* @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;
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncryptionType.Deriver getDeriverType() {
|
||||
return EncryptionType.Deriver.SCRYPT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate AES key.
|
||||
*
|
||||
|
@ -94,7 +99,7 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
|
|||
* @throws KeyCrypterException
|
||||
*/
|
||||
@Override
|
||||
public Key deriveKey(CharSequence password) throws KeyCrypterException {
|
||||
public Key deriveKey(String password) throws KeyCrypterException {
|
||||
byte[] passwordBytes = null;
|
||||
try {
|
||||
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);
|
||||
return new Key(keyBytes, scryptParameters.getSalt());
|
||||
return new Key(keyBytes, scryptParameters.getSalt(), getDeriverType());
|
||||
} catch (Exception e) {
|
||||
throw new KeyCrypterException("Could not generate key from password and salt.", e);
|
||||
} finally {
|
||||
|
@ -136,15 +141,6 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
|
|||
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
|
||||
public String toString() {
|
||||
return "AES-" + KEY_LENGTH * 8 + "-CBC, Scrypt (" + scryptParametersString() + ")";
|
||||
|
@ -163,7 +159,7 @@ public class ScryptKeyCrypter extends AESKeyCrypter {
|
|||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
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 {
|
|
@ -1,6 +1,7 @@
|
|||
package com.sparrowwallet.drongo.wallet;
|
||||
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.crypto.Pbkdf2KeyDeriver;
|
||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -108,7 +109,8 @@ public class Bip39MnemonicCode {
|
|||
String mnemonic = String.join(" ", words);
|
||||
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
|
||||
public EncryptionType getEncryptionType() {
|
||||
return EncryptionType.ENCRYPTED_SCRYPT_AES;
|
||||
return new EncryptionType(EncryptionType.Deriver.SCRYPT, EncryptionType.Crypter.AES_CBC_PKCS7);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -174,27 +174,41 @@ public class DeterministicSeed implements EncryptableItem {
|
|||
return type;
|
||||
}
|
||||
|
||||
public DeterministicSeed encrypt(KeyCrypter keyCrypter, Key aesKey) {
|
||||
public DeterministicSeed encrypt(String password) {
|
||||
if(encryptedMnemonicCode != null) {
|
||||
throw new IllegalArgumentException("Trying to encrypt twice");
|
||||
}
|
||||
if(mnemonicCode == null) {
|
||||
throw new IllegalArgumentException("Mnemonic missing so cannot encrypt");
|
||||
}
|
||||
EncryptedData encryptedMnemonic = keyCrypter.encrypt(getMnemonicAsBytes(), null, aesKey);
|
||||
return new DeterministicSeed(encryptedMnemonic, needsPassphrase, creationTimeSeconds, type);
|
||||
KeyDeriver keyDeriver = getEncryptionType().getDeriver().getKeyDeriver();
|
||||
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() {
|
||||
return getMnemonicString().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public DeterministicSeed decrypt(KeyCrypter crypter, Key aesKey) {
|
||||
public DeterministicSeed decrypt(String password) {
|
||||
if(!isEncrypted()) {
|
||||
throw new IllegalStateException("Cannot decrypt unencrypted seed");
|
||||
}
|
||||
List<String> mnemonic = decodeMnemonicCode(crypter.decrypt(encryptedMnemonicCode, aesKey));
|
||||
return new DeterministicSeed(mnemonic, needsPassphrase, creationTimeSeconds, type);
|
||||
KeyDeriver keyDeriver = getEncryptionType().getDeriver().getKeyDeriver(encryptedMnemonicCode.getKeySalt());
|
||||
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
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.sparrowwallet.drongo.wallet;
|
||||
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.crypto.Pbkdf2KeyDeriver;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.Normalizer;
|
||||
|
@ -33,7 +34,8 @@ public class ElectrumMnemonicCode {
|
|||
String mnemonic = Normalizer.normalize(mnemonicWords, 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) {
|
||||
if(seed != null && !seed.isEncrypted()) {
|
||||
KeyCrypter keyCrypter = new ScryptKeyCrypter();
|
||||
seed = seed.encrypt(keyCrypter, keyCrypter.deriveKey(password));
|
||||
seed = seed.encrypt(password);
|
||||
}
|
||||
}
|
||||
|
||||
public void decrypt(String password) {
|
||||
if(seed != null && seed.isEncrypted()) {
|
||||
KeyCrypter keyCrypter = new ScryptKeyCrypter(seed.getEncryptedData().getKeySalt());
|
||||
seed = seed.decrypt(keyCrypter, keyCrypter.deriveKey(password));
|
||||
seed = seed.decrypt(password);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,10 @@ public class ECIESKeyCrypterTest {
|
|||
byte[] testMessageBytes = testMessage.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);
|
||||
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;
|
||||
|
||||
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.Test;
|
||||
|
||||
|
@ -12,17 +9,10 @@ public class DeterministicSeedTest {
|
|||
public void testEncryption() {
|
||||
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 encryptedSeed = seed.encrypt(keyCrypter, key);
|
||||
DeterministicSeed encryptedSeed = seed.encrypt("pass");
|
||||
|
||||
System.out.println(Utils.bytesToHex(encryptedSeed.getEncryptedData().getInitialisationVector()));
|
||||
System.out.println(Utils.bytesToHex(encryptedSeed.getEncryptedData().getEncryptedBytes()));
|
||||
|
||||
KeyCrypter keyCrypter2 = new ScryptKeyCrypter();
|
||||
Key key2 = keyCrypter2.deriveKey("pass");
|
||||
seed = encryptedSeed.decrypt(keyCrypter2, key2);
|
||||
Assert.assertEquals(words, seed.getMnemonicString());
|
||||
DeterministicSeed decryptedSeed = encryptedSeed.decrypt("pass");
|
||||
Assert.assertEquals(words, decryptedSeed.getMnemonicString());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue