181

What I need is to encrypt string which will show up in 2D barcode(PDF-417) so when someone get an idea to scan it will get nothing readable.

Other requirements:

  • should not be complicated
  • it should not consist of RSA, PKI infrastructure, key pairs, etc.

It must be simple enough to get rid of the people snooping around, and easy to decrypt for other companies interested in getting that data. They call us, we tell them the standard or give them some simple key which can then be used for decryption.

Probably those companies could use different technologies so it would be good to stick to some standard which is not tied to some special platform or technology.

What do you suggest? Is there some Java class doing encrypt() & decrypt() without much complication in achieving high security standards?

Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
ante.sabo
  • 3,141
  • 6
  • 28
  • 36
  • Visit: http://www.software-architect.net/articles/using-strong-encryption-in-java/introduction.html – Nilesh Sep 28 '17 at 07:10
  • 2
    **Warning**. A lot of the answers below show one method or other to perform *any kind of cryptography* on Java. Answers *may* not reflect good cryptographic practices and *may* not be reviewed well; *there is no such thing as copy / paste security*. Answers *should* at least take string conversion into account. The actual question with the 2D barcode included is way too broad, and should require a customer specific solution. – Maarten Bodewes Dec 04 '18 at 10:12

16 Answers16

227

This is the first page that shows up via Google and the security vulnerabilities in all the implementations make me cringe so I'm posting this to add information regarding encryption for others as it has been 7 Years from the original post. I hold a Masters Degree in Computer Engineering and spent a lot of time studying and learning Cryptography so I'm throwing my two cents to make the internet a safer place.

Also, do note that a lot of implementation might be secure for a given situation, but why use those and potentially accidentally make a mistake? Use the strongest tools you have available unless you have a specific reason not to. Overall I highly advise using a library and staying away from the nitty gritty details if you can.

UPDATE 4/5/18: I rewrote some parts to make them simpler to understand and changed the recommended library from Jasypt to Google's new library Tink, I would recommend completely removing Jasypt from an existing setup.

Foreword

I will outline the basics of secure symmetric cryptography below and point out common mistakes I see online when people implement crypto on their own with the standard Java library. If you want to just skip all the details run over to Google's new library Tink import that into your project and use AES-GCM mode for all your encryptions and you shall be secure.

Now if you want to learn the nitty gritty details on how to encrypt in java read on :)

Block Ciphers

First thing first you need to pick a symmetric key Block Cipher. A Block Cipher is a computer function/program used to create Pseudo-Randomness. Pseudo-Randomness is fake randomness that no computer other than a Quantum Computer would be able to tell the difference between it and real randomness. The Block Cipher is like the building block to cryptography, and when used with different modes or schemes we can create encryptions.

Now regarding Block Cipher Algorithms available today, Make sure to NEVER, I repeat NEVER use DES, I would even say NEVER use 3DES. The only Block Cipher that even Snowden's NSA release was able to verify being truly as close to Pseudo-Random as possible is AES 256. There also exists AES 128; the difference is AES 256 works in 256-bit blocks, while AES 128 works in 128 blocks. All in all, AES 128 is considered secure although some weaknesses have been discovered, but 256 is as solid as it gets.

Fun fact DES was broken by the NSA back when it was initially founded and actually kept a secret for a few years. Although some people still claim 3DES is secure, there are quite a few research papers that have found and analyzed weaknesses in 3DES.

Encryption Modes

Encryption is created when you take a block cipher and use a specific scheme so that the randomness is combined with a key to creating something that is reversible as long as you know the key. This is referred to as an Encryption Mode.

Here is an example of an encryption mode and the simplest mode known as ECB just so you can visually understand what is happening:

ECB Mode

The encryption modes you will see most commonly online are the following:

ECB CTR, CBC, GCM

There exist other modes outside of the ones listed and researchers are always working toward new modes to improve existing problems.

Now let's move on to implementations and what is secure. NEVER use ECB this is bad at hiding repeating data as shown by the famous Linux penguin.Linux Penguin Example

When implementing in Java, note that if you use the following code, ECB mode is set by default:

Cipher cipher = Cipher.getInstance("AES");

... DANGER THIS IS A VULNERABILITY! and unfortunately, this is seen all over StackOverflow and online in tutorials and examples.

Nonces and IVs

In response to the issue found with ECB mode nounces also known as IVs were created. The idea is that we generate a new random variable and attach it to every encryption so that when you encrypt two messages that are the same they come out different. The beauty behind this is that an IV or nonce is public knowledge. That means an attacker can have access to this but as long as they don't have your key, they cant do anything with that knowledge.

Common issues I will see is that people will set the IV as a static value as in the same fixed value in their code. and here is the pitfall to IVs the moment you repeat one you actually compromise the entire security of your encryption.

Generating A Random IV

SecureRandom randomSecureRandom = new SecureRandom();
byte[] iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);

Note: SHA1 is broken but I couldn't find how to implement SHA256 into this use case properly, so if anyone wants to take a crack at this and update it would be awesome! Also SHA1 attacks still are unconventional as it can take a few years on a huge cluster to crack. Check out details here.

CTR Implementation

No padding is required for CTR mode.

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

CBC Implementation

If you choose to implement CBC Mode do so with PKCS7Padding as follows:

 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");

CBC and CTR Vulnerability and Why You Should Use GCM

Although some other modes such as CBC and CTR are secure they run into the issue where an attacker can flip the encrypted data, changing its value when decrypted. So let's say you encrypt an imaginary bank message "Sell 100", your encrypted message looks like this "eu23ng" the attacker changes one bit to "eu53ng" and all of a sudden when decrypted your message, it reads as "Sell 900".

To avoid this the majority of the internet uses GCM, and every time you see HTTPS they are probably using GCM. GCM signs the encrypted message with a hash and checks to verify that the message has not been changed using this signature.

I would avoid implementing GCM because of its complexity. You are better off using Googles new library Tink because here again if you accidentally repeat an IV you are compromising the key in the case with GCM, which is the ultimate security flaw. New researchers are working towards IV repeat resistant encryption modes where even if you repeat the IV the key is not in danger but this has yet to come mainstream.

Now if you do want to implement GCM, here is a link to a nice GCM implementation. However, I can not ensure the security or if its properly implemented but it gets the basis down. Also note with GCM there is no padding.

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

Keys vs Passwords

Another very important note, is that when it comes to cryptography a Key and a Password are not the same things. A Key in cryptography needs to have a certain amount of entropy and randomness to be considered secure. This is why you need to make sure to use the proper cryptographic libraries to generate the key for you.

So you really have two implementations you can do here, the first is to use the code found on this StackOverflow thread for Random Key Generation. This solution uses a secure random number generator to create a key from scratch that you can the use.

The other less secure option is to use, user input such as a password. The issue as we discussed is that the password doesn't have enough entropy, so we would have to use PBKDF2, an algorithm that takes the password and strengthens it. Here is a StackOverflow implementation I liked. However Google Tink library has all this built in and you should take advantage of it.

Android Developers

One important point to point out here is know that your android code is reverse engineerable and most cases most java code is too. That means if you store the password in plain text in your code. A hacker can easily retrieve it. Usually, for these type of encryption, you want to use Asymmetric Cryptography and so on. This is outside the scope of this post so I will avoid diving into it.

An interesting reading from 2013: Points out that 88% of Crypto implementations in Android were done improperly.

Final Thoughts

Once again I would suggest avoid implementing the java library for crypto directly and use Google Tink, it will save you the headache as they have really done a good job of implementing all the algorithms properly. And even then make sure you check up on issues brought up on the Tink github, vulnerabilities popup here and there.

If you have any questions or feedback feel free to comment! Security is always changing and you need to do your best to keep up with it :)

Konstantino Sparakis
  • 2,862
  • 3
  • 18
  • 30
  • @Seraf Haha thank you, need to sit down and edit it, looking back some things could be better said :) – Konstantino Sparakis Sep 03 '17 at 13:42
  • and is it expected from such APIs to not have a change for long time ? It was last updated on - **Jasypt 1.9.2 RELEASED! (February 25th, 2014)**. I think, its pretty normal for such APIs but just checking. – Sabir Khan Oct 09 '17 at 11:48
  • 1
    @SabirKhan It could be a cause for concern but the core algorithms still have not been broken so I wouldn't be too worried about that. In the case where you don't trust it also check out https://github.com/google/keyczar, It was developed by googles security team. – Konstantino Sparakis Oct 18 '17 at 16:13
  • 1
    @KonstantinoSparakis: If I did not misinterpret the documentation for Jasypt's BasicTextEncryptor and StrongTextEncryptor, those classes use **DES** and **3DES** for encrypting, which is exactly what you tell the readers not to use. IMO you should replace the given code examples with one that makes use of Jasypt's StandardPBEStringEncryptor and manually defines an AES algorithm to use. – xpages-noob Mar 23 '18 at 07:00
  • @xpages-noob, That's interesting I will give it a thorough look through I was confident they were using CBC properly. I personally have moved away from Jasypt and started using github.com/google/keyczar, will update this post. :) Thanks for looking out! – Konstantino Sparakis Mar 28 '18 at 18:31
  • 1
    @xpages-noob I updated the post. I actually found Google Tink, which is the newest supported library for crypto so you should check it out! – Konstantino Sparakis Apr 06 '18 at 12:33
  • @KonstantinoSparakis: Thanks for the update and the advice. I will check it out. – xpages-noob Apr 06 '18 at 13:41
  • From https://github.com/google/tink : "TIP The easiest way to get started with Tink is to install Bazel, then build, run and play with the hello world examples." Really? The easiest way to get started with Tink is to install a new build system? – Ryan Lundy May 22 '18 at 07:05
  • 3
    AES block size is 128 bits. In AES 256, the key size is 256 bits. Likewise, AES 192 and AES 128. Also, since Java 8, `getInstanceStrong()` method of `Cipher` is preferrable over SHA1PRNG – Saptarshi Basu Oct 26 '18 at 09:53
  • @SaptarshiBasu Thank you for that :) I will go ahead and update the post – Konstantino Sparakis Nov 02 '18 at 16:39
  • 2
    Java has included GCM for a very long time now, no Tink required. Your block size should be 12 bytes for GCM, possibly noting that a non-random nonce is possible as well. AES-256 is as strong as it gets... citation needed. The relatively low number of rounds, smallish block size and related key attacks are all indications that we could do better. AES-256 *does suffice* and remains unbroken for encryption so far. Nitpick: you're missing "CTR" from your CTR example. – Maarten Bodewes Dec 04 '18 at 09:28
  • @MaartenBodewes I wasn't aware of the gcm in Java! Will look into it and clean this up. Haha wow, I have no idea how no ones picked up the CTR missing though thank you! – Konstantino Sparakis Jan 24 '19 at 17:14
  • 1
    Glad you picked this up Konstantino. Although I'll always be skeptical about these kind of catch-it-all Q/A's, the fact that security issues are taken seriously is a good sign. It's also a good sign that these kind of no-code-delivered kind of fragments are voted up regardless (they definitely beat the "here's the crypto code, it works!" answers). – Maarten Bodewes Jan 24 '19 at 17:29
  • 2
    @MaartenBodewes just ran through your profile and answers! super impressed with your work I am equally concerned with the lack of there being a better way to properly point at secure code on sites like StackOverflow. Google searches pushing solutions with known vulnerabilities just makes me cringe. – Konstantino Sparakis Jan 24 '19 at 18:24
  • 1
    Don't do `SecureRandom.getInstance("SHA1PRNG");`, just do `new SecureRandom();`. [Source 1](https://tersesystems.com/blog/2015/12/17/the-right-way-to-use-securerandom/), [Source 2](https://tersesystems.com/blog/2015/12/17/the-right-way-to-use-securerandom/). – TheGreatContini Sep 13 '21 at 04:14
110

I'd recommend to use some standard symmetric cypher that is widely available like DES, 3DES or AES. While that is not the most secure algorithm, there are loads of implementations and you'd just need to give the key to anyone that is supposed to decrypt the information in the barcode. javax.crypto.Cipher is what you want to work with here.

Let's assume the bytes to encrypt are in

byte[] input;

Next, you'll need the key and initialization vector bytes

byte[] keyBytes;
byte[] ivBytes;

Now you can initialize the Cipher for the algorithm that you select:

// wrap key data in Key/IV specs to pass to cipher
SecretKeySpec key = new SecretKeySpec(keyBytes, "DES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// create the cipher with the algorithm you choose
// see javadoc for Cipher class for more info, e.g.
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");

Encryption would go like this:

cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted= new byte[cipher.getOutputSize(input.length)];
int enc_len = cipher.update(input, 0, input.length, encrypted, 0);
enc_len += cipher.doFinal(encrypted, enc_len);

And decryption like this:

cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = new byte[cipher.getOutputSize(enc_len)];
int dec_len = cipher.update(encrypted, 0, enc_len, decrypted, 0);
dec_len += cipher.doFinal(decrypted, dec_len);
KyleMit
  • 30,350
  • 66
  • 462
  • 664
VoidPointer
  • 17,651
  • 15
  • 54
  • 58
  • 12
    Can I suggest you update this example to reference the `DESede` algorithm? Since this is a popular question (and answer), it would be a shame to encourage people to use DES, since the cipher is so weak by today's standards. – Duncan Jones Oct 16 '14 at 06:50
  • something wrong with javax.crypto.BadPaddingException: Given final block not properly padded while decript – curiousity Oct 17 '14 at 13:16
  • 2
    @Duncan Indeed DES is weak but I suppose AES would be preferable over DESede (aka TipleDES): [http://security.stackexchange.com/a/26181/69785](http://security.stackexchange.com/a/26181/69785) – Piovezan Feb 29 '16 at 14:31
  • 2
    This should be updated to have AES/GCM/NoPadding, DES is vulnerable to bruteforce attacks, TripleDes is not recommended either – Konstantino Sparakis May 07 '17 at 02:13
  • 2
    The answer from Konstantino Sparakis below is SO much better than this one. – Steve Apr 28 '19 at 16:28
13

thanks ive made this class using your code maybe someone finds it userfull

object crypter

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


public class ObjectCrypter {

private Cipher deCipher;
private Cipher enCipher;
private SecretKeySpec key;
private IvParameterSpec ivSpec;


public ObjectCrypter(byte[] keyBytes,   byte[] ivBytes) {
    // wrap key data in Key/IV specs to pass to cipher


     ivSpec = new IvParameterSpec(ivBytes);
    // create the cipher with the algorithm you choose
    // see javadoc for Cipher class for more info, e.g.
    try {
         DESKeySpec dkey = new  DESKeySpec(keyBytes);
          key = new SecretKeySpec(dkey.getKey(), "DES");
         deCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
         enCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
    } catch (NoSuchAlgorithmException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
public byte[] encrypt(Object obj) throws InvalidKeyException, InvalidAlgorithmParameterException, IOException, IllegalBlockSizeException, ShortBufferException, BadPaddingException {
    byte[] input = convertToByteArray(obj);
    enCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);

    return enCipher.doFinal(input);




//  cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
//  byte[] encypted = new byte[cipher.getOutputSize(input.length)];
//  int enc_len = cipher.update(input, 0, input.length, encypted, 0);
//  enc_len += cipher.doFinal(encypted, enc_len);
//  return encypted;


}
public Object decrypt( byte[]  encrypted) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException, ClassNotFoundException {
    deCipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

    return convertFromByteArray(deCipher.doFinal(encrypted));

}



private Object convertFromByteArray(byte[] byteObject) throws IOException,
        ClassNotFoundException {
    ByteArrayInputStream bais;

    ObjectInputStream in;
    bais = new ByteArrayInputStream(byteObject);
    in = new ObjectInputStream(bais);
    Object o = in.readObject();
    in.close();
    return o;

}



private byte[] convertToByteArray(Object complexObject) throws IOException {
    ByteArrayOutputStream baos;

    ObjectOutputStream out;

    baos = new ByteArrayOutputStream();

    out = new ObjectOutputStream(baos);

    out.writeObject(complexObject);

    out.close();

    return baos.toByteArray();

}


}
sherif
  • 2,282
  • 19
  • 21
  • posted a related question [here](http://stackoverflow.com/questions/21685389/java-encryption-ivspec-random-or-not)! – user2023507 Feb 10 '14 at 18:53
  • Shouldn't it be the case that passing differentKeys during encrypt and decrypt should not return the text back? That doesn't seem to be happening here. PS: I am using different objects of this class to perform this test. – instanceOfObject Aug 12 '15 at 18:22
12

You can use Jasypt

With Jasypt, encrypting and checking a password can be as simple as...

StrongTextEncryptor textEncryptor = new StrongTextEncryptor();
textEncryptor.setPassword(myEncryptionPassword);

Encryption:

String myEncryptedText = textEncryptor.encrypt(myText);

Decryption:

String plainText = textEncryptor.decrypt(myEncryptedText);

Gradle:

compile group: 'org.jasypt', name: 'jasypt', version: '1.9.2'

Features:

Jasypt provides you with easy unidirectional (digest) and bidirectional encryption techniques.

Open API for use with any JCE provider, and not only the default Java VM one. Jasypt can be easily used with well-known providers like Bouncy Castle. Learn more.

Higher security for your users' passwords. Learn more.

Binary encryption support. Jasypt allows the digest and encryption of binaries (byte arrays). Encrypt your objects or files when needed (for being sent over the net, for example).

Number encryption support. Besides texts and binaries, it allows the digest and encryption of numeric values (BigInteger and BigDecimal, other numeric types are supported when encrypting for Hibernate persistence). Learn more.

Completely thread-safe.

Support for encryptor/digester pooling, in order to achieve high performance in multi-processor/multi-core systems.

Includes a lightweight ("lite") version of the library for better manageability in size-restrictive environments like mobile platforms.

Provides both easy, no-configuration encryption tools for users new to encryption, and also highly configurable standard encryption tools, for power-users.

Hibernate 3 and 4 optional integration for persisting fields of your mapped entities in an encrypted manner. Encryption of fields is defined in the Hibernate mapping files, and it remains transparent for the rest of the application (useful for sensitive personal data, databases with many read-enabled users...). Encrypt texts, binaries, numbers, booleans, dates... Learn more.

Seamlessly integrable into a Spring application, with specific integration features for Spring 2, Spring 3.0 and Spring 3.1. All the digesters and encryptors in jasypt are designed to be easily used (instantiated, dependency-injected...) from Spring. And, because of their being thread-safe, they can be used without synchronization worries in a singleton-oriented environment like Spring. Learn more: Spring 2, Spring 3.0, Spring 3.1.

Spring Security (formerly Acegi Security) optional integration for performing password encryption and matching tasks for the security framework, improving the security of your users' passwords by using safer password encryption mechanisms and providing you with a higher degree of configuration and control. Learn more.

Provides advanced functionality for encrypting all or part of an application's configuration files, including sensitive information like database passwords. Seamlessly integrate encrypted configuration into plain, Spring-based and/or Hibernate-enabled applications. Learn more.

Provides easy to use CLI (Command Line Interface) tools to allow developers initialise their encrypted data and include encryption/decryption/digest operations in maintenance tasks or scripts. Learn more.

Integrates into Apache Wicket, for more robust encryption of URLs in your secure applications.

Comprehensive guides and javadoc documentation, to allow developers to better understand what they are really doing to their data.

Robust charset support, designed to adequately encrypt and digest texts whichever the original charset is. Complete support for languages like Japanese, Korean, Arabic... with no encoding or platform issues.

Very high level of configuration capabilities: The developer can implement tricks like instructing an "encryptor" to ask a, for example, remote HTTPS server for the password to be used for encryption. It lets you meet your security needs.

Community
  • 1
  • 1
Kamil Nękanowicz
  • 6,254
  • 7
  • 34
  • 51
  • 1
    But what security does `Jasypt` provide? I cannot figure it out from their website. Is it indistinguishable under chosen-plaintext attacks? Integrity? Confidentiality? – trichner Dec 08 '18 at 03:49
  • Jasypt does not implement any encryption algorithms, but instead delegates to the ones already provided by a JCE (Java Cryptography Extension) provider. It is "simplified encryption" because it is much easier to use and integrate than the standard APIs, but you will get the same encryption power as if you were using the JCE API directly, or even more, as jasypt will enforce best practices and use standards that will ensure that you always get the highest level of security from the encryption features present in your Java installation. – Kamil Nękanowicz Mar 03 '22 at 10:47
11

Update on 12-DEC-2019

Unlike some other modes like CBC, GCM mode does not require the IV to be unpredictable. The only requirement is that the IV has to be unique for each invocation with a given key. If it repeats once for a given key, security can be compromised. An easy way to achieve this is to use a random IV from a strong pseudo random number generator as shown below.

Using a sequence or timestamp as IV is also possible, but it may not be as trivial as it may sound. For example, if the system does not correctly keep track of the sequences already used as IV in a persistent store, an invocation may repeat an IV after a system reboot. Likewise, there is no perfect clock. Computer clock readjusts etc.

Also, the key should be rotated after every 2^32 invocations. For further details on the IV requirement, refer to this answer and the NIST recommendations.


This is the encryption & decryption code I just wrote in Java 8 considering the following points. Hope someone would find this useful:

  1. Encryption Algorithm: Block cipher AES with 256 bits key is considered secure enough. To encrypt a complete message, a mode needs to be selected. Authenticated encryption (which provides both confidentiality and integrity) is recommended. GCM, CCM and EAX are most commonly used authenticated encryption modes. GCM is usually preferred and it performs well in Intel architectures which provide dedicated instructions for GCM. All these three modes are CTR-based (counter-based) modes and therefore they do not need padding. As a result they are not vulnerable to padding related attacks

  2. An initialization Vector (IV) is required for GCM. The IV is not a secret. The only requirement being it has to be random or unpredictable. In Java, the SecuredRandom class is meant to produce cryptographically strong pseudo random numbers. The pseudo-random number generation algorithm can be specified in the getInstance() method. However, since Java 8, the recommended way is to use getInstanceStrong() method which will use the strongest algorithm configured and provided by the Provider

  3. NIST recommends 96 bit IV for GCM to promote interoperability, efficiency, and simplicity of design

  4. To ensure additional security, in the following implementation SecureRandom is re-seeded after producing every 2^16 bytes of pseudo random byte generation

  5. The recipient needs to know the IV to be able to decrypt the cipher text. Therefore the IV needs to be transferred along with the cipher text. Some implementations send the IV as AD (Associated Data) which means that the authentication tag will be calculated on both the cipher text and the IV. However, that is not required. The IV can be simply pre-pended with the cipher text because if the IV is changed during transmission due to a deliberate attack or network/file system error, the authentication tag validation will fail anyway

  6. Strings should not be used to hold the clear text message or the key as Strings are immutable and thus we cannot clear them after use. These uncleared Strings then linger in the memory and may show up in a heap dump. For the same reason, the client calling these encryption or decryption methods should clear all the variables or arrays holding the message or the key after they are no longer needed.

  7. No provider is hard coded in the code following the general recommendations

  8. Finally for transmission over network or storage, the key or the cipher text should be encoded using Base64 encoding. The details of Base64 can be found here. The Java 8 approach should be followed

Byte arrays can be cleared using:

Arrays.fill(clearTextMessageByteArray, Byte.MIN_VALUE);

However, as of Java 8, there is no easy way to clear SecretKeyspec and SecretKey as the implementations of these two interfaces do not seem to have implemented the method destroy() of the interface Destroyable. In the following code, a separate method is written to clear the SecretKeySpec and SecretKey using reflection.

Key should be generated using one of the two approaches mentioned below.

Note that keys are secrets like passwords, but unlike passwords which are meant for human use, keys are meant to be used by cryptographic algorithms and hence should be generated using the above way only.

package com.sapbasu.javastudy;

import java.lang.reflect.Field;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

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

public class Crypto {

  private static final int AUTH_TAG_SIZE = 128; // bits

  // NIST recommendation: "For IVs, it is recommended that implementations
  // restrict support to the length of 96 bits, to
  // promote interoperability, efficiency, and simplicity of design."
  private static final int IV_LEN = 12; // bytes

  // number of random number bytes generated before re-seeding
  private static final double PRNG_RESEED_INTERVAL = Math.pow(2, 16);

  private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";

  private static final List<Integer> ALLOWED_KEY_SIZES = Arrays
      .asList(new Integer[] {128, 192, 256}); // bits

  private static SecureRandom prng;

  // Used to keep track of random number bytes generated by PRNG
  // (for the purpose of re-seeding)
  private static int bytesGenerated = 0;

  public byte[] encrypt(byte[] input, SecretKeySpec key) throws Exception {

    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Length of message cannot be 0");
    }

    if (!ALLOWED_KEY_SIZES.contains(key.getEncoded().length * 8)) {
      throw new IllegalArgumentException("Size of key must be 128, 192 or 256");
    }

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

    byte[] iv = getIV(IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    cipher.init(Cipher.ENCRYPT_MODE, key, gcmParamSpec);
    byte[] messageCipher = cipher.doFinal(input);

    // Prepend the IV with the message cipher
    byte[] cipherText = new byte[messageCipher.length + IV_LEN];
    System.arraycopy(iv, 0, cipherText, 0, IV_LEN);
    System.arraycopy(messageCipher, 0, cipherText, IV_LEN,
        messageCipher.length);
    return cipherText;
  }

  public byte[] decrypt(byte[] input, SecretKeySpec key) throws Exception {
    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Input array cannot be empty");
    }

    byte[] iv = new byte[IV_LEN];
    System.arraycopy(input, 0, iv, 0, IV_LEN);

    byte[] messageCipher = new byte[input.length - IV_LEN];
    System.arraycopy(input, IV_LEN, messageCipher, 0, input.length - IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
    cipher.init(Cipher.DECRYPT_MODE, key, gcmParamSpec);

    return cipher.doFinal(messageCipher);
  }

  public byte[] getIV(int bytesNum) {

    if (bytesNum < 1) throw new IllegalArgumentException(
        "Number of bytes must be greater than 0");

    byte[] iv = new byte[bytesNum];

    prng = Optional.ofNullable(prng).orElseGet(() -> {
      try {
        prng = SecureRandom.getInstanceStrong();
      } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Wrong algorithm name", e);
      }
      return prng;
    });

    if (bytesGenerated > PRNG_RESEED_INTERVAL || bytesGenerated == 0) {
      prng.setSeed(prng.generateSeed(bytesNum));
      bytesGenerated = 0;
    }

    prng.nextBytes(iv);
    bytesGenerated = bytesGenerated + bytesNum;

    return iv;
  }

  private static void clearSecret(Destroyable key)
      throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException, SecurityException {
    Field keyField = key.getClass().getDeclaredField("key");
    keyField.setAccessible(true);
    byte[] encodedKey = (byte[]) keyField.get(key);
    Arrays.fill(encodedKey, Byte.MIN_VALUE);
  }
}

The encryption key can be generated primarily in two ways:

  • Without any password

    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(KEY_LEN, SecureRandom.getInstanceStrong());
    SecretKey secretKey = keyGen.generateKey();
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);
    
  • With password

    SecureRandom random = SecureRandom.getInstanceStrong();
    byte[] salt = new byte[32];
    random.nextBytes(salt);
    PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterations, 
       keyLength);
    SecretKeyFactory keyFactory = 
        SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    SecretKey secretKey = keyFactory.generateSecret(keySpec);
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);
    

Update Based on Comments

As pointed out by @MaartenBodewes, my answer did not handle any String as is required by the question. Therefore, I'll make an attempt to fill that gap just in case someone stumbles upon this answer and leaves wondering about handling String.

As indicated earlier in the answer, handling sensitive information in a String is, in general, not a good idea because String is immutable and thus we cannot clear it off after use. And as we know, even when a String doesn't have a strong reference, the garbage collector does not immediately rush to remove it off heap. Thus, the String continues to be around in the memory for an unknown window of time even though it is not accessible to the program. The issue with that is, a heap dump during that time frame would reveal the sensitive information. Therefore, it is always better to handle all sensitive information in a byte array or char array and then fill the array with 0s once their purpose is served.

However, with all that knowledge, if we still end up in a situation where the sensitive information to be encrypted is in a String, we first need to convert it into a byte array and invoke the encrypt and decrypt functions introduced above. (The other input key can be generated using the code snippet provided above).

A String can be converted into bytes in the following way:

byte[] inputBytes = inputString.getBytes(StandardCharsets.UTF_8);

As of Java 8, String is internally stored in heap with UTF-16 encoding. However, we have used UTF-8 here as it usually takes less space than UTF-16, especially for ASCII characters.

Likewise, the encrypted byte array can also be converted into a String as below:

String encryptedString = new String(encryptedBytes, StandardCharsets.UTF_8);
Saptarshi Basu
  • 8,640
  • 4
  • 39
  • 58
  • 1
    As much as I want to upvote this answer as it does look to adhere to current crypto practices, I don't see any string handling at all, making it more like just a description on how to use GCM mode. As such it fails to *answer the question*. – Maarten Bodewes Dec 04 '18 at 09:36
  • 2
    @MaartenBodewes Thanks so much for taking time to review and share feedback. I wrote this with the understanding that encrypting a `String` using the functions created above would be trivial. However, on a second look after reading your comment I understand that it may not be obvious. I'll surely edit to add those details. – Saptarshi Basu Dec 04 '18 at 09:44
10

Warning

Do not use this as some kind of security measurement.

The encryption mechanism in this post is a One-time pad, which means that the secret key can be easily recovered by an attacker using 2 encrypted messages. XOR 2 encrypted messages and you get the key. That simple!

Pointed out by Moussa


I am using Sun's Base64Encoder/Decoder which is to be found in Sun's JRE, to avoid yet another JAR in lib. That's dangerous from point of using OpenJDK or some other's JRE. Besides that, is there another reason I should consider using Apache commons lib with Encoder/Decoder?

public class EncryptUtils {
    public static final String DEFAULT_ENCODING = "UTF-8"; 
    static BASE64Encoder enc = new BASE64Encoder();
    static BASE64Decoder dec = new BASE64Decoder();

    public static String base64encode(String text) {
        try {
            return enc.encode(text.getBytes(DEFAULT_ENCODING));
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }//base64encode

    public static String base64decode(String text) {
        try {
            return new String(dec.decodeBuffer(text), DEFAULT_ENCODING);
        } catch (IOException e) {
            return null;
        }
    }//base64decode

    public static void main(String[] args) {
        String txt = "some text to be encrypted";
        String key = "key phrase used for XOR-ing";
        System.out.println(txt + " XOR-ed to: " + (txt = xorMessage(txt, key)));

        String encoded = base64encode(txt);       
        System.out.println(" is encoded to: " + encoded + " and that is decoding to: " + (txt = base64decode(encoded)));
        System.out.print("XOR-ing back to original: " + xorMessage(txt, key));
    }

    public static String xorMessage(String message, String key) {
        try {
            if (message == null || key == null) return null;

            char[] keys = key.toCharArray();
            char[] mesg = message.toCharArray();

            int ml = mesg.length;
            int kl = keys.length;
            char[] newmsg = new char[ml];

            for (int i = 0; i < ml; i++) {
                newmsg[i] = (char)(mesg[i] ^ keys[i % kl]);
            }//for i

            return new String(newmsg);
        } catch (Exception e) {
            return null;
        }
    }//xorMessage
}//class
Community
  • 1
  • 1
ante.sabo
  • 3,141
  • 6
  • 28
  • 36
  • 1
    I also used this solution proposal via sun.misc.BASE64Encoder but when using rather large strings to encode, the encoder returned chunked strings (76 characters each). I then switched to Apache Commons Codec Base64 which offers non-chunking encoding methods! – basZero Mar 19 '12 at 15:14
  • 89
    The encryption mechanism you described is VERY DANGEROUS if used more than once. that is the reason why it is called One-time pad. The secret key can be easily recovered by an attacker using 2 encrypted messages. xor 2 encrypted messages and you get the key. That simple! – xtrem Oct 11 '12 at 01:20
  • 4
    Its idea is not to be heavy one, just to bounce off people from trying to read what is written in PDF-417 2D barcodes. And anyway, there are only some indexes not crucial to anyone... – ante.sabo Oct 11 '12 at 16:16
  • 3
    OK. Just concerned that someone uses this as an encryption mechanism. – xtrem Oct 12 '12 at 00:15
  • For Encryption , encoder(eg.BASE64Encoder ) can be avoided to have brute force attacks. – Jagrut Dalwadi May 13 '16 at 13:00
  • @xtrem we both agree that reusing one-time pads is a very bad idea. Correct me if I'm wrong but retrieving the key is a bit more complicated. Let's say we have clear text messages M1 and M2, and a unique pad P. We'll get "encrypted" (M1 xor P) and (M2 xor P). If we xor those two encrypted messages, we get (M1 xor M2). It's not too far from breaking the system, but it's not that immediate. – ascobol Sep 20 '21 at 14:56
5

How about this:

// It is assumed that neither "input" nor "secret" is NULL
private static byte[] xor(final byte[] input, final byte[] secret) {
    final byte[] output = new byte[input.length];
    if (secret.length == 0) {
        throw new IllegalArgumentException("empty security key");
    }
    int spos = 0;
    for (int pos = 0; pos < input.length; ++pos) {
        output[pos] = (byte) (input[pos] ^ secret[spos]);
        ++spos;
        if (spos >= secret.length) {
            spos = 0;
        }
    }
    return output;
}

Works fine for me and is rather compact. Keep in mind that it's a very weak encryption.

yegor256
  • 102,010
  • 123
  • 446
  • 597
  • what will happen if entry parameter secret == null or input == null ? working with bytes rather then with strings is ok, but was irrelevant in my case.. only thing what matters is that this must be readable and decodable with any device, in any character encoding possible... – ante.sabo Dec 28 '12 at 12:42
  • @ante.sabo apparently, it will throw an NPE. This is the only thing to do with NULLs. – Miha_x64 Jun 01 '17 at 10:15
  • 1
    As long as `input.length <= secret.length` holds and no `secret` is ever re-used this is secure and called a `one-time-pad`. In the cases of `input.length > secret.length` this is a variant of the [Vigenère cipher](https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher) and considered very weak. – trichner Jan 10 '20 at 14:46
5

Here a simple solution with only java.* and javax.crypto.* dependencies for encryption of bytes providing confidentiality and integrity. It shall be indistinguishable under a choosen plaintext attack for short messages in the order of kilobytes.

It uses AES in the GCM mode with no padding, a 128bit key is derived by PBKDF2 with lots of iterations and a static salt from the provided password. This makes sure brute forcing passwords is hard and distributes the entropy over the entire key.

A random initialisation vector (IV) is generated and will be prepended to the ciphertext. Furthermore, the static byte 0x01 is prepended as the first byte as a 'version'.

The entire message goes into the message authentication code (MAC) generated by AES/GCM.

Here it goes, zero external dependencies encryption class providing confidentiality and integrity:

package ch.n1b.tcrypt.utils;

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * This class implements AES-GCM symmetric key encryption with a PBKDF2 derived password.
 * It provides confidentiality and integrity of the plaintext.
 *
 * @author Thomas Richner
 * @created 2018-12-07
 */
public class AesGcmCryptor {

    // https://crypto.stackexchange.com/questions/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode
    private static final byte VERSION_BYTE = 0x01;
    private static final int VERSION_BYTE_LENGTH = 1;
    private static final int AES_KEY_BITS_LENGTH = 128;


    // fixed AES-GCM constants
    private static final String GCM_CRYPTO_NAME = "AES/GCM/NoPadding";
    private static final int GCM_IV_BYTES_LENGTH = 12;
    private static final int GCM_TAG_BYTES_LENGTH = 16;

    // can be tweaked, more iterations = more compute intensive to brute-force password
    private static final int PBKDF2_ITERATIONS = 1024;

    // protects against rainbow tables
    private static final byte[] PBKDF2_SALT = hexStringToByteArray("4d3fe0d71d2abd2828e7a3196ea450d4");

    public String encryptString(char[] password, String plaintext) throws CryptoException {

        byte[] encrypted = null;
        try {
            encrypted = encrypt(password, plaintext.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException //
                | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException //
                | InvalidKeySpecException e) {
            throw new CryptoException(e);
        }
        return byteArrayToHexString(encrypted);
    }

    public String decryptString(char[] password, String ciphertext)
            throws CryptoException {

        byte[] ct = hexStringToByteArray(ciphertext);
        byte[] plaintext = null;
        try {
            plaintext = decrypt(password, ct);
        } catch (AEADBadTagException e) {
            throw new CryptoException(e);
        } catch ( //
                NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeySpecException //
                        | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException //
                        | BadPaddingException e) {
            throw new CryptoException(e);
        }
        return new String(plaintext, StandardCharsets.UTF_8);
    }

    /**
     * Decrypts an AES-GCM encrypted ciphertext and is
     * the reverse operation of {@link AesGcmCryptor#encrypt(char[], byte[])}
     *
     * @param password   passphrase for decryption
     * @param ciphertext encrypted bytes
     * @return plaintext bytes
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws IllegalArgumentException           if the length or format of the ciphertext is bad
     * @throws CryptoException
     */
    public byte[] decrypt(char[] password, byte[] ciphertext)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        // input validation
        if (ciphertext == null) {
            throw new IllegalArgumentException("ciphertext cannot be null");
        }

        if (ciphertext.length <= VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH + GCM_TAG_BYTES_LENGTH) {
            throw new IllegalArgumentException("ciphertext too short");
        }

        // the version must match, we don't decrypt other versions
        if (ciphertext[0] != VERSION_BYTE) {
            throw new IllegalArgumentException("wrong version: " + ciphertext[0]);
        }

        // input seems legit, lets decrypt and check integrity

        // derive key from password
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);

        // init cipher
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec params = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8,
                ciphertext,
                VERSION_BYTE_LENGTH,
                GCM_IV_BYTES_LENGTH
        );
        cipher.init(Cipher.DECRYPT_MODE, key, params);

        final int ciphertextOffset = VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH;

        // add version and IV to MAC
        cipher.updateAAD(ciphertext, 0, ciphertextOffset);

        // decipher and check MAC
        return cipher.doFinal(ciphertext, ciphertextOffset, ciphertext.length - ciphertextOffset);
    }

    /**
     * Encrypts a plaintext with a password.
     * <p>
     * The encryption provides the following security properties:
     * Confidentiality + Integrity
     * <p>
     * This is achieved my using the AES-GCM AEAD blockmode with a randomized IV.
     * <p>
     * The tag is calculated over the version byte, the IV as well as the ciphertext.
     * <p>
     * Finally the encrypted bytes have the following structure:
     * <pre>
     *          +-------------------------------------------------------------------+
     *          |         |               |                             |           |
     *          | version | IV bytes      | ciphertext bytes            |    tag    |
     *          |         |               |                             |           |
     *          +-------------------------------------------------------------------+
     * Length:     1B        12B            len(plaintext) bytes            16B
     * </pre>
     * Note: There is no padding required for AES-GCM, but this also implies that
     * the exact plaintext length is revealed.
     *
     * @param password  password to use for encryption
     * @param plaintext plaintext to encrypt
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws NoSuchPaddingException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws InvalidKeySpecException
     */
    public byte[] encrypt(char[] password, byte[] plaintext)
            throws NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException,
            InvalidKeySpecException {

        // initialise random and generate IV (initialisation vector)
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);
        final byte[] iv = new byte[GCM_IV_BYTES_LENGTH];
        SecureRandom random = SecureRandom.getInstanceStrong();
        random.nextBytes(iv);

        // encrypt
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, spec);

        // add IV to MAC
        final byte[] versionBytes = new byte[]{VERSION_BYTE};
        cipher.updateAAD(versionBytes);
        cipher.updateAAD(iv);

        // encrypt and MAC plaintext
        byte[] ciphertext = cipher.doFinal(plaintext);

        // prepend VERSION and IV to ciphertext
        byte[] encrypted = new byte[1 + GCM_IV_BYTES_LENGTH + ciphertext.length];
        int pos = 0;
        System.arraycopy(versionBytes, 0, encrypted, 0, VERSION_BYTE_LENGTH);
        pos += VERSION_BYTE_LENGTH;
        System.arraycopy(iv, 0, encrypted, pos, iv.length);
        pos += iv.length;
        System.arraycopy(ciphertext, 0, encrypted, pos, ciphertext.length);

        return encrypted;
    }

    /**
     * We derive a fixed length AES key with uniform entropy from a provided
     * passphrase. This is done with PBKDF2/HMAC256 with a fixed count
     * of iterations and a provided salt.
     *
     * @param password passphrase to derive key from
     * @param salt     salt for PBKDF2 if possible use a per-key salt, alternatively
     *                 a random constant salt is better than no salt.
     * @param keyLen   number of key bits to output
     * @return a SecretKey for AES derived from a passphrase
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    private SecretKey deriveAesKey(char[] password, byte[] salt, int keyLen)
            throws NoSuchAlgorithmException, InvalidKeySpecException {

        if (password == null || salt == null || keyLen <= 0) {
            throw new IllegalArgumentException();
        }
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password, salt, PBKDF2_ITERATIONS, keyLen);
        SecretKey pbeKey = factory.generateSecret(spec);

        return new SecretKeySpec(pbeKey.getEncoded(), "AES");
    }

    /**
     * Helper to convert hex strings to bytes.
     * <p>
     * May be used to read bytes from constants.
     */
    private static byte[] hexStringToByteArray(String s) {

        if (s == null) {
            throw new IllegalArgumentException("Provided `null` string.");
        }

        int len = s.length();
        if (len % 2 != 0) {
            throw new IllegalArgumentException("Invalid length: " + len);
        }

        byte[] data = new byte[len / 2];
        for (int i = 0; i < len - 1; i += 2) {
            byte b = (byte) toHexDigit(s, i);
            b <<= 4;
            b |= toHexDigit(s, i + 1);
            data[i / 2] = b;
        }
        return data;
    }

    private static int toHexDigit(String s, int pos) {
        int d = Character.digit(s.charAt(pos), 16);
        if (d < 0) {
            throw new IllegalArgumentException("Cannot parse hex digit: " + s + " at " + pos);
        }
        return d;
    }

    private static String byteArrayToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }

    public class CryptoException extends Exception {

        public CryptoException(Throwable cause) {
            super(cause);
        }
    }
}

Here the entire project with a nice CLI: https://github.com/trichner/tcrypt

Edit: now with appropriate encryptString and decryptString

trichner
  • 840
  • 1
  • 11
  • 22
  • This is incredible. Thank you! I learned a lot from your code and after creating the BadVersionException Exception class, your code worked perfectly the first time. Excellent!! – Morkus Nov 29 '18 at 16:05
  • I like this attempt. That said ... Salt should be random, not static. Iterations probably should not be static either. GCM already includes the IV in the calculation of the tag. It does not contain the version number though. You should not specify the provider for portability, the "SunJCE" one will be the default on the platforms that support it. This code does not contain any message string handling, which is required for this *particular question*. – Maarten Bodewes Dec 04 '18 at 09:45
  • Alright, I cleaned it up a bit more and added the requested `encryptString` and `decryptString` :) – trichner Dec 08 '18 at 03:37
  • This worked very well; ty for the code. It should be noted that this code requires API 19 (Kit Kat) or above to function properly. – PGMacDesign Mar 08 '19 at 19:33
4

Here's my implementation from meta64.com as a Spring Singleton. If you want to create a ciper instance for each call that would work also, and then you could remove the 'synchronized' calls, but beware 'cipher' is not thread-safe.

import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

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

@Component
@Scope("singleton")
public class Encryptor {

    @Value("${aeskey}")
    private String keyStr;

    private Key aesKey = null;
    private Cipher cipher = null;

    synchronized private void init() throws Exception {
        if (keyStr == null || keyStr.length() != 16) {
            throw new Exception("bad aes key configured");
        }
        if (aesKey == null) {
            aesKey = new SecretKeySpec(keyStr.getBytes(), "AES");
            cipher = Cipher.getInstance("AES");
        }
    }

    synchronized public String encrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.ENCRYPT_MODE, aesKey);
        return toHexString(cipher.doFinal(text.getBytes()));
    }

    synchronized public String decrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.DECRYPT_MODE, aesKey);
        return new String(cipher.doFinal(toByteArray(text)));
    }

    public static String toHexString(byte[] array) {
        return DatatypeConverter.printHexBinary(array);
    }

    public static byte[] toByteArray(String s) {
        return DatatypeConverter.parseHexBinary(s);
    }

    /*
     * DO NOT DELETE
     * 
     * Use this commented code if you don't like using DatatypeConverter dependency
     */
    // public static String toHexStringOld(byte[] bytes) {
    // StringBuilder sb = new StringBuilder();
    // for (byte b : bytes) {
    // sb.append(String.format("%02X", b));
    // }
    // return sb.toString();
    // }
    //
    // public static byte[] toByteArrayOld(String s) {
    // int len = s.length();
    // byte[] data = new byte[len / 2];
    // for (int i = 0; i < len; i += 2) {
    // data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i +
    // 1), 16));
    // }
    // return data;
    // }
}
mkobit
  • 43,979
  • 12
  • 156
  • 150
  • 3
    This will encrypt with ECB mode which is horrible. You should be setting at least CBC mode or GCM Mode – Konstantino Sparakis May 03 '17 at 07:15
  • Thanks for the suggestion Konstantinto, i googled that and found some code that uses "AES/CBC/PKCS5Padding" as the Init string for Cipher, instead of just "AES", but i will look into into it more. Or if you want you can provide the actual fix, so others can see the better way. However, aside from the CBC detail I believe my solution is the simplest and securest, and deserves to be upvoted above all the rest. –  May 03 '17 at 20:11
  • Yea no worries, Crypto is a complicated subject. Sadly every implementation on this page is broken and sadly it's the first page that pops up when using google to search for "how to do java encryption." When I get a chance I will try to fix all of them. – Konstantino Sparakis May 03 '17 at 20:38
  • My example is the same as this: http://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html#SimpleEncrEx Except I needed Cipher.getInstance("AES/ECB/PKCS5Padding"); My code assumes there is some properties file with a perfectly 16 byte long encryption key, but for encrypting a string from a 'user supplied' password the oracle page (linked above) shows the way to do that also. –  May 05 '17 at 02:10
  • Also we could use getBytes("UTF-8"), instead of getBytes(), to be sure the code runs the same on all platforms. –  May 05 '17 at 02:18
  • 1
    So the problem with ECB is that it is extremely vulnerable to frequency analysis. There is the famous example of the Linux penguin, https://blog.filippo.io/the-ecb-penguin/ see how although the image is encrypted you can still tell that it is a penguin. I went ahead and wrote my thoughts on the subject down below :) http://stackoverflow.com/a/43779197/2607972 – Konstantino Sparakis May 07 '17 at 01:59
  • Also, the key is too short. When it comes to cryptography a key and password are not the same things. Passwords generally don't have enough entropy. So one thing you could do is first strengthen the key with an algorithm like PBKDF2, then take the hash of that and use that as the key. This allows the user to have a shorter password. – Konstantino Sparakis May 07 '17 at 02:02
  • Sorry, I see what you are saying, I suppose if you really want your encryption to be of very small size then the frequency analysis is not your issue in such a case. As long as you are only encrypting a single block of 16 bytes with the 16-byte key then you are technically "Safe" but this is still easy to brute force as 2^16 key space is not a difficult computation at all. To avoid this I would use CBC with PKCS7 padding. – Konstantino Sparakis May 07 '17 at 02:38
  • But regarding it being a solution to this given problem yes I agree your solution is very simple and works for the given case as no one would put in that much effort to break this and they don't have much to gain in doing so I suppose. – Konstantino Sparakis May 07 '17 at 02:38
3

I would consider using something like https://www.bouncycastle.org/ It is a prebuilt library that allows you to encrypt whatever you like with a number of different Ciphers I understand that you only want to protect from snooping, but if you really want to protect the information, using Base64 won't actually protect you.

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
hdost
  • 883
  • 13
  • 22
  • 1
    Just recommending a random crypto library with ciphers is not an answer to the question. Besides that, why not use the build-in ciphers? – Maarten Bodewes Dec 04 '18 at 09:53
3

Like many of the guys have already told, you should use a standard cypher that is overly used like DES or AES.

A simple example of how you can encrypt and decrypt a string in java using AES.

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

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

public class EncryptorDemo {

    public static String encrypt(String key, String randomVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.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 text: "  + Base64.encodeBase64String(encrypted));
            return Base64.encodeBase64String(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String randomVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.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[] originalText = cipher.doFinal(Base64.decodeBase64(encrypted));
            System.out.println("decrypted text: "  + new String(originalText));
            return new String(originalText);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        String key = "JavasEncryptDemo"; // 128 bit key
        String randomVector = "RandomJavaVector"; // 16 bytes IV
        decrypt(key, randomVector, encrypt(key, randomVector, "Anything you want to encrypt!"));

    }
}
viveknaskar
  • 2,136
  • 1
  • 20
  • 35
  • CBC is no longer a secure mode. Padding is vulnerable to padding Oracle attacks. Also, handling the key and messages in String is not safe. They'll linger in the String pool and appear in a heap dump – Saptarshi Basu Oct 27 '18 at 07:08
  • 2
    Appreciate the comment. This was a simple example of Java's encrypt and decrypt methods as the user was asking. The question was asked some 9 years ago and was answered based on that. Thanks. – viveknaskar Oct 28 '18 at 08:09
  • 2
    Yes this seems a simple way to introduce encrypt/decrypt. Worked like a charm for me.... Thanks. – Codewrapper Dec 26 '19 at 09:02
2

Here are some links you can read what Java supports

Encrypting/decrypting a data stream.

This example demonstrates how to encrypt (using a symmetric encryption algorithm such as AES, Blowfish, RC2, 3DES, etc) a large amount of data. The data is passed in chunks to one of the encrypt methods: EncryptBytes, EncryptString, EncryptBytesENC, or EncryptStringENC. (The method name indicates the type of input (string or byte array) and the return type (encoded string or byte array). The FirstChunk and LastChunk properties are used to indicate whether a chunk is the first, middle, or last in a stream to be encrypted. By default, both FirstChunk and LastChunk equal true -- meaning that the data passed is the entire amount.

JCERefGuide

Java Encryption Examples

Markus Lausberg
  • 12,177
  • 6
  • 40
  • 66
1

Here is a copy/paste solution. I also recommend reading and voting for @Konstantino's answer even though it doesn't provider any code. The initialization vector (IV) is like a salt - it doesn't have to be kept secret. I am new to GCM and apparently AAD is optional and only used in certain circumstances. Set the key in the environment variable SECRET_KEY_BASE. Use something like KeePass to generate a 32 character password. This solution is modeled after my Ruby solution.

    public static String encrypt(String s) {
        try {
            byte[] input = s.getBytes("UTF-8");
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            // generate IV
            SecureRandom secureRandom = SecureRandom.getInstanceStrong();
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            secureRandom.nextBytes(ivBytes);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes); // 96 bit tag length
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
            // generate AAD
//          byte[] aadBytes = new byte[cipher.getBlockSize()];
//          secureRandom.nextBytes(aadBytes);
//          cipher.updateAAD(aadBytes);
            // encrypt
            byte[] encrypted = cipher.doFinal(input);
            byte[] returnBytes = new byte[ivBytes.length + encrypted.length];
//          byte[] returnBytes = new byte[ivBytes.length + aadBytes.length + encrypted.length];
            System.arraycopy(ivBytes, 0, returnBytes, 0, ivBytes.length);
//          System.arraycopy(aadBytes, 0, returnBytes, ivBytes.length, aadBytes.length);
            System.arraycopy(encrypted, 0, returnBytes, ivBytes.length, encrypted.length);
//          System.arraycopy(encrypted, 0, returnBytes, ivBytes.length+aadBytes.length, encrypted.length);
            String encryptedString = Base64.getEncoder().encodeToString(returnBytes);
            return encryptedString;
        } catch (UnsupportedEncodingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "encrypt()", "Could not encrypt string: " + e.getMessage());
            return null;
        }
    }

    public static String decrypt(String s) {
        if (s == null || s.length() == 0) return "";
        try {
            byte[] encrypted = Base64.getDecoder().decode(s);
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            System.arraycopy(encrypted, 0, ivBytes, 0, ivBytes.length);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
//          cipher.updateAAD(encrypted, ivBytes.length, cipher.getBlockSize());
            byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize(), encrypted.length - cipher.getBlockSize());
//          byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize()*2, encrypted.length - cipher.getBlockSize()*2);
            String decryptedString = new String(decrypted, "UTF-8");
            return decryptedString;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedEncodingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "decrypt()", "Could not decrypt string: " + e.getMessage());
            return null;
        }
    }

Here is an example:

    String s = "This is a test.";
    String enc = Utils.encrypt(s);
    System.out.println(enc);
    // fQHfYjbD+xAuN5XzH2ojk/EWNeKXUrKRSfx8LU+5dpuKkM/pueCMBjKCZw==
    String dec = Utils.decrypt(enc);
    System.out.println(dec);
    // This is a test.
Chloe
  • 25,162
  • 40
  • 190
  • 357
-4

You might want to consider some automated tool to do the encryption / decryption code generation eg. https://www.stringencrypt.com/java-encryption/

It can generate different encryption and decryption code each time for the string or file encryption.

It's pretty handy when it comes to fast string encryption without using RSA, AES etc.

Sample results:

// encrypted with https://www.stringencrypt.com (v1.1.0) [Java]
// szTest = "Encryption in Java!"
String szTest = "\u9E3F\uA60F\uAE07\uB61B\uBE1F\uC62B\uCE2D\uD611" +
                "\uDE03\uE5FF\uEEED\uF699\uFE3D\u071C\u0ED2\u1692" +
                "\u1E06\u26AE\u2EDC";

for (int iatwS = 0, qUJQG = 0; iatwS < 19; iatwS++)
{
        qUJQG = szTest.charAt(iatwS);
        qUJQG ++;
        qUJQG = ((qUJQG << 5) | ( (qUJQG & 0xFFFF) >> 11)) & 0xFFFF;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 6) | (qUJQG << 10)) & 0xFFFF;
        qUJQG ^= iatwS;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 3) | (qUJQG << 13)) & 0xFFFF;
        qUJQG ^= 0xFFFF;
        qUJQG ^= 0xB6EC;
        qUJQG = ((qUJQG << 8) | ( (qUJQG & 0xFFFF) >> 8)) & 0xFFFF;
        qUJQG --;
        qUJQG = (((qUJQG & 0xFFFF) >> 5) | (qUJQG << 11)) & 0xFFFF;
        qUJQG ++;
        qUJQG ^= 0xFFFF;
        qUJQG += iatwS;
        szTest = szTest.substring(0, iatwS) + (char)(qUJQG & 0xFFFF) + szTest.substring(iatwS + 1);
}

System.out.println(szTest);

We use it all the time in our company.

Bartosz Wójcik
  • 1,079
  • 2
  • 13
  • 31
  • This is [security through obscurity](https://en.wikipedia.org/wiki/Security_through_obscurity) and isn't really secure. – Chloe Mar 08 '19 at 20:24
  • This question is asking for actual modern cryptographic-strength encryption like AES, not just obfuscation to make strings harder to statically extract. This doesn't even appear to keep any state between characters so its susceptible to frequency analysis. (Single-alphabet [substitution cipher](https://en.wikipedia.org/wiki/Substitution_cipher), except over UTF-16 codepoints instead of the Latin alphabet. But if you use it on English ASCII text, you only get a few unique 16-bit character values, unless I'm misreading this) – Peter Cordes Nov 26 '19 at 09:37
-5
public static String encryptParams(String myTextInput) {

        String myKey = "40674244454045cb9a70040a30e1c007";
        String myVector = "@1B2c3D4e5F6g7H8";

        String encData = "";

        try{
            JavaEncryprtionUtil encUtil = new JavaEncryprtionUtil();
            encData = Base64.encodeToString(encUtil.encrypt(myTextInput.getBytes("UTF-8"), myKey.getBytes("UTF-8"), myVector.getBytes("UTF-8")),Base64.DEFAULT);
            System.out.println(encData);
        }catch(NoSuchAlgorithmException ex){
            ex.printStackTrace();
        }catch(NoSuchPaddingException ex){
            ex.printStackTrace();
        }catch(InvalidKeyException ex){
            ex.printStackTrace();
        }catch(InvalidAlgorithmParameterException ex){
            ex.printStackTrace();
        }catch(IllegalBlockSizeException ex){
            ex.printStackTrace();
        }catch(BadPaddingException ex){
            ex.printStackTrace();
        }catch(UnsupportedEncodingException ex){
            ex.printStackTrace();
        }

        return encData;
    }
Community
  • 1
  • 1
rishikesh
  • 15
  • 5
-5
String s1="arshad"; 
char[] s2=s1.toCharArray(); 
int s3= s2.length; 

  System.out.println(s3);
 int i=0; 

// for(int j=0;j<s3;j++) 
// System.out.println(s2[j]); 

for(i=0;i<((s3)/2);i++) { 

char z,f=10; 
z=(char) (s2[i] * f); 
s2[i]=s2[(s3-1)-i]; 
s2[(s3-1)-i]=z; 

String b=new String(s2);

 print(b);  }
Cardinal System
  • 2,749
  • 3
  • 21
  • 42
  • Formally it encrypts the data into unreadable format. To decrypt use same code. And change s[i]*f to s[I]/f. – Arshad shaik Aug 16 '17 at 03:44
  • This is [security through obscurity](https://en.wikipedia.org/wiki/Security_through_obscurity) and isn't really secure. – Chloe Mar 08 '19 at 20:23
  • This is a classic example of "Security through obscurity in no security at all." The OP asked a question on encryption. This is simplistic and laughable approach is NOT encryption but obfuscation. – ONE Dec 31 '20 at 21:11