* Add command line programs Encrypt.java and Decrypt.java

* EncryptedInputStream: throw a PasswordException if AESEngine.safeDecrypt() returns null
This commit is contained in:
HungryHobo
2011-03-05 23:21:22 +00:00
parent 4e44fc204e
commit 596d19d212
8 changed files with 387 additions and 26 deletions

View File

@ -30,6 +30,8 @@ import net.i2p.data.DataHelper;
import net.i2p.util.Log;
public class Configuration {
public static final String KEY_DERIVATION_PARAMETERS_FILE = "derivparams"; // name of the KDF parameter cache file, relative to I2P_BOTE_SUBDIR
private static final String I2P_BOTE_SUBDIR = "i2pbote"; // relative to the I2P app dir
private static final String CONFIG_FILE_NAME = "i2pbote.config";
private static final String DEST_KEY_FILE_NAME = "local_dest.key";
@ -39,7 +41,6 @@ public class Configuration {
private static final String ADDRESS_BOOK_FILE_NAME = "addressBook";
private static final String MESSAGE_ID_CACHE_FILE = "msgidcache.txt";
private static final String PASSWORD_FILE = "password";
private static final String KEY_DERIVATION_PARAMETERS_FILE = "derivparams";
private static final String OUTBOX_DIR = "outbox"; // relative to I2P_BOTE_SUBDIR
private static final String RELAY_PKT_SUBDIR = "relay_pkt"; // relative to I2P_BOTE_SUBDIR
private static final String INCOMPLETE_SUBDIR = "incomplete"; // relative to I2P_BOTE_SUBDIR

View File

@ -26,6 +26,7 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -231,6 +232,23 @@ public class Util {
}
}
/**
* Tests if a directory contains a file with a given name.
* @param directory
* @param filename
* @return
*/
public static boolean contains(File directory, final String filename) {
String[] matches = directory.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.equals(filename);
}
});
return matches!=null && matches.length>0;
}
/**
* Creates an I2P destination with a null certificate from 384 bytes that
* are read from a <code>ByteBuffer</code>.

View File

@ -654,7 +654,7 @@ public class Email extends MimeMessage {
ByteArrayInputStream inputStream = new ByteArrayInputStream(uncompressedArray);
ByteArrayOutputStream compressedStream = new ByteArrayOutputStream();
Encoder lzmaEncoder = new Encoder();
lzmaEncoder.SetDictionarySize(1<<22); // dictionary size = 4 MBytes
lzmaEncoder.SetDictionarySize(1<<20); // dictionary size = 1 MByte
lzmaEncoder.SetEndMarkerMode(true); // by using an end marker, the uncompressed size doesn't need to be stored with the compressed data
lzmaEncoder.WriteCoderProperties(compressedStream);
lzmaEncoder.Code(inputStream, compressedStream, -1, -1, null);

View File

@ -0,0 +1,116 @@
/**
* Copyright (C) 2009 HungryHobo@mail.i2p
*
* The GPG fingerprint for HungryHobo@mail.i2p is:
* 6DD3 EAA2 9990 29BC 4AD2 7486 1E2C 7B61 76DC DC12
*
* This file is part of I2P-Bote.
* I2P-Bote is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* I2P-Bote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with I2P-Bote. If not, see <http://www.gnu.org/licenses/>.
*/
package i2p.bote.fileencryption;
import i2p.bote.Util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
/**
* A command line program for decrypting I2P-Bote files.
*/
public class Decrypt {
private Decrypt(String[] args) {
if (args.length < 1) {
printUsage();
System.exit(1);
}
File inputFile = new File(args[0]);
if (!inputFile.exists()) {
System.err.println("File not found: " + inputFile.getAbsolutePath());
System.exit(1);
}
byte[] password = promptForPassword();
if (password == null)
System.exit(0);
InputStream fileInputStream = null;
OutputStream output = null;
try {
fileInputStream = new FileInputStream(inputFile);
InputStream encryptedInputStream = new EncryptedInputStream(fileInputStream, password);
if (args.length < 2)
Util.copy(encryptedInputStream, System.out);
else {
File outputFile = new File(args[1]);
output = new FileOutputStream(outputFile);
Util.copy(encryptedInputStream, output);
}
} catch (IOException e) {
System.err.println("I/O error: " + e.getLocalizedMessage());
System.exit(1);
} catch (GeneralSecurityException e) {
System.err.println("Error: " + e.getLocalizedMessage());
System.exit(1);
} catch (PasswordException e) {
System.err.println("Wrong password.");
System.exit(1);
} finally {
if (fileInputStream != null)
try {
fileInputStream.close();
} catch (IOException e) {
System.err.println("Error closing input file.");
}
if (output != null)
try {
output.close();
} catch (IOException e) {
System.err.println("Error closing output file.");
}
}
}
private void printUsage() {
System.out.println("Syntax: Decrypt <input file> [output file]");
System.out.println();
System.out.println("Decrypts an input file and writes it to an output file.");
System.out.println("Existing files are overwritten without warning.");
System.out.println("If no output file is given, stdout is used instead.");
}
private byte[] promptForPassword() {
System.out.print("Enter I2P-Bote password: ");
char[] passwordChars = System.console().readPassword();
if (passwordChars != null)
return new String(passwordChars).getBytes();
else
return null;
}
/**
* @param args
*/
public static void main(String[] args) {
new Decrypt(args);
}
}

View File

@ -0,0 +1,216 @@
/**
* Copyright (C) 2009 HungryHobo@mail.i2p
*
* The GPG fingerprint for HungryHobo@mail.i2p is:
* 6DD3 EAA2 9990 29BC 4AD2 7486 1E2C 7B61 76DC DC12
*
* This file is part of I2P-Bote.
* I2P-Bote is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* I2P-Bote is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with I2P-Bote. If not, see <http://www.gnu.org/licenses/>.
*/
package i2p.bote.fileencryption;
import static i2p.bote.fileencryption.FileEncryptionConstants.SALT_LENGTH;
import i2p.bote.Configuration;
import i2p.bote.Util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
/**
* A command line program for encrypting files in the I2P-Bote format.
*/
public class Encrypt {
private static final String VERBOSE_OPTION = "-v";
private static final String DERIV_PARAMS_FILE_OPTION = "-d";
private boolean verbose;
private File inputFile;
private File outputFile;
private File derivParamsFile;
private Encrypt(String[] args) {
if (args.length < 1) {
printUsage();
System.exit(1);
}
init(args);
if (!inputFile.exists()) {
System.err.println("File not found: " + inputFile.getAbsolutePath());
System.exit(1);
}
byte[] password = promptForPassword();
if (password == null)
System.exit(0);
InputStream input = null;
OutputStream encryptedOutputStream = null;
try {
DerivedKey derivedKey = getDerivedKey(password, inputFile);
input = new FileInputStream(inputFile);
OutputStream output;
if (outputFile == null)
output = System.out;
else
output = new FileOutputStream(outputFile);
if (verbose) {
System.out.println("Parameters:");
System.out.println(" N = " + derivedKey.scryptParams.N);
System.out.println(" r = " + derivedKey.scryptParams.r);
System.out.println(" p = " + derivedKey.scryptParams.p);
System.out.println(" Salt = " + Base64.encode(derivedKey.salt));
}
encryptedOutputStream = new EncryptedOutputStream(output, derivedKey);
Util.copy(input, encryptedOutputStream);
if (verbose)
System.out.println("Encryption finished.");
} catch (IOException e) {
System.err.println("I/O error: " + e.getLocalizedMessage());
System.exit(1);
} catch (GeneralSecurityException e) {
System.err.println("Error: " + e.getLocalizedMessage());
System.exit(1);
} catch (PasswordException e) {
System.err.println("Wrong password.");
System.exit(1);
} finally {
if (input != null)
try {
input.close();
} catch (IOException e) {
System.err.println("Error closing input file.");
}
if (encryptedOutputStream != null)
try {
encryptedOutputStream.close();
} catch (IOException e) {
System.err.println("Error closing output file.");
}
}
}
private void printUsage() {
System.out.println("Syntax: Encrypt [v] [-d file] <input file> [output file]");
System.out.println();
System.out.println("Encrypts an input file and writes it to an output file.");
System.out.println("Existing files are overwritten without warning.");
System.out.println("If no output file is given, stdout is used instead.");
System.out.println();
System.out.println("Options:");
System.out.println(" " + VERBOSE_OPTION + " Print some additional messages");
System.out.println(" " + DERIV_PARAMS_FILE_OPTION + " file Read derivation parameters from this file");
}
/**
* Reads command line parameters into instance variables
*/
private void init(String[] args) {
for (int i=0; i<args.length; i++) {
String arg = args[i];
if (VERBOSE_OPTION.equals(arg))
verbose = true;
else if (DERIV_PARAMS_FILE_OPTION.equals(arg)) {
i++;
if (i >= args.length)
System.err.println("Warning: " + DERIV_PARAMS_FILE_OPTION + " not followed by a filename, ignoring.");
else
derivParamsFile = new File(args[i]);
}
else if (inputFile == null)
inputFile = new File(arg);
else
outputFile = new File(arg);
}
if (verbose) {
System.out.println("Input file: " + inputFile.getAbsolutePath());
if (outputFile == null)
System.out.println("No output file given, writing to stdout.");
else
System.out.println("Output file: " + outputFile.getAbsolutePath());
}
}
private byte[] promptForPassword() {
System.out.print("Enter a password: ");
char[] passwordChars1 = System.console().readPassword();
System.out.print("Enter the password again: ");
char[] passwordChars2 = System.console().readPassword();
if (!Arrays.equals(passwordChars1, passwordChars2)) {
System.err.println("The two password don't match.");
System.exit(1);
}
if (passwordChars1 != null)
return new String(passwordChars1).getBytes();
else
return null;
}
private DerivedKey getDerivedKey(byte[] password, File inputFile) throws GeneralSecurityException {
if (derivParamsFile == null) {
// the derivation parameters file is in the I2P-Bote root dir, so search all parent directories
File parentDir = inputFile.getParentFile();
while (derivParamsFile==null && parentDir!=null) {
boolean paramsFileFound = Util.contains(parentDir, Configuration.KEY_DERIVATION_PARAMETERS_FILE);
if (paramsFileFound)
derivParamsFile = new File(parentDir, Configuration.KEY_DERIVATION_PARAMETERS_FILE);
else
parentDir = parentDir.getParentFile();
}
}
if (derivParamsFile != null) {
if (verbose)
System.out.println("Using derivation parameters file: " + derivParamsFile.getAbsolutePath());
try {
return FileEncryptionUtil.getEncryptionKey(password, derivParamsFile);
}
catch (IOException e) {
System.out.println("Can't create key from derivation parameters file: " + e.getLocalizedMessage());
}
}
// derivation parameters file not found or not readable, use default params and random salt
if (verbose)
System.out.println("No derivation parameters file available, creating new salt.");
I2PAppContext appContext = I2PAppContext.getGlobalContext();
byte[] salt = new byte[SALT_LENGTH];
appContext.random().nextBytes(salt);
byte[] key = FileEncryptionUtil.getEncryptionKey(password, salt, FileEncryptionConstants.KDF_PARAMETERS);
return new DerivedKey(salt, FileEncryptionConstants.KDF_PARAMETERS, key);
}
/**
* @param args
*/
public static void main(String[] args) {
new Encrypt(args);
}
}

View File

@ -49,23 +49,23 @@ public class EncryptedInputStream extends FilterInputStream {
* @param upstream
* @param passwordHolder
* @throws IOException
* @throws PasswordException
* @throws GeneralSecurityException
* @throws PasswordException
*/
public EncryptedInputStream(InputStream upstream, PasswordHolder passwordHolder) throws IOException, PasswordException, GeneralSecurityException {
public EncryptedInputStream(InputStream upstream, PasswordHolder passwordHolder) throws IOException, GeneralSecurityException, PasswordException {
super(upstream);
byte[] password = passwordHolder.getPassword();
if (password == null)
throw new PasswordException();
DerivedKey cachedKey = passwordHolder.getKey();
decryptedData = new ByteArrayInputStream(readInputStream(upstream, password, cachedKey));
byte[] bytes = readInputStream(upstream, password, cachedKey);
decryptedData = new ByteArrayInputStream(bytes);
}
public EncryptedInputStream(InputStream upstream, byte[] password) throws IOException, GeneralSecurityException {
public EncryptedInputStream(InputStream upstream, byte[] password) throws IOException, GeneralSecurityException, PasswordException {
super(upstream);
byte[] bytes = readInputStream(upstream, password, null);
if (bytes == null)
bytes = new byte[0];
decryptedData = new ByteArrayInputStream(bytes);
}
@ -98,6 +98,9 @@ public class EncryptedInputStream extends FilterInputStream {
I2PAppContext appContext = I2PAppContext.getGlobalContext();
byte[] decryptedData = appContext.aes().safeDecrypt(encryptedData, key, iv);
// null from safeDecrypt() means failure
if (decryptedData == null)
throw new PasswordException();
return decryptedData;
}

View File

@ -26,6 +26,7 @@ import static i2p.bote.fileencryption.FileEncryptionConstants.KEY_LENGTH;
import static i2p.bote.fileencryption.FileEncryptionConstants.PASSWORD_FILE_PLAIN_TEXT;
import i2p.bote.Util;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@ -57,6 +58,23 @@ public class FileEncryptionUtil {
return key;
}
static DerivedKey getEncryptionKey(byte[] password, File derivParamFile) throws GeneralSecurityException, IOException {
DataInputStream inputStream = null;
try {
inputStream = new DataInputStream(new FileInputStream(derivParamFile));
byte[] salt = new byte[FileEncryptionConstants.SALT_LENGTH];
inputStream.read(salt);
SCryptParameters scryptParams = new SCryptParameters(inputStream);
byte[] key = FileEncryptionUtil.getEncryptionKey(password, salt, scryptParams);
DerivedKey derivedKey = new DerivedKey(salt, scryptParams, key);
return derivedKey;
}
finally {
if (inputStream != null)
inputStream.close();
}
}
/**
* Decrypts a file with a given password and returns <code>true</code> if the decrypted
* text is {@link FileEncryptionConstants#PASSWORD_FILE_PLAIN_TEXT}; <code>false</code>
@ -77,6 +95,9 @@ public class FileEncryptionUtil {
byte[] decryptedText = Util.readBytes(inputStream);
return Arrays.equals(PASSWORD_FILE_PLAIN_TEXT, decryptedText);
}
catch (PasswordException e) {
return false;
}
finally {
if (inputStream != null)
inputStream.close();
@ -122,8 +143,9 @@ public class FileEncryptionUtil {
* @param newKey
* @throws IOException
* @throws GeneralSecurityException
* @throws PasswordException
*/
public static void changePassword(File file, byte[] oldPassword, DerivedKey newKey) throws IOException, GeneralSecurityException {
public static void changePassword(File file, byte[] oldPassword, DerivedKey newKey) throws IOException, GeneralSecurityException, PasswordException {
InputStream inputStream = null;
byte[] decryptedData = null;
try {

View File

@ -27,10 +27,8 @@ import i2p.bote.Configuration;
import i2p.bote.Util;
import i2p.bote.service.I2PBoteThread;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
@ -91,21 +89,8 @@ public class PasswordCache extends I2PBoteThread implements PasswordHolder {
// read salt + scrypt parameters from file if available
File derivParamFile = configuration.getKeyDerivationParametersFile();
if (derivParamFile.exists()) {
DataInputStream inputStream = null;
try {
inputStream = new DataInputStream(new FileInputStream(derivParamFile));
salt = new byte[FileEncryptionConstants.SALT_LENGTH];
inputStream.read(salt);
SCryptParameters scryptParams = new SCryptParameters(inputStream);
byte[] key = FileEncryptionUtil.getEncryptionKey(password, salt, scryptParams);
derivedKey = new DerivedKey(salt, scryptParams, key);
}
finally {
if (inputStream != null)
inputStream.close();
}
}
if (derivParamFile.exists())
derivedKey = FileEncryptionUtil.getEncryptionKey(password, derivParamFile);
// if necessary, create a new salt and key and write the derivation parameters to the cache file
if (derivedKey==null || !derivedKey.scryptParams.equals(KDF_PARAMETERS)) {