/* * [OTP.java] * * Summary: One Time Pad encryption and decryption. * * Copyright: (c) 2012-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 2012-12-21 initial release. */ package com.mindprod.otp; import com.mindprod.common18.EIO; import javax.swing.JLabel; import javax.swing.JProgressBar; import javax.swing.SwingUtilities; import java.awt.Color; import java.awt.Font; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.security.SecureRandom; import java.util.Arrays; import static java.lang.System.*; /** * One Time Pad encryption and decryption. * * @author Roedy Green, Canadian Mind Products * @version 1.0 2012-12-21 initial release. * @since 2012-12-21 */ public final class OTP { /** * should the wipe utility also delete the file? */ static final boolean DELETE_WIPED_FILE = true; /** * true if want application frames resizable, false if you want them frozen to the original size. */ static final boolean RESIZABLE = false; /** * should we wipe and delete the encryptede file after decryption */ static final boolean WIPE_ENCRYPTED_FILE = true; /** * should we wipe and delete the original plain text file on encryption */ static final boolean WIPE_PLAIN_TEXT_FILE = true; /** * maximum number of bytes we pad the message with to disguise message length */ static final short MAX_DISGUISE_BYTES = 100; /** * how many bytes at a time do we do I/O to wipe a file. Bigger is faster, but progress is lurchier */ static final int CHUNKSIZE = 1024 * 1024; /** * minimum size of a PAD. If it has fewer than this many bytes left, we discard it. */ static final int MININUM_PAD_SIZE = 100; /** * how many passes of writes of random bytes do you use to wipe the file if it is on hard disk. */ static final int PASSES_FOR_HARD_DISK = 35; static final String DIFFERENT_CURRENT_DIRS = "Encrypt and Decrypt must run with different current " + "directories."; static final String EVEN_WHEN_SIMULATING = "Even when simulating, sender and receiver must have two " + "copies of pads in different directories."; static final String START_OVER = "Please erase all *.pad files and inventory.ser and start over with new " + "pad files."; /** * Frame background */ static final Color BACKGROUND_FOR_FRAME = new Color( 0xebe9da ); /** * normal instruction colour */ static final Color FOREGROUND_FOR_BORDER = new Color( 0xc0c0c0 )/* grey */; /** * for label */ static final Color FOREGROUND_FOR_BUTTON = new Color( 0x7f9db9 )/* pale French blue */; /** * normal instruction colour */ static final Color FOREGROUND_FOR_FILENAME = new Color( 0x7f9db9 )/* pale French blue */; /** * normal instruction colour */ static final Color FOREGROUND_FOR_INSTRUCTIONS = new Color( 0x7f9db9 )/* pale French blue */; /** * colour of the progress bar. */ static final Color FOREGROUND_FOR_PROGRESS = new Color( 0xa2b8cf ) /* French blue */; /** * warnings colour */ static final Color FOREGROUND_FOR_WARNING = new Color( 0xdc143c/* crimson */ ); /** * directory for clear text, in current dir */ static final File clearDir = new File( "clear" ); /** * directory for encrypted files, in current dir */ static final File encryptedDir = new File( "encrypted" ); /** * directory for pads, in current dir */ static final File keysDir = new File( "keys" ); /** * font for filenames */ static final Font FONT_FOR_FILENAME = new Font( "Dialog", Font.PLAIN, 14 ); /** * font for instructions */ static final Font FONT_FOR_INSTRUCTIONS = new Font( "Dialog", Font.PLAIN, 12 ); /** * used to generated random bytes used to wipe files */ static final SecureRandom wheel = new SecureRandom(); private static final int FIRST_COPYRIGHT_YEAR = 2012; /** * how many passes of writes of random bytes do you use to wipe the file if it is on an SSD or USB Flash drive. * Wiping multiple times damages the drive. */ private static final int PASSES_FOR_SSD = 2; /** * undisplayed copyright notice * * @noinspection UnusedDeclaration */ private static final String EMBEDDED_COPYRIGHT = "Copyright: (c) 2012-2017 Roedy Green, Canadian Mind Products, http://mindprod.com"; /** * date this version released. */ private static final String RELEASE_DATE = "2012-12-21"; /** * Title of the Applet */ private static final String TITLE_STRING = "One Time Pad Encryption/Decryption"; /** * Version */ private static final String VERSION_STRING = "1.0"; /** * track progress */ private static int ticks = 0; /** * calculate how many wiping passese should be used for a given file * * @param fileToWipe File we are about to wipe * * @return number of passes */ private static int calcPasses( final File fileToWipe ) { final File partitionToWipe = new File( fileToWipe.getAbsolutePath().substring( 0, 3 ) ); return partitionToWipe.getTotalSpace() < 250000000000L ? PASSES_FOR_SSD : PASSES_FOR_HARD_DISK; } /** * put message in the instruction slot * * @param message message to display * @param warn true if this is a warning. */ private static void instruct( final JLabel instructions, final String message, final boolean warn ) { SwingUtilities.invokeLater( new Runnable() { public void run() { instructions.setText( message ); instructions.setForeground( warn ? OTP.FOREGROUND_FOR_WARNING : OTP.FOREGROUND_FOR_INSTRUCTIONS ); } } ); } static void mkDirs() { if ( !clearDir.isDirectory() ) { if ( !clearDir.mkdir() ) { err.println( "unable to create clear directory" ); System.exit( 2 ); } } if ( !encryptedDir.isDirectory() ) { if ( !encryptedDir.mkdir() ) { err.println( "unable to create encrypted directory" ); System.exit( 2 ); } } if ( !keysDir.isDirectory() ) { if ( !keysDir.mkdir() ) { err.println( "unable to create keys directory" ); System.exit( 2 ); } } } /** * mark one unit of progress * * @param progressBar JProgressBar where to register progress. */ private static void tick( final JProgressBar progressBar ) { ticks++; SwingUtilities.invokeLater( new Runnable() { public void run() { progressBar.setValue( ticks ); } } ); } /** * wipes given pad file, but does not delete. Should be run on non EDT thread. * * @param kind e.g. pad, encrypted file, plaintext file * @param fileToWipe file we are about to wipe * @param offset where to start wiping in the file. * @param length length of region to wipe * @param delete true if should delete after wiping. * @param instructions where to post instructions. * @param progressBar were to post progress */ static void wipe( final String kind, final File fileToWipe, final long offset, final long length, final boolean delete, final JLabel instructions, final JProgressBar progressBar ) { final String fullFileToWipeName = EIO.getCanOrAbsPath( fileToWipe ); if ( !fileToWipe.exists() ) { instructions.setText( "Oops: " + kind + " " + fullFileToWipeName + " has already been deleted." ); instructions.setForeground( OTP.FOREGROUND_FOR_WARNING ); return; } if ( !fileToWipe.canWrite() ) { // canWrite true implies file exists. instructions.setText( "Oops: The operating system refused permission to wipe " + kind + " " + fullFileToWipeName ); instructions.setForeground( OTP.FOREGROUND_FOR_WARNING ); return; } try { byte[] randomChunk = new byte[ OTP.CHUNKSIZE ]; long chunks = ( length / OTP.CHUNKSIZE ); int lastChunkSize = ( int ) ( length % OTP.CHUNKSIZE ); int passes = calcPasses( fileToWipe ); final int progressTicks = 2 /* open/close */ + passes * ( ( int ) chunks + 2 ); SwingUtilities.invokeLater( new Runnable() { public void run() { progressBar.setMaximum( progressTicks ); } } ); instruct( instructions, "Wiping " + kind + " " + fullFileToWipeName + " with " + passes + " passes", false ); ticks = 0; RandomAccessFile raf = new RandomAccessFile( fileToWipe, "rw" ); tick( progressBar ); for ( int pass = 0; pass < passes; pass++ ) { raf.seek( offset ); for ( int chunkNo = 0; chunkNo < chunks; chunkNo++ ) { if ( pass == passes - 1 ) { Arrays.fill( randomChunk, ( byte ) 0 ); } else { wheel.nextBytes( randomChunk ); } raf.write( randomChunk ); tick( progressBar ); } if ( pass == passes - 1 ) { Arrays.fill( randomChunk, ( byte ) 0 ); } else { wheel.nextBytes( randomChunk ); } raf.write( randomChunk, 0, lastChunkSize ); tick( progressBar ); raf.getFD().sync(); tick( progressBar ); } raf.close(); tick( progressBar ); instruct( instructions, kind + " " + fullFileToWipeName + " successfully wiped", false ); if ( delete ) { if ( fileToWipe.delete() ) { instruct( instructions, kind + " " + fullFileToWipeName + " successfully wiped and deleted.", false ); } else { instruct( instructions, "Oops: " + kind + " " + fullFileToWipeName + " successfully wiped but could not be deleted.", true ); } } else { instruct( instructions, kind + " " + fullFileToWipeName + " successfully wiped (but not deleted).", false ); } } catch ( IOException e ) { instruct( instructions, "Oops: " + kind + " " + fullFileToWipeName + " could not be wiped.", true ); } } /** * Main class for client. This a Java Web Start, not a JApplet. * * @param args not used. */ public static void main( String[] args ) { // dummy to hold configuration constants } }