I have written a small application to encrypt and decrypt Strings using AES. Here is the code:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class AesEncryptionTest {
static IvParameterSpec initialisationVector = generateInitialisationVector();
static SecretKey encryptionKey = generateKey();
static String plainText = "test text 123\0\0\0";
public static void main(String [] args) {
try {
System.out.println("Initial Plain Text = " + plainText);
byte[] encryptedText = encrypt(plainText, encryptionKey);
System.out.println("Encrypted Text = " + encryptedText);
String decryptedText = decrypt(encryptedText, encryptionKey);
System.out.println("Decrypted Text = " + decryptedText);
} catch (Exception e) {
e.printStackTrace();
}
}
public static byte[] encrypt(String plainText, SecretKey encryptionKey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, initialisationVector);
return cipher.doFinal(plainText.getBytes("UTF-8"));
}
public static String decrypt(byte[] encryptedText, SecretKey encryptionKey) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
cipher.init(Cipher.DECRYPT_MODE, encryptionKey, initialisationVector);
return new String(cipher.doFinal(encryptedText),"UTF-8");
}
public static SecretKey generateKey() {
SecretKey secretKey = null;
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
secretKey = keyGenerator.generateKey();
} catch (NoSuchAlgorithmException ex) {
// Whine a little
}
return secretKey;
}
public static IvParameterSpec generateInitialisationVector() {
byte[] initVector = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(initVector);
return new IvParameterSpec(initVector);
}
}
Output:
Initial Plain Text = test text 123
Encrypted Text = [B#407dcb32
Decrypted Text = test text 123
My main areas of concern are around encrypting into a byte array and decrypting back to a String. I know that this can introduce unexpected behaviour and loss of data. While this has not been observed in my testing, could anyone suggest any changes that would help combat this? I think I have this covered by ensuring UTF-8 is used both ways.
If anyone see's any other red flags with my code and how I have done this, I'm open to criticism/suggestions.
Many thanks!
You're calling toString() on a byte[] which is never a good idea. Basically it's not giving you any useful information.
If you want to convert arbitrary binary data into a string, I'd suggest using hex or base64, both of which are covered elsewhere. There's no indication that you've actually lost any information here in the encryption/decryption - the problem is your display of the encrypted data. So long as you don't try to treat that as simple encoded text data (because it isn't) you should be fine. In particular, your code is already specifying UTF-8 as the conversion from the original text to unencrypted binary data, and vice versa - so that's safe.
If you don't need to convert the byte array to a string, it's simplest to avoid doing so in the first place. (For example, you could write it to a file still in the binary form very simply, then load it back into a byte array later.)
You asked for other red flags, so I'll give you a few pointers regarding the crypto:
Generally you don't have to provide the provider name when you use an algorithm name. Specifying the provider makes your code less portable.
It is better to use a standardized padding mode such as "/PKCS5Padding" (identical to PKCS#7 padding in Java). If you want to use the current padding mode you can configure the Bouncy Castle provider and specify "/ZeroBytePadding". This padding mode does not work correctly for plaintext that ends with zero valued bytes.
You store the IV in the same class variable as the key. I know this is just test code, but normally the IV need to be send or established at both sides. The most common way to use the same key at both sides is to prefix the IV to the ciphertext.
The size of the IV depends on the cipher. It is always 16 for AES, but you may want to make the IV size configurable or use the Cipher.getBlockSize() method.
Use GCM mode (available since 1.8) encryption if you also want authenticity/integrity and protection against padding oracle attacks.
You should use a fresh, random IV for each encrypt, instead of generating an IV just once.
the way to make sure the conversion is without loss is to use the same Charset when converting back and forth as you do.
Creating a string of the encrypted data is however not safe for further use; it can contain any and all sequences of bytes and might not fit into whatever Charset you originally used (you're not making this error, just pointing it out).
You're also printing the hashcode of the byte[] mid way in the code, not the individual bytes.
Related
I need to decrypt an AES (PKCS#7) encoded string in my Flutter mobile application.
The string is got from a QR Code, which has been generated from a Java application and contains the AES encoded String.
The Java encoding :
import java.security.Security;
import java.nio.charset.StandardCharsets;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class MyClass {
public static void main(String[] args) throws Exception {
String toEncode = "firstname.lastname#mycompany.com;12";
String encoded = pleaseEncodeMe(toEncode);
System.out.println(encoded);
}
private static String pleaseEncodeMe(String plainText) throws Exception {
Security.addProvider(new BouncyCastleProvider());
final String encryptionAlgorithm = "AES/CBC/PKCS7PADDING";
final String encryptionKey = "WHatAnAWEsoMeKey";
final SecretKeySpec keySpecification = new SecretKeySpec(encryptionKey.getBytes(StandardCharsets.UTF_8), encryptionAlgorithm);
final Cipher cipher = Cipher.getInstance(encryptionAlgorithm, "BC");
cipher.init(Cipher.ENCRYPT_MODE, keySpecification);
final byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
return Base64.encodeBase64URLSafeString(encryptedBytes);
}
}
Output : AIRTEuNmSuQtYuysv93w3w83kJJ6sg7kaU7XzA8xrAjOp-lKYPp1brtDAPbhSJmT
The Dart decoding :
void main() {
print(decodeMeOrDie("AIRTEuNmSuQtYuysv93w3w83kJJ6sg7kaU7XzA8xrAjOp-lKYPp1brtDAPbhSJmT"));
}
String decodeMeOrDie(String encryptedString) {
final key = Key.fromUtf8("WHatAnAWEsoMeKey");
final iv = IV.fromLength(16);
final encrypter = Encrypter(AES(key, mode: AESMode.cbc, padding: "PKCS7"));
return encrypter.decrypt64(encryptedString, iv: iv);
}
Output : Y��=X�Rȑ�"Qme#mycompany.com;12
You can see that only a part of the string is decoded.
Two things must be taken into account:
1) For decryption, the IV used for encryption is required.
2) For security reasons, a new IV must be randomly generated for each encryption so that no IV is used more than once with the same key, here.
Therfore, the IV must be passed from the encryption-side to the decryption-side. This doesn't happen automatically, but has to be implemented.
One possibility is to concatenate the byte-arrays of IV and ciphertext. Usually the IV is placed before the ciphertext and the result is Base64-encoded (if required), e.g. in Java:
// Concatenate IV and ciphertext
byte[] iv = ...
byte[] ciphertext = ...
byte[] ivAndCiphertext = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, ivAndCiphertext, 0, iv.length);
System.arraycopy(ciphertext, 0, ivAndCiphertext, iv.length, ciphertext.length);
// If required: Base64-encoding
This data is transmitted to the decryption-side, which separates both parts after Base64-decoding. In the case of AES-CBC, the IV is 16 bytes long, so the first 16 bytes represent the IV and the rest the ciphertext. The IV doesn't need to be encrypted because it isn't secret.
Specifically for your case this means that you have to concatenate IV and ciphertext on the Java-side and to Base64-encode the result. On the Dart-side you have to Base64-decode first and then both parts, IV and ciphertext, can be separated and used for the following decryption.
There are two ways to generate the IV before encryption: Implicit generation by the Cipher-instance as in your example or explicit generation e.g. via SecureRandom. Both alternatives are discussed here. If the IV is generated implicitly (via the Cipher-instance), then this IV must be determined via the Cipher-instance, since it is later required for decryption:
// Determine IV from cipher for later decryption
byte[] iv = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
If the IV is determined explicitly (e.g. using SecureRandom), it must be passed to the Cipher-instance so that it will be used in the running encryption. This is done using an IvParameterSpec.
// Assign IV to cipher so that it is used for current encryption
byte[] iv = ...
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretkeySpec, ivParameterSpec);
A hard-coded key is in general not good practice (except for testing purposes perhaps). However, the topic of key generation/management is outside the scope of this answer. There are already a lot of questions and answers on this subject. If your question is not covered by these answers, please post a new question. A hard-coded IV doesn't occur within the above architecture and should only be used for testing purposes.
If it can help someone, here is the code I ended up with, in dart (it uses the encrypt package) :
/// Decode the specified QR code encrypted string
static String decodeQrCode(String encryptedString) {
try {
// pad the encrypted base64 string with '=' characters until length matches a multiple of 4
final int toPad = encryptedString.length % 4;
if (toPad != 0) {
encryptedString = encryptedString.padRight(encryptedString.length + toPad, "=");
}
// get first 16 bytes which is the initialization vector
final iv = encrypt.IV(Uint8List.fromList(base64Decode(encryptedString).getRange(0, 16).toList()));
// get cipher bytes (without initialization vector)
final encrypt.Encrypted encrypted = encrypt.Encrypted(Uint8List.fromList(
base64Decode(encryptedString).getRange(16, base64Decode(encryptedString).length).toList()));
// decrypt the string using the key and the initialization vector
final key = encrypt.Key.fromUtf8(YOUR_KEY);
final encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7"));
return encrypter.decrypt(encrypted, iv: iv);
} catch (e) {
_log.severe("Error while decoding QR code : $e");
return null;
}
}
Thank you for taking you time to assist me with this!
THIS POST HAS BEEN EDITED FOR LESS INFORMATION SEE THE EDITED PART
Well I have spend ours of research on this matter and I ended up with a working piece of code..
But Encryption is not a place to make mistakes, and I wanted to ask if my code is actualy secure! It's really important for me because I want to implement it to a program so my code is...
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
public class EncryptFile{
private static final String FILE_IN = "./EncryptFile.java";
private static final String FILE_ENCR = "./EncryptFile_encr.java";
private static final String FILE_DECR = "./EncryptFile_decr.java";
public static void main(String []args){
try
{
Encryption("passwordisnottheactual", Files.readAllBytes(Paths.get(FILE_IN)));
Decryption("passwordisnottheactual");
}catch(Exception e){
System.out.println(e.getMessage());
}
}
private static void Encryption(String Key, byte[] byteArray) throws Exception
{
// Decode the base64 encoded Key
byte[] decodedKey = Base64.getDecoder().decode(Key);
// Rebuild the key using SecretKeySpec
SecretKey secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
// Cipher gets AES Algorithm instance
Cipher AesCipher = Cipher.getInstance("AES");
//Initialize AesCipher with Encryption Mode, Our Key and A ?SecureRandom?
AesCipher.init(Cipher.ENCRYPT_MODE, secretKey, new SecureRandom());
byte[] byteCipherText = AesCipher.doFinal(byteArray);
//Write Bytes To File
Files.write(Paths.get(FILE_ENCR), byteCipherText);
}
private static void Decryption(String Key) throws Exception
{
//Ddecode the base64 encoded string
byte[] decodedKey = Base64.getDecoder().decode(Key);
//Rebuild key using SecretKeySpec
SecretKey secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
//Read All The Bytes From The File
byte[] cipherText = Files.readAllBytes(Paths.get(FILE_ENCR));
//Cipher gets AES Algorithm Instance
Cipher AesCipher = Cipher.getInstance("AES");
//Initialize it in Decrypt mode, with our Key, and a ?SecureRandom?
AesCipher.init(Cipher.DECRYPT_MODE, secretKey, new SecureRandom());
byte[] bytePlainText = AesCipher.doFinal(cipherText);
Files.write(Paths.get(FILE_DECR), bytePlainText);
}
}
EDIT
Possible duplicate of Simple Java AES encrypt/decrypt example – JFPicard
Well it could be but these answers Use IVParameterSpec and I wanted to know if
this line of code is actually secure or if it is bad practice:
AesCipher.init(Cipher.DECRYPT_MODE, secretKey, new SecureRandom());
because I use a new SecureRandom() every time,
and I haven't seen anyone use a SecureRandom object like this.
Encryption key
The password is passes as a string but the Encryption function Base64 decoded it, that is a coding error.
When a password is used the encryption key should be derived from it with the PBKDF2 (aka Rfc2898DeriveBytes) function.
When using key derivation the salt and iteration count needs to be available for decryption, often they are provided in a prefix to the encrypted data.
Encryption mode
No encryption mode is supplied.
Use CBC mode with a random IV.
Just prefix the encrypted data with the IV for use on decryption.
Padding
AES is a block cipher and as such requires the input data size to be a multiple of the block size.
Specify PKCS#7 (née PKCS#5) padding, it will add padding on encryption and remove it on decryption.
On decryption do not return "padding" errors, they can provide a "Padding Oracle" attack.
Explicit
Specify all encryption parameters and sizes.
Do not rely on implementation defaults.
Encryption authentication
Consider if there is a need to know if the data is decrypted correctly.
Versioning
Add a version indicator so that if changes are necessary later there is an compatibility path.
Or consider using RNCryptor which handles all this and more.
Update: (thx Andy for the comment)
If GCM mode is available and interoperability across platforms and libraries is not an issue GCM is arguably a better encryption mode. GCM has authentication and padding build-in making it more robust and an easier secure solution.
I have to implement basic encryption in my program. I can use Base64 it was rejected by the client. So I am using the following methods. The problem which I am facing is the there are special characters in the encrypted which are resulting in exceptions. Can I change this code to somehow encrypt into plain text without special characters.
protected static byte[] encrypt(String text)
{
try
{
String key = "6589745268754125";
// Create key and cipher
Key aesKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
// encrypt the text
cipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] encrypted = cipher.doFinal(text.getBytes());
return encrypted;
}
catch(Exception ex)
{
WriteLog("Encryption Failed");
WriteLog(ex.getMessage());
return null;
}
}
protected static String decrypt(byte[] pass)
{
try
{
String key = "6589745268754125";
// Create key and cipher
Key aesKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
// decrypt the text
cipher.init(Cipher.DECRYPT_MODE, aesKey);
String decrypted = new String(cipher.doFinal(pass));
return decrypted;
}
catch(Exception ex)
{
WriteLog("Encryption Failed");
WriteLog(ex.getMessage());
return null;
}
}
The exception message says "Given final block not properly padded"
javax.crypto.BadPaddingException: Given final block not properly padded
so, basically you don't know about encryption and have the problem that your client wants encryption
ok, a quick headsup:
encoding: transforming an input to an output that holds identical information but in another representation ... ex: 1,2,3 -> a,b,c
as you can see the output looks differently but holds the same information
please note that no secret information is necessary to encode/decode
encryption: might look similar at first glance but here you need some secrets ... an encryption takes 2 inputs ... a secret and the input data
the resulting output can be decrypted, but ONLY if you have the corresponding secret
if your client wants you to encrypt something, make sure that thing can be represented as bytes ... encrypting a string... not good... encrypting a string that has been transformed into < insert arbitrary byte encoding here, for example unicode > ... ok
encryptions usually handle bytes (let's not care about historic ciphers here)
when you decide for an encryption/cipher you have to know that there are essentially 2 distinct groups: symetric and asymetric
symetric: the same key (read secret) you use to encrypt will be needed for decryption
asymetric: there are keypairs consisting of a public and a private part (public/private key) the public part is used for encryption, the private part is used for decryption ... makes no sense unless you have different parties that need to exchange keys
asymetric ciphers are usually used to encrypt decrypt the keys for symetric ciphers because they are SLOW while symetric ciphers usually are FAST
asymetric ciphers are not intended to encrypt large amounts of data
symetric ciphers are intended for bulk data
if your goal is just to keep an information encrypted while it is laying around on a harddisk, a symetric cipher is what you want
you will need a key for the cipher to operate ... and... you will have the problem where to store it ... so if you can, have the user enter a sufficiently complex password ... use the password and a function called PBKDF2 with a sufficiently high iteration count (sufficiently high= increase this number until the process takes either a few seconds if you only need this on startup, or until your users start complaining about the delay) to make binary key from the password.
use this key for AES in GCM mode (symetric cipher)
the cipher will want something called IV or initialization vector ...
the iv is no secret, you may prepend this thing to your ciphertext as clear text information
the iv needs to be the size of one block of your cipher, so in the case of AES 128 bit = 16 byte
so your IV when encrypting is a 16 byte (unique) random number (means that you may not use an IV two times or more: persist the used IVs and when getting a new one, check if it was already stored, if yes startover IV generation, if no, store it and then use it)
when decrypting, read the prepended cleartext IV from your file (first 16 byte)
if you just want to store the ciphertext on disk, write it into a binary file
if the file has to contain only printable text apply an encoding like base16/32/64 before writing your bytes to the file and decode into a byte array before decrypting (unless your data is too big for that, then you will have to find/write a stream wrapper that will add/strip encoding for you)
If the client doesn't like Base64, then try Base32 or Base16 (= hex). They are less common but well defined alternatives to Base64.
You might also find out exactly why the client doesn't want you to use Base64.
You should Base64 the encrypted content. It's usual technique by the way.
I guess the client's problem wasn't Base64 format itself but the fact, that Base64 isn't (a strong) encryption.
The problem was padding. I had use AES/CBC/NoPadding and make sure that my strings are multiple of 16 bytes. So in addition to changing the ecryption and decryption I had to add two methods. One to add \0 i.e. implicit null terminators to the end end of the text to make it a multiple of 16 and another to remove them after decryption. So the final version is like this.
public class crypto {
static String IV = "AAAAAAAAAAAAAAAA";
static String plaintext = "my non padded text";
static String encryptionKey = "0123456789abcdef";
public static void main(String[] args)
{
byte[] cipher = encrypt(plaintext);
String decrypted = decrypt(cipher);
}
protected static String covertto16Byte(String plainText)
{
while(plainText.length()%16 != 0)
plainText += "\0";
return plainText;
}
protected static String removePadding(String plainText)
{
return plainText.replace("\0","");
}
protected static byte[] encrypt(String plainText)
{
try
{
String _plaintText_16 = covertto16Byte(plainText);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
cipher.init(Cipher.ENCRYPT_MODE, key,new IvParameterSpec(IV.getBytes("UTF-8")));
return cipher.doFinal(_plaintText_16.getBytes("UTF-8"));
} catch (Exception ex)
{
//catch mechanism
return null;
}
}
protected static String decrypt(byte[] cipherText)
{
try
{
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
cipher.init(Cipher.DECRYPT_MODE, key,new IvParameterSpec(IV.getBytes("UTF-8")));
return removePadding(new String(cipher.doFinal(cipherText), "UTF-8"));
} catch (Exception ex)
{
//catch mechanism
return null;
}
}
}
I am aware of a question very similar to this (How do I encrypt in Python and decrypt in Java?) but I have a different problem.
My problem is, I am not able to decrypt in Java correctly. Despite using the correct key and IV, I still get garbage characters after decryption. I don't have any compile/run-time errors or exceptions in Java so I believe I am using the right parameters for decryption.
Python Encryption Code -
from Crypto.Cipher import AES
import base64
key = '0123456789012345'
iv = 'RandomInitVector'
raw = 'samplePlainText'
cipher = AES.new(key,AES.MODE_CFB,iv)
encrypted = base64.b64encode(iv + cipher.encrypt(raw))
Java Decryption Code -
private static String KEY = "0123456789012345";
public static String decrypt(String encrypted_encoded_string) throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
String plain_text = "";
try{
byte[] encrypted_decoded_bytes = Base64.getDecoder().decode(encrypted_encoded_string);
String encrypted_decoded_string = new String(encrypted_decoded_bytes);
String iv_string = encrypted_decoded_string.substring(0,16); //IV is retrieved correctly.
IvParameterSpec iv = new IvParameterSpec(iv_string.getBytes());
SecretKeySpec skeySpec = new SecretKeySpec(KEY.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
plain_text = new String(cipher.doFinal(encrypted_decoded_bytes));//Returns garbage characters
return plain_text;
} catch (Exception e) {
System.err.println("Caught Exception: " + e.getMessage());
}
return plain_text;
}
Is there anything obvious that I am missing?
The Cipher Feedback (CFB) mode of operation is a family of modes. It is parametrized by the segment size (or register size). PyCrypto has a default segment size of 8 bit and Java (actually OpenJDK) has a default segment size the same as the block size (128 bit for AES).
If you want CFB-128 in pycrypto, you can use AES.new(key, AES.MODE_CFB, iv, segment_size=128). If you want CFB-8 in Java, you can use Cipher.getInstance("AES/CFB8/NoPadding");.
Now that we have that out the way, you have other problems:
Always specify the character set you're using, because it can change between different JVMs: new String(someBytes, "UTF-8") and someString.getBytes("UTF-8"). When you do, be consistent.
Never use a String to store binary data (new String(encrypted_decoded_bytes);). You can copy the bytes directly: IvParameterSpec iv = new IvParameterSpec(Arrays.copyOf(encrypted_decoded_bytes, 16)); and cipher.doFinal(Arrays.copyOfRange(encrypted_decoded_bytes, 16, encrypted_decoded_bytes.length)).
In Java, you're assuming that the IV is written in front of the ciphertext and then encoded together, but in Python, you're never doing anything with the IV. I guess you posted incomplete code.
It is crucial for CFB mode to use a different IV every time if the key stays the same. If you don't change the IV for every encryption, you will create a multi-time pad which enables an attacker to deduce the plaintext even without knowing the key.
I am having some troubles with the following code - I seem to be getting an IllegalBlocksizeException and am unsure what it is that I maybe doing incorrectly here? Would it be possible to get some advice / pointers?
Thanks
public class Encryption
{
private SecretKeyFactory factory;
private SecretKey tmp;
private SecretKey secret;
private Cipher cipher;
private byte[] iv;
private byte[] cipherText;
private final KeySpec spec = new PBEKeySpec("somepassword".toCharArray(), SALT, 65536, 256);
private static final byte[] SALT = {(byte)0xc3, (byte)0x23, (byte)0x71, (byte)0x1c, (byte)0x2e, (byte)0xc2, (byte)0xee, (byte)0x77};
public Encryption()
{
try
{
factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
tmp = factory.generateSecret(spec);
secret = new SecretKeySpec(tmp.getEncoded(), "AES");
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
iv = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public String encrypt(String valueToEncrypt) throws Exception
{
cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(iv));
cipherText = cipher.doFinal(Base64.decodeBase64(valueToEncrypt.getBytes()));
return Base64.encodeBase64String(cipherText);
}
public String decrypt(String encryptedValueToDecrypt) throws Exception
{
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
return new String(cipher.doFinal(new Base64().encode(encryptedValueToDecrypt.getBytes())));
}
public static void main(String[] args ) throws Exception
{
Encryption manager = new Encryption();
String encrypted = manager.encrypt("this is a string which i would like to encrypt");
System.out.println(encrypted);
String decrypted = manager.decrypt(encrypted);
System.out.println(decrypted);
System.out.println(encrypted.equals(decrypted));
}
}
The exception is as follows
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:750)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:313)
at javax.crypto.Cipher.doFinal(Cipher.java:2087)
at encrypt.Encryption.decrypt(Encryption.java:52)
at encrypt.Encryption.main(Encryption.java:60)
The only approach to begin with the implementation of cryptographic algorithms, from the functional point of view (please keep in mind that a working code is not necessarily a secure one, and a lot of thought should come in that direction), is incremental: first try raw AES with a fixed key, then add the key generated by PBKDF2 and only later Base64. The latter is just an encoding tool and should be the easiest part of the process.
But let's take a look to the code:
1. The initialization seems fine, if your goal is to generate the key out of a password.
2. During the decryption, this line stands off:
cipherText = cipher.doFinal(Base64.decodeBase64(valueToEncrypt.getBytes()));
valueToEncrypt is a readable string, but you're trying to decrypt it. Since it has only lowercase letters and spaces, it might not trigger an error, but you're trying to base64-decode something that hasn't been base64-encoded. It would make more sense to try:
cipherText = cipher.doFinal(valueToEncrypt.getBytes());
Then the cipherText can be base64-encoded.
For the decryption part, undo the operations in encryption in the reverse order. If you encrypted and then base64-encoded, then base64-decode first and then decrypt.
As a final recommendation: think modular. Encode in one line and encrypt in another, so if you want to remove or add a layer you just have to toggle the comments on one line.
You have reversed the base-64 encoding and decoding operations. Base-64 takes raw bytes and makes them into printable text. You can encode the output of an encryption operation to make it printable. But then you will need to base‑64–decode that text before trying to decrypt it.
This part of your decrypt() method is causing the problem:
cipher.doFinal(new Base64().encode(encryptedValueToDecrypt.getBytes()))
That should be:
cipher.doFinal(Base64.decodeBase64(encryptedValueToDecrypt.getBytes()))
Asking for "pointers" is pretty open-ended. The best pointer I can give you: don't write this code yourself. Choose a package that provides a higher-level API, selecting high-security algorithms and applying them according to best practices. You don't know what you are doing, and you won't be able to write secure code. But using a high quality, open source library might help you begin to learn more about encryption.
You probably should base64 decode encryptedValueToDecrypt before you decrypt it.