encrypt and encode URL parameters spring mvc - java

I am writing a Spring mvc app and it needs to send an email with a link and encrypted params. The user will click on the link and I need to decrypt the params in the new page. So I am writing a util class to encrypt and decrypt along with encoding and decoding the parameters.
When I run my stand alone java class(using for testing) - I get the following error when the decryption is called(encrypting, encoding, decoding works fine).
java.security.InvalidAlgorithmParameterException: Wrong IV length: must be 16 bytes long
at com.sun.crypto.provider.CipherCore.init(CipherCore.java:430)
at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:217)
at javax.crypto.Cipher.implInit(Cipher.java:791)
at javax.crypto.Cipher.chooseProvider(Cipher.java:849)
at javax.crypto.Cipher.init(Cipher.java:1348)
My class is below
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class TestEncryptionEncode2 {
private String keyString = "asdfgh";
public static void main(String[] args) throws Exception {
TestEncryptionEncode2 api = new TestEncryptionEncode2();
String input = "abcdwer.comq1234";
try {
String[] encrypted = api.encryptObject(input);
// url may differ.. based upon project initial context
System.out.println("http://localhost:8080/view?d="+encrypted[0]+"&v="+encrypted[1]);
Object obj = api.decryptObject(encrypted[0], encrypted[1]);
System.out.println("Object Decrypted: "+obj.toString());
}catch(Exception e) {
//logger.debug("Unable to encrypt view id: "+id, e);
e.printStackTrace();
}
System.out.println("DONEE ");
}
private String[] encryptObject(Object obj) throws Exception {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
ObjectOutput out = new ObjectOutputStream(stream);
try {
// Serialize the object
out.writeObject(obj);
byte[] serialized = stream.toByteArray();
System.out.println("serialized "+serialized[0]);
// Setup the cipher and Init Vector
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[cipher.getBlockSize()];
System.out.println("cipher.getBlockSize() "+cipher.getBlockSize());
System.out.println("iv.length "+iv.length);
new SecureRandom().nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// Hash the key with SHA-256 and trim the output to 128-bit for the key
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(keyString.getBytes());
byte[] key = new byte[16];
System.arraycopy(digest.digest(), 0, key, 0, key.length);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
// encrypt
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
// Encrypt & Encode the input
byte[] encrypted = cipher.doFinal(serialized);
byte[] base64Encoded = Base64.encodeBase64(encrypted);
String base64String = new String(base64Encoded);
String urlEncodedData = URLEncoder.encode(base64String,"UTF-8");
// Encode the Init Vector
byte[] base64IV = Base64.encodeBase64(iv);
String base64IVString = new String(base64IV);
String urlEncodedIV = URLEncoder.encode(base64IVString, "UTF-8");
System.out.println("urlEncodedData.length "+urlEncodedData.length());
System.out.println("urlEncodedIV.length "+urlEncodedIV.length());
return new String[] {urlEncodedData, urlEncodedIV};
}finally {
stream.close();
out.close();
}
}
/**
* Decrypts the String and serializes the object
* #param base64Data
* #param base64IV
* #return
* #throws Exception
*/
public Object decryptObject(String base64Data, String base64IV) throws Exception {
System.out.println("decryptObject "+base64Data);
System.out.println("decryptObject "+base64IV);
// Decode the data
byte[] encryptedData = Base64.decodeBase64(base64Data.getBytes());
// Decode the Init Vector
byte[] rawIV = Base64.decodeBase64(base64IV.getBytes());
System.out.println("rawIV "+rawIV.length);
for (int i=0;i < rawIV.length;i++ ){
System.out.println("---------"+rawIV[i]);
}
// Configure the Cipher
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(rawIV);
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(keyString.getBytes());
byte[] key = new byte[16];
System.arraycopy(digest.digest(), 0, key, 0, key.length);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); //////////////////////////////This is the error line
// Decrypt the data..
byte[] decrypted = cipher.doFinal(encryptedData);
// Deserialize the object
ByteArrayInputStream stream = new ByteArrayInputStream(decrypted);
ObjectInput in = new ObjectInputStream(stream);
Object obj = null;
try {
obj = in.readObject();
System.out.println("objobj "+obj);
}
catch(Exception e) {
//logger.debug("Unable to encrypt view id: "+id, e);
e.printStackTrace();
}finally {
stream.close();
in.close();
}
return obj;
}
}
This is the error line - cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
Do I need to add anything else? Thanks.
EDIT: Thanks, thanks worked. For some reason, I thought I am already decoding the data.
EDIT2: If the text to decode has + in it, then the decoded text will have space instead of +. So I had to replace all the spaces with + back when decrypting. Hopefully this helps someone.

The decryption process should be just reverse of encryption process.
Here in encryption you are doing URL encoding before returning it to user. So you must do URL decoding first in your decryption process.
Here is complete decryption process working with URL decoding:
public Object decryptObject(String base64Data, String base64IV) throws Exception {
System.out.println("decryptObject " + base64Data);
System.out.println("decryptObject " + base64IV);
String urlDecodedData=URLDecoder.decode(base64Data,"UTF-8");
// Decode the data
byte[] encryptedData = Base64.decodeBase64(urlDecodedData.getBytes());
String urlDecodedIV=URLDecoder.decode(base64IV,"UTF-8");
// Decode the Init Vector
byte[] rawIV = Base64.decodeBase64(urlDecodedIV.getBytes());
System.out.println("rawIV " + rawIV.length);
// Configure the Cipher
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(rawIV);
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(keyString.getBytes());
byte[] key = new byte[16];
System.arraycopy(digest.digest(), 0, key, 0, key.length);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); //////////////////////////////This is the error line
// Decrypt the data..
byte[] decrypted = cipher.doFinal(encryptedData);
// Deserialize the object
ByteArrayInputStream stream = new ByteArrayInputStream(decrypted);
ObjectInput in = new ObjectInputStream(stream);
Object obj = null;
try {
obj = in.readObject();
System.out.println("objobj " + obj);
} catch (Exception e) {
//logger.debug("Unable to encrypt view id: "+id, e);
e.printStackTrace();
} finally {
stream.close();
in.close();
}
return obj;
}

Related

Right way to do encryption in java

I'm completely new with encryption stuff and i have some questions about encryption in java
I use to do this for RSA encryption in java
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPTION_MODE,publicKey);
byte result = cipher.doFinal(data);
and same way to use AES and I use this code to generate a AES key
SecureRandom random = new SecureRandom();
byte [] key = new byte [16];
random.nextByte(key);
SecretKeySpec secretKey = new SecretKeySpec(key,"AES");
but as i saw in other programs code this is not how they use encryption i always see they use something as IV param in AES and they never use "AES" or "RSA" to get a cipher instance.
Is the way i using to encrypt data safe?
I'm sure that i missing something
UPDATE:
I also have a question about changing data size in AES encryption in the way that i use to encrypt data with AES it changes the data size from 1024 to 1040
byte key [] = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(key);
SecretKeySpec keySpec = new SecretKeySpec(key,"AES");
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.ENCRYPT_MODE,keySpec);
FileInputStream in = new FileInputStream("test.txt");
byte [] buffer = new byte[1024];
byte [] encrypted;
while (in.read()>0){
encrypted = c.doFinal(buffer);
System.out.println(encrypted.length);
}
the out put is:
1040
1040
.
.
1040
Simply encrypted data size is always 16 bytes more than original data
Do i have to deal with this or it's because i using Cipher.getInstance("AES");
This is not the recommended way and you will need to change it. You may want to have a better look on StackOverflow. Your question is (in)directly answered in this post How to encrypt String in Java.
Make sure you look further down to all the answers. For example this one will probably help you to understand more.
The most simple is to use the below class
package com.toptal.gif_downloader.tools;
import java.io.UnsupportedEncodingException;
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 CipherDecrypt {
private static SecretKeySpec secretKey;
private static byte[] key;
public static void setKey(final 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);
secretKey = new SecretKeySpec(key, "AES");
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
e.printStackTrace();
}
}
public static String Encrypt(final String strToEncrypt, final String secret) {
try {
setKey(secret);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
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 static String Decrypt(final String strToDecrypt, final String secret) {
try {
setKey(secret);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
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;
}
}

Decryption of Encrypted Secret Key and Encrypted String

So I think I have encrypted my secret key and String well but decryption is becoming the problem for me. Below is my code:
package ReadFileExample;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.KeyStore;
public class generatekey {
static Cipher cipher;
public static void main(String[] args) throws Exception {
// generating a symmetric key using the AES algorithm
KeyGenerator generator = KeyGenerator.getInstance("AES");
// 128 bit key
generator.init(256);
//generates a secret key
SecretKey secretkey = generator.generateKey();
// returns an AES cipher
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//print key
System.out.println("Key: " + cipher);
String plainText = "Hello World";
// call to method encrypt
String hexEncryptedByteText = encrypt(plainText, secretkey);
// print orignial text and encrypted text
System.out.println("Plain Text: " + plainText);
System.out.println("Encrypted Text: " + hexEncryptedByteText);
int plainTextlength = plainText.length();
System.out.println("length of text: " + plainTextlength);
// allows to write data to a file
FileOutputStream fos = null;
// write bytes to file
BufferedOutputStream bos = null;
// create file to which data needs to be written
String fileName = "C:/Users/******/newFile.txt";
try{
// allows written data to go into the written path
fos = new FileOutputStream(fileName);
// converts written data into bytes
bos = new BufferedOutputStream(fos);
// writes the encrypted text into file
bos.write(hexEncryptedByteText.length());
System.out.println("encryptedText has been written successfully in "
+fileName);
// allows to catch bug in code
} catch (IOException e) {
e.printStackTrace();
} finally {
try{
// check for null exception
if (bos != null){
bos.close();
}
// check for null exception
if (fos != null){
fos.close();
}
} catch (IOException e){
e.printStackTrace();
}
}
// creates a file input stream by opening a path to the file needed
FileInputStream fin = new FileInputStream("C:/Users/*****/public.cert");
// implements the X509 certificate type
CertificateFactory f = CertificateFactory.getInstance("X.509");
// initalizes data found in the file
X509Certificate certificate = (X509Certificate)f.generateCertificate(fin);
// gets public key from this certificate
PublicKey pk = certificate.getPublicKey();
System.out.println(pk);
String hexEncryptedByteKey = encryptedKey(pk, secretkey);
System.out.println("Encrypted Key: " + hexEncryptedByteKey);
System.out.println("Encrypted Key length: " + hexEncryptedByteKey.length());
// allows to write data to a file
FileOutputStream newFos = null;
// write bytes to file
BufferedOutputStream newBos = null;
// create file to which data needs to be written
String fileNameKey = "C:/Users/****/symmetric.txt";
try{
// allows written data to go into the written path
newFos = new FileOutputStream(fileNameKey);
// converts written data into bytes
newBos = new BufferedOutputStream(newFos);
// writes the encrypted text into file
newBos.write(hexEncryptedByteKey.length());
System.out.println("encryptedKey has been written successfully in "
+fileNameKey);
// allows to catch bug in code
} catch (IOException e) {
e.printStackTrace();
} finally {
try{
// check for null exception
if (newBos != null){
newBos.close();
}
// check for null exception
if (newFos != null){
newFos.close();
}
} catch (IOException e){
e.printStackTrace();
}
}
// load keystore to get private key
KeyStore ks = KeyStore.getInstance("JKS");
String password = "*****";
char[] passwordChar = password.toCharArray();
System.out.println("password: " + passwordChar);
// locate file
try (FileInputStream fis = new FileInputStream("C:/Users/*****/keystore.jks")) {
ks.load(fis, passwordChar);
}
// protect password for keystore
KeyStore.ProtectionParameter protParam = new KeyStore.PasswordProtection(passwordChar);
// get private key from keystore
KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry)
ks.getEntry("*****", protParam);
PrivateKey myPrivateKey = pkEntry.getPrivateKey();
System.out.println("private key: " + myPrivateKey);
//method declaration
String decryptedKey = decryptedKey(myPrivateKey, hexEncryptedByteKey);
System.out.println("decrypted Key: " + decryptedKey);
String hexDecryptedByteText = decryptedTextHex(decryptedKey, hexEncryptedByteText);
System.out.println("key: " + hexDecryptedByteText);
}
public static String encrypt(String plainText, SecretKey secretkey) throws Exception {
//Encodes the string into a sequence of bytes
byte[] plainTextByte = plainText.getBytes();
//intialize cipher to encryption mode
cipher.init(Cipher.ENCRYPT_MODE, secretkey);
//data is encrypted
byte[] encryptedByte = cipher.doFinal(plainTextByte);
//Base64.Encoder encoder = Base64.getEncoder();
//encodes bytes into a string using Base64
byte[] encryptedByteText = Base64.getEncoder().encode(plainTextByte);
String hexEncryptedByteText = DatatypeConverter.printHexBinary(plainTextByte);
// return the string encrypted text to the main method
return hexEncryptedByteText;
}
public static String encryptedKey(PublicKey pk, SecretKey secretkey) throws Exception {
// data written to byte array
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// writes data types to the output stream
ObjectOutputStream writter = new ObjectOutputStream(baos);
//specific object of secretkey is written to the output stream
writter.writeObject(secretkey);
//creates a byte array
byte[] plainTextByteKey = baos.toByteArray();
//creates a cipher using the RSA algorithm
Cipher cipher = Cipher.getInstance("RSA");
// initalizes cipher for encryption using the public key
cipher.init(Cipher.ENCRYPT_MODE, pk);
//encrypts data
//byte[] encryptedByteKey = Base64.getEncoder().encode(plainTextByteKey);
String hexEncryptedByteKey = DatatypeConverter.printHexBinary(plainTextByteKey);
//Base64.Encoder encoderKey = Base64.getEncoder();
// encodes the byte array into a string.
//String encryptedTextKey = new String(encryptedByteKey);
return hexEncryptedByteKey;
}
private static String decryptedKey(PrivateKey myPrivateKey, String hexEncryptedByteKey) throws Exception {
//ByteArrayOutputStream baosDecrypt = new ByteArrayOutputStream();
//ObjectOutputStream writterDecrypt = new ObjectOutputStream(baosDecrypt);
//writterDecrypt.writeObject(hexEncryptedByteKey);
//byte[] byteKeyDecrypt = baosDecrypt.toByteArray();
Cipher cipher;
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, myPrivateKey);
//cipher.doFinal();
//byte [] decryptedKey = Base64.getDecoder().decode(byteKeyDecrypt);
//String decryptedTextKey = new String(byteKeyDecrypt);
byte[] decodedHex = DatatypeConverter.parseHexBinary(hexEncryptedByteKey);
System.out.println("decoded hex key: " + decodedHex);
String decryptedKey = new String(decodedHex, "UTF-8");
return decryptedKey;
}
private static String decryptedTextHex(String decryptedKey, String hexEncryptedByteText) throws Exception {
byte[] decryptedTextByte = decryptedKey.getBytes();
byte[] textString = hexEncryptedByteText.getBytes();
SecretKey key = new SecretKeySpec(decryptedTextByte, 0, decryptedTextByte.length, "AES");
Cipher cipher;
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//IvParameterSpec iv = new IvParameterSpec(cipher.getIV());
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decodedTextHex = cipher.doFinal(textString);
byte[] decoded = Base64.getDecoder().decode(decodedTextHex);
String hexDecryptedByteText = DatatypeConverter.printHexBinary(decoded);
return hexDecryptedByteText;
}
}
This is the error I am getting:
Exception in thread "main" java.security.InvalidKeyException: Parameters missing
at com.sun.crypto.provider.CipherCore.init(CipherCore.java:469)
at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:313)
at javax.crypto.Cipher.implInit(Cipher.java:802)
at javax.crypto.Cipher.chooseProvider(Cipher.java:864)
at javax.crypto.Cipher.init(Cipher.java:1249)
at javax.crypto.Cipher.init(Cipher.java:1186)
at ReadFileExample.generatekey.decryptedTextHex(generatekey.java:289)
at ReadFileExample.generatekey.main(generatekey.java:202)
I am not sure what is wrong. If my decryption of key is wrong or just the decryption of the String. I get no errors when it comes to the decryption of the key however.
For some more information: I generated a secret key, encrypted a String with the secret key and then encrypted the secret key with a generated public key. Then I decrypted the secret key with the private key and lastly I need to decrypt the String with the decrypted secret key.
Help is greatly appreciated. I have been working on this for so long and I just don't know what to do anymore. ]
EDIT: That other question has nothing to do with my question. I don't even have that same error message and I have already downloaded the JCE as that solution has stated.
You have to be carefull with the key size, AES is a 128-bit block cipher supporting keys of 128, 192, and 256 bits so if your key size is any different you will get exceptions, also if you keysize is greater than 128 then the code wont work unless you have Unlimited policy files. Basically there is quite a bit detail to this. If you want to see some working code check out this link: https://github.com/Jsondb/jsondb-core/blob/master/src/main/java/io/jsondb/crypto/DefaultAESCBCCipher.java

How to do Encryption and Decryption with RSA in android

I am trying to encrypt and decrypt the image file with my Helper class
RSAHelper.java
package com.lib;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;
/**
* Created by shobhan.
*/
public class RSAHelper {
private final static String RSA = "RSA";
private PublicKey publicKey;
private PrivateKey privateKey;
public RSAHelper() throws Exception {
/**
* generate RSA keys
*/
generateKey();
}
private void generateKey() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
publicKey = keyPair.getPublic();
privateKey = keyPair.getPrivate();
}
/**
* Used to do encryptFile the file
*
* #param srcPath File path to be encrypted
* #param destPath Encrypts the file in srcPath and creates a file in destPath
* #return true if encryption success, false otherwise
*/
public boolean encryptFile(String srcPath, String destPath) {
try {
FileInputStream fileInputStream = new FileInputStream(srcPath);
FileOutputStream fileOutputStream = new FileOutputStream(destPath);
// byte[] key = "12345678".getBytes();
// SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.PUBLIC_KEY, publicKey);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// cipher.init(Cipher.ENCRYPT_MODE, keySpec);
// CipherOutputStream cipherOutputStream = new
// CipherOutputStream(fileOutputStream, cipher);
byte[] buf = new byte[117];
byte[] encryptedData = null;
int read;
while ((read = fileInputStream.read(buf)) > 0) {
encryptedData = cipher.doFinal(buf);
fileOutputStream.write(encryptedData);
// cipherOutputStream.write(buf);
}
fileInputStream.close();
fileOutputStream.flush();
// cipherOutputStream.close();
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* Used to do decryptFile the file
*
* #param srcPath File path to be decrypted
* #param destPath Decrypts the file in srcPath and creates a file in destPath
* #return true if encryption success, false otherwise
*/
public boolean decryptFile(String srcPath, String destPath) {
try {
FileInputStream fileInputStream = new FileInputStream(srcPath);
FileOutputStream fileOutputStream = new FileOutputStream(destPath);
// byte[] key = "12345678".getBytes();
// SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.PRIVATE_KEY, privateKey);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// cipher.init(Cipher.DECRYPT_MODE, keySpec);
// CipherOutputStream cipherOutputStream = new
// CipherOutputStream(fileOutputStream, cipher);
byte[] buf = new byte[128];
byte[] decryptedData = null;
int read;
while ((read = fileInputStream.read(buf)) > 0) {
decryptedData = cipher.doFinal(buf);
fileOutputStream.write(decryptedData);
// cipherOutputStream.write(buf);
}
fileInputStream.close();
fileOutputStream.flush();
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
in Activity I used like
RSAHelper rsaHelper = null;
try {
rsaHelper = new RSAHelper();
} catch (Exception e) {
e.printStackTrace();
}
String src = getExternalFilesDir("sdcard").getAbsolutePath() + "/mother.png";
String dest = src.replace("mother","motherEnc");
String decrypted = dest.replace("motherEnc","motherDec");
rsaHelper.encryptFile(src, dest);
rsaHelper.decryptFile(dest, decrypted);
but the decrypted file is not original(means corrupted).
Same code working when I execute in windows desktop.
Am I doing anything wrong?
Thanks in advance.
Continuing the list begun in the comment by #zaph:
You are initializing the cipher twice, luckily with identical modes so it's just confusing but not the source of your bug.
You are relying on defaults for the Cipher transformation, a frequent source of non-portability, bugs and/or confusion. Always explicitly specify the Cipher transformation fully, like Cipher.getInstance("RSA/ECB/PKCS1PADDING") or Cipher.getInstance("RSA/ECB/NOPADDING").
You are incorrectly assuming that InputStream.read() will always return a full buffer of bytes if they are available. That might be how current implementations of FileInputStream actually work, but that is just luck. That behavior can change without warning at any time. InputStream implementors only need to obey the InputStream API contract, and so that is all you should rely on. You do record the number of bytes actually read, but then you don't do anything with it.
You are flushing your OutputStream but not closing it. You should close it instead.
Your bug is caused by number 5. Since there is no guarantee that your input file is an exact multiple of 117 bytes, the last block will likely be less than 117 bytes. However, you then encrypt the full 117 byte block, of which the trailing bytes are simply what was leftover from the previous read. So your decrypted file will be a multiple of 117, and will match up to the original length, with the trailing bytes being identical to the trailing bytes of the previous block.

AES decryption, first block garbage [duplicate]

What's wrong with the following example?
The problem is that the first part of the decrypted string is nonsense. However, the rest is fine, I get...
Result: `£eB6O�geS��i are you? Have a nice day.
#Test
public void testEncrypt() {
try {
String s = "Hello there. How are you? Have a nice day.";
// Generate key
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
SecretKey aesKey = kgen.generateKey();
// Encrypt cipher
Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);
// Encrypt
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);
cipherOutputStream.write(s.getBytes());
cipherOutputStream.flush();
cipherOutputStream.close();
byte[] encryptedBytes = outputStream.toByteArray();
// Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
// Decrypt
outputStream = new ByteArrayOutputStream();
ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes);
CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher);
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
outputStream.write(buf, 0, bytesRead);
}
System.out.println("Result: " + new String(outputStream.toByteArray()));
}
catch (Exception ex) {
ex.printStackTrace();
}
}
Lot of people including myself face lot of issues in making this work due to missing some information like, forgetting to convert to Base64, initialization vectors, character set, etc. So I thought of making a fully functional code.
Hope this will be useful to you all:
To compile you need additional Apache Commons Codec jar, which is available here:
http://commons.apache.org/proper/commons-codec/download_codec.cgi
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class Encryptor {
public static String encrypt(String key, String initVector, String value) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
System.out.println("encrypted string: "
+ Base64.encodeBase64String(encrypted));
return Base64.encodeBase64String(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String key, String initVector, String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String key = "Bar12345Bar12345"; // 128 bit key
String initVector = "RandomInitVector"; // 16 bytes IV
System.out.println(decrypt(key, initVector,
encrypt(key, initVector, "Hello World")));
}
}
In this answer I choose to approach the "Simple Java AES encrypt/decrypt example" main theme and not the specific debugging question because I think this will profit most readers.
This is a simple summary of my blog post about AES encryption in Java so I recommend reading through it before implementing anything. I will however still provide a simple example to use and give some pointers what to watch out for.
In this example I will choose to use authenticated encryption with Galois/Counter Mode or GCM mode. The reason is that in most case you want integrity and authenticity in combination with confidentiality (read more in the blog).
AES-GCM Encryption/Decryption Tutorial
Here are the steps required to encrypt/decrypt with AES-GCM with the Java Cryptography Architecture (JCA). Do not mix with other examples, as subtle differences may make your code utterly insecure.
1. Create Key
As it depends on your use-case, I will assume the simplest case: a random secret key.
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");
Important:
always use a strong pseudorandom number generator like SecureRandom
use 16 byte / 128 bit long key (or more - but more is seldom needed)
if you want a key derived from a user password, look into a password hash function (or KDF) with stretching property like PBKDF2 or bcrypt
if you want a key derived from other sources, use a proper key derivation function (KDF) like HKDF (Java implementation here). Do not use simple cryptographic hashes for that (like SHA-256).
2. Create the Initialization Vector
An initialization vector (IV) is used so that the same secret key will create different cipher texts.
byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);
Important:
never reuse the same IV with the same key (very important in GCM/CTR mode)
the IV must be unique (ie. use random IV or a counter)
the IV is not required to be secret
always use a strong pseudorandom number generator like SecureRandom
12 byte IV is the correct choice for AES-GCM mode
3. Encrypt with IV and Key
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);
Important:
use 16 byte / 128 bit authentication tag (used to verify integrity/authenticity)
the authentication tag will be automatically appended to the cipher text (in the JCA implementation)
since GCM behaves like a stream cipher, no padding is required
use CipherInputStream when encrypting large chunks of data
want additional (non-secret) data checked if it was changed? You may want to use associated data with cipher.updateAAD(associatedData); More here.
3. Serialize to Single Message
Just append IV and ciphertext. As stated above, the IV doesn't need to be secret.
ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
Optionally encode with Base64 if you need a string representation. Either use Android's or Java 8's built-in implementation (do not use Apache Commons Codec - it's an awful implementation). Encoding is used to "convert" byte arrays to string representation to make it ASCII safe e.g.:
String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);
4. Prepare Decryption: Deserialize
If you have encoded the message, first decode it to byte array:
byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)
Important:
be careful to validate input parameters, so to avoid denial of service attacks by allocating too much memory.
5. Decrypt
Initialize the cipher and set the same parameters as with the encryption:
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);
Important:
don't forget to add associated data with cipher.updateAAD(associatedData); if you added it during encryption.
A working code snippet can be found in this gist.
Note that most recent Android (SDK 21+) and Java (7+) implementations should have AES-GCM. Older versions may lack it. I still choose this mode, since it is easier to implement in addition to being more efficient compared to similar mode of Encrypt-then-Mac (with e.g. AES-CBC + HMAC). See this article on how to implement AES-CBC with HMAC.
Here a solution without Apache Commons Codec's Base64:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class AdvancedEncryptionStandard
{
private byte[] key;
private static final String ALGORITHM = "AES";
public AdvancedEncryptionStandard(byte[] key)
{
this.key = key;
}
/**
* Encrypts the given plain text
*
* #param plainText The plain text to encrypt
*/
public byte[] encrypt(byte[] plainText) throws Exception
{
SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return cipher.doFinal(plainText);
}
/**
* Decrypts the given byte array
*
* #param cipherText The data to decrypt
*/
public byte[] decrypt(byte[] cipherText) throws Exception
{
SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(cipherText);
}
}
Usage example:
byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8);
byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8);
AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard(
encryptionKey);
byte[] cipherText = advancedEncryptionStandard.encrypt(plainText);
byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText);
System.out.println(new String(plainText));
System.out.println(new String(cipherText));
System.out.println(new String(decryptedCipherText));
Prints:
Hello world!
դ;��LA+�ߙb*
Hello world!
Looks to me like you are not dealing properly with your Initialization Vector (IV).
It's been a long time since I last read about AES, IVs and block chaining, but your line
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
does not seem to be OK. In the case of AES, you can think of the initialization vector as the "initial state" of a cipher instance, and this state is a bit of information that you can not get from your key but from the actual computation of the encrypting cipher. (One could argue that if the IV could be extracted from the key, then it would be of no use, as the key is already given to the cipher instance during its init phase).
Therefore, you should get the IV as a byte[] from the cipher instance at the end of your encryption
cipherOutputStream.close();
byte[] iv = encryptCipher.getIV();
and you should initialize your Cipher in DECRYPT_MODE with this byte[] :
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Then, your decryption should be OK.
Hope this helps.
The IV that your using for decryption is incorrect. Replace this code
//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
With this code
//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
And that should solve your problem.
Below includes an example of a simple AES class in Java. I do not recommend using this class in production environments, as it may not account for all of the specific needs of your application.
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class AES
{
public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
{
return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes);
}
public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
{
return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes);
}
private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
{
final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
byte[] transformedBytes = null;
try
{
final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
cipher.init(mode, keySpec, ivSpec);
transformedBytes = cipher.doFinal(messageBytes);
}
catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e)
{
e.printStackTrace();
}
return transformedBytes;
}
public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException
{
//Retrieved from a protected local file.
//Do not hard-code and do not version control.
final String base64Key = "ABEiM0RVZneImaq7zN3u/w==";
//Retrieved from a protected database.
//Do not hard-code and do not version control.
final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE=";
//Extract the iv and the ciphertext from the shadow entry.
final String[] shadowData = shadowEntry.split(":");
final String base64Iv = shadowData[0];
final String base64Ciphertext = shadowData[1];
//Convert to raw bytes.
final byte[] keyBytes = Base64.getDecoder().decode(base64Key);
final byte[] ivBytes = Base64.getDecoder().decode(base64Iv);
final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext);
//Decrypt data and do something with it.
final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes);
//Use non-blocking SecureRandom implementation for the new IV.
final SecureRandom secureRandom = new SecureRandom();
//Generate a new IV.
secureRandom.nextBytes(ivBytes);
//At this point instead of printing to the screen,
//one should replace the old shadow entry with the new one.
System.out.println("Old Shadow Entry = " + shadowEntry);
System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8));
System.out.println("New Shadow Entry = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes)));
}
}
Note that AES has nothing to do with encoding, which is why I chose to handle it separately and without the need of any third party libraries.
Online Editor Runnable version:-
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//import org.apache.commons.codec.binary.Base64;
import java.util.Base64;
public class Encryptor {
public static String encrypt(String key, String initVector, String value) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
//System.out.println("encrypted string: "
// + Base64.encodeBase64String(encrypted));
//return Base64.encodeBase64String(encrypted);
String s = new String(Base64.getEncoder().encode(encrypted));
return s;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String key, String initVector, String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String key = "Bar12345Bar12345"; // 128 bit key
String initVector = "RandomInitVector"; // 16 bytes IV
System.out.println(encrypt(key, initVector, "Hello World"));
System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
}
}
This is an improvement over the accepted answer.
Changes:
(1) Using random IV and prepend it to the encrypted text
(2) Using SHA-256 to generate a key from a passphrase
(3) No dependency on Apache Commons
public static void main(String[] args) throws GeneralSecurityException {
String plaintext = "Hello world";
String passphrase = "My passphrase";
String encrypted = encrypt(passphrase, plaintext);
String decrypted = decrypt(passphrase, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);
}
private static SecretKeySpec getKeySpec(String passphrase) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return new SecretKeySpec(digest.digest(passphrase.getBytes(UTF_8)), "AES");
}
private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
return Cipher.getInstance("AES/CBC/PKCS5PADDING");
}
public static String encrypt(String passphrase, String value) throws GeneralSecurityException {
byte[] initVector = new byte[16];
SecureRandom.getInstanceStrong().nextBytes(initVector);
Cipher cipher = getCipher();
cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
byte[] encrypted = cipher.doFinal(value.getBytes());
return DatatypeConverter.printBase64Binary(initVector) +
DatatypeConverter.printBase64Binary(encrypted);
}
public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
Cipher cipher = getCipher();
cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
return new String(original);
}
It's often the good idea to rely on standard library provided solution:
private static void stackOverflow15554296()
throws
NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException,
BadPaddingException
{
// prepare key
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecretKey aesKey = keygen.generateKey();
String aesKeyForFutureUse = Base64.getEncoder().encodeToString(
aesKey.getEncoded()
);
// cipher engine
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// cipher input
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] clearTextBuff = "Text to encode".getBytes();
byte[] cipherTextBuff = aesCipher.doFinal(clearTextBuff);
// recreate key
byte[] aesKeyBuff = Base64.getDecoder().decode(aesKeyForFutureUse);
SecretKey aesDecryptKey = new SecretKeySpec(aesKeyBuff, "AES");
// decipher input
aesCipher.init(Cipher.DECRYPT_MODE, aesDecryptKey);
byte[] decipheredBuff = aesCipher.doFinal(cipherTextBuff);
System.out.println(new String(decipheredBuff));
}
This prints "Text to encode".
Solution is based on Java Cryptography Architecture Reference Guide and https://stackoverflow.com/a/20591539/146745 answer.
Another solution using java.util.Base64 with Spring Boot
Encryptor Class
package com.jmendoza.springboot.crypto.cipher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
#Component
public class Encryptor {
#Value("${security.encryptor.key}")
private byte[] key;
#Value("${security.encryptor.algorithm}")
private String algorithm;
public String encrypt(String plainText) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return new String(Base64.getEncoder().encode(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))));
}
public String decrypt(String cipherText) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
}
}
EncryptorController Class
package com.jmendoza.springboot.crypto.controller;
import com.jmendoza.springboot.crypto.cipher.Encryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/cipher")
public class EncryptorController {
#Autowired
Encryptor encryptor;
#GetMapping(value = "encrypt/{value}")
public String encrypt(#PathVariable("value") final String value) throws Exception {
return encryptor.encrypt(value);
}
#GetMapping(value = "decrypt/{value}")
public String decrypt(#PathVariable("value") final String value) throws Exception {
return encryptor.decrypt(value);
}
}
application.properties
server.port=8082
security.encryptor.algorithm=AES
security.encryptor.key=M8jFt46dfJMaiJA0
Example
http://localhost:8082/cipher/encrypt/jmendoza
2h41HH8Shzc4BRU3hVDOXA==
http://localhost:8082/cipher/decrypt/2h41HH8Shzc4BRU3hVDOXA==
jmendoza
Optimized version of the accepted answer.
no 3rd party libs
includes IV into the encrypted message (can be public)
password can be of any length
Code:
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Encryptor {
public static byte[] getRandomInitialVector() {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] initVector = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(initVector);
return initVector;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static byte[] passwordTo16BitKey(String password) {
try {
byte[] srcBytes = password.getBytes("UTF-8");
byte[] dstBytes = new byte[16];
if (srcBytes.length == 16) {
return srcBytes;
}
if (srcBytes.length < 16) {
for (int i = 0; i < dstBytes.length; i++) {
dstBytes[i] = (byte) ((srcBytes[i % srcBytes.length]) * (srcBytes[(i + 1) % srcBytes.length]));
}
} else if (srcBytes.length > 16) {
for (int i = 0; i < srcBytes.length; i++) {
dstBytes[i % dstBytes.length] += srcBytes[i];
}
}
return dstBytes;
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
return null;
}
public static String encrypt(String key, String value) {
return encrypt(passwordTo16BitKey(key), value);
}
public static String encrypt(byte[] key, String value) {
try {
byte[] initVector = Encryptor.getRandomInitialVector();
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return Base64.getEncoder().encodeToString(encrypted) + " " + Base64.getEncoder().encodeToString(initVector);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String key, String encrypted) {
return decrypt(passwordTo16BitKey(key), encrypted);
}
public static String decrypt(byte[] key, String encrypted) {
try {
String[] encryptedParts = encrypted.split(" ");
byte[] initVector = Base64.getDecoder().decode(encryptedParts[1]);
if (initVector.length != 16) {
return null;
}
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedParts[0]));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}
Usage:
String key = "Password of any length.";
String encrypted = Encryptor.encrypt(key, "Hello World");
String decrypted = Encryptor.decrypt(key, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);
Example output:
QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
Hello World

Initial bytes incorrect after Java AES/CBC decryption

What's wrong with the following example?
The problem is that the first part of the decrypted string is nonsense. However, the rest is fine, I get...
Result: `£eB6O�geS��i are you? Have a nice day.
#Test
public void testEncrypt() {
try {
String s = "Hello there. How are you? Have a nice day.";
// Generate key
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
SecretKey aesKey = kgen.generateKey();
// Encrypt cipher
Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);
// Encrypt
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);
cipherOutputStream.write(s.getBytes());
cipherOutputStream.flush();
cipherOutputStream.close();
byte[] encryptedBytes = outputStream.toByteArray();
// Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
// Decrypt
outputStream = new ByteArrayOutputStream();
ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes);
CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher);
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
outputStream.write(buf, 0, bytesRead);
}
System.out.println("Result: " + new String(outputStream.toByteArray()));
}
catch (Exception ex) {
ex.printStackTrace();
}
}
Lot of people including myself face lot of issues in making this work due to missing some information like, forgetting to convert to Base64, initialization vectors, character set, etc. So I thought of making a fully functional code.
Hope this will be useful to you all:
To compile you need additional Apache Commons Codec jar, which is available here:
http://commons.apache.org/proper/commons-codec/download_codec.cgi
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class Encryptor {
public static String encrypt(String key, String initVector, String value) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
System.out.println("encrypted string: "
+ Base64.encodeBase64String(encrypted));
return Base64.encodeBase64String(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String key, String initVector, String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String key = "Bar12345Bar12345"; // 128 bit key
String initVector = "RandomInitVector"; // 16 bytes IV
System.out.println(decrypt(key, initVector,
encrypt(key, initVector, "Hello World")));
}
}
In this answer I choose to approach the "Simple Java AES encrypt/decrypt example" main theme and not the specific debugging question because I think this will profit most readers.
This is a simple summary of my blog post about AES encryption in Java so I recommend reading through it before implementing anything. I will however still provide a simple example to use and give some pointers what to watch out for.
In this example I will choose to use authenticated encryption with Galois/Counter Mode or GCM mode. The reason is that in most case you want integrity and authenticity in combination with confidentiality (read more in the blog).
AES-GCM Encryption/Decryption Tutorial
Here are the steps required to encrypt/decrypt with AES-GCM with the Java Cryptography Architecture (JCA). Do not mix with other examples, as subtle differences may make your code utterly insecure.
1. Create Key
As it depends on your use-case, I will assume the simplest case: a random secret key.
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");
Important:
always use a strong pseudorandom number generator like SecureRandom
use 16 byte / 128 bit long key (or more - but more is seldom needed)
if you want a key derived from a user password, look into a password hash function (or KDF) with stretching property like PBKDF2 or bcrypt
if you want a key derived from other sources, use a proper key derivation function (KDF) like HKDF (Java implementation here). Do not use simple cryptographic hashes for that (like SHA-256).
2. Create the Initialization Vector
An initialization vector (IV) is used so that the same secret key will create different cipher texts.
byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);
Important:
never reuse the same IV with the same key (very important in GCM/CTR mode)
the IV must be unique (ie. use random IV or a counter)
the IV is not required to be secret
always use a strong pseudorandom number generator like SecureRandom
12 byte IV is the correct choice for AES-GCM mode
3. Encrypt with IV and Key
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);
Important:
use 16 byte / 128 bit authentication tag (used to verify integrity/authenticity)
the authentication tag will be automatically appended to the cipher text (in the JCA implementation)
since GCM behaves like a stream cipher, no padding is required
use CipherInputStream when encrypting large chunks of data
want additional (non-secret) data checked if it was changed? You may want to use associated data with cipher.updateAAD(associatedData); More here.
3. Serialize to Single Message
Just append IV and ciphertext. As stated above, the IV doesn't need to be secret.
ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
Optionally encode with Base64 if you need a string representation. Either use Android's or Java 8's built-in implementation (do not use Apache Commons Codec - it's an awful implementation). Encoding is used to "convert" byte arrays to string representation to make it ASCII safe e.g.:
String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);
4. Prepare Decryption: Deserialize
If you have encoded the message, first decode it to byte array:
byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)
Important:
be careful to validate input parameters, so to avoid denial of service attacks by allocating too much memory.
5. Decrypt
Initialize the cipher and set the same parameters as with the encryption:
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);
Important:
don't forget to add associated data with cipher.updateAAD(associatedData); if you added it during encryption.
A working code snippet can be found in this gist.
Note that most recent Android (SDK 21+) and Java (7+) implementations should have AES-GCM. Older versions may lack it. I still choose this mode, since it is easier to implement in addition to being more efficient compared to similar mode of Encrypt-then-Mac (with e.g. AES-CBC + HMAC). See this article on how to implement AES-CBC with HMAC.
Here a solution without Apache Commons Codec's Base64:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class AdvancedEncryptionStandard
{
private byte[] key;
private static final String ALGORITHM = "AES";
public AdvancedEncryptionStandard(byte[] key)
{
this.key = key;
}
/**
* Encrypts the given plain text
*
* #param plainText The plain text to encrypt
*/
public byte[] encrypt(byte[] plainText) throws Exception
{
SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return cipher.doFinal(plainText);
}
/**
* Decrypts the given byte array
*
* #param cipherText The data to decrypt
*/
public byte[] decrypt(byte[] cipherText) throws Exception
{
SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(cipherText);
}
}
Usage example:
byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8);
byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8);
AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard(
encryptionKey);
byte[] cipherText = advancedEncryptionStandard.encrypt(plainText);
byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText);
System.out.println(new String(plainText));
System.out.println(new String(cipherText));
System.out.println(new String(decryptedCipherText));
Prints:
Hello world!
դ;��LA+�ߙb*
Hello world!
Looks to me like you are not dealing properly with your Initialization Vector (IV).
It's been a long time since I last read about AES, IVs and block chaining, but your line
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
does not seem to be OK. In the case of AES, you can think of the initialization vector as the "initial state" of a cipher instance, and this state is a bit of information that you can not get from your key but from the actual computation of the encrypting cipher. (One could argue that if the IV could be extracted from the key, then it would be of no use, as the key is already given to the cipher instance during its init phase).
Therefore, you should get the IV as a byte[] from the cipher instance at the end of your encryption
cipherOutputStream.close();
byte[] iv = encryptCipher.getIV();
and you should initialize your Cipher in DECRYPT_MODE with this byte[] :
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Then, your decryption should be OK.
Hope this helps.
The IV that your using for decryption is incorrect. Replace this code
//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
With this code
//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
And that should solve your problem.
Below includes an example of a simple AES class in Java. I do not recommend using this class in production environments, as it may not account for all of the specific needs of your application.
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class AES
{
public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
{
return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes);
}
public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
{
return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes);
}
private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
{
final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
byte[] transformedBytes = null;
try
{
final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
cipher.init(mode, keySpec, ivSpec);
transformedBytes = cipher.doFinal(messageBytes);
}
catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e)
{
e.printStackTrace();
}
return transformedBytes;
}
public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException
{
//Retrieved from a protected local file.
//Do not hard-code and do not version control.
final String base64Key = "ABEiM0RVZneImaq7zN3u/w==";
//Retrieved from a protected database.
//Do not hard-code and do not version control.
final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE=";
//Extract the iv and the ciphertext from the shadow entry.
final String[] shadowData = shadowEntry.split(":");
final String base64Iv = shadowData[0];
final String base64Ciphertext = shadowData[1];
//Convert to raw bytes.
final byte[] keyBytes = Base64.getDecoder().decode(base64Key);
final byte[] ivBytes = Base64.getDecoder().decode(base64Iv);
final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext);
//Decrypt data and do something with it.
final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes);
//Use non-blocking SecureRandom implementation for the new IV.
final SecureRandom secureRandom = new SecureRandom();
//Generate a new IV.
secureRandom.nextBytes(ivBytes);
//At this point instead of printing to the screen,
//one should replace the old shadow entry with the new one.
System.out.println("Old Shadow Entry = " + shadowEntry);
System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8));
System.out.println("New Shadow Entry = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes)));
}
}
Note that AES has nothing to do with encoding, which is why I chose to handle it separately and without the need of any third party libraries.
Online Editor Runnable version:-
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//import org.apache.commons.codec.binary.Base64;
import java.util.Base64;
public class Encryptor {
public static String encrypt(String key, String initVector, String value) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
//System.out.println("encrypted string: "
// + Base64.encodeBase64String(encrypted));
//return Base64.encodeBase64String(encrypted);
String s = new String(Base64.getEncoder().encode(encrypted));
return s;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String key, String initVector, String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String key = "Bar12345Bar12345"; // 128 bit key
String initVector = "RandomInitVector"; // 16 bytes IV
System.out.println(encrypt(key, initVector, "Hello World"));
System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
}
}
This is an improvement over the accepted answer.
Changes:
(1) Using random IV and prepend it to the encrypted text
(2) Using SHA-256 to generate a key from a passphrase
(3) No dependency on Apache Commons
public static void main(String[] args) throws GeneralSecurityException {
String plaintext = "Hello world";
String passphrase = "My passphrase";
String encrypted = encrypt(passphrase, plaintext);
String decrypted = decrypt(passphrase, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);
}
private static SecretKeySpec getKeySpec(String passphrase) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return new SecretKeySpec(digest.digest(passphrase.getBytes(UTF_8)), "AES");
}
private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
return Cipher.getInstance("AES/CBC/PKCS5PADDING");
}
public static String encrypt(String passphrase, String value) throws GeneralSecurityException {
byte[] initVector = new byte[16];
SecureRandom.getInstanceStrong().nextBytes(initVector);
Cipher cipher = getCipher();
cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
byte[] encrypted = cipher.doFinal(value.getBytes());
return DatatypeConverter.printBase64Binary(initVector) +
DatatypeConverter.printBase64Binary(encrypted);
}
public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
Cipher cipher = getCipher();
cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
return new String(original);
}
It's often the good idea to rely on standard library provided solution:
private static void stackOverflow15554296()
throws
NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException,
BadPaddingException
{
// prepare key
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecretKey aesKey = keygen.generateKey();
String aesKeyForFutureUse = Base64.getEncoder().encodeToString(
aesKey.getEncoded()
);
// cipher engine
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// cipher input
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] clearTextBuff = "Text to encode".getBytes();
byte[] cipherTextBuff = aesCipher.doFinal(clearTextBuff);
// recreate key
byte[] aesKeyBuff = Base64.getDecoder().decode(aesKeyForFutureUse);
SecretKey aesDecryptKey = new SecretKeySpec(aesKeyBuff, "AES");
// decipher input
aesCipher.init(Cipher.DECRYPT_MODE, aesDecryptKey);
byte[] decipheredBuff = aesCipher.doFinal(cipherTextBuff);
System.out.println(new String(decipheredBuff));
}
This prints "Text to encode".
Solution is based on Java Cryptography Architecture Reference Guide and https://stackoverflow.com/a/20591539/146745 answer.
Another solution using java.util.Base64 with Spring Boot
Encryptor Class
package com.jmendoza.springboot.crypto.cipher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
#Component
public class Encryptor {
#Value("${security.encryptor.key}")
private byte[] key;
#Value("${security.encryptor.algorithm}")
private String algorithm;
public String encrypt(String plainText) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return new String(Base64.getEncoder().encode(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))));
}
public String decrypt(String cipherText) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
}
}
EncryptorController Class
package com.jmendoza.springboot.crypto.controller;
import com.jmendoza.springboot.crypto.cipher.Encryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/cipher")
public class EncryptorController {
#Autowired
Encryptor encryptor;
#GetMapping(value = "encrypt/{value}")
public String encrypt(#PathVariable("value") final String value) throws Exception {
return encryptor.encrypt(value);
}
#GetMapping(value = "decrypt/{value}")
public String decrypt(#PathVariable("value") final String value) throws Exception {
return encryptor.decrypt(value);
}
}
application.properties
server.port=8082
security.encryptor.algorithm=AES
security.encryptor.key=M8jFt46dfJMaiJA0
Example
http://localhost:8082/cipher/encrypt/jmendoza
2h41HH8Shzc4BRU3hVDOXA==
http://localhost:8082/cipher/decrypt/2h41HH8Shzc4BRU3hVDOXA==
jmendoza
Optimized version of the accepted answer.
no 3rd party libs
includes IV into the encrypted message (can be public)
password can be of any length
Code:
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Encryptor {
public static byte[] getRandomInitialVector() {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] initVector = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(initVector);
return initVector;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static byte[] passwordTo16BitKey(String password) {
try {
byte[] srcBytes = password.getBytes("UTF-8");
byte[] dstBytes = new byte[16];
if (srcBytes.length == 16) {
return srcBytes;
}
if (srcBytes.length < 16) {
for (int i = 0; i < dstBytes.length; i++) {
dstBytes[i] = (byte) ((srcBytes[i % srcBytes.length]) * (srcBytes[(i + 1) % srcBytes.length]));
}
} else if (srcBytes.length > 16) {
for (int i = 0; i < srcBytes.length; i++) {
dstBytes[i % dstBytes.length] += srcBytes[i];
}
}
return dstBytes;
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
return null;
}
public static String encrypt(String key, String value) {
return encrypt(passwordTo16BitKey(key), value);
}
public static String encrypt(byte[] key, String value) {
try {
byte[] initVector = Encryptor.getRandomInitialVector();
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return Base64.getEncoder().encodeToString(encrypted) + " " + Base64.getEncoder().encodeToString(initVector);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String key, String encrypted) {
return decrypt(passwordTo16BitKey(key), encrypted);
}
public static String decrypt(byte[] key, String encrypted) {
try {
String[] encryptedParts = encrypted.split(" ");
byte[] initVector = Base64.getDecoder().decode(encryptedParts[1]);
if (initVector.length != 16) {
return null;
}
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedParts[0]));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}
Usage:
String key = "Password of any length.";
String encrypted = Encryptor.encrypt(key, "Hello World");
String decrypted = Encryptor.decrypt(key, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);
Example output:
QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
Hello World

Categories

Resources