Based Encryption

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. How can I create my own key? I have tried padding it out to 256 bits, but then I get an error saying that the key is too long. I do have the unlimited jurisdiction patch installed, so thats not the problem :)

Ie. The KeyGenerator looks like this ...

// Get the KeyGenerator
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128); // 192 and 256 bits may not be available

// Generate the secret key specs.
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();

Code taken from here

EDIT

I was actually padding the password out to 256 bytes, not bits, which is too long. The following is some code I am using now that I have some more experience with this.

byte[] key = null; // TODO
byte[] input = null; // TODO
byte[] output = null;
SecretKeySpec keySpec = null;
keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
output = cipher.doFinal(input)

The "TODO" bits you need to do yourself :-)


Share the password (a char[] ) and salt (a byte[] —8 bytes selected by a SecureRandom makes a good salt—which doesn't need to be kept secret) with the recipient out-of-band. Then to derive a good key from this information:

/* Derive the key, given password and salt. */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

The magic numbers (which could be defined as constants somewhere) 65536 and 256 are the key derivation iteration count and the key size, respectively.

The key derivation function is iterated to require significant computational effort, and that prevents attackers from quickly trying many different passwords. The iteration count can be changed depending on the computing resources available.

The key size can be reduced to 128 bits, which is still considered "strong" encryption, but it doesn't give much of a safety margin if attacks are discovered that weaken AES.

Used with a proper block-chaining mode, the same derived key can be used to encrypt many messages. In CBC, a random initialization vector (IV) is generated for each message, yielding different cipher text even if the plain text is identical. CBC may not be the most secure mode available to you (see AEAD below); there are many other modes with different security properties, but they all use a similar random input. In any case, the outputs of each encryption operation are the cipher text and the initialization vector:

/* Encrypt the message. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] ciphertext = cipher.doFinal("Hello, World!".getBytes("UTF-8"));

Store the ciphertext and the iv . On decryption, the SecretKey is regenerated in exactly the same way, using using the password with the same salt and iteration parameters. Initialize the cipher with this key and the initialization vector stored with the message:

/* Decrypt the message, given derived key and initialization vector. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
String plaintext = new String(cipher.doFinal(ciphertext), "UTF-8");
System.out.println(plaintext);

Java 7 included API support for AEAD cipher modes, and the "SunJCE" provider included with OpenJDK and Oracle distributions implements these beginning with Java 8. One of these modes is strongly recommended in place of CBC; it will protect the integrity of the data as well as their privacy.


A java.security.InvalidKeyException with the message "Illegal key size or default parameters" means that the cryptography strength is limited; the unlimited strength jurisdiction policy files are not in the correct location. In a JDK, they should be placed under ${jdk}/jre/lib/security

Based on the problem description, it sounds like the policy files are not correctly installed. Systems can easily have multiple Java runtimes; double-check to make sure that the correct location is being used.


Consider using the Spring Security Crypto Module

The Spring Security Crypto module provides support for symmetric encryption, key generation, and password encoding. The code is distributed as part of the core module but has no dependencies on any other Spring Security (or Spring) code.

It's provides a simple abstraction for encryption and seems to match what's required here,

The "standard" encryption method is 256-bit AES using PKCS #5's PBKDF2 (Password-Based Key Derivation Function #2). This method requires Java 6. The password used to generate the SecretKey should be kept in a secure place and not be shared. The salt is used to prevent dictionary attacks against the key in the event your encrypted data is compromised. A 16-byte random initialization vector is also applied so each encrypted message is unique.

A look at the internals reveals a structure similar to erickson's answer.

As noted in the question, this also requires the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy (else you'll encounter InvalidKeyException: Illegal Key Size ). It's downloadable for Java 6, Java 7 and Java 8.

Example usage

import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.security.crypto.keygen.KeyGenerators;

public class CryptoExample {
    public static void main(String[] args) {
        final String password = "I AM SHERLOCKED";  
        final String salt = KeyGenerators.string().generateKey();

        TextEncryptor encryptor = Encryptors.text(password, salt);      
        System.out.println("Salt: "" + salt + """);

        String textToEncrypt = "*royal secrets*";
        System.out.println("Original text: "" + textToEncrypt + """);

        String encryptedText = encryptor.encrypt(textToEncrypt);
        System.out.println("Encrypted text: "" + encryptedText + """);

        // Could reuse encryptor but wanted to show reconstructing TextEncryptor
        TextEncryptor decryptor = Encryptors.text(password, salt);
        String decryptedText = decryptor.decrypt(encryptedText);
        System.out.println("Decrypted text: "" + decryptedText + """);

        if(textToEncrypt.equals(decryptedText)) {
            System.out.println("Success: decrypted text matches");
        } else {
            System.out.println("Failed: decrypted text does not match");
        }       
    }
}

And sample output,

Salt: "feacbc02a3a697b0"
Original text: "*royal secrets*"
Encrypted text: "7c73c5a83fa580b5d6f8208768adc931ef3123291ac8bc335a1277a39d256d9a" 
Decrypted text: "*royal secrets*"
Success: decrypted text matches

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();
            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 + "nn");
          }
          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();
          }
       }
    
    
    }
    
    链接地址: http://www.djcxy.com/p/49150.html

    上一篇: 用CBC进行AES加密

    下一篇: 基于加密