From e20501d95422bb4ef76002cb7a42c46b856143d9 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Sun, 17 May 2020 12:51:56 +0200 Subject: [PATCH] refactor keycrypters --- .../java/com/sparrowwallet/drongo/Utils.java | 20 --- .../drongo/crypto/AESKeyCrypter.java | 17 +-- .../drongo/crypto/Argon2KeyDeriver.java | 13 ++ .../drongo/crypto/AsymmetricKeyCrypter.java | 14 -- .../drongo/crypto/AsymmetricKeyDeriver.java | 11 ++ .../drongo/crypto/DoubleSha256KeyDeriver.java | 20 +++ .../drongo/crypto/ECIESKeyCrypter.java | 23 +--- .../sparrowwallet/drongo/crypto/ECKey.java | 2 +- .../drongo/crypto/EncryptableItem.java | 2 +- .../drongo/crypto/EncryptedData.java | 25 +++- .../drongo/crypto/EncryptionType.java | 129 +++++++++++++++++- .../com/sparrowwallet/drongo/crypto/Key.java | 8 +- .../drongo/crypto/KeyCrypter.java | 32 +---- .../drongo/crypto/KeyDeriver.java | 13 ++ .../drongo/crypto/Pbkdf2KeyDeriver.java | 50 +++++++ ...tKeyCrypter.java => ScryptKeyDeriver.java} | 32 ++--- .../drongo/wallet/Bip39MnemonicCode.java | 4 +- .../drongo/wallet/DeterministicSeed.java | 28 +++- .../drongo/wallet/ElectrumMnemonicCode.java | 4 +- .../sparrowwallet/drongo/wallet/Keystore.java | 6 +- .../drongo/crypto/ECIESKeyCrypterTest.java | 5 +- .../drongo/crypto/ScryptKeyCrypterTest.java | 38 ------ .../drongo/crypto/ScryptKeyDeriverTest.java | 36 +++++ .../drongo/wallet/DeterministicSeedTest.java | 16 +-- 24 files changed, 360 insertions(+), 188 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/drongo/crypto/Argon2KeyDeriver.java create mode 100644 src/main/java/com/sparrowwallet/drongo/crypto/AsymmetricKeyDeriver.java create mode 100644 src/main/java/com/sparrowwallet/drongo/crypto/DoubleSha256KeyDeriver.java create mode 100644 src/main/java/com/sparrowwallet/drongo/crypto/KeyDeriver.java create mode 100644 src/main/java/com/sparrowwallet/drongo/crypto/Pbkdf2KeyDeriver.java rename src/main/java/com/sparrowwallet/drongo/crypto/{ScryptKeyCrypter.java => ScryptKeyDeriver.java} (89%) delete mode 100644 src/test/java/com/sparrowwallet/drongo/crypto/ScryptKeyCrypterTest.java create mode 100644 src/test/java/com/sparrowwallet/drongo/crypto/ScryptKeyDeriverTest.java diff --git a/src/main/java/com/sparrowwallet/drongo/Utils.java b/src/main/java/com/sparrowwallet/drongo/Utils.java index 1466f37..6d37401 100644 --- a/src/main/java/com/sparrowwallet/drongo/Utils.java +++ b/src/main/java/com/sparrowwallet/drongo/Utils.java @@ -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 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(); - } } diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/AESKeyCrypter.java b/src/main/java/com/sparrowwallet/drongo/crypto/AESKeyCrypter.java index 4825284..3020dbe 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/AESKeyCrypter.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/AESKeyCrypter.java @@ -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; + } } diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/Argon2KeyDeriver.java b/src/main/java/com/sparrowwallet/drongo/crypto/Argon2KeyDeriver.java new file mode 100644 index 0000000..e2d4bd5 --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/crypto/Argon2KeyDeriver.java @@ -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; + } +} diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/AsymmetricKeyCrypter.java b/src/main/java/com/sparrowwallet/drongo/crypto/AsymmetricKeyCrypter.java index c38a90a..e2ac29e 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/AsymmetricKeyCrypter.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/AsymmetricKeyCrypter.java @@ -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. * diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/AsymmetricKeyDeriver.java b/src/main/java/com/sparrowwallet/drongo/crypto/AsymmetricKeyDeriver.java new file mode 100644 index 0000000..5a5815b --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/crypto/AsymmetricKeyDeriver.java @@ -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; +} diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/DoubleSha256KeyDeriver.java b/src/main/java/com/sparrowwallet/drongo/crypto/DoubleSha256KeyDeriver.java new file mode 100644 index 0000000..9f358a2 --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/crypto/DoubleSha256KeyDeriver.java @@ -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; + } +} diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/ECIESKeyCrypter.java b/src/main/java/com/sparrowwallet/drongo/crypto/ECIESKeyCrypter.java index 1c958e6..21f3e2e 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/ECIESKeyCrypter.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/ECIESKeyCrypter.java @@ -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)); diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/ECKey.java b/src/main/java/com/sparrowwallet/drongo/crypto/ECKey.java index f6907fd..d182079 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/ECKey.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/ECKey.java @@ -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); } /** diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/EncryptableItem.java b/src/main/java/com/sparrowwallet/drongo/crypto/EncryptableItem.java index 3c9df08..d9ffb1c 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/EncryptableItem.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/EncryptableItem.java @@ -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. */ diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/EncryptedData.java b/src/main/java/com/sparrowwallet/drongo/crypto/EncryptedData.java index ecfa98b..d5152c8 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/EncryptedData.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/EncryptedData.java @@ -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); } } diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/EncryptionType.java b/src/main/java/com/sparrowwallet/drongo/crypto/EncryptionType.java index 09ec194..01540c1 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/EncryptionType.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/EncryptionType.java @@ -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 + + ']'; + } } diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/Key.java b/src/main/java/com/sparrowwallet/drongo/crypto/Key.java index 45c9010..a379153 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/Key.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/Key.java @@ -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; + } } diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/KeyCrypter.java b/src/main/java/com/sparrowwallet/drongo/crypto/KeyCrypter.java index 03f79ff..373ebc2 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/KeyCrypter.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/KeyCrypter.java @@ -1,34 +1,6 @@ package com.sparrowwallet.drongo.crypto; -/** - *

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:

- * - *

(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.

- *

(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.

- *

(3) To decrypt an EncryptedData, repeat step (1) to get a Key, then call decrypt().

- * - *

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.

- */ 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(); } diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/KeyDeriver.java b/src/main/java/com/sparrowwallet/drongo/crypto/KeyDeriver.java new file mode 100644 index 0000000..2bbd2aa --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/crypto/KeyDeriver.java @@ -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(); +} diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/Pbkdf2KeyDeriver.java b/src/main/java/com/sparrowwallet/drongo/crypto/Pbkdf2KeyDeriver.java new file mode 100644 index 0000000..5afba91 --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/crypto/Pbkdf2KeyDeriver.java @@ -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; + } +} diff --git a/src/main/java/com/sparrowwallet/drongo/crypto/ScryptKeyCrypter.java b/src/main/java/com/sparrowwallet/drongo/crypto/ScryptKeyDeriver.java similarity index 89% rename from src/main/java/com/sparrowwallet/drongo/crypto/ScryptKeyCrypter.java rename to src/main/java/com/sparrowwallet/drongo/crypto/ScryptKeyDeriver.java index 8fe0f1e..a8a475d 100644 --- a/src/main/java/com/sparrowwallet/drongo/crypto/ScryptKeyCrypter.java +++ b/src/main/java/com/sparrowwallet/drongo/crypto/ScryptKeyDeriver.java @@ -21,8 +21,8 @@ import java.util.Objects; *

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.

*/ -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 { diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/Bip39MnemonicCode.java b/src/main/java/com/sparrowwallet/drongo/wallet/Bip39MnemonicCode.java index 9e666e5..3dc0d21 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/Bip39MnemonicCode.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/Bip39MnemonicCode.java @@ -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(); } /** diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/DeterministicSeed.java b/src/main/java/com/sparrowwallet/drongo/wallet/DeterministicSeed.java index 5e3246e..1422c16 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/DeterministicSeed.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/DeterministicSeed.java @@ -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 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 mnemonic = decodeMnemonicCode(keyCrypter.decrypt(encryptedMnemonicCode, key)); + DeterministicSeed seed = new DeterministicSeed(mnemonic, needsPassphrase, creationTimeSeconds, type); + seed.setPassphrase(passphrase); + + return seed; } @Override diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/ElectrumMnemonicCode.java b/src/main/java/com/sparrowwallet/drongo/wallet/ElectrumMnemonicCode.java index fae0782..03ccf81 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/ElectrumMnemonicCode.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/ElectrumMnemonicCode.java @@ -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(); } /** diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java b/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java index b421158..bf4d690 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java @@ -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); } } } diff --git a/src/test/java/com/sparrowwallet/drongo/crypto/ECIESKeyCrypterTest.java b/src/test/java/com/sparrowwallet/drongo/crypto/ECIESKeyCrypterTest.java index 09b0bd6..da84478 100644 --- a/src/test/java/com/sparrowwallet/drongo/crypto/ECIESKeyCrypterTest.java +++ b/src/test/java/com/sparrowwallet/drongo/crypto/ECIESKeyCrypterTest.java @@ -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); diff --git a/src/test/java/com/sparrowwallet/drongo/crypto/ScryptKeyCrypterTest.java b/src/test/java/com/sparrowwallet/drongo/crypto/ScryptKeyCrypterTest.java deleted file mode 100644 index 573f505..0000000 --- a/src/test/java/com/sparrowwallet/drongo/crypto/ScryptKeyCrypterTest.java +++ /dev/null @@ -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); - } -} diff --git a/src/test/java/com/sparrowwallet/drongo/crypto/ScryptKeyDeriverTest.java b/src/test/java/com/sparrowwallet/drongo/crypto/ScryptKeyDeriverTest.java new file mode 100644 index 0000000..27b81c2 --- /dev/null +++ b/src/test/java/com/sparrowwallet/drongo/crypto/ScryptKeyDeriverTest.java @@ -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); + } +} diff --git a/src/test/java/com/sparrowwallet/drongo/wallet/DeterministicSeedTest.java b/src/test/java/com/sparrowwallet/drongo/wallet/DeterministicSeedTest.java index daaa8ad..6dea0ab 100644 --- a/src/test/java/com/sparrowwallet/drongo/wallet/DeterministicSeedTest.java +++ b/src/test/java/com/sparrowwallet/drongo/wallet/DeterministicSeedTest.java @@ -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()); } }