/**
* Copyright 2012 multibit.org
*
* Licensed under the MIT license (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://opensource.org/licenses/mit-license.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.multibit.crypto;
import com.google.bitcoin.core.Utils;
import com.google.bitcoin.crypto.KeyCrypterException;
import junit.framework.TestCase;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.UUID;
public class KeyCrypterOpenSSLTest extends TestCase {
private static final Logger log = LoggerFactory.getLogger(KeyCrypterOpenSSLTest.class);
private static final String TEST_STRING1 = "The quick brown fox jumps over the lazy dog. 01234567890 !@#$%^&*()-=[]{};':|`~,./<>?";
// Chinese translation for 'The fee cannot be smaller than the minimum fee
private static final String TEST_STRING2 = "\u4ea4\u6613\u8d39\u7528\u5fc5\u987b\u81f3\u5c11 0.0001 BTC\u3002\u4fdd\u6301\u539f\u6709\u8d39\u7528\u8bbe\u7f6e\u3002";
// Nonsense bytes for encryption test.
private static final byte[] TEST_BYTES1= new byte[]{0, -101, 2, 103, -4, 105, 6, 107, 8, -109, 10, 111, -12, 113, 14, -115, 16, 117, -18, 119, 20, 121, 22, 123, -24, 125, 26, 127, -28, 29, -30, 31};
private static final CharSequence PASSWORD1 = "aTestPassword";
private static final CharSequence PASSWORD2 = "0123456789";
private static final CharSequence WRONG_PASSWORD = "thisIsTheWrongPassword";
// Moscow in Russian in Cyrillic.
private static final CharSequence PASSWORD3 = "\u041c\u043e\u0441\u043a\u0432\u0430";
@Test
public void testEncryptDecryptGood1() throws Exception {
KeyCrypterOpenSSL encrypterDecrypter = new KeyCrypterOpenSSL();
// Encrypt.
String cipherText = encrypterDecrypter.encrypt(TEST_STRING1, PASSWORD1);
assertNotNull(cipherText);
log.debug("\nEncrypterDecrypterTest: cipherText = \n---------------\n" + cipherText + "\n---------------\n");
// Decrypt.
String rebornPlainText = encrypterDecrypter.decrypt(cipherText, PASSWORD1);
log.debug("Original: " + Utils.bytesToHexString(TEST_STRING1.getBytes()));
log.debug("Reborn : " + Utils.bytesToHexString(rebornPlainText.getBytes()));
assertEquals(TEST_STRING1, rebornPlainText);
}
public void testEncryptDecryptGood2() throws Exception {
KeyCrypterOpenSSL encrypterDecrypter = new KeyCrypterOpenSSL();
// Create a longer encryption string.
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < 100; i++) {
stringBuffer.append(i + " ").append(TEST_STRING1);
}
System.out.println("EncrypterDecrypterTest: String to encrypt has length " + stringBuffer.toString().length());
String cipherText = encrypterDecrypter.encrypt(stringBuffer.toString(), PASSWORD2);
assertNotNull(cipherText);
System.out.println("EncrypterDecrypterTest: CipherText has length " + cipherText.length());
String reconstructedPlainText = encrypterDecrypter.decrypt(cipherText, PASSWORD2);
assertEquals(stringBuffer.toString(), reconstructedPlainText);
}
/**
* Test with random plain text strings and random passwords.
* UUIDs are used and hence will only cover hex characters (and te separator hyphen).
*/
public void testEncryptDecryptGood3() throws Exception {
KeyCrypterOpenSSL encrypterDecrypter = new KeyCrypterOpenSSL();
int numberOfTests = 100;
System.out.print("EncrypterDecrypterTest: Trying random UUIDs for plainText and passwords :");
for (int i = 0; i < numberOfTests; i++) {
// Create a UUID as the plaintext and use another for the password.
String plainText = UUID.randomUUID().toString();
String password = UUID.randomUUID().toString();
String cipherText = encrypterDecrypter.encrypt(plainText, password);
assertNotNull(cipherText);
String reconstructedPlainText = encrypterDecrypter.decrypt(cipherText, password);
assertEquals(plainText, reconstructedPlainText);
System.out.print('.');
}
System.out.println(" Done.");
}
public void testEncryptDecryptWrongPassword() throws Exception {
KeyCrypterOpenSSL encrypterDecrypter = new KeyCrypterOpenSSL();
// create a longer encryption string
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < 100; i++) {
stringBuffer.append(i + " ").append(TEST_STRING1);
}
String cipherText = encrypterDecrypter.encrypt(stringBuffer.toString(), PASSWORD2);
assertNotNull(cipherText);
try {
encrypterDecrypter.decrypt(cipherText, WRONG_PASSWORD);
fail("Decrypt with wrong password did not throw exception");
} catch (KeyCrypterException ede) {
assertTrue(ede.getMessage().indexOf("Could not decrypt") > -1);
}
}
public void testEncryptJavaDecryptOpenSSL() throws Exception, IOException {
KeyCrypterOpenSSL encrypterDecrypter = new KeyCrypterOpenSSL();
// create a longer encryption string
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < 1000; i++) {
stringBuffer.append(i + " ").append(TEST_STRING1);
}
System.out.println("EncrypterDecrypterTest: String to encrypt has length " + stringBuffer.toString().length());
String cipherText = encrypterDecrypter.encrypt(stringBuffer.toString(), PASSWORD2);
// Create temporary ciphertext file.
File temporaryCipherTextFile = File.createTempFile("EncrypterDecrypterTest", ".cipher");
// Delete temp file when program exits.
temporaryCipherTextFile.deleteOnExit();
// write the cipher text to a file
writeToFile(cipherText, temporaryCipherTextFile);
// create a file to write the decoded text to
File temporaryPlainTextFile = File.createTempFile("EncrypterDecrypterTest", ".plain");
// Delete temp file when program exits.
temporaryPlainTextFile.deleteOnExit();
// run the openssl equivalent command to decrypt the ciphertext
try {
String commandToRun = "openssl enc -d -p -aes-256-cbc -a -in " + temporaryCipherTextFile.getAbsolutePath()
+ " -out " + temporaryPlainTextFile.getAbsolutePath() + " -pass pass:" + PASSWORD2.toString();
System.out.println("EncrypterDecrypterTest: Decrypting command = '" + commandToRun + "'");
Process p = Runtime.getRuntime().exec(commandToRun);
p.waitFor();
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = reader.readLine();
while (line != null) {
System.out.println("EncrypterDecrypterTest: " + line);
line = reader.readLine();
}
// read in the decrypted text
String rebornPlainText = readFile(temporaryPlainTextFile).trim();
assertEquals(stringBuffer.toString(), rebornPlainText);
} catch (IOException e1) {
fail("Could not run OpenSSL command to decrypt file");
} catch (InterruptedException e2) {
fail("Could not run OpenSSL command to decrypt file");
}
}
public void testEncryptOpenSSLDecryptJava() throws Exception, IOException {
KeyCrypterOpenSSL encrypterDecrypter = new KeyCrypterOpenSSL();
// Create temporary plaintext file.
File temporaryPlainTextFile = File.createTempFile("EncrypterDecrypterTest", ".plain");
// Delete temp file when program exits.
temporaryPlainTextFile.deleteOnExit();
// write the plain text to a file
writeToFile(TEST_STRING1, temporaryPlainTextFile);
// create a file to write the encrypted text to
File temporaryCipherTextFile = File.createTempFile("EncrypterDecrypterTest", ".cipher");
// Delete temp file when program exits.
temporaryCipherTextFile.deleteOnExit();
// run the openssl equivalent command to encrypt the plaintext
try {
String commandToRun = "openssl enc -p -aes-256-cbc -a -in " + temporaryPlainTextFile.getAbsolutePath()
+ " -out " + temporaryCipherTextFile.getAbsolutePath() + " -pass pass:" + PASSWORD2.toString();
System.out.println("EncrypterDecrypterTest: Encrypting command = '" + commandToRun + "'");
Process p = Runtime.getRuntime().exec(commandToRun);
p.waitFor();
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = reader.readLine();
while (line != null) {
System.out.println("EncrypterDecrypterTest: " + line);
line = reader.readLine();
}
// read in the decrypted text
String cipherText = readFile(temporaryCipherTextFile).trim();
String rebornPlainText = encrypterDecrypter.decrypt(cipherText, PASSWORD2);
assertEquals(TEST_STRING1, rebornPlainText);
} catch (IOException e1) {
fail("Could not run OpenSSL command to encrypt file");
} catch (InterruptedException e2) {
fail("Could not run OpenSSL command to encrypt file");
}
}
@Test
public void testEncryptDecryptNullAndBlank() throws Exception {
KeyCrypterOpenSSL encrypterDecrypter = new KeyCrypterOpenSSL();
// Encrypt a null string - it gets treated as an empty string.
String cipherText = encrypterDecrypter.encrypt((String)null, PASSWORD1);
assertNotNull(cipherText);
// Decrypt.
String rebornPlainText = encrypterDecrypter.decrypt(cipherText, PASSWORD1);
assertEquals("", rebornPlainText);
// Encrypt empty string.
cipherText = encrypterDecrypter.encrypt("", PASSWORD1);
assertNotNull(cipherText);
// Decrypt.
rebornPlainText = encrypterDecrypter.decrypt(cipherText, PASSWORD1);
assertEquals("", rebornPlainText);
}
@Test
public void testEncryptDecryptInternational() throws Exception {
KeyCrypterOpenSSL encrypterDecrypter = new KeyCrypterOpenSSL();
// Encrypt.
String cipherText = encrypterDecrypter.encrypt(TEST_STRING2, PASSWORD3);
assertNotNull(cipherText);
log.debug("\nEncrypterDecrypterTest: cipherText = \n---------------\n" + cipherText + "\n---------------\n");
// Decrypt.
String rebornPlainText = encrypterDecrypter.decrypt(cipherText, PASSWORD3);
log.debug("Original: " + Utils.bytesToHexString(TEST_STRING2.getBytes()));
log.debug("Reborn : " + Utils.bytesToHexString(rebornPlainText.getBytes()));
assertEquals(TEST_STRING2, rebornPlainText);
}
@Test
public void testEncryptDecryptBytes1() throws Exception {
KeyCrypterOpenSSL encrypterDecrypter = new KeyCrypterOpenSSL();
// Encrypt bytes.
byte[] cipherBytes = encrypterDecrypter.encrypt(TEST_BYTES1, PASSWORD1);
assertNotNull(cipherBytes);
log.debug("\nEncrypterDecrypterTest: cipherBytes = \nlength = " + cipherBytes.length + "\n---------------\n" + Utils.bytesToHexString(cipherBytes) + "\n---------------\n");
// Decrypt bytes. Note the result is zero padded to a cipher block length so you have to truncate it to your expected length.
byte[] rebornPlainBytes = encrypterDecrypter.decrypt(cipherBytes, PASSWORD1);
byte[] truncatedRebornBytes = new byte[TEST_BYTES1.length];
System.arraycopy(rebornPlainBytes, 0, truncatedRebornBytes, 0, TEST_BYTES1.length);
log.debug("Original: " + Utils.bytesToHexString(TEST_BYTES1));
log.debug("Reborn : " + Utils.bytesToHexString(truncatedRebornBytes));
assertEquals( Utils.bytesToHexString(TEST_BYTES1), Utils.bytesToHexString(truncatedRebornBytes));
}
private void writeToFile(String textToWrite, File destinationFile) throws IOException {
if (!destinationFile.exists()) {
destinationFile.createNewFile();
}
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(destinationFile);
fileOutputStream.write(textToWrite.getBytes(KeyCrypterOpenSSL.STRING_ENCODING));
} finally {
}
}
private String readFile(File file) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(file));
String line = null;
StringBuilder stringBuilder = new StringBuilder();
String ls = System.getProperty("line.separator");
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
stringBuilder.append(ls);
}
return stringBuilder.toString();
}
}