* Add command line programs Encrypt.java and Decrypt.java
* EncryptedInputStream: throw a PasswordException if AESEngine.safeDecrypt() returns null
This commit is contained in:
@ -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
|
||||
|
@ -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>.
|
||||
|
@ -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);
|
||||
|
116
src/i2p/bote/fileencryption/Decrypt.java
Normal file
116
src/i2p/bote/fileencryption/Decrypt.java
Normal 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);
|
||||
}
|
||||
}
|
216
src/i2p/bote/fileencryption/Encrypt.java
Normal file
216
src/i2p/bote/fileencryption/Encrypt.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)) {
|
||||
|
Reference in New Issue
Block a user