package org.ripple.power.txns.btc; import org.ripple.bouncycastle.crypto.BufferedBlockCipher; import org.ripple.bouncycastle.crypto.engines.AESFastEngine; import org.ripple.bouncycastle.crypto.modes.CBCBlockCipher; import org.ripple.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; import org.ripple.bouncycastle.crypto.params.KeyParameter; import org.ripple.bouncycastle.crypto.params.ParametersWithIV; import org.ripple.power.Helper; import java.io.EOFException; import java.math.BigInteger; import java.security.SecureRandom; /** * EncryptedPrivateKey contains an encrypted private key, the initial vector used * to encrypt the key, and the salt used to derive the encryption key. */ public class EncryptedPrivateKey implements ByteSerializable { /** Key length (bytes) */ private static final int KEY_LENGTH = 32; /** AES block size (bytes) */ private static final int BLOCK_LENGTH = 16; /** Strong random number generator */ private static final SecureRandom secureRandom = new SecureRandom(); /** Encrypted private key bytes */ private byte[] encKeyBytes; /** Encryption initial vector */ private byte[] iv; /** Salt used to derive the encryption key */ private byte[] salt; /** * Create a new EncryptedPrivateKey using the supplied private key and key phrase * * @param privKey Private key * @param keyPhrase Phrase used to derive the encryption key * @throws ECException Unable to complete a cryptographic function */ public EncryptedPrivateKey(BigInteger privKey, String keyPhrase) throws ECException { // // Derive the AES encryption key // salt = new byte[KEY_LENGTH]; secureRandom.nextBytes(salt); KeyParameter aesKey = deriveKey(keyPhrase, salt); // // Encrypt the private key using the generated AES key // try { iv = new byte[BLOCK_LENGTH]; secureRandom.nextBytes(iv); ParametersWithIV keyWithIV = new ParametersWithIV(aesKey, iv); CBCBlockCipher blockCipher = new CBCBlockCipher(new AESFastEngine()); BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(blockCipher); cipher.init(true, keyWithIV); byte[] privKeyBytes = privKey.toByteArray(); int encryptedLength = cipher.getOutputSize(privKeyBytes.length); encKeyBytes = new byte[encryptedLength]; int length = cipher.processBytes(privKeyBytes, 0, privKeyBytes.length, encKeyBytes, 0); cipher.doFinal(encKeyBytes, length); } catch (Exception exc) { throw new ECException("Unable to encrypt the private key", exc); } } /** * Creates a new EncryptedPrivateKey from the serialized data * * @param keyBytes Serialized key * @throws EOFException End-of-data while processing serialized data */ public EncryptedPrivateKey(byte[] keyBytes) throws EOFException { this(new SerializedBuffer(keyBytes)); } /** * Creates a new EncryptedPrivateKey from the serialized data * * @param inBuffer Input buffer * @throws EOFException End-of-data while processing serialized data */ public EncryptedPrivateKey(SerializedBuffer inBuffer) throws EOFException { encKeyBytes = inBuffer.getBytes(); iv = inBuffer.getBytes(); salt = inBuffer.getBytes(); } /** * Get the byte stream for this encrypted private key * * @param outBuffer Output buffer * @return Output buffer */ @Override public SerializedBuffer getBytes(SerializedBuffer outBuffer) { outBuffer.putVarInt(encKeyBytes.length) .putBytes(encKeyBytes) .putVarInt(iv.length) .putBytes(iv) .putVarInt(salt.length) .putBytes(salt); return outBuffer; } /** * Get the byte stream for this encrypted private key * * @return Byte array containing the serialized encrypted private key */ @Override public byte[] getBytes() { return getBytes(new SerializedBuffer()).toByteArray(); } /** * Returns the decrypted private key * * @param keyPhrase Key phrase used to derive the encryption key * @return Private key * @throws ECException Unable to complete a cryptographic function */ public BigInteger getPrivKey(String keyPhrase) throws ECException { KeyParameter aesKey = deriveKey(keyPhrase, salt); // // Decrypt the private key using the generated AES key // BigInteger privKey; try { ParametersWithIV keyWithIV = new ParametersWithIV(aesKey, iv); CBCBlockCipher blockCipher = new CBCBlockCipher(new AESFastEngine()); BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(blockCipher); cipher.init(false, keyWithIV); int bufferLength = cipher.getOutputSize(encKeyBytes.length); byte[] outputBytes = new byte[bufferLength]; int length1 = cipher.processBytes(encKeyBytes, 0, encKeyBytes.length, outputBytes, 0); int length2 = cipher.doFinal(outputBytes, length1); int actualLength = length1 + length2; byte[] privKeyBytes = new byte[actualLength]; System.arraycopy(outputBytes, 0, privKeyBytes, 0, actualLength); privKey = new BigInteger(privKeyBytes); } catch (Exception exc) { throw new ECException("Unable to decrypt the private key", exc); } return privKey; } /** * Derive the AES encryption key from the key phrase and the salt * * @param keyPhrase Key phrase * @param salt Salt * @return Key parameter * @throws ECException Unable to complete cryptographic function */ private KeyParameter deriveKey(String keyPhrase, byte[] salt) throws ECException { KeyParameter aesKey; try { byte[] stringBytes = keyPhrase.getBytes("UTF-8"); byte[] digest = Helper.singleDigest(stringBytes); byte[] doubleDigest = new byte[digest.length+salt.length]; System.arraycopy(digest, 0, doubleDigest, 0, digest.length); System.arraycopy(salt, 0, doubleDigest, digest.length, salt.length); byte[] keyBytes = Helper.singleDigest(doubleDigest); aesKey = new KeyParameter(keyBytes); } catch (Exception exc) { throw new ECException("Unable to convert passphrase to a byte array", exc); } return aesKey; } }