Решено: AEADBadTagException. Я пробовал несколько способов и теперь храню соль и вектор инициализации в зашифрованном файле - PullRequest
0 голосов
/ 07 февраля 2020

Вектор инициализации и соль сохраняются вместе с зашифрованными данными, поэтому вам нужно будет только указать пароль. Он нужен мне для распространения моих личных ключей подписи кода, чтобы у меня был доступ с любого компьютера, на котором я работаю, для расшифровки и подписания любых изменений. Цель состоит в том, чтобы перейти на AES-256, но у него были проблемы, как описано в заголовке. Вот (ИСПРАВЛЕНО) код:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException; 
import java.security.InvalidKeyException; 
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException; 
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.io.IOUtils;
/**
 *
 * @author Tim
 */
public class CryptoTool {
    public static final String className = "CryptoTool";
    public static boolean debug = false;

    public CryptoTool() {
    }

    public byte[] encryptFile(String filename,char[] pass, byte[] salt, byte[] iv) {
        // generate key
        SecretKeySpec secretKeySpec = generateKeyFromPassword(pass,salt);
        // erase local password, don't worry, it's fast enough, (local to the function)
        for(int i = 0; i < pass.length;i++) pass[i] = '\0'; // NULL character (end of string for C/C++)

        byte[] data = getBytesUTF8(filename);
        byte[] output = null;
        if (!new File(filename).exists()) {
            System.out.println(className + "encryptFile: non-existent file: " + filename);
            // if you don't have a file to operate on, I mean, C'mon, get it together
            return null;
        }
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv); 
        byte[] aadData = "symService".getBytes();
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec, new SecureRandom());
            cipher.updateAAD(aadData);
            output = cipher.doFinal(data);
            byte[] encrypted = new byte[output.length + 20];
            System.arraycopy(salt  , 0, encrypted, 0 , 8);
            System.arraycopy(iv    , 0, encrypted, 8 , 12);
            System.arraycopy(output, 0, encrypted, 20,output.length);
            writeBytesTo(encrypted,filename + ".encrypted");
            //System.out.println("Encrypted To: " + filename + ".encrypted");
            new File(filename).delete();
        } catch (NoSuchAlgorithmException nsae) {
            System.out.println("No Such Algorithm: " + nsae);
        } catch (InvalidKeyException ike) {
            System.out.println("Invalid Key: " + ike);
        } catch (InvalidAlgorithmParameterException iape) {
            System.out.println("InvalidAlgorithmParameterException: " + iape);
        } catch (NoSuchPaddingException nspe) {
            System.out.println("NoSuchPaddingException: " + nspe);
        } catch (IllegalBlockSizeException ibse) {
            System.out.println("IllegalBlockSizeException: " + ibse);
        } catch (BadPaddingException bpe) {
            System.out.println("BadPaddingException: " + bpe);
        }
        return iv;
    }    

    public boolean decryptFile(char[] pass,String outPath) {
        // must pass a valid file, note that outPath should end in ".encrypted"
        if (outPath == null || !new File(outPath + ".encrypted").exists()) return false;

        // get the contents of the file
        byte[] input = getBytesUTF8(outPath + ".encrypted");

        // from the contents, get the salt and generate the key, so we can erase the password
        byte[] salt = new byte[8];
        System.arraycopy(input,0,salt,0,8);
        if (debug) {
            for(int i = 0; i < salt.length;i++) System.out.print((char)salt[i]);
            System.out.println();
        }

        // generate the key
        SecretKeySpec secretKeySpec = generateKeyFromPassword(pass,salt);
        for(int i = 0; i < pass.length;i++) pass[i] = '\0';

        // get the initalization vector
        byte[] iv = new byte[12];
        System.arraycopy(input, 8, iv, 0, 12);
        if (debug) {
            for(int i = 0; i < salt.length;i++) System.out.print((char)salt[i]);
            System.out.println();
        }

        // copy the encrypted data to a byte array
        byte[] encrypted = new byte[input.length - 20];
        System.arraycopy(input, 20, encrypted, 0, input.length - 20);

        // get the authenticated additional data (the tag we're having trouble with)
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv); 
        byte[] aadData = "symService".getBytes();

        // decrypt
        byte[] decrypted = null;
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec, new SecureRandom());
            cipher.updateAAD(aadData);
            decrypted = cipher.doFinal(encrypted);
            // save the decrypted file to its' original filename (removes the .encrypted from the filename)
            writeBytesTo(decrypted,outPath);
            // delete the encrypted file 
            new File(outPath + ".encrypted").delete();
            // if we made it this far, nothing failed, so return true; decryption successful
            return true;
        } catch (NoSuchAlgorithmException nsae) {
            System.out.println("No Such Algorithm: " + nsae);
        } catch (InvalidKeyException ike) {
            System.out.println("Invalid Key: " + ike);
        } catch (InvalidAlgorithmParameterException iape) {
            System.out.println("InvalidAlgorithmParameterException: " + iape);
        } catch (NoSuchPaddingException nspe) {
            System.out.println("NoSuchPaddingException: " + nspe);
        } catch (IllegalBlockSizeException ibse) {
            System.out.println("IllegalBlockSizeException: " + ibse);
        } catch (BadPaddingException bpe) {
            System.out.println("BadPaddingException: " + bpe);
            bpe.printStackTrace();
        }
        // otherwise, something went wrong, return failure code
        return false;
    }

    public static void main(String[] args) { 
/*      The purpose of this test is to encrypt and decrypt a keystore file so we
        may distribute our keys, without fear of someone hacking the data and
        signing code we don't authorize.
        If it works, we can distribute our keys and have immediate access to them
        without fear that anyone else can gain access to them. This allows us to 
        deploy from any machine attached to the Internet.
*/

        // toggle 'test' in order to test both encryption and decryption, or separately.
        boolean test =  true;

        CryptoTool tool = new CryptoTool();
        char[] pass = new RequestPassword().getPassword();
        SecureRandom sr = new SecureRandom();
        byte[] salt = new byte[8];
        sr.nextBytes(salt);
        byte[] iv = new byte[12];
        // maybe we could skip this next line, so delete that and just use one instance of SecureRandom???
        sr = new SecureRandom();
        sr.nextBytes(iv);
        if (debug) {
            for(int i = 0; i < salt.length;i++) System.out.print((char)salt[i]);
            System.out.println();
            for(int i = 0; i < iv.length;i++) System.out.print((char)iv[i]);
            System.out.println();
        }
        String plainFile = "c:\\java\\keystore_private";

        // if test is true, encrypt also, otherwise, just decrypt (in this case,
        // salt and IV should be generated, else, get them from the encrypted file.
        if (test) {
            tool.encryptFile(plainFile, pass, salt, iv);
        }
        if (new File(plainFile + ".encrypted").exists()) 
            System.out.println("Encrypted To: " + plainFile + ".encrypted");
        pass = new RequestPassword().getPassword();
        if (tool.decryptFile(pass,plainFile)) {
            System.out.println("Decrypted To: " + plainFile);
        } else {
            System.out.println("Couldn't Decrypt");
        }
        // does erasing in a function(method) also erase original? Local to method call.
        if (debug) {
            System.out.print("Pass: '");
            for(int i = 0; i < pass.length;i++) System.out.print(pass[i]);
            System.out.println("'");
        }
        System.exit(0);
    } 

    public byte[] getBytesUTF8(String filename) {
        FileInputStream in = null;
        try {
            in = new FileInputStream(filename);
        } catch (FileNotFoundException fnfe) {
            // So using this package, that shouldn't happend, but anyway
            System.out.println(className + ".getBytesUTF8: File not found: " + filename);
        }
        // okay, so load the class
        byte[] data = null;
        try { 
            data = IOUtils.toByteArray(in);
        } catch (IOException ioe) {
            System.err.println("IOException: reading class data.");
        }
        try {
            in.close();
        } catch (IOException ioe) {
            System.out.println("Unable to close: " + filename);
        }
        return data;

    }

    public void writeBytesTo(byte[] data, String filename) {
        FileOutputStream output = null;
        try {
            output = new FileOutputStream(new File(filename));
        } catch (FileNotFoundException fnfe) {
            System.err.println("Couldn't create: " + filename);
        }
        try {
            IOUtils.write(data, output);
            //System.out.println("Wrote file: " + filename);
        } catch (IOException ioe) {
            System.err.println("Couldn't write data to: " + filename);
        }
        try {
            output.close();
        } catch (IOException ioe) {
            System.out.println("Couldn't close: " + filename);
        }
    }

    public SecretKeySpec generateKeyFromPassword(char[] password, byte[] salt) {
        /* Derive the key, given password and salt. */
        SecretKeyFactory factory = null;
        try {
            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        } catch (NoSuchAlgorithmException nsae) {
            System.out.println("No Such Algorithm: " + nsae);
        }
        KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
        SecretKey tmp = null;
        try { 
            tmp = factory.generateSecret(spec);
        } catch (InvalidKeySpecException ikse) {
            System.out.println("Inavalid Key Spec Exception: " + ikse);        
        }
        SecretKeySpec keySpec = new SecretKeySpec(tmp.getEncoded(), "AES");        
        return keySpec;
    }
}

1 Ответ

0 голосов
/ 08 февраля 2020

Я понял это. Код стирал пароль, поэтому на этапе расшифровки проход был бы равен «», что, как ожидается, не было правильным. Таким образом, aeadException ссылается на неверный проход или iv.

...