408

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 :-)

erickson
  • 249,448
  • 50
  • 371
  • 469
Nippysaurus
  • 19,402
  • 18
  • 71
  • 124
  • Could you clarify: does calling kgen.init(256) work? – Mitch Wheat Jun 14 '09 at 02:45
  • 2
    Yes, but this automatically generates a key ... but since I want to encrypt data between two places, I need to know the key beforehand, so I need to specify one instead of "generate" one. I can specify a 16bit one which works for 128bit encryption which works. I have tried a 32bit one for 256bit encryption, but it did not work as expected. – Nippysaurus Jun 14 '09 at 03:25
  • 4
    If I understand correctly, you are trying to use a pre-arranged, 256-bit key, specified, for example, as an array of bytes. If so, DarkSquid's approach using SecretKeySpec should work. It's also possible to derive an AES key from a password; if that's what you are after, please let me know, and I'll show you the correct way to to do it; simply hashing a password isn't the best practice. – erickson Jun 14 '09 at 04:13
  • Be careful about padding a number, you may be making your AES less secure. – Joshua Jun 14 '09 at 04:24
  • 1
    @erickson: that is exatly what i need to do (derive an AES key from a password). – Nippysaurus Jun 14 '09 at 04:59
  • @erickson: Nope. I got both .NET and Java to compile something in what I thought was 256bit, but the results were different. I might not need to do the Java side after all. As a last resort we will probably use bouncycastle :) – Nippysaurus Jun 16 '09 at 02:18

9 Answers9

497

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 Cipher Block Chaining (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(StandardCharsets.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), StandardCharsets.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.

erickson
  • 249,448
  • 50
  • 371
  • 469
  • Argh. Salts are not necessary for symmetric encryption. IVs serve a similar purpose, and are prepended to the ciphertext by most (all?) crypto libraries. – Nick Johnson Jun 14 '09 at 21:02
  • 33
    @Nick: Read PKCS #5. Salts are necessary for PBKDF2, which is why the API for password-based encryption requires them as input for key derivation. Without salts, a dictionary attack could be used, enabling a pre-computed list of the most likely symmetric encryption keys. Cipher IVs and key-derivation salts serve different purposes. IVs allow one reuse the same key for multiple messages. Salts prevent dictionary attacks on the key. – erickson Jun 14 '09 at 23:26
  • Does this usage of SecureKey and PBEKeySpec produce a Password-based key that is RFC2898-compliant? http://www.ietf.org/rfc/rfc2898.txt – Cheeso Jul 31 '09 at 13:26
  • 1
    Yes, that is the "PBKDF2" of the SecretKeyFactory algorithm name. It is referring to PKCS #5's PBKDF2 (Password-Based Key Derivation Function #2). – erickson Jul 31 '09 at 17:47
  • 1
    If you were storing encrypted data in a database using the method above, would you store the ciphertext (blob) and the iv (char) in the database, and the password and salts would be supplied to the client application by the user or in the application's configuration files? – JeeBee Oct 13 '09 at 17:10
  • 2
    In that case, I'd store the ciphertext in one field, the IV in another, and the "salt" and "iterations" together in a third. I'd prompt the user in the client application for the password, and derive the key using the stored salt and iterations. Then initialize the cipher with the derived key and stored IV, and decrypt the content. – erickson Oct 13 '09 at 17:57
  • 1
    @erickson: in case you store iv, salt, etc. in the database, why not just use 'PBEWithMD5AndDES' and append the salt to the encrypted text, after loading u can strip the salt and use it for decryption. This way PBE is implemented in jasypt framework: www.jasypt.org – Chris Oct 15 '09 at 15:46
  • 2
    First, that would be DES encryption, not AES. Most providers don't have good support for the `PBEwithand` algorithms; for example, the SunJCE doesn't provide and PBE for AES. Second, enabling jasypt is a non-goal. A package that purports to offer security without requiring an understanding of the underlying principles seems dangerous prima facie. – erickson Oct 15 '09 at 16:10
  • @erickson so the lack of support is the only criteria not to use 'PBEWithMD5AndDES'? Are there any further criterias not to use this algo for enryption of data stored in a db? – Chris Oct 15 '09 at 17:02
  • Although MD5 is "broken" in some applications, I don't think that it is a problem here. But the weakness of DES is definitely a problem. The tiny key of DES can be broken in hours or days using a few thousand dollars worth of equipment. – erickson Oct 15 '09 at 19:34
  • 6
    I've implemented @erickson's answer as a class: https://github.com/mrclay/jSecureEdit/tree/master/src/org/mrclay/crypto (PBE does the work, PBEStorage is a value object for storing the IV/ciphertext together.) – Steve Clay Apr 21 '11 at 04:10
  • Trying to implement this with Doug's code below. erickson says to "Share the password (a char[]) and salt (a byte[]—8" with recipient but then "send the ciphertext and the iv to the recipient" which is it? the iv or the salt? They are different correct? – wufoo Apr 10 '12 at 14:36
  • @wufoo They *are* different, and you share both. You'll probably share the salt (for key derivation) one time, at the same time you set up the password itself. But every message you send after that should include the unique IV that was used to encrypt that message (and *only* that message. – erickson Apr 10 '12 at 18:18
  • @wufoo By the way, Doug's code has a number of security flaws. I can't recommend it as an example. – erickson Apr 10 '12 at 18:21
  • @erickson I am just storing hashed/encrypted passwords in a database. Can your example be used for safe storage of passwords, rather than using a sha1sum of the password and a salt? – Andy Nuss Apr 17 '12 at 13:36
  • 3
    @AndyNuss This example is for reversible encryption, which generally should not be used for passwords. You *can* use the PBKDF2 key derivation to "hash" passwords securely. That means that in the example above, you'd store the result of `tmp.getEncoded()` as the hash. You should also store the `salt` and the iterations (65536 in this example) so that you can recompute the hash when someone tries to authenticate. In this case, generate the salt with a cryptographic random number generator each time the password is changed. – erickson Apr 17 '12 at 16:03
  • 6
    For running this code, make sure you have the right Unlimited Strength Jurisdiction Policy Files in your JRE as stated in http://www.ngs.ac.uk/tools/jcepolicyfiles – Amir Moghimi Jun 25 '12 at 06:01
  • Is there a way of doing this without Unlimited Strength Jurisdiction Policy files? – Archimedes Trajano Jul 08 '13 at 01:04
  • @ArchimedesTrajano Not with the Java Cryptography Architecture. I would suggest using AES-128. AES-128 is good. NIST recommends it for applications through 2030. If you really need AES-256 and can't get it through the JCA, you can use a non-standard API, like BouncyCastle's "lightweight" crypto API directly to bypass the restrictions. I can't speak to the legal ramifications of doing so (your software might be illegal to import into certain regions). – erickson Jul 08 '13 at 18:11
  • 2
    +1 but if you send the encrypted data over the network as implied in the answer you have to make sure that integrity of the message is ensured. In other words you need to generate another key to use a MAC, or you could use an authenticated mode of encryption such as GCM. – Maarten Bodewes Sep 18 '13 at 22:17
  • Yes, I wrote this before the APIs were available to do GCM in Java SE, but they were introduced in Java 7, and implementations for them in the standard SunJCE provider were added in Java 8. So if you have the right provider in Java 7, or you are using Java 8, using GCM is highly recommended to avoid [Moxie Marlinspike's Cryptographic Doom Principle.](http://www.thoughtcrime.org/blog/the-cryptographic-doom-principle/) – erickson May 07 '14 at 18:14
  • 2
    Also note that *if* the salt is fully random each time the plaintext is encrypted, that you may skip the creation (and therefore sending/communication) of a random IV. Unique/random IV's are only required if the key does not change for each encryption, but the use of a random salt will result in different keys for each encryption. – Maarten Bodewes May 09 '14 at 13:57
  • Yep, I'm assuming that the salt and rounds are fixed when the password is chosen, and stored as key derivation parameters somewhere. – erickson May 09 '14 at 17:21
  • 1
    After reading all of this I have few questions: 1) do you have to use `params.getParameterSpec(IvParameterSpec.class).getIV()` or can you just call ` cipher.getIV(); ` ? 2) Lets say I'm trying to implememnt simple chat application and I want to set up passsword once and then two people can communicate with each other. Does IV being generated only once and then used to encrypt every message or does it have to be generated for every message(if yes, then how) ? – tvoloshyn May 15 '14 at 15:54
  • 2
    @voltar Yes, you can call `getIV()`. For your chat application, each message would have a different IV. What constitutes a message depends on how you write your code. If you create one `CipherOutputStream` and use it to encrypt a socket's `OutputStream` for the duration of the chat session, then there'd only be one IV. If you are posting individual chats to a web application, then there'd be a unique IV for each chat. Basically, a new IV should be generated after calling `doFinal()` on the `Cipher`. – erickson May 15 '14 at 16:06
  • Thank you for a such a quick answer ! So every time, you call getIV() the new vector is returned(according to docs) ? – tvoloshyn May 15 '14 at 16:12
  • @voltar No. Everytime you call an `init()` method on `Cipher`, an new IV is generated. You can reuse a `Cipher` instance without calling `init()` again, but then its IV won't change, which is a problem. The `getIV()` documentation does *not* say a new IV is chosen with each call. Maybe you are getting confused by the "new buffer" wording; that just means the current IV is copied to a new array that isn't referenced anywhere else. – erickson May 15 '14 at 16:26
  • what is meant by key out out-of-band. – Doug Hauf Jul 10 '14 at 19:18
  • 1
    @DougHauf It means you have to [share the key beforehand,](http://en.m.wikipedia.org/wiki/Out-of-band_agreement) by some secure means; it can't be sent over the insecure channel. – erickson Jul 10 '14 at 20:19
  • Beginning with Android 4.4 KitKat, we’ve made a subtle change to the behavior of SecretKeyFactory. This change may break some applications that use symmetric encryption – Nick Jian Sep 10 '14 at 04:33
  • @NickJian Are you talking about [this?](http://android-developers.blogspot.ca/2013/12/changes-to-secretkeyfactory-api-in.html) – erickson Sep 10 '14 at 04:41
  • Yes, I was trying to post the link also, but anti-spam stopped me from doing it. http://android-developers.blogspot.ca/2013/12/changes-to-secretkeyfactory-api-in.html – Nick Jian Sep 11 '14 at 10:36
  • 1
    does the encryption code comply to this openssl command? openssl enc -aes-256-cbc -a -in t.txt -k testpass – mohamnag Sep 11 '15 at 08:08
  • @mohamnag No, by default, OpenSSL uses a non-standard, low-quality key derivation algorithm instead of PBKDF2, like I show above. However, I wrote [some Java code to decrypt messages encrypted with OpenSSL.](http://stackoverflow.com/a/761902/3474) For AES-256, you'd need to adjust the length of the derived key (you'd need 32 bytes for the key, and the next 16 bytes for the IV), and change the transform to `"AES/CBC/PKCS5Padding"`. – erickson Sep 11 '15 at 19:06
  • thanks for the info, I'm having the trouble that I should recreate the exact openssl behaviour to interact with an external system. so I'm looking for the java equivalent of that openssl command: http://stackoverflow.com/questions/32508961/java-equivalent-of-an-openssl-aes-cbc-encryption – mohamnag Sep 12 '15 at 08:09
  • 2
    **Warning**: *sending* CBC ciphertext leads to vulnerabilities with regards to padding oracle attacks as well as making your plaintext vulnerable to changes. To send ciphertext with AES-256, use TLS with AES-256 ciphersuite, possibly using PSK with the derived key. – Maarten Bodewes Sep 27 '15 at 23:23
  • @MaartenBodewes Good point. I have re-worded the answer in that respect, and included a recommendation for AEAD. As time allows I'll an an example. – erickson Sep 28 '15 at 16:37
  • 2
    PBKDF2WithHmacSHA256 is not supported in java6 and java 7 – happy Jul 09 '16 at 07:25
  • The above code that generates the key is *NOT* correct because it is using a PBE based encryption key "PBKDF2WithHmacSHA256" to generate the secret key. And then it is associating that generated secret key with an "AES" encryption algorithm. That is wrong! The algorithm used to generate the key *MUST* be the same one being associated in the SecretKey. – rubens Oct 28 '16 at 01:39
  • 2
    @user2213684 You are wrong. The PBKDF algorithms are the correct way to derive keys from a password for any symmetric algorithm. And specifying AES as the algorithm and using a `PBEKeySpec` will fail. Why did you make this comment when you don't have any experience or knowledge about it? – erickson Oct 28 '16 at 02:27
  • @Neri Read the whole post. The solution for that exact error message is discussed. – erickson Feb 15 '17 at 18:41
  • @erickson I have seen but failed to solve it. Thanks let me go with 128 which works fine. – Neri Feb 16 '17 at 13:05
  • @Neri You have to make sure that the correct files are installed in the correct place. Why that isn't happening in your case would take more detailed troubleshooting, in a new question. But you are right, 128 offers plenty of security. 256 is just extra insurance in case some weakness is found someday in AES; it provides a margin of safety that might never be necessary. – erickson Feb 16 '17 at 16:44
  • Useful, but missing wrapper / imports? Good area for website / new (complete) example @erickson ? I think so. – DaveP Feb 27 '17 at 16:02
  • @erickson Thanks for this great overview. In an other question you recommend someone to use a crypto library. What are the benefits of using a library? I personally want to avoid using tons of code I can not (safely) verify.. Would the solution above (in 256 bit) provide a decent protection when saving passwords / banking data, if implemented correctly? – Martin Pfeffer Mar 30 '17 at 13:41
  • 1
    @codenamezero @Neri did you installed the *matching* policy files in your java installation? There are different.. for Java7 you will need these http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html if you are running Java8 you will need the package for Java8. Insert them in `JAVA_HOME/jre/lib/security` on my mac the path looks like `/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/security/INSERT HERE` – Martin Pfeffer Mar 30 '17 at 13:52
  • 1
    @MartinPfeffer Yes, at the heart of a good Java crypto library for password-based encryption, I would expect to find essentially the same code as I wrote above. Password managers like PasswordSafe typically use the same approach. I would, however, strongly recommend an AEAD mode, which I mention but don't demonstrate. You could open another question for that. – erickson Mar 30 '17 at 16:18
  • Given this sample, with is the openssl commandline equivalent of decrypting the ciphertext? I'm absolutely no crypto guy.... – JointEffort Aug 24 '18 at 14:07
  • @JointEffort There isn't one. OpenSSL is principally a library intended to be used in other applications; its CLI tools, especially the `enc` command, are more of an afterthought. The main problem is that its PBKDF2 (the algorithm to convert password to a key) support is not exposed through its command-line interface. – erickson Aug 24 '18 at 15:31
  • 2
    @erickson: just missed! 1.1.1 released 2018-09-11 adds support for `-pbkdf2` in `enc` -- defaulting to iter 10000 and hmac-sha256 but these can be changed – dave_thompson_085 Sep 19 '19 at 23:26
  • @dave_thompson_085 thanks for the tip. I may try to see how to inter operate with Java. – erickson Sep 20 '19 at 00:47
82

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
Community
  • 1
  • 1
John McCarthy
  • 5,354
  • 2
  • 27
  • 40
  • Can you use that module without loading all of Spring? They don't seem to have made jar files available for download. – theglauber Mar 13 '14 at 15:56
  • 5
    @theglauber Yes, you can use the module without Spring Security or the Spring framework. From looking at the [pom](https://github.com/spring-projects/spring-security/blob/master/crypto/pom.xml), the only runtime dependency is apache [commons-logging 1.1.1](http://goo.gl/N3SYEU). You can [pull in the jar with maven](http://goo.gl/yjxUBp) or [download it directly from the official binary repo](http://repo.spring.io/libs-release/org/springframework/security/spring-security-crypto/) (see [Spring 4 binaries download](http://stackoverflow.com/q/20551709) for more info on Spring binaries). – John McCarthy Mar 13 '14 at 16:48
  • Looks like the interfaces have changed in the latest version? (3.2.5) – Blankman Nov 03 '14 at 00:24
  • @Blankman Hmm, I pulled in spring-security-crypto 3.2.5.RELEASE, and the example still executes as expected. What are you seeing that's unexpected? – John McCarthy Nov 03 '14 at 16:04
  • Sorry it does work, my test project wasn't compiling for other reasons. Very sorry about that thanks. – Blankman Nov 04 '14 at 16:12
  • Any idea about this error? "Unable to initialize due to invalid secret key" I have already included required dependencies in "jre/lib/security". – TechnoCrat Mar 11 '15 at 08:29
  • @TechnoCrat I haven't seen this. Do you have a "caused by" exception with any additional details? See also [How to solve InvalidKeyException](http://examples.javacodegeeks.com/core-java/security/invalidkeyexception/java-security-invalidkeyexception-how-to-solve-invalidkeyexception/) which might have some ideas. – John McCarthy Mar 11 '15 at 19:19
  • 1
    Is it possible to set the key length to 128-bits? Modifying security folder in every PC is not an option for me. – IvanRF Jul 17 '15 at 16:36
  • 1
    @IvanRF sorry, doesn't look like it. 256 is hard coded in the [source](http://grepcode.com/file/repo1.maven.org/maven2/org.springframework.security/spring-security-crypto/4.0.0.RELEASE/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java#56) – John McCarthy Jul 20 '15 at 15:17
  • 2
    The `NULL_IV_GENERATOR` used by the Spring utility is not secure. If the application doesn't provide an IV, let the provider choose it, and query it after initialization. – erickson May 03 '16 at 19:12
34

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();
      }
   }


}
wufoo
  • 10,915
  • 12
  • 49
  • 78
  • 13
    This is basically the same answer as that of Erickson, surrounded by a - not-that-well-programmed-in-my-opinion - wrapper.`printStackTrace()` – Maarten Bodewes May 09 '14 at 14:06
  • 2
    @owlstead - This is a great answer. It shows how to encrypt a stream by encrypting the byte buffer, instead of having everything in memory. Erickson's answer wont work for large files, that does not fit in memory. So +1 to wufoo. :) – dynamokaj Nov 16 '14 at 15:32
  • 2
    @dynamokaj The use of `CipherInputStream` and `CipherOutputStream` is not much of a problem. Shuffling all the exceptions under the table is a problem. The fact that the salt suddenly has become a field and that the IV is required is a problem. The fact that it doesn't follow Java coding conventions is a problem. And the fact that this only works on files while it wasn't asked for is a problem. And that the rest of the code is basically a copy doesn't help either. But maybe I'll tweak it to make it better, as suggested... – Maarten Bodewes Nov 16 '14 at 19:25
  • @owlstead I agree that the coding could have looked better I have cut it down to 1/4 or something, but I like that he introduced me to the CipherInputStream and CipherOutputStream, since that was just what I needed yesterday! ;) – dynamokaj Nov 17 '14 at 19:38
  • why twice? fout.close(); fout.close (); – Marian Paździoch Feb 20 '17 at 12:40
  • I have created a password-encrypted stream with data integrity check and password fail fast and compression: https://github.com/jjYBdx4IL/misc/tree/master/encryption-utils More additions are welcome. – user1050755 Aug 15 '17 at 15:44
  • @MarianPaździoch - good catch. It's been awhile since I posted this, but I would surmise it's a typo. There's no logical reason for two `fout()` calls. – wufoo Dec 14 '18 at 15:50
7

Generating your own key from a byte array is easy:

byte[] raw = ...; // 32 bytes in size for a 256 bit key
Key skey = new javax.crypto.spec.SecretKeySpec(raw, "AES");

But creating a 256-bit key isn't enough. If the key generator cannot generate 256-bit keys for you, then the Cipher class probably doesn't support AES 256-bit either. You say you have the unlimited jurisdiction patch installed, so the AES-256 cipher should be supported (but then 256-bit keys should be too, so this might be a configuration problem).

Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skey);
byte[] encrypted = cipher.doFinal(plainText.getBytes());

A workaround for lack of AES-256 support is to take some freely available implementation of AES-256, and use it as a custom provider. This involves creating your own Provider subclass and using it with Cipher.getInstance(String, Provider). But this can be an involved process.

waqas
  • 9,173
  • 2
  • 18
  • 10
  • 5
    You should always indicate the mode and padding algorithm. Java uses the unsafe ECB mode by default. – Maarten Bodewes Feb 24 '12 at 23:54
  • You cannot create your own provider, providers have to be signed (can't believe I read over this mistake initially). Even if you could, the restriction of the key size is in the implementation of `Cipher`, not in the provider itself. You can use AES-256 in Java 8 and lower, but you need to use a proprietary API. Or a runtime that does not pose restrictions on the key size of course. – Maarten Bodewes May 09 '14 at 14:01
  • Recent versions of the OpenJDK (and Android) do not have restrictions on adding your own security / crypto provider. But you do so at your own risk, of course. If you forget to keep your libraries up to date you might expose yourself to security risks. – Maarten Bodewes Mar 24 '19 at 13:57
  • 1
    @MaartenBodewes+ OpenJDK never had the 'limited crypto policy' issue in the first place, and _Oracle_ JDK removed it over a year ago for 8u161 and 9 up (and maybe some lower now-pay-only versions but I haven't checked those) – dave_thompson_085 Sep 19 '19 at 23:26
6

What I've done in the past is hash the key via something like SHA256, then extract the bytes from the hash into the key byte[].

After you have your byte[] you can simply do:

SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = cipher.doFinal(clearText.getBytes());
DarkSquid
  • 2,638
  • 1
  • 20
  • 19
  • 12
    For others: this isn't a very secure method. You should use PBKDF 2 specified in PKCS#5. erickson said how to do this above. DarkSquid's method is vulnerable to password attacks and also doesn't work unless your plaintext's size is a multiple of AES's block size (128 bits) because he left out padding. Also it doesn't specify the mode; read Wikipedia's Block Cipher Modes of Operation for concerns. – Hut8 Apr 28 '11 at 06:55
  • 1
    @DarkSquid `Cipher aes256 = Cipher.getInstance("AES/OFB/NoPadding"); MessageDigest keyDigest = MessageDigest.getInstance("SHA-256"); byte[] keyHash = keyDigest.digest(secret.getBytes("UTF-8")); SecretKeySpec key = new SecretKeySpec(keyHash, "AES"); aes256.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(initializationVector));` I'm also doing the same as suggested in your answer but I still end up with this java.security.InvalidKeyException: Illegal key size Is downloading JCE policy file mandatory? – Niranjan Subramanian Apr 16 '14 at 10:46
  • 2
    DO NOT USE this method in any type of production environment. When starting out with Password-Based encryption a lot of users get overwhelmed by walls of code and don't understand how dictionary attacks and other simple hacks work. While it can be frustrating to learn, it is a worthwhile investment to research this. Here's a good beginners article: http://adambard.com/blog/3-wrong-ways-to-store-a-password/ – IcedDante Nov 02 '14 at 17:38
1

Adding to @Wufoo's edits, the following version uses InputStreams rather than files to make working with a variety of files easier. It also stores the IV and Salt in the beginning of the file, making it so only the password needs to be tracked. Since the IV and Salt do not need to be secret, this makes life a little easier.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import java.security.AlgorithmParameters;
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 java.util.logging.Level;
import java.util.logging.Logger;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
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;

public class AES {
    public final static int SALT_LEN     = 8;
    static final String     HEXES        = "0123456789ABCDEF";
    String                  mPassword    = null;
    byte[]                  mInitVec     = null;
    byte[]                  mSalt        = new byte[SALT_LEN];
    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 AES(String password) {
        mPassword = password;
    }

    public static String byteToHex(byte[] raw) {
        if (raw == null) {
            return null;
        }

        final StringBuilder hex = new StringBuilder(2 * raw.length);

        for (final byte b : raw) {
            hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
        }

        return hex.toString();
    }

    public static byte[] hexToByte(String hexString) {
        int    len = hexString.length();
        byte[] ba  = new byte[len / 2];

        for (int i = 0; i < len; i += 2) {
            ba[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
                                + Character.digit(hexString.charAt(i + 1), 16));
        }

        return ba;
    }

    /**
     * debug/print messages
     * @param msg
     */
    private void Db(String msg) {
        System.out.println("** Crypt ** " + msg);
    }

    /**
     * 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(InputStream inputStream, OutputStream outputStream)
            throws IOException, IllegalBlockSizeException, BadPaddingException {
        try {
            long             totalread = 0;
            int              nread     = 0;
            byte[]           inbuf     = new byte[MAX_FILE_BUF];
            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 :" + byteToHex(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 :" + byteToHex(mInitVec));
            outputStream.write(mSalt);
            outputStream.write(mInitVec);

            while ((nread = inputStream.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[] tmpBuf = mEcipher.update(trimbuf);

                // I don't think this should happen, but just in case..
                if (tmpBuf != null) {
                    outputStream.write(tmpBuf);
                }
            }

            // finalize the encryption since we've done it in blocks of MAX_FILE_BUF
            byte[] finalbuf = mEcipher.doFinal();

            if (finalbuf != null) {
                outputStream.write(finalbuf);
            }

            outputStream.flush();
            inputStream.close();
            outputStream.close();
            outputStream.close();
            Db("wrote " + totalread + " encrypted bytes");
        } catch (InvalidKeyException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidParameterSpecException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchPaddingException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidKeySpecException ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * 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(InputStream inputStream, OutputStream outputStream)
            throws IllegalBlockSizeException, BadPaddingException, IOException {
        try {
            CipherInputStream cin;
            long              totalread = 0;
            int               nread     = 0;
            byte[]            inbuf     = new byte[MAX_FILE_BUF];

            // Read the Salt
            inputStream.read(this.mSalt);
            Db("generated salt :" + byteToHex(mSalt));

            SecretKeyFactory factory = null;
            SecretKey        tmp     = null;
            SecretKey        secret  = null;

            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");

            // Set the appropriate size for mInitVec by Generating a New One
            AlgorithmParameters params = mDecipher.getParameters();

            mInitVec = params.getParameterSpec(IvParameterSpec.class).getIV();

            // Read the old IV from the file to mInitVec now that size is set.
            inputStream.read(this.mInitVec);
            Db("mInitVec is :" + byteToHex(mInitVec));
            mDecipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(mInitVec));

            // creating a decoding stream from the FileInputStream above using the cipher created from setupDecrypt()
            cin = new CipherInputStream(inputStream, 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
                outputStream.write(trimbuf);
            }

            outputStream.flush();
            cin.close();
            inputStream.close();
            outputStream.close();
            Db("wrote " + totalread + " encrypted bytes");
        } catch (Exception ex) {
            Logger.getLogger(AES.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * 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;
        AES    en      = new AES("mypassword");

        /*
         * write out encrypted file
         */
        try {
            en.WriteEncryptedFile(new FileInputStream(input), new FileOutputStream(eoutput));
            System.out.printf("File encrypted to " + eoutput.getName() + "\niv:" + iv + "\nsalt:" + salt + "\n\n");
        } catch (IllegalBlockSizeException | BadPaddingException | IOException e) {
            e.printStackTrace();
        }

        /*
         * decrypt file
         */
        AES dc = new AES("mypassword");

        /*
         * write out decrypted file
         */
        try {
            dc.ReadEncryptedFile(new FileInputStream(eoutput), new FileOutputStream(doutput));
            System.out.println("decryption finished to " + doutput.getName());
        } catch (IllegalBlockSizeException | BadPaddingException | IOException e) {
            e.printStackTrace();
        }
    }
}
Doug
  • 5,243
  • 7
  • 54
  • 89
  • 1
    This solution seems to use some awkward buffer handling and absolutely sub-par exception handling, basically logging them and then forgetting about them. Be warned that using CBC is OK for files but not for transport security. Using PBKDF2 and AES can of course be defended, in that sense it may be a good foundation for a solution. – Maarten Bodewes Mar 16 '16 at 23:20
1

(Maybe helpful for others with a similar requirement)

I had a similar requirement to use AES-256-CBC encrypt and decrypt in Java.

To achieve (or specify) the 256-byte encryption/decryption, Java Cryptography Extension (JCE) policy should set to "Unlimited"

It can be set in the java.security file under $JAVA_HOME/jre/lib/security (for JDK) or $JAVA_HOME/lib/security (for JRE)

crypto.policy=unlimited

Or in the code as

Security.setProperty("crypto.policy", "unlimited");

Java 9 and later versions have this enabled by default.

Praveen
  • 1,199
  • 1
  • 10
  • 20
0

Consider using Encryptor4j of which I am the author.

First make sure you have Unlimited Strength Jurisdiction Policy files installed before your proceed so that you can use 256-bit AES keys.

Then do the following:

String password = "mysupersecretpassword"; 
Key key = KeyFactory.AES.keyFromPassword(password.toCharArray());
Encryptor encryptor = new Encryptor(key, "AES/CBC/PKCS7Padding", 16);

You can now use the encryptor to encrypt your message. You can also perform streaming encryption if you'd like. It automatically generates and prepends a secure IV for your convenience.

If it's a file that you wish to compress take a look at this answer Encrypting a large file with AES using JAVA for an even simpler approach.

whitebrow
  • 1,610
  • 16
  • 21
  • 2
    Hi Martin, you should always indicate that you're the writer of the library if you want to point it out. There are oodles of crypto wrappers trying to make things easy. Does this one have a security paper or has it received any reviews to make it worth our while? – Maarten Bodewes Mar 24 '19 at 14:03
-1

Use this class for encryption. It works.

public class ObjectCrypter {


    public static byte[] encrypt(byte[] ivBytes, byte[] keyBytes, byte[] mes) 
            throws NoSuchAlgorithmException,
            NoSuchPaddingException,
            InvalidKeyException,
            InvalidAlgorithmParameterException,
            IllegalBlockSizeException,
            BadPaddingException, IOException {

        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = null;
        cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
        return  cipher.doFinal(mes);

    }

    public static byte[] decrypt(byte[] ivBytes, byte[] keyBytes, byte[] bytes) 
            throws NoSuchAlgorithmException,
            NoSuchPaddingException,
            InvalidKeyException,
            InvalidAlgorithmParameterException,
            IllegalBlockSizeException,
            BadPaddingException, IOException, ClassNotFoundException {

        AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec);
        return  cipher.doFinal(bytes);

    }
}

And these are ivBytes and a random key;

String key = "e8ffc7e56311679f12b6fc91aa77a5eb";

byte[] ivBytes = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
keyBytes = key.getBytes("UTF-8");
jeprubio
  • 14,400
  • 5
  • 32
  • 47
Engineer
  • 49
  • 3
  • 11
    "it works".... yes, but it does not meet the requirements for creating a cryptographically secure solution (nor does it meet Java coding standards with regards to exception handling, in my opinion). – Maarten Bodewes May 09 '14 at 14:13
  • 2
    IV is initialized to zero. Search for BEAST and ACPA attacks. – Michele Giuseppe Fadda Nov 13 '15 at 10:07
  • Exceptions out the wazoo, the method of generating the "random" key, and a zero IV is a problem with this implementation, but those problems are trivial to fix. +1. – Phil Oct 15 '16 at 03:41