package org.ripple.power.wallet; import java.security.SecureRandom; import java.io.UnsupportedEncodingException; import java.util.Arrays; import org.apache.commons.codec.binary.Base64; import org.ripple.bouncycastle.crypto.BufferedBlockCipher; import org.ripple.bouncycastle.crypto.CipherParameters; import org.ripple.bouncycastle.crypto.PBEParametersGenerator; import org.ripple.bouncycastle.crypto.engines.AESFastEngine; import org.ripple.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator; import org.ripple.bouncycastle.crypto.modes.CBCBlockCipher; import org.ripple.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; import org.ripple.bouncycastle.crypto.params.ParametersWithIV; import org.ripple.power.CoinUtils; import org.ripple.power.config.LSystem; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class OpenSSL { private Logger log = LoggerFactory.getLogger(OpenSSL.class); private static final int NUMBER_OF_ITERATIONS = 1024; private static final int KEY_LENGTH = 256; private static final int IV_LENGTH = 128; private static final int SALT_LENGTH = 8; public static final String OPENSSL_SALTED_TEXT = "Salted__"; public byte[] openSSLSaltedBytes; private String openSSLMagicText = null; public static final int NUMBER_OF_CHARACTERS_TO_MATCH_IN_OPENSSL_MAGIC_TEXT = 10; private static SecureRandom secureRandom = new SecureRandom(); public OpenSSL() { try { openSSLSaltedBytes = OPENSSL_SALTED_TEXT.getBytes(LSystem.encoding); openSSLMagicText = Base64 .encodeBase64String( OpenSSL.OPENSSL_SALTED_TEXT .getBytes(LSystem.encoding)) .substring( 0, OpenSSL.NUMBER_OF_CHARACTERS_TO_MATCH_IN_OPENSSL_MAGIC_TEXT); } catch (UnsupportedEncodingException e) { log.error("Could not construct EncrypterDecrypter", e.getMessage()); } } private CipherParameters getAESPasswordKey(CharSequence password, byte[] salt) throws Exception { try { PBEParametersGenerator generator = new OpenSSLPBEParametersGenerator(); generator.init(PBEParametersGenerator .PKCS5PasswordToBytes(convertToCharArray(password)), salt, NUMBER_OF_ITERATIONS); ParametersWithIV key = (ParametersWithIV) generator .generateDerivedParameters(KEY_LENGTH, IV_LENGTH); return key; } catch (Exception e) { throw new Exception( "Could not generate key from password of length " + password.length() + " and salt '" + CoinUtils.toHex(salt), e); } } public String encrypt(String plainText, CharSequence password) throws Exception { try { byte[] plainTextAsBytes; if (plainText == null) { plainTextAsBytes = new byte[0]; } else { plainTextAsBytes = plainText.getBytes(LSystem.encoding); } byte[] encryptedBytes = encrypt(plainTextAsBytes, password); byte[] encryptedBytesPlusSaltedText = concat(openSSLSaltedBytes, encryptedBytes); return Base64.encodeBase64String(encryptedBytesPlusSaltedText); } catch (Exception e) { throw new Exception("Could not encrypt string '" + plainText + "'", e); } } public byte[] encrypt(byte[] plainTextAsBytes, CharSequence password) throws Exception { try { byte[] salt = new byte[SALT_LENGTH]; secureRandom.nextBytes(salt); ParametersWithIV key = (ParametersWithIV) getAESPasswordKey( password, salt); BufferedBlockCipher cipher = new PaddedBufferedBlockCipher( new CBCBlockCipher(new AESFastEngine())); cipher.init(true, key); byte[] encryptedBytes = new byte[cipher .getOutputSize(plainTextAsBytes.length)]; int length = cipher.processBytes(plainTextAsBytes, 0, plainTextAsBytes.length, encryptedBytes, 0); cipher.doFinal(encryptedBytes, length); return concat(salt, encryptedBytes); } catch (Exception e) { throw new Exception("Could not encrypt bytes '" + CoinUtils.toHex(plainTextAsBytes) + "'", e); } } public String decrypt(String textToDecode, CharSequence password) throws Exception { try { final byte[] decodeTextAsBytes = Base64.decodeBase64(textToDecode .getBytes(LSystem.encoding)); int saltPrefixTextLength = openSSLSaltedBytes.length; byte[] cipherBytes = new byte[decodeTextAsBytes.length - saltPrefixTextLength]; System.arraycopy(decodeTextAsBytes, saltPrefixTextLength, cipherBytes, 0, decodeTextAsBytes.length - saltPrefixTextLength); byte[] decryptedBytes = decrypt(cipherBytes, password); return new String(decryptedBytes, LSystem.encoding).trim(); } catch (Exception e) { throw new Exception("Could not decrypt input string", e); } } public byte[] decrypt(byte[] bytesToDecode, CharSequence password) throws Exception { try { byte[] salt = new byte[SALT_LENGTH]; System.arraycopy(bytesToDecode, 0, salt, 0, SALT_LENGTH); byte[] cipherBytes = new byte[bytesToDecode.length - SALT_LENGTH]; System.arraycopy(bytesToDecode, SALT_LENGTH, cipherBytes, 0, bytesToDecode.length - SALT_LENGTH); ParametersWithIV key = (ParametersWithIV) getAESPasswordKey( password, salt); BufferedBlockCipher cipher = new PaddedBufferedBlockCipher( new CBCBlockCipher(new AESFastEngine())); cipher.init(false, key); byte[] decryptedBytes = new byte[cipher .getOutputSize(cipherBytes.length)]; int length = cipher.processBytes(cipherBytes, 0, cipherBytes.length, decryptedBytes, 0); cipher.doFinal(decryptedBytes, length); return decryptedBytes; } catch (Exception e) { throw new Exception("Could not decrypt input string", e); } } private byte[] concat(byte[] arrayA, byte[] arrayB) { byte[] result = new byte[arrayA.length + arrayB.length]; System.arraycopy(arrayA, 0, result, 0, arrayA.length); System.arraycopy(arrayB, 0, result, arrayA.length, arrayB.length); return result; } private char[] convertToCharArray(CharSequence charSequence) { if (charSequence == null) { return null; } char[] charArray = new char[charSequence.length()]; for (int i = 0; i < charSequence.length(); i++) { charArray[i] = charSequence.charAt(i); } return charArray; } public byte[] getOpenSSLSaltedBytes() { return openSSLSaltedBytes; } public String getOpenSSLMagicText() { return openSSLMagicText; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(openSSLSaltedBytes); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof OpenSSL)) { return false; } return true; } }