Java 256-bit AES Password-Based Encryption

后端 未结 9 1390
名媛妹妹
名媛妹妹 2020-11-21 05:22

I need to implement 256 bit AES encryption, but all the examples I have found online use a \"KeyGenerator\" to generate a 256 bit key, but I would like to use my own passkey

9条回答
  •  南方客
    南方客 (楼主)
    2020-11-21 05:53

    After reading through erickson's suggestions, and gleaning what I could from a couple other postings and this example here, I've attempted to update Doug's code with the recommended changes. Feel free to edit to make it better.

    • Initialization Vector is no longer fixed
    • encryption key is derived using code from erickson
    • 8 byte salt is generated in setupEncrypt() using SecureRandom()
    • decryption key is generated from the encryption salt and password
    • decryption cipher is generated from decryption key and initialization vector
    • removed hex twiddling in lieu of org.apache.commons codec Hex routines

    Some notes: This uses a 128 bit encryption key - java apparently won't do 256 bit encryption out-of-the-box. Implementing 256 requires installing some extra files into the java install directory.

    Also, I'm not a crypto person. Take heed.

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.security.AlgorithmParameters;
    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.InvalidParameterSpecException;
    import java.security.spec.KeySpec;
    
    import javax.crypto.BadPaddingException;
    import javax.crypto.Cipher;
    import javax.crypto.CipherInputStream;
    import javax.crypto.CipherOutputStream;
    import javax.crypto.IllegalBlockSizeException;
    import javax.crypto.NoSuchPaddingException;
    import javax.crypto.SecretKey;
    import javax.crypto.SecretKeyFactory;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.PBEKeySpec;
    import javax.crypto.spec.SecretKeySpec;
    
    import org.apache.commons.codec.DecoderException;
    import org.apache.commons.codec.binary.Hex;
    
    public class Crypto
    {
        String mPassword = null;
        public final static int SALT_LEN = 8;
        byte [] mInitVec = null;
        byte [] mSalt = null;
        Cipher mEcipher = null;
        Cipher mDecipher = null;
        private final int KEYLEN_BITS = 128; // see notes below where this is used.
        private final int ITERATIONS = 65536;
        private final int MAX_FILE_BUF = 1024;
    
        /**
         * create an object with just the passphrase from the user. Don't do anything else yet 
         * @param password
         */
        public Crypto (String password)
        {
            mPassword = password;
        }
    
        /**
         * return the generated salt for this object
         * @return
         */
        public byte [] getSalt ()
        {
            return (mSalt);
        }
    
        /**
         * return the initialization vector created from setupEncryption
         * @return
         */
        public byte [] getInitVec ()
        {
            return (mInitVec);
        }
    
        /**
         * debug/print messages
         * @param msg
         */
        private void Db (String msg)
        {
            System.out.println ("** Crypt ** " + msg);
        }
    
        /**
         * this must be called after creating the initial Crypto object. It creates a salt of SALT_LEN bytes
         * and generates the salt bytes using secureRandom().  The encryption secret key is created 
         * along with the initialization vectory. The member variable mEcipher is created to be used
         * by the class later on when either creating a CipherOutputStream, or encrypting a buffer
         * to be written to disk.
         *  
         * @throws NoSuchAlgorithmException
         * @throws InvalidKeySpecException
         * @throws NoSuchPaddingException
         * @throws InvalidParameterSpecException
         * @throws IllegalBlockSizeException
         * @throws BadPaddingException
         * @throws UnsupportedEncodingException
         * @throws InvalidKeyException
         */
        public void setupEncrypt () throws NoSuchAlgorithmException, 
                                                               InvalidKeySpecException, 
                                                               NoSuchPaddingException, 
                                                               InvalidParameterSpecException, 
                                                               IllegalBlockSizeException, 
                                                               BadPaddingException, 
                                                               UnsupportedEncodingException, 
                                                               InvalidKeyException
        {
            SecretKeyFactory factory = null;
            SecretKey tmp = null;
    
            // crate secureRandom salt and store  as member var for later use
             mSalt = new byte [SALT_LEN];
            SecureRandom rnd = new SecureRandom ();
            rnd.nextBytes (mSalt);
            Db ("generated salt :" + Hex.encodeHexString (mSalt));
    
            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    
            /* Derive the key, given password and salt. 
             * 
             * in order to do 256 bit crypto, you have to muck with the files for Java's "unlimted security"
             * The end user must also install them (not compiled in) so beware. 
             * see here:  http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
             */
            KeySpec spec = new PBEKeySpec (mPassword.toCharArray (), mSalt, ITERATIONS, KEYLEN_BITS);
            tmp = factory.generateSecret (spec);
            SecretKey secret = new SecretKeySpec (tmp.getEncoded(), "AES");
    
            /* Create the Encryption cipher object and store as a member variable
             */
            mEcipher = Cipher.getInstance ("AES/CBC/PKCS5Padding");
            mEcipher.init (Cipher.ENCRYPT_MODE, secret);
            AlgorithmParameters params = mEcipher.getParameters ();
    
            // get the initialization vectory and store as member var 
            mInitVec = params.getParameterSpec (IvParameterSpec.class).getIV();
    
            Db ("mInitVec is :" + Hex.encodeHexString (mInitVec));
        }
    
    
    
        /**
         * If a file is being decrypted, we need to know the pasword, the salt and the initialization vector (iv). 
         * We have the password from initializing the class. pass the iv and salt here which is
         * obtained when encrypting the file initially.
         *   
         * @param initvec
         * @param salt
         * @throws NoSuchAlgorithmException
         * @throws InvalidKeySpecException
         * @throws NoSuchPaddingException
         * @throws InvalidKeyException
         * @throws InvalidAlgorithmParameterException
         * @throws DecoderException
         */
        public void setupDecrypt (String initvec, String salt) throws NoSuchAlgorithmException, 
                                                                                           InvalidKeySpecException, 
                                                                                           NoSuchPaddingException, 
                                                                                           InvalidKeyException, 
                                                                                           InvalidAlgorithmParameterException, 
                                                                                           DecoderException
        {
            SecretKeyFactory factory = null;
            SecretKey tmp = null;
            SecretKey secret = null;
    
            // since we pass it as a string of input, convert to a actual byte buffer here
            mSalt = Hex.decodeHex (salt.toCharArray ());
           Db ("got salt " + Hex.encodeHexString (mSalt));
    
            // get initialization vector from passed string
            mInitVec = Hex.decodeHex (initvec.toCharArray ());
            Db ("got initvector :" + Hex.encodeHexString (mInitVec));
    
    
            /* Derive the key, given password and salt. */
            // in order to do 256 bit crypto, you have to muck with the files for Java's "unlimted security"
            // The end user must also install them (not compiled in) so beware. 
            // see here: 
          // http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files.shtml
            factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            KeySpec spec = new PBEKeySpec(mPassword.toCharArray (), mSalt, ITERATIONS, KEYLEN_BITS);
    
            tmp = factory.generateSecret(spec);
            secret = new SecretKeySpec(tmp.getEncoded(), "AES");
    
            /* Decrypt the message, given derived key and initialization vector. */
            mDecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            mDecipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(mInitVec));
        }
    
    
        /**
         * This is where we write out the actual encrypted data to disk using the Cipher created in setupEncrypt().
         * Pass two file objects representing the actual input (cleartext) and output file to be encrypted.
         * 
         * there may be a way to write a cleartext header to the encrypted file containing the salt, but I ran
         * into uncertain problems with that. 
         *  
         * @param input - the cleartext file to be encrypted
         * @param output - the encrypted data file
         * @throws IOException
         * @throws IllegalBlockSizeException
         * @throws BadPaddingException
         */
        public void WriteEncryptedFile (File input, File output) throws 
                                                                                              IOException, 
                                                                                              IllegalBlockSizeException, 
                                                                                              BadPaddingException
        {
            FileInputStream fin;
            FileOutputStream fout;
            long totalread = 0;
            int nread = 0;
            byte [] inbuf = new byte [MAX_FILE_BUF];
    
            fout = new FileOutputStream (output);
            fin = new FileInputStream (input);
    
            while ((nread = fin.read (inbuf)) > 0 )
            {
                Db ("read " + nread + " bytes");
                totalread += nread;
    
                // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
                // and results in full blocks of MAX_FILE_BUF being written. 
                byte [] trimbuf = new byte [nread];
                for (int i = 0; i < nread; i++)
                    trimbuf[i] = inbuf[i];
    
                // encrypt the buffer using the cipher obtained previosly
                byte [] tmp = mEcipher.update (trimbuf);
    
                // I don't think this should happen, but just in case..
                if (tmp != null)
                    fout.write (tmp);
            }
    
            // finalize the encryption since we've done it in blocks of MAX_FILE_BUF
            byte [] finalbuf = mEcipher.doFinal ();
            if (finalbuf != null)
                fout.write (finalbuf);
    
            fout.flush();
            fin.close();
            fout.close();
    
            Db ("wrote " + totalread + " encrypted bytes");
        }
    
    
        /**
         * Read from the encrypted file (input) and turn the cipher back into cleartext. Write the cleartext buffer back out
         * to disk as (output) File.
         * 
         * I left CipherInputStream in here as a test to see if I could mix it with the update() and final() methods of encrypting
         *  and still have a correctly decrypted file in the end. Seems to work so left it in.
         *  
         * @param input - File object representing encrypted data on disk 
         * @param output - File object of cleartext data to write out after decrypting
         * @throws IllegalBlockSizeException
         * @throws BadPaddingException
         * @throws IOException
         */
        public void ReadEncryptedFile (File input, File output) throws 
                                                                                                                                                IllegalBlockSizeException, 
                                                                                                                                                BadPaddingException, 
                                                                                                                                                IOException
        {
            FileInputStream fin; 
            FileOutputStream fout;
            CipherInputStream cin;
            long totalread = 0;
            int nread = 0;
            byte [] inbuf = new byte [MAX_FILE_BUF];
    
            fout = new FileOutputStream (output);
            fin = new FileInputStream (input);
    
            // creating a decoding stream from the FileInputStream above using the cipher created from setupDecrypt()
            cin = new CipherInputStream (fin, mDecipher);
    
            while ((nread = cin.read (inbuf)) > 0 )
            {
                Db ("read " + nread + " bytes");
                totalread += nread;
    
                // create a buffer to write with the exact number of bytes read. Otherwise a short read fills inbuf with 0x0
                byte [] trimbuf = new byte [nread];
                for (int i = 0; i < nread; i++)
                    trimbuf[i] = inbuf[i];
    
                // write out the size-adjusted buffer
                fout.write (trimbuf);
            }
    
            fout.flush();
            cin.close();
            fin.close ();       
            fout.close();   
    
            Db ("wrote " + totalread + " encrypted bytes");
        }
    
    
        /**
         * adding main() for usage demonstration. With member vars, some of the locals would not be needed
         */
        public static void main(String [] args)
        {
    
            // create the input.txt file in the current directory before continuing
            File input = new File ("input.txt");
            File eoutput = new File ("encrypted.aes");
            File doutput = new File ("decrypted.txt");
            String iv = null;
            String salt = null;
            Crypto en = new Crypto ("mypassword");
    
            /*
             * setup encryption cipher using password. print out iv and salt
             */
            try
          {
              en.setupEncrypt ();
              iv = Hex.encodeHexString (en.getInitVec ()).toUpperCase ();
              salt = Hex.encodeHexString (en.getSalt ()).toUpperCase ();
          }
          catch (InvalidKeyException e)
          {
              e.printStackTrace();
          }
          catch (NoSuchAlgorithmException e)
          {
              e.printStackTrace();
          }
          catch (InvalidKeySpecException e)
          {
              e.printStackTrace();
          }
          catch (NoSuchPaddingException e)
          {
              e.printStackTrace();
          }
          catch (InvalidParameterSpecException e)
          {
              e.printStackTrace();
          }
          catch (IllegalBlockSizeException e)
          {
              e.printStackTrace();
          }
          catch (BadPaddingException e)
          {
              e.printStackTrace();
          }
          catch (UnsupportedEncodingException e)
          {
              e.printStackTrace();
          }
    
            /*
             * write out encrypted file
             */
            try
          {
              en.WriteEncryptedFile (input, eoutput);
              System.out.printf ("File encrypted to " + eoutput.getName () + "\niv:" + iv + "\nsalt:" + salt + "\n\n");
          }
          catch (IllegalBlockSizeException e)
          {
              e.printStackTrace();
          }
          catch (BadPaddingException e)
          {
              e.printStackTrace();
          }
          catch (IOException e)
          {
              e.printStackTrace();
          }
    
    
            /*
             * decrypt file
             */
            Crypto dc = new Crypto ("mypassword");
            try
          {
              dc.setupDecrypt (iv, salt);
          }
          catch (InvalidKeyException e)
          {
              e.printStackTrace();
          }
          catch (NoSuchAlgorithmException e)
          {
              e.printStackTrace();
          }
          catch (InvalidKeySpecException e)
          {
              e.printStackTrace();
          }
          catch (NoSuchPaddingException e)
          {
              e.printStackTrace();
          }
          catch (InvalidAlgorithmParameterException e)
          {
              e.printStackTrace();
          }
          catch (DecoderException e)
          {
              e.printStackTrace();
          }
    
            /*
             * write out decrypted file
             */
            try
          {
              dc.ReadEncryptedFile (eoutput, doutput);
              System.out.println ("decryption finished to " + doutput.getName ());
          }
          catch (IllegalBlockSizeException e)
          {
              e.printStackTrace();
          }
          catch (BadPaddingException e)
          {
              e.printStackTrace();
          }
          catch (IOException e)
          {
              e.printStackTrace();
          }
       }
    
    
    }
    

提交回复
热议问题