import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.InvalidKeyException;
import javax.swing.JOptionPane;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.crypto.Cipher;
import java.security.Key;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import java.io.ByteArrayOutputStream;
import java.security.PublicKey;
import java.security.MessageDigest;
import java.security.Signature;
import java.security.SignatureException;

public class JavaSecretKeyPublicKeyDecryptionWithSigning {
    
    public JavaSecretKeyPublicKeyDecryptionWithSigning() throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
            BadPaddingException, ClassNotFoundException, InvalidKeyException, IllegalBlockSizeException, SignatureException  {
        
        String algorithmName = "AES";
        Cipher cipher = Cipher.getInstance(algorithmName);
        
        // Read Bill's private key
        ObjectInputStream keyIn = new ObjectInputStream(new FileInputStream("BillsRSAPrivateKey"));
        PrivateKey privKey = (PrivateKey)keyIn.readObject();
        System.out.println(privKey);
        keyIn.close();
        
        while(true) {
            String fileName = JOptionPane.showInputDialog("Enter (Encrypted) filename","GenerateRSAPublicPrivateKeyPair.java");
            if( fileName == null ) break;
            FileInputStream in = new FileInputStream("Encrypted" + fileName);
            
            // Read encrypted wrapped secretKey
            // Read wrapped key length and convert from hex
            String len = "";
            for(int i = 0; i < 4; i++ ) {
                byte byte1 = (byte)in.read();
                byte byte2 = (byte)in.read();
                len = (char)byte1 + "" + (char)byte2 + len;
            } // end for
            int inLen = Integer.parseInt(len,16);
            
            // Read wrapped key and convert from hex
            byte[] wrappedKey = new byte[inLen];
            String keyString = "";
            for( int i = 0; i < inLen; i++ ) {
                byte byte1 = (byte)in.read();
                byte byte2 = (byte)in.read();
                String hex = (char)byte1 + "" + (char)byte2;
                wrappedKey[i] = (byte)Integer.parseInt(hex,16);
            } // end while
            
            // Unwrap the secret key
            Cipher keyCipher = Cipher.getInstance("RSA");
            keyCipher.init(Cipher.UNWRAP_MODE,privKey);
            Key secretKey = keyCipher.unwrap(wrappedKey,algorithmName,Cipher.SECRET_KEY);
            
            // Decrypt File
            FileOutputStream out = new FileOutputStream("Decrypted" + fileName);
            cipher.init(Cipher.DECRYPT_MODE,secretKey);
            decryptFile(in,cipher,out);
            
            // Get signature public key
            ObjectInputStream pubKeyIn = new ObjectInputStream(new FileInputStream("BillsPublicSigningKey"));
            PublicKey pubSigningKey = (PublicKey)pubKeyIn.readObject();
            System.out.println(pubSigningKey);
            pubKeyIn.close();
            ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
            int next;
            while( (next = in.read()) != -1 ) {
                byte nextByte1 = (byte)next;
                byte nextByte2 = (byte)in.read();
                String decodeByte = (char)nextByte1 + "" + (char)nextByte2;
                byteBuffer.write((byte)Integer.parseInt(decodeByte,16));
            } // end while
            byte[] signature = byteBuffer.toByteArray();
            System.out.println("Signature =\n" + byteToHex(signature));
            // Hash decrypted message
            MessageDigest SHA1alg = MessageDigest.getInstance("SHA-1");
            FileInputStream decryptIn = new FileInputStream("Decrypted" + fileName);
            // if first = true then a bit of the message is changed
            boolean first = false;
            while( (next = decryptIn.read()) != -1 ) {
                if(first) {
                    next = next^0x01;
                    first = false;
                } // end if
                SHA1alg.update((byte)next);
            } // end while
            byte[] messageHash = SHA1alg.digest();
            Signature verifyAlg = Signature.getInstance("DSA");
            verifyAlg.initVerify(pubSigningKey);
            System.out.println(verifyAlg);
            verifyAlg.update(messageHash);
            boolean check = verifyAlg.verify(signature);
            if( check ) {
                System.out.println("\nThe signatures match.");
            } else {
                System.out.println("\nThe signatures DO NOT match!");
            } // end if
            
            // Show encrypted file and decrypted file
            String code = getFile("Encrypted" + fileName);
            String decode = getFile("Decrypted" + fileName);
            String outString = "Coded message =\n"+code+ "\n\nDecoded message = \n"+decode;
            JTextArea outArea = new JTextArea(outString,30,60);
            JOptionPane.showMessageDialog(null,new JScrollPane(outArea));
        } // end while
        
    }// end JavaSecretKeyPublicKeyEncryption
    
    public static void main(String[] a) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,SignatureException,
            BadPaddingException, ClassNotFoundException, InvalidKeyException, IllegalBlockSizeException {
        new JavaSecretKeyPublicKeyDecryptionWithSigning();
        System.exit(0);
    } // end main
    
    public void decryptFile(FileInputStream in, Cipher cipher, FileOutputStream out) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
            BadPaddingException, ClassNotFoundException, InvalidKeyException, IllegalBlockSizeException  {
        ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
        String encryptedString = "";
        int next;
        while( (next = in.read()) != 0 ) {
            if( (char)next == '\n' ) next = in.read();
            if( next == 0 ) break;
            byte nextByte1 = (byte)next;
            byte nextByte2 = (byte)in.read();
            String decodeByte = (char)nextByte1 + "" + (char)nextByte2;
            byteBuffer.write((byte)Integer.parseInt(decodeByte,16));
        } // end while
        byte[] messageBytes = byteBuffer.toByteArray();
        byte[] outBytes = cipher.doFinal(messageBytes);
        for( int i = 0; i < outBytes.length; i++ ) {
            out.write((byte)outBytes[i]);
        } // end for
        //in.close();
        out.close();
    } // end decryptFile
    
    String getFile(String fileName) throws IOException {
        FileInputStream in = new FileInputStream(fileName);
        int next;
        StringBuffer message = new StringBuffer();
        while( (next = in.read()) != -1 ) {
            byte nextByte = (byte)next;
            message.append((char)nextByte);
        } // end while
        in.close();
        return message.toString();
    }  // end getMessage
    
    public String convertToHex(byte b) {
        String hex = Integer.toString(b&0xFF,16).toUpperCase();
        if( hex.length() == 1 ) hex = "0" + hex;
        return hex;
    } // end convertToHex
    
    String byteToHex(byte[] bytes) {
        String out = "";
        for( int i = 0; i < bytes.length; i++ ) {
            out += convertToHex(bytes[i]);
        } // end for
        return out;
    }  // end byteToHex
    
} // end JavaSecretKeyPublicKeyDecryptionWithSigning
