119

What's wrong with the following example?

The problem is that the first part of the decrypted string is nonsense. However, the rest is fine, I get...

Result: `£eB6O�geS��i are you? Have a nice day.
@Test
public void testEncrypt() {
  try {
    String s = "Hello there. How are you? Have a nice day.";

    // Generate key
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    kgen.init(128);
    SecretKey aesKey = kgen.generateKey();

    // Encrypt cipher
    Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);

    // Encrypt
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);
    cipherOutputStream.write(s.getBytes());
    cipherOutputStream.flush();
    cipherOutputStream.close();
    byte[] encryptedBytes = outputStream.toByteArray();

    // Decrypt cipher
    Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
    decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

    // Decrypt
    outputStream = new ByteArrayOutputStream();
    ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes);
    CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher);
    byte[] buf = new byte[1024];
    int bytesRead;
    while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
        outputStream.write(buf, 0, bytesRead);
    }

    System.out.println("Result: " + new String(outputStream.toByteArray()));

  } 
  catch (Exception ex) {
    ex.printStackTrace();
  }
}
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
TedTrippin
  • 3,525
  • 5
  • 28
  • 46
  • 51
    DO NOT USE ANY ANSWER OF THIS QUESTION IN SERIOUS PROJECT ! All the example provided in this question are vulnerable to padding oracle and are overall very bad cryptography usage. You will introduce serious cryptography vulnerability in your project by using any of the snippet below. – HoLyVieR Aug 09 '16 at 19:06
  • 17
    @HoLyVieR, Regarding the following quotes: *"You shouldn't be developing your own cryptography library"* and *"use a high level API that your framework provides."* No one here is developing their own cryptography library. We are simply using the already existing, high level API that the java framework provides. You sir are wildly inaccurate. – k170 Sep 02 '16 at 00:11
  • 2
    @k170 They are definitely not "high level cryptography" API. As a good rule of thumb, if you have to type or copy/paste AES, you are not using an high level cryptography API. Correctly assembling AES with the proper algorithm to prevent cryptographic attack is hard and you should avoid doing it if you have no expertise in cryptography. You're just shooting yourself in the foot without realising it. – HoLyVieR Sep 02 '16 at 21:39
  • 1
    Fair enough point about insecure examples, feel free to leave a comment. As far as this question goes, I was getting started and just needed some help with a specific question. Good developers will do the necessary investigation as to how secure something is, bad developers will not. – TedTrippin Sep 05 '16 at 17:20
  • 10
    @MaartenBodewes, Just because you both agree does not imply that you're both correct. Good developers know the difference between wrapping a high level API and rewriting a low level API. **Good readers will notice that the OP asked for a "simple java AES encrypt/decrypt example" and that's exactly what he got**. I also don't agree with the other answers, which is why I posted an answer of my own. Perhaps you guys should try the same and enlighten us all with your expertise. – k170 Sep 05 '16 at 18:10
  • @k170 I've started [a discussion on Meta instead](http://meta.stackoverflow.com/questions/334000/sample-code-within-questions-about-cryptography-encryption). Feel free to contribute. Note that I don't agree on what was actually asked. I put the actual question below the provided sample code in the question (I did *not* edit the question itself). – Maarten Bodewes Sep 06 '16 at 21:22
  • @MaartenBodewes, thanks for the link. I've edited my post. – k170 Sep 07 '16 at 03:40
  • @danieljimenez I did "You shouldn't be developing your own cryptography library in the first place". Seriously follow this advice. Your framework (and also plenty of library) already provide you way to encrypt data without having to choose cipher and cipher mode. Use them ! The low-level API of Java have plenty of caveats that are too long to explain in a comments. – HoLyVieR Apr 18 '17 at 17:26
  • 6
    @HoLyVieR That really is the most absurd thing I have ever read on SO! Who are you to tell people what they can and can not develop? – TedTrippin Apr 19 '17 at 11:34
  • 16
    I still see no examples @HoLyVieR. Let's see some, or pointers to libraries? Not constructive at all. – danieljimenez Apr 21 '17 at 19:49

10 Answers10

229

Lot of people including myself face lot of issues in making this work due to missing some information like, forgetting to convert to Base64, initialization vectors, character set, etc. So I thought of making a fully functional code.

Hope this will be useful to you all: To compile you need additional Apache Commons Codec jar, which is available here: http://commons.apache.org/proper/commons-codec/download_codec.cgi

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());
            System.out.println("encrypted string: "
                    + Base64.encodeBase64String(encrypted));

            return Base64.encodeBase64String(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(decrypt(key, initVector,
                encrypt(key, initVector, "Hello World")));
    }
}
sudeep
  • 735
  • 1
  • 9
  • 8
Chand Priyankara
  • 6,739
  • 2
  • 40
  • 63
  • 47
    If you don't want to depend on 3rd party Apache Commons Codec library you can use JDK's *[javax.xml.bind.DatatypeConverter](http://docs.oracle.com/javase/7/docs/api/javax/xml/bind/DatatypeConverter.html)* to perform Base64 encoding/decoding: `System.out.println("encrypted string:" + DatatypeConverter.printBase64Binary(encrypted));` `byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted));` – curd0 Oct 08 '15 at 17:47
  • 13
    Are you using a constant IV?! – vianna77 May 09 '16 at 14:34
  • 38
    Java 8 already has Base64 tools: java.util.Base64.getDecoder() and java.util.Base64.getEncoder() – Hristo Stoyanov May 20 '16 at 17:29
  • 18
    The IV doesn't have to be secret, but it has to be unpredictable for CBC mode (and unique for CTR). It can be sent along with the ciphertext. A common way to do this is by prefixing the IV to the ciphertext and slicing it off before decryption. It should be generated through `SecureRandom` – Artjom B. May 21 '16 at 08:51
  • 2
    In Android, rather than using the Apache Commons Codec library, you can use `android.util.Base64`. The code I used is `return Base64.encodeToString(encrypted, Base64.DEFAULT);` and `byte[] original = cipher.doFinal(Base64.decode(encrypted, Base64.DEFAULT));`. – ban-geoengineering Jun 29 '16 at 14:59
  • 3
    As Hristo Stoyanov said, to the Java 8 way replace in encrypt method: `return Base64.getEncoder().encodeToString(encrypted);` and in decrypt method: `byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));` – Laszlo Lugosi Jul 21 '16 at 13:13
  • 15
    A password is not a key. An IV should be random. – Maarten Bodewes Sep 05 '16 at 16:41
  • That didn't work for me. What worked was replacing some of your (@chandpriyankara) code with : final byte[] iv = new byte[16]; Arrays.fill(iv, (byte) 0x00); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); .. // the rest of preparations ecipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParameterSpec); – Hashim Akhtar Oct 20 '16 at 09:50
  • @HashimAkhtar May I suggest that you replace `Arrays.fill(iv, (byte) 0x00)` with `new SecureRandom().nextBytes(iv)`? – Scruffy Nov 24 '16 at 04:30
  • If you need Base16 string instead of Base64, use [javax.xml.bind.DatatypeConverter's](http://docs.oracle.com/javase/7/docs/api/javax/xml/bind/DatatypeConverter.html) [parseHexBinary()](http://docs.oracle.com/javase/7/docs/api/javax/xml/bind/DatatypeConverter.html#parseHexBinary(java.lang.String)) and [printHexBinary()](http://docs.oracle.com/javase/7/docs/api/javax/xml/bind/DatatypeConverter.html#printHexBinary(byte[])) methods. – entpnerd Apr 04 '17 at 04:19
  • Why are the methods static? I saw similar examples elsewhere and they all use static methods. Why? – kp91 Aug 11 '17 at 10:31
  • Resource allocation for the considered function happens only once, not per each object instance. – Chand Priyankara Aug 14 '17 at 04:36
  • Quick question: is it an 128-bit encryption since the key the key is ? – Sumanth Varada Dec 28 '18 at 14:14
  • 1
    it is better to use value.getBytes(StandardCharsets.UTF_8) when converting a String to Byte[] ; and String(original, StandardCharsets.UTF_8) when converting an array of bytes to a String to make the conversion plateform independent – ihebiheb Feb 18 '19 at 19:47
  • 3
    See https://littlemaninmyhead.wordpress.com/2021/09/15/if-you-copied-any-of-these-popular-stackoverflow-encryption-code-snippets-then-you-did-it-wrong/ – Meiogordo Sep 17 '21 at 11:25
83

In this answer I choose to approach the "Simple Java AES encrypt/decrypt example" main theme and not the specific debugging question because I think this will profit most readers.

This is a simple summary of my blog post about AES encryption in Java so I recommend reading through it before implementing anything. I will however still provide a simple example to use and give some pointers what to watch out for.

In this example I will choose to use authenticated encryption with Galois/Counter Mode or GCM mode. The reason is that in most case you want integrity and authenticity in combination with confidentiality (read more in the blog).

AES-GCM Encryption/Decryption Tutorial

Here are the steps required to encrypt/decrypt with AES-GCM with the Java Cryptography Architecture (JCA). Do not mix with other examples, as subtle differences may make your code utterly insecure.

1. Create Key

As it depends on your use-case, I will assume the simplest case: a random secret key.

SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");

Important:

2. Create the Initialization Vector

An initialization vector (IV) is used so that the same secret key will create different cipher texts.

byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);

Important:

3. Encrypt with IV and Key

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);

Important:

  • use 16 byte / 128 bit authentication tag (used to verify integrity/authenticity)
  • the authentication tag will be automatically appended to the cipher text (in the JCA implementation)
  • since GCM behaves like a stream cipher, no padding is required
  • use CipherInputStream when encrypting large chunks of data
  • want additional (non-secret) data checked if it was changed? You may want to use associated data with cipher.updateAAD(associatedData); More here.

3. Serialize to Single Message

Just append IV and ciphertext. As stated above, the IV doesn't need to be secret.

ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();

Optionally encode with Base64 if you need a string representation. Either use Android's or Java 8's built-in implementation (do not use Apache Commons Codec - it's an awful implementation). Encoding is used to "convert" byte arrays to string representation to make it ASCII safe e.g.:

String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);

4. Prepare Decryption: Deserialize

If you have encoded the message, first decode it to byte array:

byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)

Important:

5. Decrypt

Initialize the cipher and set the same parameters as with the encryption:

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);

Important:

  • don't forget to add associated data with cipher.updateAAD(associatedData); if you added it during encryption.

A working code snippet can be found in this gist.


Note that most recent Android (SDK 21+) and Java (7+) implementations should have AES-GCM. Older versions may lack it. I still choose this mode, since it is easier to implement in addition to being more efficient compared to similar mode of Encrypt-then-Mac (with e.g. AES-CBC + HMAC). See this article on how to implement AES-CBC with HMAC.

Patrick
  • 33,984
  • 10
  • 106
  • 126
  • 1
    The problem is that asking for examples is explicitly off topic on SO. And the bigger problem is that these are unreviewed pieces of code, that are hard to validate. I appreciate the effort, but I don't think that SO should be the place for this. – Maarten Bodewes May 06 '19 at 21:48
  • 2
    I do admire the effort though, so I'll just point out a single mistake: "the iv must be unpredictable in combination with being unique (ie. use random iv)" - this is true for CBC mode but not for GCM. – Maarten Bodewes May 06 '19 at 21:52
  • `this is true for CBC mode but not for GCM` do you mean the whole part, or only it not actually needing to be unpredictable? – Patrick May 07 '19 at 06:33
  • 2
    `but I don't think that SO should be the place for this.` you are probably right, but it seems that most will just stick with SO. Maybe most of the users won't put in the necessary time to fully understand the topic, but maybe a couple will be notched in the right direction - how do you think beginners guides should be published? The matter of fact is, that e.g. in Java/JCE the architecture is really hard to understand especially for someone not coming from cryptography studies - and there is hardly any good ones? – Patrick May 07 '19 at 06:37
  • Not being unpredictable. The 12 byte value is a nonce, and it just needs to be unique (or rather, the key / nonce combination needs to be unique). If you don't get the topic then you should probably not use low level primitives in the first place (which are now often called "hazmat" for higher level protocols). I agree though that it is tricky to find good protocol implementations to use. – Maarten Bodewes May 07 '19 at 07:03
  • 2
    "If you don't get the topic then you should probably not use low level primitives in the first place" sure, that SHOULD be the case, many developers still do it. Im not sure refraining from putting up high quality content regarding security/cryptography in places where often there isn't much is the right solution for this. - thx for pointing to my mistake btw – Patrick May 07 '19 at 07:26
  • 1
    OK, just because I like the answer w.r.t. contents (rather than purpose): the IV handling can be simplified especially during decryption: Java makes it easy to create an IV directly from an existing byte array after all. Same goes for decryption, which doesn't have to start at offset 0. All this copying is simply not necessary. Also *if* you have to send a length for the IV (do you?) then why not use a single (unsigned) byte - you're not going to go past 255 bytes for the IV, right? – Maarten Bodewes May 08 '19 at 11:55
  • @MaartenBodewes you are absolutely right, I've improved the decryption and removed the iv length. The only downside is, that the more efficent encryption is harder to read/understand. – Patrick Apr 19 '20 at 20:49
  • Well, isn't that the purpose? ;) Already voted up your answer, so can't do that anymore. – Maarten Bodewes Apr 19 '20 at 20:52
38

Here a solution without Apache Commons Codec's Base64:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class AdvancedEncryptionStandard
{
    private byte[] key;

    private static final String ALGORITHM = "AES";

    public AdvancedEncryptionStandard(byte[] key)
    {
        this.key = key;
    }

    /**
     * Encrypts the given plain text
     *
     * @param plainText The plain text to encrypt
     */
    public byte[] encrypt(byte[] plainText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

        return cipher.doFinal(plainText);
    }

    /**
     * Decrypts the given byte array
     *
     * @param cipherText The data to decrypt
     */
    public byte[] decrypt(byte[] cipherText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);

        return cipher.doFinal(cipherText);
    }
}

Usage example:

byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8);
byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8);
AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard(
        encryptionKey);
byte[] cipherText = advancedEncryptionStandard.encrypt(plainText);
byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText);

System.out.println(new String(plainText));
System.out.println(new String(cipherText));
System.out.println(new String(decryptedCipherText));

Prints:

Hello world!
դ;��LA+�ߙb*
Hello world!
BullyWiiPlaza
  • 17,329
  • 10
  • 113
  • 185
  • 6
    This is a perfectly functionnal example, just as @chandpriyankara's. But why define a signature of `encrypt(String)` and not `encrypt(byte[] )` ?. Encryption (decryption too) is a byte based process (AES is anyway). Encryption takes bytes as input, and outputs bytes, so does decryption (case in point : the `Cipher` object does). Now, one particular use case may be to have encrypted bytes coming from a String, or be sent as a String (base64 MIME attachment for a Mail...), but that is an issue of encoding bytes, for which there exists hundreds of solutions, totally unrelated to AES/encryption. – GPI Apr 25 '15 at 16:29
  • 3
    @GPI: Yes, but I find it more useful with `Strings` since that's basically what I work with 95% of the time and you end up converting anyway. – BullyWiiPlaza Apr 25 '15 at 20:03
  • 11
    No, this is not equivalent to chandpriyankara's code! Your code uses ECB which is generally insecure and not wanted. Should explicitly specify CBC. When CBC is specified, your code will break. – Dan Sep 28 '15 at 03:52
  • 2
    Perfectly functional, utterly insecure, and using very bad programming practices. the class is named badly. The key size is not checked in advance. But most importantly, the code uses the insecure ECB mode, **hiding the issue in the original question**. Finally, it doesn't specify a character encoding, which means that the decoding to text may fail on other platforms. – Maarten Bodewes May 06 '19 at 21:36
24

Looks to me like you are not dealing properly with your Initialization Vector (IV). It's been a long time since I last read about AES, IVs and block chaining, but your line

IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());

does not seem to be OK. In the case of AES, you can think of the initialization vector as the "initial state" of a cipher instance, and this state is a bit of information that you can not get from your key but from the actual computation of the encrypting cipher. (One could argue that if the IV could be extracted from the key, then it would be of no use, as the key is already given to the cipher instance during its init phase).

Therefore, you should get the IV as a byte[] from the cipher instance at the end of your encryption

  cipherOutputStream.close();
  byte[] iv = encryptCipher.getIV();

and you should initialize your Cipher in DECRYPT_MODE with this byte[] :

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

Then, your decryption should be OK. Hope this helps.

Beryllium
  • 12,808
  • 10
  • 56
  • 86
GPI
  • 9,088
  • 2
  • 31
  • 38
  • Thanks for helping a newbie. I cobbled this example from other posts. I don't suppose you know how to avoid the need for an IV? I've seen, but not tried, other AES examples which don't use it. – TedTrippin Mar 21 '13 at 18:25
  • Ignore that, I've found the answer! I need to use AES/ECB/PKCS5Padding. – TedTrippin Mar 21 '13 at 18:28
  • 20
    **Most** times you **don't** want to use ECB. Just google why. – João Fernandes Mar 22 '13 at 04:43
  • Hello, I'm trying to use this as is (hope you don't mind) with the fixes, finding a working example of encryption/decryption in Java that I could just plug into my code seems downright impossible. What I can't figure out is, where does the passphrase come in? You seem to generate the key out of thin air, with no input from the user. What sort of encryption is that, if the user doesn't have to provide any sort of secret key in order to decrypt the data? It just doesn't make any sense to me. – Shaggydog Jul 20 '13 at 20:38
  • @Shaggydog : this is a different question altogether. Generating and storing keys/IVs is a difficult problem (tm). You could look at http://stackoverflow.com/questions/2037021/aes-encryption-and-key-storage . But basically, you're right that in this sample, we generate keys out of thin air. Another way could be to prompt the user for a passphrase and use that as the input of a key generation algorithm... Look at "password based key derivation" a.k.a. PBKDF2 as a keyword. See the accepted solution at http://stackoverflow.com/questions/992019/java-256-bit-aes-password-based-encryption – GPI Jul 22 '13 at 08:48
  • I think the accepted answer method of setting the IV is best although it could be set from the key as demonstrated. – Mushy Jul 07 '16 at 18:56
  • 2
    @Mushy : agreed that choosing and explicitely setting a IV, from a trusted random source, is better than just letting the Cihper instance pick up one. On the other hand, this answer adresses the original issue of confusing the initialization vector for the key. Which is why it was upvoted at first. Now, this post has become more of a sample code go-to point, and people here made some great exemple - just beside of what the original question was about. – GPI Jul 08 '16 at 09:27
  • 3
    @GPI Upvoted. The other "great examples" are not that great, and they do not actually address the question at all. Instead this seems to have been the place for newbies to blindly copy cryptographic samples without understanding that there may be possible security issues - and, as always, there are. – Maarten Bodewes Sep 06 '16 at 20:29
19

The IV that your using for decryption is incorrect. Replace this code

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

With this code

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

And that should solve your problem.


Below includes an example of a simple AES class in Java. I do not recommend using this class in production environments, as it may not account for all of the specific needs of your application.

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AES 
{
    public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
        final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        byte[] transformedBytes = null;

        try
        {
            final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

            cipher.init(mode, keySpec, ivSpec);

            transformedBytes = cipher.doFinal(messageBytes);
        }        
        catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) 
        {
            e.printStackTrace();
        }
        return transformedBytes;
    }

    public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        //Retrieved from a protected local file.
        //Do not hard-code and do not version control.
        final String base64Key = "ABEiM0RVZneImaq7zN3u/w==";

        //Retrieved from a protected database.
        //Do not hard-code and do not version control.
        final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE=";

        //Extract the iv and the ciphertext from the shadow entry.
        final String[] shadowData = shadowEntry.split(":");        
        final String base64Iv = shadowData[0];
        final String base64Ciphertext = shadowData[1];

        //Convert to raw bytes.
        final byte[] keyBytes = Base64.getDecoder().decode(base64Key);
        final byte[] ivBytes = Base64.getDecoder().decode(base64Iv);
        final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext);

        //Decrypt data and do something with it.
        final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes);

        //Use non-blocking SecureRandom implementation for the new IV.
        final SecureRandom secureRandom = new SecureRandom();

        //Generate a new IV.
        secureRandom.nextBytes(ivBytes);

        //At this point instead of printing to the screen, 
        //one should replace the old shadow entry with the new one.
        System.out.println("Old Shadow Entry      = " + shadowEntry);
        System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8));
        System.out.println("New Shadow Entry      = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes)));
    }
}

Note that AES has nothing to do with encoding, which is why I chose to handle it separately and without the need of any third party libraries.

k170
  • 383
  • 5
  • 18
  • First off, you havent answered the original question. Second, why you answering an already answered, well accepted question? I thought the protection was supposed to stop this spam. – TedTrippin Oct 17 '15 at 16:15
  • 14
    Like the accepted answer, I chose to answer your question via example. I provided a fully functional piece of code, which shows you how to properly deal with the initialization vector, among other things. As for your second question, I felt that an updated answer was needed as the Apache codec is no longer necessary. So no this is not spam. Stop trippin. – k170 Oct 17 '15 at 20:19
  • 8
    An **IV** has a specific purpose which is to **randomize the ciphertext** and provide semantic security. If you use the same key+IV pair then attackers can determine whether you've sent a message with the same prefix as before. The IV doesn't have to be secret, but it has to be unpredictable. A common way is to simply prefix the IV to the ciphertext and slice it off before decryption. – Artjom B. Jun 14 '16 at 16:42
  • 4
    downvote: hardcoded IV, see Artjom B. comment above why it is bad – Murmel Aug 25 '16 at 10:54
  • 1
    CTR mode should be paired with NoPadding. CTR mode certainly is not required instead of CBC (unless padding oracles apply), but if CTR **is** used, then use `"/NoPadding"`. CTR is a mode that turns AES in a stream cipher, and a stream cipher operates on bytes instead of blocks. – Maarten Bodewes Sep 07 '16 at 06:52
1

Online Editor Runnable version:-

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//import org.apache.commons.codec.binary.Base64;
import java.util.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));

            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());

            //System.out.println("encrypted string: "
              //      + Base64.encodeBase64String(encrypted));

            //return Base64.encodeBase64String(encrypted);
            String s = new String(Base64.getEncoder().encode(encrypted));
            return s;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(encrypt(key, initVector, "Hello World"));
        System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
    }
}
Bhupesh Pant
  • 4,053
  • 5
  • 45
  • 70
1

This is an improvement over the accepted answer.

Changes:

(1) Using random IV and prepend it to the encrypted text

(2) Using SHA-256 to generate a key from a passphrase

(3) No dependency on Apache Commons

public static void main(String[] args) throws GeneralSecurityException {
    String plaintext = "Hello world";
    String passphrase = "My passphrase";
    String encrypted = encrypt(passphrase, plaintext);
    String decrypted = decrypt(passphrase, encrypted);
    System.out.println(encrypted);
    System.out.println(decrypted);
}

private static SecretKeySpec getKeySpec(String passphrase) throws NoSuchAlgorithmException {
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    return new SecretKeySpec(digest.digest(passphrase.getBytes(UTF_8)), "AES");
}

private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
    return Cipher.getInstance("AES/CBC/PKCS5PADDING");
}

public static String encrypt(String passphrase, String value) throws GeneralSecurityException {
    byte[] initVector = new byte[16];
    SecureRandom.getInstanceStrong().nextBytes(initVector);
    Cipher cipher = getCipher();
    cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] encrypted = cipher.doFinal(value.getBytes());
    return DatatypeConverter.printBase64Binary(initVector) +
            DatatypeConverter.printBase64Binary(encrypted);
}

public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
    byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
    Cipher cipher = getCipher();
    cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
    return new String(original);
}
wvdz
  • 16,251
  • 4
  • 53
  • 90
  • A hash is still not a password based key generation function / PBKDF. Either you use a randomized key or you use a PBKDF such as PBKDF2 / Password Based Encryption. – Maarten Bodewes May 07 '19 at 12:49
  • @MaartenBodewes Can you suggest an improvement? – wvdz May 08 '19 at 07:55
  • PBKDF2 is present in Java, so I think I just suggested one. OK, I didn't *code* one, but that's asking for a bit too much in my opinion. There are plenty of examples of Password Based Encryption. – Maarten Bodewes May 08 '19 at 13:31
  • @MaartenBodewes I thought it might be a simple fix. Out of curiosity, what would be specific vulnerabilities when using this code as is? – wvdz May 09 '19 at 09:55
0

It's often the good idea to rely on standard library provided solution:

private static void stackOverflow15554296()
    throws
        NoSuchAlgorithmException, NoSuchPaddingException,        
        InvalidKeyException, IllegalBlockSizeException,
        BadPaddingException
{

    // prepare key
    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    SecretKey aesKey = keygen.generateKey();
    String aesKeyForFutureUse = Base64.getEncoder().encodeToString(
            aesKey.getEncoded()
    );

    // cipher engine
    Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

    // cipher input
    aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
    byte[] clearTextBuff = "Text to encode".getBytes();
    byte[] cipherTextBuff = aesCipher.doFinal(clearTextBuff);

    // recreate key
    byte[] aesKeyBuff = Base64.getDecoder().decode(aesKeyForFutureUse);
    SecretKey aesDecryptKey = new SecretKeySpec(aesKeyBuff, "AES");

    // decipher input
    aesCipher.init(Cipher.DECRYPT_MODE, aesDecryptKey);
    byte[] decipheredBuff = aesCipher.doFinal(cipherTextBuff);
    System.out.println(new String(decipheredBuff));
}

This prints "Text to encode".

Solution is based on Java Cryptography Architecture Reference Guide and https://stackoverflow.com/a/20591539/146745 answer.

Community
  • 1
  • 1
andrej
  • 4,518
  • 2
  • 39
  • 39
-1

Another solution using java.util.Base64 with Spring Boot

Encryptor Class

package com.jmendoza.springboot.crypto.cipher;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

@Component
public class Encryptor {

    @Value("${security.encryptor.key}")
    private byte[] key;
    @Value("${security.encryptor.algorithm}")
    private String algorithm;

    public String encrypt(String plainText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        return new String(Base64.getEncoder().encode(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))));
    }

    public String decrypt(String cipherText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
    }
}

EncryptorController Class

package com.jmendoza.springboot.crypto.controller;

import com.jmendoza.springboot.crypto.cipher.Encryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cipher")
public class EncryptorController {

    @Autowired
    Encryptor encryptor;

    @GetMapping(value = "encrypt/{value}")
    public String encrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.encrypt(value);
    }

    @GetMapping(value = "decrypt/{value}")
    public String decrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.decrypt(value);
    }
}

application.properties

server.port=8082
security.encryptor.algorithm=AES
security.encryptor.key=M8jFt46dfJMaiJA0

Example

http://localhost:8082/cipher/encrypt/jmendoza

2h41HH8Shzc4BRU3hVDOXA==

http://localhost:8082/cipher/decrypt/2h41HH8Shzc4BRU3hVDOXA==

jmendoza

-2

Optimized version of the accepted answer.

  • no 3rd party libs

  • includes IV into the encrypted message (can be public)

  • password can be of any length

Code:

import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Encryptor {
    public static byte[] getRandomInitialVector() {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
            byte[] initVector = new byte[cipher.getBlockSize()];
            randomSecureRandom.nextBytes(initVector);
            return initVector;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static byte[] passwordTo16BitKey(String password) {
        try {
            byte[] srcBytes = password.getBytes("UTF-8");
            byte[] dstBytes = new byte[16];

            if (srcBytes.length == 16) {
                return srcBytes;
            }

            if (srcBytes.length < 16) {
                for (int i = 0; i < dstBytes.length; i++) {
                    dstBytes[i] = (byte) ((srcBytes[i % srcBytes.length]) * (srcBytes[(i + 1) % srcBytes.length]));
                }
            } else if (srcBytes.length > 16) {
                for (int i = 0; i < srcBytes.length; i++) {
                    dstBytes[i % dstBytes.length] += srcBytes[i];
                }
            }

            return dstBytes;
        } catch (UnsupportedEncodingException ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String encrypt(String key, String value) {
        return encrypt(passwordTo16BitKey(key), value);
    }

    public static String encrypt(byte[] key, String value) {
        try {
            byte[] initVector = Encryptor.getRandomInitialVector();
            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());
            return Base64.getEncoder().encodeToString(encrypted) + " " + Base64.getEncoder().encodeToString(initVector);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String encrypted) {
        return decrypt(passwordTo16BitKey(key), encrypted);
    }

    public static String decrypt(byte[] key, String encrypted) {
        try {
            String[] encryptedParts = encrypted.split(" ");
            byte[] initVector = Base64.getDecoder().decode(encryptedParts[1]);
            if (initVector.length != 16) {
                return null;
            }

            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedParts[0]));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }
}

Usage:

String key = "Password of any length.";
String encrypted = Encryptor.encrypt(key, "Hello World");
String decrypted = Encryptor.decrypt(key, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);

Example output:

QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
Hello World
ue7m
  • 51
  • 4