简体   繁体   中英

In Java how to use AES encryption so that a static string/text will result in similar encrypted message each time?

I have a situation where I have to store the encrypted from of a text in an excel sheet and my java code should read that encrypted text from a excel sheet and convert it back to original text and hit the server with original plain text. I'm trying to achieve this with AES encryption/decryption logic but I'm not able to achieve it as every time I convert the plain text into encrypted format for it to be stored in the excel sheet it results in a different encrypted string each time so I'm not able to decrypt it back to original, what I want is for a same static string/text my AES encrypted text should also be static. I know even if my encrypted string is dynamic I can convert it back to decrypted format but for my case what I want is my encrypted text should also remain static. How can I achieve this?

Ex. If my plain text is "This is a question" my encrypted string is different each time like below

Enter This is a question Encrypted Data: mFsue8JGwLcJQTiBzM0HLVvdDXKPNGsG/O7N60joH+Ozgg== Decrypted Data: This is a question

Enter This is a question Encrypted Data: FdBz3cGS4NphK14Fw8Me4daM4lVzdrK47WUMSRiUVe+juQ== Decrypted Data: This is a question

See the encrypted data's are different each time. I want that to be static

The classes which I'm using are as below (please note these are not my exact classes but they implement the same logic which I have used. These example classes may have some non used variables and methods, I have mentioned just for reference)

Method.java

import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class Method {

private static SecretKey key;
private final int KEY_SIZE = 128;
private final int DATA_LENGTH = 128;
private Cipher encryptionCipher;

/*
 * public void init() throws Exception { KeyGenerator keyGenerator =
 * KeyGenerator.getInstance("AES"); keyGenerator.init(KEY_SIZE); key =
 * keyGenerator.generateKey();
 * 
 * 
 * }
 */

// String to Key


  public static void getKeyFromPassword(String toEnc, String salt) throws
  NoSuchAlgorithmException, InvalidKeySpecException { SecretKeyFactory factory
  = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); KeySpec spec = new
  PBEKeySpec(toEnc.toCharArray(), salt.getBytes(), 65536, 128); SecretKey
  originalKey = new SecretKeySpec(factory.generateSecret(spec).getEncoded(),"AES"); 
  key= originalKey; }
  
 
/*
 * public static void convertStringToSecretKeyto(String string) {
 * 
 * 
 * byte[] bytesEncoded = Base64.getEncoder().encode(string.getBytes()); byte[]
 * decodedKey = Base64.getDecoder().decode(string); key = new
 * SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
 * System.out.println(bytesEncoded); System.out.println(decodedKey);
 * 
 * }
 */

   public String encrypt(String data) throws Exception {
    byte[] dataInBytes = data.getBytes();
    encryptionCipher = Cipher.getInstance("AES/GCM/NoPadding");
    
    encryptionCipher.init(Cipher.ENCRYPT_MODE, key);
    byte[] encryptedBytes = encryptionCipher.doFinal(dataInBytes);
    return encode(encryptedBytes);
   }

    public String decrypt(String encryptedData) throws Exception {
    byte[] dataInBytes = decode(encryptedData);
    Cipher decryptionCipher = Cipher.getInstance("AES/GCM/NoPadding");

    GCMParameterSpec spec = new GCMParameterSpec(DATA_LENGTH, 
    encryptionCipher.getIV());
    decryptionCipher.init(Cipher.DECRYPT_MODE, key, spec);
    byte[] decryptedBytes = decryptionCipher.doFinal(dataInBytes);
    return new String(decryptedBytes);
    }

    private String encode(byte[] data) {
    return Base64.getEncoder().encodeToString(data);
    }

    private byte[] decode(String data) {
    return Base64.getDecoder().decode(data);
    } 
}

Main.class

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.util.Base64;
import java.util.Scanner;
public class Cypher {

public static void main(String[] args) {
    // TODO Auto-generated method stub
    
    try {
        Method aes_encryption = new Method();
 
        
 //       aes_encryption.init();
       Method.getKeyFromPassword("Texty text","Salty salt");
        
        System.out.println("Enter");
        Scanner sc= new Scanner(System.in);
        String s= sc.nextLine();
        String encryptedData = aes_encryption.encrypt(s);
        String decryptedData = aes_encryption.decrypt(encryptedData);
      

        System.out.println("Encrypted Data : " + encryptedData);
       System.out.println("Decrypted Data : " + decryptedData);
    } catch (Exception ignored) {
    }
}
    
    

}

Nope, you don't want to do that.

In cryprography, data needs to be salted and padded with random bits of data before and/or after the original message in order to preserve it from attacks.

What you are asking for goes against cryprography basic rules. You simply won't find any library that does what you are asking for, except if you create or tweak one by yourself.

Then where's the issue?

My Java is a little bit rusty, however it seems that you are generating a new secret key on the fly each time in this line of code:

originalKey = new SecretKeySpec(factory.generateSecret(spec).getEncoded(),"AES");
key = originalKey;

What you should do is to call originalKey = new SecretKeySpec(factory.generateSecret(spec).getEncoded(),"AES"); only once (outside your function) and save the result somewhere safe . Then, use this same key for your encryptions and decryptions. In this case you'll be able to revert to your original message even when you restart your program.

I tried your code as-is and it works perfectly, just fix the key so you don't use different keys between different sessions.

This is not really an implementation issue; your issue is with the used encryption scheme.

The nonce or IV are used to randomize your ciphertext. This gives you an important property: identical plaintext will encrypt to randomized ciphertext. This is required for a cipher to be secure, as you could otherwise identify identical plaintext messages.

Now you could just fix the IV or nonce, but that means that you will leak information early: if the first blocks of plaintext are identical then those blocks will result in identical ciphertext. Instead you can use an encryption scheme where each of the ciphertext bits depend on all of the plaintext bits.

There are several schemes that offer this kind of property:

  • biIGE mode: the bi-directional infinite garble extension mode is an encryption mode that runs both forward and then backwards. It is an odd mode that isn't used or implemented much;
  • FPE modes: format preserving encryption can be used to encrypt X plaintext bits into exactly X ciphertext bits, it is mainly used for smaller plaintext messages such as credit cards (so that there aren't any additional storage requirements for encrypted credit card numbers);
  • AES-SIV mode: synthetic IV mode is probably the best option, it can encrypt large(r) messages, and offers authenticity as well as the MAC / authentication tag doubles as IV. It has the drawback that it does require additional room for the synthetic IV / authentication tag. It is also a two-pass mode (all plaintext bytes are visited twice), which means that it won't be as performant as other generic modes.

There is AES-GCM-SIV for a performant option of the latter.

Of course, as indicated by others, you really want to make sure that you need this kind of functionality: not having messages encrypt to the same ciphertext is a rather fundamental security property of a cipher.

You want to make sure, that the encryption of the original always produces the same result. Then you have to remove the element of Randomness during encrypting.

You can create the iv yourself once. And use that in your init-calls then the encryption result will always be the same:

    // create the "random" element once, randomly.
    SecureRandom secureRandom = new SecureRandom();
    byte[] fixedIv = new byte[16];
    secureRandom.nextBytes(fixedIv);

// always use the same "random" element
encryptionCipher.init(Cipher.ENCRYPT_MODE, key, fixedIv);

Afterwards you need to make sure to use that fixedIv all the time together with the key.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM