mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 18:16:45 +00:00
securely handle mnemonic seed in memory
This commit is contained in:
parent
9f5f5689bb
commit
d2bd335e76
11 changed files with 129 additions and 74 deletions
|
@ -1,5 +1,8 @@
|
||||||
package com.sparrowwallet.drongo;
|
package com.sparrowwallet.drongo;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@ -97,4 +100,40 @@ public class SecureString implements CharSequence {
|
||||||
chars[i] = pad[i] ^ charAt;
|
chars[i] = pad[i] ^ charAt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] toBytesUTF8(CharSequence charSequence) {
|
||||||
|
CharBuffer charBuffer = CharBuffer.wrap(charSequence);
|
||||||
|
ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
|
||||||
|
byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
|
||||||
|
Arrays.fill(byteBuffer.array(), (byte)0); // clear sensitive data
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SecureString fromBytesUTF8(byte[] bytes) {
|
||||||
|
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
|
||||||
|
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(byteBuffer);
|
||||||
|
SecureString secureString = new SecureString(charBuffer);
|
||||||
|
Arrays.fill(charBuffer.array(), (char)0);
|
||||||
|
return secureString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] toBytesUTF16(CharSequence charSequence) {
|
||||||
|
byte[] byteArray = new byte[charSequence.length() << 1];
|
||||||
|
for(int i = 0; i < charSequence.length(); i++) {
|
||||||
|
int bytePosition = i << 1;
|
||||||
|
byteArray[bytePosition] = (byte) ((charSequence.charAt(i)&0xFF00)>>8);
|
||||||
|
byteArray[bytePosition + 1] = (byte) (charSequence.charAt(i)&0x00FF);
|
||||||
|
}
|
||||||
|
return byteArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidUTF16(CharSequence charSequence) {
|
||||||
|
for (int i = 0; i < charSequence.length(); i++) {
|
||||||
|
if (Character.isLowSurrogate(charSequence.charAt(i)) && (i == 0 || !Character.isHighSurrogate(charSequence.charAt(i - 1)))
|
||||||
|
|| Character.isHighSurrogate(charSequence.charAt(i)) && (i == charSequence.length() -1 || !Character.isLowSurrogate(charSequence.charAt(i + 1)))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -283,40 +283,4 @@ public class Utils {
|
||||||
hmacSha512.doFinal(out, 0);
|
hmacSha512.doFinal(out, 0);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] toBytesUTF8(CharSequence charSequence) {
|
|
||||||
CharBuffer charBuffer = CharBuffer.wrap(charSequence);
|
|
||||||
ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
|
|
||||||
byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
|
|
||||||
Arrays.fill(byteBuffer.array(), (byte)0); // clear sensitive data
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SecureString fromBytesUTF8(byte[] bytes) {
|
|
||||||
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
|
|
||||||
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(byteBuffer);
|
|
||||||
SecureString secureString = new SecureString(charBuffer);
|
|
||||||
Arrays.fill(charBuffer.array(), (char)0);
|
|
||||||
return secureString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] toBytesUTF16(CharSequence charSequence) {
|
|
||||||
byte[] byteArray = new byte[charSequence.length() << 1];
|
|
||||||
for(int i = 0; i < charSequence.length(); i++) {
|
|
||||||
int bytePosition = i << 1;
|
|
||||||
byteArray[bytePosition] = (byte) ((charSequence.charAt(i)&0xFF00)>>8);
|
|
||||||
byteArray[bytePosition + 1] = (byte) (charSequence.charAt(i)&0x00FF);
|
|
||||||
}
|
|
||||||
return byteArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isValidUTF16(CharSequence charSequence) {
|
|
||||||
for (int i = 0; i < charSequence.length(); i++) {
|
|
||||||
if (Character.isLowSurrogate(charSequence.charAt(i)) && (i == 0 || !Character.isHighSurrogate(charSequence.charAt(i - 1)))
|
|
||||||
|| Character.isHighSurrogate(charSequence.charAt(i)) && (i == charSequence.length() -1 || !Character.isLowSurrogate(charSequence.charAt(i + 1)))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,9 @@ public class AESKeyCrypter implements KeyCrypter {
|
||||||
final int length1 = cipher.processBytes(cipherBytes, 0, cipherBytes.length, decryptedBytes, 0);
|
final int length1 = cipher.processBytes(cipherBytes, 0, cipherBytes.length, decryptedBytes, 0);
|
||||||
final int length2 = cipher.doFinal(decryptedBytes, length1);
|
final int length2 = cipher.doFinal(decryptedBytes, length1);
|
||||||
|
|
||||||
return Arrays.copyOf(decryptedBytes, length1 + length2);
|
byte[] decrypted = Arrays.copyOf(decryptedBytes, length1 + length2);
|
||||||
|
Arrays.fill(decryptedBytes, (byte)0);
|
||||||
|
return decrypted;
|
||||||
} catch (InvalidCipherTextException e) {
|
} catch (InvalidCipherTextException e) {
|
||||||
throw new KeyCrypterException.InvalidCipherText("Could not decrypt bytes", e);
|
throw new KeyCrypterException.InvalidCipherText("Could not decrypt bytes", e);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.SecureString;
|
||||||
import de.mkammerer.argon2.Argon2Advanced;
|
import de.mkammerer.argon2.Argon2Advanced;
|
||||||
import de.mkammerer.argon2.Argon2Factory;
|
import de.mkammerer.argon2.Argon2Factory;
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ public class Argon2KeyDeriver implements KeyDeriver, AsymmetricKeyDeriver {
|
||||||
@Override
|
@Override
|
||||||
public Key deriveKey(CharSequence password) throws KeyCrypterException {
|
public Key deriveKey(CharSequence password) throws KeyCrypterException {
|
||||||
Argon2Advanced argon2 = Argon2Factory.createAdvanced(Argon2Factory.Argon2Types.ARGON2id, argon2Parameters.saltLength, argon2Parameters.hashLength);
|
Argon2Advanced argon2 = Argon2Factory.createAdvanced(Argon2Factory.Argon2Types.ARGON2id, argon2Parameters.saltLength, argon2Parameters.hashLength);
|
||||||
byte[] hash = argon2.rawHash(argon2Parameters.iterations, argon2Parameters.memory, argon2Parameters.parallelism, Utils.toBytesUTF8(password), salt);
|
byte[] hash = argon2.rawHash(argon2Parameters.iterations, argon2Parameters.memory, argon2Parameters.parallelism, SecureString.toBytesUTF8(password), salt);
|
||||||
return new Key(hash, salt, getDeriverType());
|
return new Key(hash, salt, getDeriverType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.SecureString;
|
||||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public class DoubleSha256KeyDeriver implements KeyDeriver {
|
public class DoubleSha256KeyDeriver implements KeyDeriver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Key deriveKey(CharSequence password) throws KeyCrypterException {
|
public Key deriveKey(CharSequence password) throws KeyCrypterException {
|
||||||
byte[] passwordBytes = Utils.toBytesUTF8(password);
|
byte[] passwordBytes = SecureString.toBytesUTF8(password);
|
||||||
byte[] sha256 = Sha256Hash.hash(passwordBytes);
|
byte[] sha256 = Sha256Hash.hash(passwordBytes);
|
||||||
byte[] doubleSha256 = Sha256Hash.hash(sha256);
|
byte[] doubleSha256 = Sha256Hash.hash(sha256);
|
||||||
return new Key(doubleSha256, null, getDeriverType());
|
return new Key(doubleSha256, null, getDeriverType());
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.SecureString;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class EncryptionType {
|
public class EncryptionType {
|
||||||
|
@ -12,7 +11,7 @@ public class EncryptionType {
|
||||||
return new KeyDeriver() {
|
return new KeyDeriver() {
|
||||||
@Override
|
@Override
|
||||||
public Key deriveKey(CharSequence password) throws KeyCrypterException {
|
public Key deriveKey(CharSequence password) throws KeyCrypterException {
|
||||||
return new Key(Utils.toBytesUTF8(password), null, NONE);
|
return new Key(SecureString.toBytesUTF8(password), null, NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class Key {
|
public class Key {
|
||||||
private final byte[] keyBytes;
|
private final byte[] keyBytes;
|
||||||
private final byte[] salt;
|
private final byte[] salt;
|
||||||
|
@ -22,4 +24,9 @@ public class Key {
|
||||||
public EncryptionType.Deriver getDeriver() {
|
public EncryptionType.Deriver getDeriver() {
|
||||||
return deriver;
|
return deriver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
Arrays.fill(keyBytes, (byte)0);
|
||||||
|
Arrays.fill(salt, (byte)0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.SecureString;
|
||||||
import org.bouncycastle.crypto.digests.SHA512Digest;
|
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||||
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
|
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
|
||||||
import org.bouncycastle.crypto.params.KeyParameter;
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
@ -36,7 +36,7 @@ public class Pbkdf2KeyDeriver implements KeyDeriver, AsymmetricKeyDeriver {
|
||||||
@Override
|
@Override
|
||||||
public Key deriveKey(CharSequence password) throws KeyCrypterException {
|
public Key deriveKey(CharSequence password) throws KeyCrypterException {
|
||||||
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA512Digest());
|
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA512Digest());
|
||||||
gen.init(Utils.toBytesUTF8(password), salt, iterationCount);
|
gen.init(SecureString.toBytesUTF8(password), salt, iterationCount);
|
||||||
byte[] keyBytes = ((KeyParameter)gen.generateDerivedParameters(512)).getKey();
|
byte[] keyBytes = ((KeyParameter)gen.generateDerivedParameters(512)).getKey();
|
||||||
return new Key(keyBytes, salt, getDeriverType());
|
return new Key(keyBytes, salt, getDeriverType());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package com.sparrowwallet.drongo.crypto;
|
package com.sparrowwallet.drongo.crypto;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.SecureString;
|
||||||
import org.bouncycastle.crypto.generators.SCrypt;
|
import org.bouncycastle.crypto.generators.SCrypt;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -103,7 +103,7 @@ public class ScryptKeyDeriver implements KeyDeriver {
|
||||||
public Key deriveKey(CharSequence password) throws KeyCrypterException {
|
public Key deriveKey(CharSequence password) throws KeyCrypterException {
|
||||||
byte[] passwordBytes = null;
|
byte[] passwordBytes = null;
|
||||||
try {
|
try {
|
||||||
passwordBytes = Utils.toBytesUTF8(password);
|
passwordBytes = SecureString.toBytesUTF8(password);
|
||||||
byte[] salt = new byte[0];
|
byte[] salt = new byte[0];
|
||||||
if (scryptParameters.getSalt() != null) {
|
if (scryptParameters.getSalt() != null) {
|
||||||
salt = scryptParameters.getSalt();
|
salt = scryptParameters.getSalt();
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package com.sparrowwallet.drongo.wallet;
|
package com.sparrowwallet.drongo.wallet;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.SecureString;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.crypto.*;
|
import com.sparrowwallet.drongo.crypto.*;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ public class DeterministicSeed implements EncryptableItem {
|
||||||
//Session only storage
|
//Session only storage
|
||||||
private transient String passphrase;
|
private transient String passphrase;
|
||||||
|
|
||||||
public DeterministicSeed(String mnemonicString, String passphrase, long creationTimeSeconds, Type type) {
|
public DeterministicSeed(CharSequence mnemonicString, String passphrase, long creationTimeSeconds, Type type) {
|
||||||
this(decodeMnemonicCode(mnemonicString), passphrase, creationTimeSeconds, type);
|
this(decodeMnemonicCode(mnemonicString), passphrase, creationTimeSeconds, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,15 +123,6 @@ public class DeterministicSeed implements EncryptableItem {
|
||||||
return encryptedMnemonicCode != null;
|
return encryptedMnemonicCode != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
if(isEncrypted()) {
|
|
||||||
return encryptedMnemonicCode.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return getMnemonicString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the seed as hex or null if encrypted. */
|
/** Returns the seed as hex or null if encrypted. */
|
||||||
public String toHexString() throws MnemonicException {
|
public String toHexString() throws MnemonicException {
|
||||||
byte[] seed = getSeedBytes();
|
byte[] seed = getSeedBytes();
|
||||||
|
@ -183,7 +174,10 @@ public class DeterministicSeed implements EncryptableItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyCrypter keyCrypter = getEncryptionType().getCrypter().getKeyCrypter();
|
KeyCrypter keyCrypter = getEncryptionType().getCrypter().getKeyCrypter();
|
||||||
EncryptedData encryptedMnemonic = keyCrypter.encrypt(getMnemonicAsBytes(), null, key);
|
byte[] mnemonicBytes = getMnemonicAsBytes();
|
||||||
|
EncryptedData encryptedMnemonic = keyCrypter.encrypt(mnemonicBytes, null, key);
|
||||||
|
Arrays.fill(mnemonicBytes != null ? mnemonicBytes : new byte[0], (byte)0);
|
||||||
|
|
||||||
DeterministicSeed seed = new DeterministicSeed(encryptedMnemonic, needsPassphrase, creationTimeSeconds, type);
|
DeterministicSeed seed = new DeterministicSeed(encryptedMnemonic, needsPassphrase, creationTimeSeconds, type);
|
||||||
seed.setPassphrase(passphrase);
|
seed.setPassphrase(passphrase);
|
||||||
|
|
||||||
|
@ -191,12 +185,15 @@ public class DeterministicSeed implements EncryptableItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getMnemonicAsBytes() {
|
private byte[] getMnemonicAsBytes() {
|
||||||
String mnemonicString = getMnemonicString();
|
SecureString mnemonicString = getMnemonicString();
|
||||||
if(mnemonicString == null) {
|
if(mnemonicString == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return mnemonicString.getBytes(StandardCharsets.UTF_8);
|
byte[] mnemonicBytes = SecureString.toBytesUTF8(mnemonicString);
|
||||||
|
mnemonicString.clear();
|
||||||
|
|
||||||
|
return mnemonicBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeterministicSeed decrypt(CharSequence password) {
|
public DeterministicSeed decrypt(CharSequence password) {
|
||||||
|
@ -206,8 +203,10 @@ public class DeterministicSeed implements EncryptableItem {
|
||||||
|
|
||||||
KeyDeriver keyDeriver = getEncryptionType().getDeriver().getKeyDeriver(encryptedMnemonicCode.getKeySalt());
|
KeyDeriver keyDeriver = getEncryptionType().getDeriver().getKeyDeriver(encryptedMnemonicCode.getKeySalt());
|
||||||
Key key = keyDeriver.deriveKey(password);
|
Key key = keyDeriver.deriveKey(password);
|
||||||
|
DeterministicSeed seed = decrypt(key);
|
||||||
|
key.clear();
|
||||||
|
|
||||||
return decrypt(key);
|
return seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeterministicSeed decrypt(Key key) {
|
public DeterministicSeed decrypt(Key key) {
|
||||||
|
@ -216,13 +215,25 @@ public class DeterministicSeed implements EncryptableItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyCrypter keyCrypter = getEncryptionType().getCrypter().getKeyCrypter();
|
KeyCrypter keyCrypter = getEncryptionType().getCrypter().getKeyCrypter();
|
||||||
List<String> mnemonic = decodeMnemonicCode(keyCrypter.decrypt(encryptedMnemonicCode, key));
|
byte[] decrypted = keyCrypter.decrypt(encryptedMnemonicCode, key);
|
||||||
|
List<String> mnemonic = decodeMnemonicCode(decrypted);
|
||||||
|
Arrays.fill(decrypted, (byte)0);
|
||||||
|
|
||||||
DeterministicSeed seed = new DeterministicSeed(mnemonic, needsPassphrase, creationTimeSeconds, type);
|
DeterministicSeed seed = new DeterministicSeed(mnemonic, needsPassphrase, creationTimeSeconds, type);
|
||||||
seed.setPassphrase(passphrase);
|
seed.setPassphrase(passphrase);
|
||||||
|
|
||||||
return seed;
|
return seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DeterministicSeed{" +
|
||||||
|
"type=" + type +
|
||||||
|
", encryptedMnemonicCode=" + encryptedMnemonicCode +
|
||||||
|
", needsPassphrase=" + needsPassphrase +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
@ -250,6 +261,15 @@ public class DeterministicSeed implements EncryptableItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
if(mnemonicCode != null) {
|
||||||
|
mnemonicCode.clear();
|
||||||
|
}
|
||||||
|
if(passphrase != null) {
|
||||||
|
passphrase = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
byte[] getEntropyBytes() throws MnemonicException {
|
byte[] getEntropyBytes() throws MnemonicException {
|
||||||
return type.getEntropyBytes(mnemonicCode);
|
return type.getEntropyBytes(mnemonicCode);
|
||||||
}
|
}
|
||||||
|
@ -260,25 +280,51 @@ public class DeterministicSeed implements EncryptableItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the mnemonic code as string, or null if unknown. */
|
/** Get the mnemonic code as string, or null if unknown. */
|
||||||
public String getMnemonicString() {
|
public SecureString getMnemonicString() {
|
||||||
StringJoiner joiner = new StringJoiner(" ");
|
StringBuilder builder = new StringBuilder();
|
||||||
if(mnemonicCode != null) {
|
if(mnemonicCode != null) {
|
||||||
for(String word : mnemonicCode) {
|
for(String word : mnemonicCode) {
|
||||||
joiner.add(word);
|
builder.append(word);
|
||||||
|
builder.append(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
return joiner.toString();
|
if(builder.length() > 0) {
|
||||||
|
builder.setLength(builder.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SecureString(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<String> decodeMnemonicCode(byte[] mnemonicCode) {
|
private static List<String> decodeMnemonicCode(byte[] mnemonicCode) {
|
||||||
return decodeMnemonicCode(new String(mnemonicCode, StandardCharsets.UTF_8));
|
SecureString secureString = SecureString.fromBytesUTF8(mnemonicCode);
|
||||||
|
List<String> words = decodeMnemonicCode(secureString);
|
||||||
|
secureString.clear();
|
||||||
|
|
||||||
|
return words;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<String> decodeMnemonicCode(String mnemonicCode) {
|
private static List<String> decodeMnemonicCode(CharSequence mnemonicCode) {
|
||||||
return Arrays.asList(mnemonicCode.split(" "));
|
List<String> words = new ArrayList<>();
|
||||||
|
StringBuilder word = new StringBuilder();
|
||||||
|
for(int i = 0; i < mnemonicCode.length(); i++) {
|
||||||
|
char c = mnemonicCode.charAt(i);
|
||||||
|
if(c != ' ') {
|
||||||
|
word.append(mnemonicCode.charAt(i));
|
||||||
|
}
|
||||||
|
if(c == ' ' || i == mnemonicCode.length() - 1) {
|
||||||
|
words.add(word.toString());
|
||||||
|
|
||||||
|
for(int j = 0; j < word.length(); j++) {
|
||||||
|
word.setCharAt(j, ' ');
|
||||||
|
}
|
||||||
|
word = new StringBuilder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return words;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeterministicSeed copy() {
|
public DeterministicSeed copy() {
|
||||||
|
|
|
@ -15,7 +15,7 @@ public class DeterministicSeedTest {
|
||||||
DeterministicSeed encryptedSeed = seed.encrypt(keyDeriver.deriveKey("pass"));
|
DeterministicSeed encryptedSeed = seed.encrypt(keyDeriver.deriveKey("pass"));
|
||||||
|
|
||||||
DeterministicSeed decryptedSeed = encryptedSeed.decrypt("pass");
|
DeterministicSeed decryptedSeed = encryptedSeed.decrypt("pass");
|
||||||
Assert.assertEquals(words, decryptedSeed.getMnemonicString());
|
Assert.assertEquals(words, decryptedSeed.getMnemonicString().asString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in a new issue