I'm trying to reverse-engineer this Java code in C#, but I keep getting wrong result:
public static String GetEncodedToken(String tokenBase64) {
String str2 = "";
try {
byte[] ivBytes = Base64Coder.decode("OSMqNE11fGUoLDg5Mmk1WQ==");
byte[] keyBytes = Base64Coder.decode("Nm4wMy5nOiM3JSpWfnwzOXFpNzRcfjB5MVNEKl8mWkw=");
byte[] tokenBytes = Base64Coder.decode(tokenBase64);
byte[] tokenEncBytes = Encrypt(ivBytes, keyBytes, tokenBytes);
str2 = URLEncoder.encode(Base64Coder.encode(tokenEncBytes), "UTF-8");
} catch (Exception e) {
}
return str2;
}
private static byte[] Encrypt(byte[] ivBytes, byte[] keyBytes, byte[] tokenBytes) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalStateException, IllegalBlockSizeException, BadPaddingException {
AlgorithmParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
Key secretKeySpec = new SecretKeySpec(keyBytes, "AES");
Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");
instance.init(1, secretKeySpec, ivParameterSpec); // 1 = CipherMode.CBC in C#???
return instance.doFinal(tokenBytes);
}
In my understanding the code above in C# translates to the following snippet:
public static string GetEncodedToken(string tokenBase64)
{
var ivBytes = Convert.FromBase64String("OSMqNE11fGUoLDg5Mmk1WQ==");
var keyBytes = Convert.FromBase64String("Nm4wMy5nOiM3JSpWfnwzOXFpNzRcfjB5MVNEKl8mWkw=");
var tokenBytes = Convert.FromBase64String(tokenBase64);
var tokenEncBytes = Crypt.Encrypt(tokenBytes, keyBytes, ivBytes);
var tokenEnc = HttpUtility.UrlEncode(Convert.ToBase64String(tokenEncBytes));
return tokenEnc;
}
private static byte[] Encrypt(byte[] tokenBytes, byte[] keyBytes, byte[] ivBytes)
{
using (var rijndaelManaged = new RijndaelManaged
{
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7,
KeySize = 128,
BlockSize = 128,
Key = keyBytes,
IV = ivBytes
})
{
return rijndaelManaged.CreateEncryptor()
.TransformFinalBlock(tokenBytes, 0, tokenBytes.Length);
}
}
However the API I'm trying to log in via C# client tells me the token is wrong :(
Related
I am getting following error while decrypting.
Once it was working fine but suddenly I am getting this error.
How can I solve this problem?
I have read many articles but couldn't get help.
java.security.InvalidKeyException: Parameters missing
at com.sun.crypto.provider.CipherCore.init(CipherCore.java:388)
at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:186)
at javax.crypto.Cipher.implInit(Cipher.java:786)
at javax.crypto.Cipher.chooseProvider(Cipher.java:848)
at javax.crypto.Cipher.init(Cipher.java:1212)
at javax.crypto.Cipher.init(Cipher.java:1152)
at
com.test.security.TestEncryptDecrypt.decrypt(TestEncryptDecrypt.java:92)
at com.test.security.TestEncryptDecrypt.main(TestEncryptDecrypt.java:59)
The code is give below:
public static void main(String []args){
try {
String encString = encrypt("PID=0000000003|ITC=NA|PRN=MNKB0701511135|AMT=1.00|CRN=INR|RU=https://www.testsite.com/testsk/servlet/TestResponseHandler?");
System.out.println("Enc : " + encString);
System.out.println("Dec : "+ decrypt(encString));
} catch (Exception e) {
e.printStackTrace();
}
}
Encrypt method
public static String encrypt(String data) throws Exception {
String keyFile = "E:\\testpath\\Keys\\0000000003.key";
byte[] keyb = Files.readAllBytes(Paths.get(keyFile));
SecretKeySpec skey = new SecretKeySpec(keyb, "AES");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, skey);
byte[] encVal = c.doFinal(data.getBytes());
return new BASE64Encoder().encode(encVal);
}
Decrypt method
public static String decrypt(String encryptedData)
throws InvalidKeyException, IllegalBlockSizeException,
BadPaddingException, NoSuchAlgorithmException,
NoSuchPaddingException, IOException {
String keyFile = "E:\\testpath\\Keys\\0000000003.key";
byte[] keyb = Files.readAllBytes(Paths.get(keyFile));
SecretKeySpec skey = new SecretKeySpec(keyb, "AES");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.DECRYPT_MODE, skey);
byte[] decordedValue = Base64.decodeBase64(encryptedData.getBytes());
byte[] decValue = c.doFinal(decordedValue);
return new String(decValue);
}
As the error message says, you are missing a parameter in the init methods. If you're using CBC you should also specify the AlgorithmParameterSpec.
Could you perhaps try the following:
byte[] paramSpecBytes = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
IvParameterSpec paramSpec = new IvParameterSpec(paramSpecBytes);
And in your encrypt and decrypt methods:
c.init(Cipher.ENCRYPT_MODE, skey, paramSpec);
c.init(Cipher.DECRYPT_MODE, skey, paramSpec);
You could fill the bytes with any value you like though, doesn't need to be zero's. Best would be some random value.
I am trying to write an encryption/decryption utility class, but not matter what I do I cannot seem to get decryption working. I keep getting a javax.crypto.BadPaddingException: Given final block not properly padded exception during decryption.
I've looked at a number of examples and other stack overflow questions but can't seem to find my mistake
public class EncryptionUtil {
private static final Log LOGGER = LogFactory.getLog(EncryptionUtil.class);
private static final String CIPHER_MODE = "AES/CBC/PKCS5PADDING";
private static final String CRYPTO_PROPERTIES_PATH = "/crypto.properties";
private static final SecretKeySpec sKey = keySpecFromProperties();
private EncryptionUtil() {}
public static byte[] encrypt(byte[] aBytes) {
try {
SecureRandom lSecureRandom = new SecureRandom();
byte[] ivBytes = new byte[16];
lSecureRandom.nextBytes(ivBytes);
IvParameterSpec lSpec = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance(CIPHER_MODE);
cipher.init(Cipher.ENCRYPT_MODE, sKey, lSpec);
byte[] encryptedBytes = cipher.doFinal(aBytes);
byte[] outBytes = new byte[encryptedBytes.length + 16];
System.arraycopy(ivBytes, 0, outBytes, 0, 16);
System.arraycopy(encryptedBytes, 0, outBytes, 16, encryptedBytes.length);
return outBytes;
} catch (Exception aEx) {
LOGGER.error("Failed to encrypt bytes");
throw new RuntimeException(aEx);
}
}
public static byte[] decrypt(byte[] aBytes) {
try {
byte[] lIvBytes = Arrays.copyOfRange(aBytes, aBytes.length - 16, aBytes.length);
byte[] lEncryptedBytes = Arrays.copyOfRange(aBytes, 0, aBytes.length - 16);
IvParameterSpec lIvSpec = new IvParameterSpec(lIvBytes);
Cipher cipher = Cipher.getInstance(CIPHER_MODE);
cipher.init(Cipher.DECRYPT_MODE, sKey, lIvSpec);
return cipher.doFinal(lEncryptedBytes);
}catch (Exception aEx){
LOGGER.error("Failed to decrypt bytes. Returning input bytes", aEx);
return aBytes;
}
}
private static SecretKeySpec keySpecFromProperties(){
try(InputStream lPropStream = EncryptionUtil.class.getResourceAsStream(CRYPTO_PROPERTIES_PATH)){
Properties cryptoProps = new Properties();
cryptoProps.load(lPropStream);
String lSecret = cryptoProps.getProperty("secret");
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(lSecret.getBytes("UTF-8"));
byte[] keyBytes = new byte[16];
System.arraycopy(digest.digest(),0, keyBytes, 0, keyBytes.length);
return new SecretKeySpec(keyBytes, "AES");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
You prepend your IV to the ciphertext on encryption, but on decryption you copy the last 16 bytes as your IV.
Whatever you do on encryption you must undo on decryption.
I have a hardcoded key with which I want to encrypt a string before storing it in SharedPreferences. This is the code I have so far:
public class TokenEncryptor {
private final static String TOKEN_KEY = "91a29fa7w46d8x41";
public static String encrypt(String plain) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AlgorithmParameterSpec ivSpec = new IvParameterSpec(new byte[16]);
SecretKeySpec newKey = new SecretKeySpec(TOKEN_KEY.getBytes(), "AES");
cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
return new String(cipher.doFinal(plain.getBytes()));
} catch (Exception e) {
Ln.e(e);
return null;
}
}
public static String decrypt(String encoded) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AlgorithmParameterSpec ivSpec = new IvParameterSpec(new byte[16]);
SecretKeySpec newKey = new SecretKeySpec(TOKEN_KEY.getBytes(), "AES");
cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec);
return new String(cipher.doFinal(encoded.getBytes()));
} catch (Exception e) {
Ln.e(e);
return null;
}
}
}
It seems to be catching an exception at the end of decrypt method:
javax.crypto.IllegalBlockSizeException: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
Can someone point me in the right direction? I have a feeling I'm doing something wrong instantiating IvParameterSpec.
When you encrypt a string with AES, you get an array of bytes back. Trying to convert those bytes directly to a string (new String(cipher.doFinal(plaintextBytes))) will cause all sorts of problems. If you require the output from your encryption method to be a string, then use Base64 rather than attempting a direct conversion. In your decryption method, convert the Base64 string back into a byte array before decrypting the byte array.
Also, do not use getBytes() since the output depends on the system defaults. Use getBytes("utf-8") or whatever. That eliminates ambiguity.
Just in case anyone is interested (or feels too lazy to do their research), here is the the result code for AES-256 encryption and decryption that I put together, with help from the accepted answer and comments:
public class TokenEncryptor {
private final static String TOKEN_KEY = "fqJfdzGDvfwbedsKSUGty3VZ9taXxMVw";
public static String encrypt(String plain) {
try {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(TOKEN_KEY.getBytes("utf-8"), "AES"), new IvParameterSpec(iv));
byte[] cipherText = cipher.doFinal(plain.getBytes("utf-8"));
byte[] ivAndCipherText = getCombinedArray(iv, cipherText);
return Base64.encodeToString(ivAndCipherText, Base64.NO_WRAP);
} catch (Exception e) {
Ln.e(e);
return null;
}
}
public static String decrypt(String encoded) {
try {
byte[] ivAndCipherText = Base64.decode(encoded, Base64.NO_WRAP);
byte[] iv = Arrays.copyOfRange(ivAndCipherText, 0, 16);
byte[] cipherText = Arrays.copyOfRange(ivAndCipherText, 16, ivAndCipherText.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(TOKEN_KEY.getBytes("utf-8"), "AES"), new IvParameterSpec(iv));
return new String(cipher.doFinal(cipherText), "utf-8");
} catch (Exception e) {
Ln.e(e);
return null;
}
}
private static byte[] getCombinedArray(byte[] one, byte[] two) {
byte[] combined = new byte[one.length + two.length];
for (int i = 0; i < combined.length; ++i) {
combined[i] = i < one.length ? one[i] : two[i - one.length];
}
return combined;
}
}
It's an extension of Artjom B answer and working for me.
public String encryptMsg(String message, SecretKey secret)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidParameterSpecException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
Cipher cipher = null;
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] cipherText = cipher.doFinal(message.getBytes("UTF-8"));
return Base64.encodeToString(cipherText, Base64.NO_WRAP);
}
public String decryptMsg(String cipherText, SecretKey secret)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidParameterSpecException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
Cipher cipher = null;
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret);
byte[] decode = Base64.decode(cipherText, Base64.NO_WRAP);
String decryptString = new String(cipher.doFinal(decode), "UTF-8");
return decryptString;
}
Kotlin version of #Oleksiy 's answer.
<script src="https://gist.github.com/kasim1011/a5a9644a60c33a4df3c29f4b34cf93a4.js"></script>
import android.util.Base64
import java.util.*
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
private const val algorithm = "AES"
private const val tokenKey = "fqJfdzGDvfwbedsKSUGty3VZ9taXxMVw"
private const val padding = "AES/CBC/PKCS5Padding"
private const val ivSize = 16
fun String.encryptAES(): String {
val tokenBytes = tokenKey.toByteArray(Charsets.UTF_8)
val secretKey = SecretKeySpec(tokenBytes, algorithm)
val ivByteArray = ByteArray(ivSize)
val iv = IvParameterSpec(ivByteArray)
val cipher = Cipher.getInstance(padding)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv)
val cipherText = cipher.doFinal(toByteArray(Charsets.UTF_8))
val ivAndCipherText = getCombinedArray(ivByteArray, cipherText)
return Base64.encodeToString(ivAndCipherText, Base64.NO_WRAP)
}
fun String.decryptAES(): String {
val tokenBytes = tokenKey.toByteArray(Charsets.UTF_8)
val secretKey = SecretKeySpec(tokenBytes, algorithm)
val ivAndCipherText = Base64.decode(this, Base64.NO_WRAP)
val cipherText = Arrays.copyOfRange(ivAndCipherText, ivSize, ivAndCipherText.size)
val ivByteArray = Arrays.copyOfRange(ivAndCipherText, 0, ivSize)
val iv = IvParameterSpec(ivByteArray)
val cipher = Cipher.getInstance(padding)
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv)
return cipher.doFinal(cipherText).toString(Charsets.UTF_8)
}
private fun getCombinedArray(one: ByteArray, two: ByteArray): ByteArray {
val combined = ByteArray(one.size + two.size)
for (i in combined.indices) {
combined[i] = if (i < one.size) one[i] else two[i - one.size]
}
return combined
}
I have a hardcoded key with which I want to encrypt a string before storing it in SharedPreferences. This is the code I have so far:
public class TokenEncryptor {
private final static String TOKEN_KEY = "91a29fa7w46d8x41";
public static String encrypt(String plain) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AlgorithmParameterSpec ivSpec = new IvParameterSpec(new byte[16]);
SecretKeySpec newKey = new SecretKeySpec(TOKEN_KEY.getBytes(), "AES");
cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
return new String(cipher.doFinal(plain.getBytes()));
} catch (Exception e) {
Ln.e(e);
return null;
}
}
public static String decrypt(String encoded) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AlgorithmParameterSpec ivSpec = new IvParameterSpec(new byte[16]);
SecretKeySpec newKey = new SecretKeySpec(TOKEN_KEY.getBytes(), "AES");
cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec);
return new String(cipher.doFinal(encoded.getBytes()));
} catch (Exception e) {
Ln.e(e);
return null;
}
}
}
It seems to be catching an exception at the end of decrypt method:
javax.crypto.IllegalBlockSizeException: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
Can someone point me in the right direction? I have a feeling I'm doing something wrong instantiating IvParameterSpec.
When you encrypt a string with AES, you get an array of bytes back. Trying to convert those bytes directly to a string (new String(cipher.doFinal(plaintextBytes))) will cause all sorts of problems. If you require the output from your encryption method to be a string, then use Base64 rather than attempting a direct conversion. In your decryption method, convert the Base64 string back into a byte array before decrypting the byte array.
Also, do not use getBytes() since the output depends on the system defaults. Use getBytes("utf-8") or whatever. That eliminates ambiguity.
Just in case anyone is interested (or feels too lazy to do their research), here is the the result code for AES-256 encryption and decryption that I put together, with help from the accepted answer and comments:
public class TokenEncryptor {
private final static String TOKEN_KEY = "fqJfdzGDvfwbedsKSUGty3VZ9taXxMVw";
public static String encrypt(String plain) {
try {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(TOKEN_KEY.getBytes("utf-8"), "AES"), new IvParameterSpec(iv));
byte[] cipherText = cipher.doFinal(plain.getBytes("utf-8"));
byte[] ivAndCipherText = getCombinedArray(iv, cipherText);
return Base64.encodeToString(ivAndCipherText, Base64.NO_WRAP);
} catch (Exception e) {
Ln.e(e);
return null;
}
}
public static String decrypt(String encoded) {
try {
byte[] ivAndCipherText = Base64.decode(encoded, Base64.NO_WRAP);
byte[] iv = Arrays.copyOfRange(ivAndCipherText, 0, 16);
byte[] cipherText = Arrays.copyOfRange(ivAndCipherText, 16, ivAndCipherText.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(TOKEN_KEY.getBytes("utf-8"), "AES"), new IvParameterSpec(iv));
return new String(cipher.doFinal(cipherText), "utf-8");
} catch (Exception e) {
Ln.e(e);
return null;
}
}
private static byte[] getCombinedArray(byte[] one, byte[] two) {
byte[] combined = new byte[one.length + two.length];
for (int i = 0; i < combined.length; ++i) {
combined[i] = i < one.length ? one[i] : two[i - one.length];
}
return combined;
}
}
It's an extension of Artjom B answer and working for me.
public String encryptMsg(String message, SecretKey secret)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidParameterSpecException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
Cipher cipher = null;
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] cipherText = cipher.doFinal(message.getBytes("UTF-8"));
return Base64.encodeToString(cipherText, Base64.NO_WRAP);
}
public String decryptMsg(String cipherText, SecretKey secret)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidParameterSpecException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
Cipher cipher = null;
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret);
byte[] decode = Base64.decode(cipherText, Base64.NO_WRAP);
String decryptString = new String(cipher.doFinal(decode), "UTF-8");
return decryptString;
}
Kotlin version of #Oleksiy 's answer.
<script src="https://gist.github.com/kasim1011/a5a9644a60c33a4df3c29f4b34cf93a4.js"></script>
import android.util.Base64
import java.util.*
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
private const val algorithm = "AES"
private const val tokenKey = "fqJfdzGDvfwbedsKSUGty3VZ9taXxMVw"
private const val padding = "AES/CBC/PKCS5Padding"
private const val ivSize = 16
fun String.encryptAES(): String {
val tokenBytes = tokenKey.toByteArray(Charsets.UTF_8)
val secretKey = SecretKeySpec(tokenBytes, algorithm)
val ivByteArray = ByteArray(ivSize)
val iv = IvParameterSpec(ivByteArray)
val cipher = Cipher.getInstance(padding)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv)
val cipherText = cipher.doFinal(toByteArray(Charsets.UTF_8))
val ivAndCipherText = getCombinedArray(ivByteArray, cipherText)
return Base64.encodeToString(ivAndCipherText, Base64.NO_WRAP)
}
fun String.decryptAES(): String {
val tokenBytes = tokenKey.toByteArray(Charsets.UTF_8)
val secretKey = SecretKeySpec(tokenBytes, algorithm)
val ivAndCipherText = Base64.decode(this, Base64.NO_WRAP)
val cipherText = Arrays.copyOfRange(ivAndCipherText, ivSize, ivAndCipherText.size)
val ivByteArray = Arrays.copyOfRange(ivAndCipherText, 0, ivSize)
val iv = IvParameterSpec(ivByteArray)
val cipher = Cipher.getInstance(padding)
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv)
return cipher.doFinal(cipherText).toString(Charsets.UTF_8)
}
private fun getCombinedArray(one: ByteArray, two: ByteArray): ByteArray {
val combined = ByteArray(one.size + two.size)
for (i in combined.indices) {
combined[i] = if (i < one.size) one[i] else two[i - one.size]
}
return combined
}
I have a hardcoded key with which I want to encrypt a string before storing it in SharedPreferences. This is the code I have so far:
public class TokenEncryptor {
private final static String TOKEN_KEY = "91a29fa7w46d8x41";
public static String encrypt(String plain) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AlgorithmParameterSpec ivSpec = new IvParameterSpec(new byte[16]);
SecretKeySpec newKey = new SecretKeySpec(TOKEN_KEY.getBytes(), "AES");
cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
return new String(cipher.doFinal(plain.getBytes()));
} catch (Exception e) {
Ln.e(e);
return null;
}
}
public static String decrypt(String encoded) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AlgorithmParameterSpec ivSpec = new IvParameterSpec(new byte[16]);
SecretKeySpec newKey = new SecretKeySpec(TOKEN_KEY.getBytes(), "AES");
cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec);
return new String(cipher.doFinal(encoded.getBytes()));
} catch (Exception e) {
Ln.e(e);
return null;
}
}
}
It seems to be catching an exception at the end of decrypt method:
javax.crypto.IllegalBlockSizeException: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
Can someone point me in the right direction? I have a feeling I'm doing something wrong instantiating IvParameterSpec.
When you encrypt a string with AES, you get an array of bytes back. Trying to convert those bytes directly to a string (new String(cipher.doFinal(plaintextBytes))) will cause all sorts of problems. If you require the output from your encryption method to be a string, then use Base64 rather than attempting a direct conversion. In your decryption method, convert the Base64 string back into a byte array before decrypting the byte array.
Also, do not use getBytes() since the output depends on the system defaults. Use getBytes("utf-8") or whatever. That eliminates ambiguity.
Just in case anyone is interested (or feels too lazy to do their research), here is the the result code for AES-256 encryption and decryption that I put together, with help from the accepted answer and comments:
public class TokenEncryptor {
private final static String TOKEN_KEY = "fqJfdzGDvfwbedsKSUGty3VZ9taXxMVw";
public static String encrypt(String plain) {
try {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(TOKEN_KEY.getBytes("utf-8"), "AES"), new IvParameterSpec(iv));
byte[] cipherText = cipher.doFinal(plain.getBytes("utf-8"));
byte[] ivAndCipherText = getCombinedArray(iv, cipherText);
return Base64.encodeToString(ivAndCipherText, Base64.NO_WRAP);
} catch (Exception e) {
Ln.e(e);
return null;
}
}
public static String decrypt(String encoded) {
try {
byte[] ivAndCipherText = Base64.decode(encoded, Base64.NO_WRAP);
byte[] iv = Arrays.copyOfRange(ivAndCipherText, 0, 16);
byte[] cipherText = Arrays.copyOfRange(ivAndCipherText, 16, ivAndCipherText.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(TOKEN_KEY.getBytes("utf-8"), "AES"), new IvParameterSpec(iv));
return new String(cipher.doFinal(cipherText), "utf-8");
} catch (Exception e) {
Ln.e(e);
return null;
}
}
private static byte[] getCombinedArray(byte[] one, byte[] two) {
byte[] combined = new byte[one.length + two.length];
for (int i = 0; i < combined.length; ++i) {
combined[i] = i < one.length ? one[i] : two[i - one.length];
}
return combined;
}
}
It's an extension of Artjom B answer and working for me.
public String encryptMsg(String message, SecretKey secret)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidParameterSpecException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
Cipher cipher = null;
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] cipherText = cipher.doFinal(message.getBytes("UTF-8"));
return Base64.encodeToString(cipherText, Base64.NO_WRAP);
}
public String decryptMsg(String cipherText, SecretKey secret)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidParameterSpecException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
Cipher cipher = null;
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret);
byte[] decode = Base64.decode(cipherText, Base64.NO_WRAP);
String decryptString = new String(cipher.doFinal(decode), "UTF-8");
return decryptString;
}
Kotlin version of #Oleksiy 's answer.
<script src="https://gist.github.com/kasim1011/a5a9644a60c33a4df3c29f4b34cf93a4.js"></script>
import android.util.Base64
import java.util.*
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
private const val algorithm = "AES"
private const val tokenKey = "fqJfdzGDvfwbedsKSUGty3VZ9taXxMVw"
private const val padding = "AES/CBC/PKCS5Padding"
private const val ivSize = 16
fun String.encryptAES(): String {
val tokenBytes = tokenKey.toByteArray(Charsets.UTF_8)
val secretKey = SecretKeySpec(tokenBytes, algorithm)
val ivByteArray = ByteArray(ivSize)
val iv = IvParameterSpec(ivByteArray)
val cipher = Cipher.getInstance(padding)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv)
val cipherText = cipher.doFinal(toByteArray(Charsets.UTF_8))
val ivAndCipherText = getCombinedArray(ivByteArray, cipherText)
return Base64.encodeToString(ivAndCipherText, Base64.NO_WRAP)
}
fun String.decryptAES(): String {
val tokenBytes = tokenKey.toByteArray(Charsets.UTF_8)
val secretKey = SecretKeySpec(tokenBytes, algorithm)
val ivAndCipherText = Base64.decode(this, Base64.NO_WRAP)
val cipherText = Arrays.copyOfRange(ivAndCipherText, ivSize, ivAndCipherText.size)
val ivByteArray = Arrays.copyOfRange(ivAndCipherText, 0, ivSize)
val iv = IvParameterSpec(ivByteArray)
val cipher = Cipher.getInstance(padding)
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv)
return cipher.doFinal(cipherText).toString(Charsets.UTF_8)
}
private fun getCombinedArray(one: ByteArray, two: ByteArray): ByteArray {
val combined = ByteArray(one.size + two.size)
for (i in combined.indices) {
combined[i] = if (i < one.size) one[i] else two[i - one.size]
}
return combined
}