I am trying to encode a String so I can store it safely in my database. Most password encoders, like bcrypt, can only hash your String, with no way of getting it back (which for a password makes sense), and I want to decode it when I retrieve the information from my database.
I tried using org.springframework.security.crypto.encrypt.Encryptors.queryableText() but the method is deprecated.
Is there any other way to do it?
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
public class CryptoManager {
//Key for encryption and decryption
public static final byte[] KEY =
{118, 106, 107, 122, 76, 99, 69, 83, 101, 103, 82, 101, 116, 75, 101, 127};
public static void main(String args[]) {
String salt = "HELLO";
String encryptedString = encrypt(salt, "HelloWorld12345");
System.out.println("Encripted string is " + encryptedString);
String decryptedString = decrypt(salt, encryptedString);
System.out.println("Decrypted string is " + decryptedString);
}
public static String encrypt(String salt, String plainText) {
if (plainText == null || plainText.isEmpty()) {
System.out.println("No data to encrypt!");
return plainText;
}
Cipher cipher = null;
String encryptedString = "";
try {
// Creating a Cipher object
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// Creating a secret key from KEY byte array
final SecretKeySpec secretKey = new SecretKeySpec(KEY, "AES");
// Initializing a Cipher object
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
// Encrypting the plain text string
byte[] encryptedText = cipher.doFinal(salt.concat(plainText).getBytes());
// Encoding the encrypted text to Base64
encryptedString = Base64.getEncoder().encodeToString(encryptedText);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| IllegalBlockSizeException | BadPaddingException ex) {
System.out.println("Exception caught while encrypting : " + ex);
}
return encryptedString;
}
public static String decrypt(String salt, String cipherText) {
if (cipherText == null || cipherText.isEmpty()) {
System.out.println("No data to decrypt!");
return cipherText;
}
String decryptedString = "";
Cipher cipher = null;
try {
// Creating a Cipher object
cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
// Creating a secret key from KEY byte array
final SecretKeySpec secretKey = new SecretKeySpec(KEY, "AES");
// Initializing a Cipher object
cipher.init(Cipher.DECRYPT_MODE, secretKey);
// Decoding from Base64
byte[] encryptedText = Base64.getDecoder().decode(cipherText.getBytes());
// Decrypting to plain text
decryptedString = new String(cipher.doFinal(encryptedText));
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException
| IllegalBlockSizeException | BadPaddingException ex) {
System.out.println("Exception caught while decrypting : " + ex);
}
return decryptedString.replace(salt, "");
}
}
Check this out for more detail
Since you want to store the data securely in database, what you really need to do is encrypt the data before writing to database from application.
To encrypt/decrypt the data from application you can use Symmetric key algorithm like AES.
To automatically convert this in entity you can add a converter in corresponding column
#Convert(converter = DataEncryptDecryptConverter.class)
#Column(name = "DATA")
private String data;
And the converter
#Converter
#Component
public class DataEncryptDecryptConverter implements AttributeConverter<String, String> {
#Override
public String convertToDatabaseColumn(String attribute) {
// Code to encrypt data
}
#Override
public String convertToEntityAttribute(String dbData) {
// Code to decrypt data
}
}
Related
I have a request where there is a field "number_token" that I need to encrypt to send to another API to validate. What is the best method to do this?
Example:
"number_token":"123456789"
encrypt:
"number_token":"iIsInN1YieyJpc3MiOiJHZXR3YXk.gcGFnQmFuayBQQI6ImIyYzMxMTlmLWU3ZjktNDZjZS05NTMxLTkyMTNlNWRjNWNiMSIsImlhdCI6MTY1OTgyNjUyOCwiZXhwIjoxNjU5ODI2NzA4fQ.mL-jivitV30N1PLq10CmI4ZWxCcBivGf5QGVus7Vsyw"
There are many factors to be considered but to get started you can use below logic to encrypt. Credits https://www.javaguides.net/2020/02/java-string-encryption-decryption-example.html?m=1
Here is the complete Java program to encrypt and decrypt string or text in Java:
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class AESEncryptionDecryption {
private static SecretKeySpec secretKey;
private static byte[] key;
private static final String ALGORITHM = "AES";
public void prepareSecreteKey(String myKey) {
MessageDigest sha = null;
try {
key = myKey.getBytes(StandardCharsets.UTF_8);
sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16);
secretKey = new SecretKeySpec(key, ALGORITHM);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
public String encrypt(String strToEncrypt, String secret) {
try {
prepareSecreteKey(secret);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return Base64.getEncoder().encodeToString(cipher.doFinal(strToEncrypt.getBytes("UTF-8")));
} catch (Exception e) {
System.out.println("Error while encrypting: " + e.toString());
}
return null;
}
public String decrypt(String strToDecrypt, String secret) {
try {
prepareSecreteKey(secret);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)));
} catch (Exception e) {
System.out.println("Error while decrypting: " + e.toString());
}
return null;
}
public static void main(String[] args) {
final String secretKey = "secrete";
String originalString = "javaguides";
AESEncryptionDecryption aesEncryptionDecryption = new AESEncryptionDecryption();
String encryptedString = aesEncryptionDecryption.encrypt(originalString, secretKey);
String decryptedString = aesEncryptionDecryption.decrypt(encryptedString, secretKey);
System.out.println(originalString);
System.out.println(encryptedString);
System.out.println(decryptedString);
}
}
I'm getting this error when I try to decrypt a message which has already been encrypted.
I've done this by first encrypting a message in this case "Testing message"
Then running the method again but decrypting instead of encrypting.
I've looked at the other questions regarding this but could not fix the problem
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
public class PBEusing {
public static void main(String[] args) throws Exception {
PBEusing pbe = new PBEusing();
String encrypt = pbe.pbe("encrypt", "passwordTest", "Testing message");
System.out.println(encrypt);
String decrypt = pbe.pbe("decrypt", "passwordTest", encrypt);
System.out.println(decrypt);
}
public static String pbe(String cipherMethod, String clientPassword, String clientMessage) throws Exception {
String method = cipherMethod.toUpperCase();
String output = "";
SecureRandom rnd = new SecureRandom();
byte[] iv = new byte[16];
rnd.nextBytes(iv);
byte[] plaintext = clientMessage.getBytes(StandardCharsets.UTF_8); // input message from user
byte[] salt = "01234567".getBytes(StandardCharsets.UTF_8);
IvParameterSpec ivParamSpec = new IvParameterSpec(iv);
PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, 10000, ivParamSpec);
PBEKeySpec keySpec = new PBEKeySpec(clientPassword.toCharArray());
try {
SecretKeyFactory kf = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_128");
SecretKey secretKey = kf.generateSecret(keySpec);
// On J2SE the SecretKeyfactory does not actually generate a key, it just wraps the password.
// The real encryption key is generated later on-the-fly when initializing the cipher
System.out.println(new String(secretKey.getEncoded()));
// Encrypt
if (method.equals("ENCRYPT")) {
Cipher enc = Cipher.getInstance("PBEWithHmacSHA256AndAES_128");
enc.init(Cipher.ENCRYPT_MODE, secretKey, pbeParamSpec);
byte[] encrypted = enc.doFinal(plaintext);
output = new BASE64Encoder().encode(encrypted);
System.out.println("Encrypted text: " + output);
} else {
// Decrypt
Cipher dec = Cipher.getInstance("PBEWithHmacSHA256AndAES_128");
dec.init(Cipher.DECRYPT_MODE, secretKey, pbeParamSpec);
byte[] decrypted = dec.doFinal(plaintext);
String test = new BASE64Encoder().encode(decrypted);
//String message = new String(test, StandardCharsets.UTF_8);
output = test;
}
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
}
return output;
}
}
You're Base64 encoding the output of the encryption but aren't Base64 decoding it again before you try to decrypt it.
Related to this question:
Cannot decrypt AES-256 GCM with Java
The Java decrypt issue seems to only be fixed if the encrypted message is short, i.e. two words or so. I've tried with the words, "hello" and "short string", and both of these words were decrypted fine. When I tried something like,
Alphanumeric string test1 with more numbers such as 5, 4, 3, 2, 1
AEADBadTagException came up again.
EDIT:
This issue is directly related to how long the encrypted message is. Two words is a bit of an exaggeration, but as long as the encrypted message is about as long as this or longer then Java will run into the exception.
Encrypted message sample:
d+nyOuSfH3wup+5KHJRQyVwVHE0nn7dOfLQsSxb2LsR1LuogHxmVobHoQSTbdyqupd/UvwGfbhkUQz+8CjIBSd7FoEVpgpYv9dAQ3GGUr3AtA+rJJrFHo/EM443sQlSOG4cIBQ7trF7udmrIhtiZ9wMchaBEJFmDBL5Jwl8ZMM0ath8VNWqfyyhghPW8U2NiORAy5mw6v07o7v3UT2
lBzJThBsM=
Decrypted with node:
this is a longer string to make the encrypted message longer than before
EDIT 2:
Java code:
package decryption;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class DecryptAES256 {
private static String salt;
private static byte[] iv;
private static byte[] encryptedMessageAndTag;
private static byte[] key;
public static void main(String[] args) {
String key = "123456789aabbccddeefffffffffffff";
String sourceText = "zMX8Xp8lCLGP3FsF7dy1uEODFG0+lhpoWR+xZPpNAXm2D39+CJUK5Kk0z4NbDfb/WbP8lHVWcTOuXf8hRA1AmtEV2G5kP3SH3mrGbyf4QthR4aOTqEQQAvt1T8LlIkBlgx32gehP/nwwm3DYyJV+NnN21Ac17L4=";
System.out.println(decrypt(key, sourceText));
}
public static String decrypt(String masterkey, String encryptedText) {
// decode encryptedText
encryptedText = new String(Base64.getDecoder().decode(encryptedText.getBytes()));
// extract the different parts
byte[] parts = encryptedText.getBytes();
salt = new String(Arrays.copyOfRange(parts, 0, 64)); // not using for testing purposes
iv = Arrays.copyOfRange(parts, 64, 76);
encryptedMessageAndTag = Arrays.copyOfRange(parts, 76, parts.length);
try {
key = masterkey.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
// not going to reach here
}
// call helper method to decrypt
byte[] decipheredText = decodeAES_256_CBC();
return new String(decipheredText);
}
private static byte[] decodeAES_256_CBC() {
try {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec params = new GCMParameterSpec(128, iv, 0, iv.length);
cipher.init(Cipher.DECRYPT_MODE, skeySpec, params);
return cipher.doFinal(encryptedMessageAndTag);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed to decrypt");
}
}
}
EDIT 3:
Cleaned up Java code for readability
I copied this code from here and modified it, but then as I've read along AES I've noticed that this is using 128 since the number of key characters are only 16, now how do I make it so that it will be AES 256, though I've also read somewhere that java does not primarily allow AES 256 and only limits to AES 128, and that you need to do some more configuration for it to happen, but I am not that sure if what he said there was true and if how do I proceed with those changes. This is also my first time handling AES.
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
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 org.apache.commons.codec.binary.Base64;
public class AES {
private static SecretKeySpec secretKey ;
private static byte[] key ;
private static String decryptedString;
private static String encryptedString;
public static void setKey(String myKey){
MessageDigest sha = null;
try {
key = myKey.getBytes("UTF-8");
sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // use only first 128 bit
// System.out.println(new String(key,"UTF-8"));
secretKey = new SecretKeySpec(key, "AES");
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
}
}
public static String getDecryptedString() {
return decryptedString;
}
public static void setDecryptedString(String decryptedString) {
AES.decryptedString = decryptedString;
}
public static String getEncryptedString() {
return encryptedString;
}
public static void setEncryptedString(String encryptedString) {
AES.encryptedString = encryptedString;
}
public static String encrypt(String initVector, String strToEncrypt) throws InvalidAlgorithmParameterException
{
try
{
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
setEncryptedString(Base64.encodeBase64String(cipher.doFinal(strToEncrypt.getBytes("UTF-8"))));
}
catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException e)
{
System.out.println("Error while encrypting: "+e.toString());
}
return null;
}
public static String decrypt(String initVector, String strToDecrypt) throws InvalidAlgorithmParameterException, UnsupportedEncodingException
{
try
{
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
setDecryptedString(new String(cipher.doFinal(Base64.decodeBase64(strToDecrypt))));
}
catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e)
{
System.out.println("Error while decrypting: "+e.toString());
}
return null;
}
public static void main(String args[]) throws InvalidAlgorithmParameterException, UnsupportedEncodingException
{
String initVector = "Bw/+ctBXLE4=#f9t"; // 16 bytes IV
final String strToEncrypt = "12345657";
final String strPssword = "ViK##Uef$#!BZFgAUW6qZiudgoQ==";
AES.setKey(strPssword);
AES.encrypt(initVector, strToEncrypt.trim());
System.out.println("String to Encrypt: " + strToEncrypt);
System.out.println("Encrypted: " + AES.getEncryptedString());
final String strToDecrypt = AES.getEncryptedString();
AES.decrypt(initVector, strToDecrypt.trim());
System.out.println("String To Decrypt : " + strToDecrypt);
System.out.println("Decrypted : " + AES.getDecryptedString());
}
}
On the server side, the encyption/decryption of the password field is done in C#.
Now, i need to implement same functionality in my android application. So, i followed this tutorial: http://ttux.net/post/3des-java-encrypter-des-java-encryption/ as below:
import java.security.MessageDigest;
import java.security.spec.KeySpec;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
import org.apache.commons.codec.binary.Base64;
public class Encrypter {
private KeySpec keySpec;
private SecretKey key;
private IvParameterSpec iv;
public Encrypter(String keyString, String ivString) {
try {
final MessageDigest md = MessageDigest.getInstance("md5");
final byte[] digestOfPassword = md.digest(Base64.decodeBase64(keyString.getBytes("utf-8")));
final byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
for (int j = 0, k = 16; j < 8;) {
keyBytes[k++] = keyBytes[j++];
}
keySpec = new DESedeKeySpec(keyBytes);
key = SecretKeyFactory.getInstance("DESede").generateSecret(keySpec);
iv = new IvParameterSpec(ivString.getBytes());
} catch(Exception e) {
e.printStackTrace();
}
}
public String encrypt(String value) {
try {
Cipher ecipher = Cipher.getInstance("DESede/CBC/PKCS5Padding","SunJCE");
ecipher.init(Cipher.ENCRYPT_MODE, key, iv);
if(value==null)
return null;
// Encode the string into bytes using utf-8
byte[] utf8 = value.getBytes("UTF8");
// Encrypt
byte[] enc = ecipher.doFinal(utf8);
// Encode bytes to base64 to get a string
return new String(Base64.encodeBase64(enc),"UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public String decrypt(String value) {
try {
Cipher dcipher = Cipher.getInstance("DESede/CBC/PKCS5Padding","SunJCE");
dcipher.init(Cipher.DECRYPT_MODE, key, iv);
if(value==null)
return null;
// Decode base64 to get bytes
byte[] dec = Base64.decodeBase64(value.getBytes());
// Decrypt
byte[] utf8 = dcipher.doFinal(dec);
// Decode using utf-8
return new String(utf8, "UTF8");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
but i dont know what values i need to provide for KeyValue and ivValue for the above code. Please help me...
Use this code to encrypt your string
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import android.util.Base64;
//string encryption
public class EncryptionHelper {
// Encrypts string and encode in Base64
public static String encryptText(String plainText) throws Exception {
// ---- Use specified 3DES key and IV from other source --------------
byte[] plaintext = plainText.getBytes();//input
byte[] tdesKeyData = Constants.getKey().getBytes();// your encryption key
byte[] myIV = Constants.getInitializationVector().getBytes();// initialization vector
Cipher c3des = Cipher.getInstance("DESede/CBC/PKCS5Padding");
SecretKeySpec myKey = new SecretKeySpec(tdesKeyData, "DESede");
IvParameterSpec ivspec = new IvParameterSpec(myIV);
c3des.init(Cipher.ENCRYPT_MODE, myKey, ivspec);
byte[] cipherText = c3des.doFinal(plaintext);
String encryptedString = Base64.encodeToString(cipherText,
Base64.DEFAULT);
// return Base64Coder.encodeString(new String(cipherText));
return encryptedString;
}
}
This is how you can encrypt the string
String encryptedPassword = EncryptionHelper.encryptText(edtText.getText().toString());
EDIT
Code for Constants.java
Class Constants {
private final String initializationVector = "INITALIZATION_VECTOR";
private final String ecnryptionKey = "ENCRYPTION_KEY";
public static String getInitializationVector() {
return initializationVector;
}
public static String getKey() {
return ecnryptionKey;
}
}
Triple DES is called "DESede" (DES using single DES Encrypt, Decrypt, Encrypt for encryption) in both Java and Android runtimes. So it is build in functionality which can be access through the Cipher class. It also lists the available algorithms. For triple DES you could use "DESede/CBC/PKCS5Padding"`. Don't forget to supply it a random IV of 8 bytes.
Triple DES should only be used for backwards compatibility. If you decide to use it at least supply it 24 bytes of key material, otherwise there is a chance that your ciphertext can be cracked. For a more modern approach use AES, preferably in an authenticated mode such as GCM ("AES/GCM/NoPadding"). Note that GCM requires a unique nonce of 12 bytes.