This is my code:
public class Encryption {
private static final String ALGO = "AES/CBC/PKCS5Padding";
private static final byte[] keyValue = new byte[]{'F','O','R','T','Y','T','W','O','F','O','R','T','Y','T','W','O'};
private static Key key = generateKey();
public static String encrypt(String DATA) throws Exception {
Cipher c = Cipher.getInstance(ALGO);
c.init(Cipher.ENCRYPT_MODE,key,new IvParameterSpec(c.getParameters().getParameterSpec(IvParameterSpec.class).getIV()));
byte[] encVal = c.doFinal(DATA.getBytes());
String encryptedValue = new BASE64Encoder().encode(encVal);
return encryptedValue;
}
public static String decrypt(String DATA) throws Exception {
System.out.println(DATA.length());
Cipher c = Cipher.getInstance(ALGO);
c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(c.getParameters().getParameterSpec(IvParameterSpec.class).getIV()));
byte[] dencVal = c.doFinal(DATA.getBytes());
String decryptedValue = new BASE64Encoder().encode(dencVal);
return decryptedValue;
}
public static Key generateKey() {
Key key = new SecretKeySpec(keyValue,"AES");
return key;
}
}
And I'm getting this error:
Error: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:913)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at Encryption.decrypt(Encryption.java:30)
at EncryptionTest.main(EncryptionTest.java:9)
What am I doing wrong?
AES/CBC/PKCS5Padding is a "block cipher" meaning in works on blocks of fixed size, 16 bytes in this case. Inputs of smaller size must be padded out with extra bytes for encryption to work.
Decryption, because it's working out output of an encryption operation, must already be a multiple of the block size. The exception is telling you that you have not provided correctly encrypted data because the input was not a multiple of the block size.
Decoding should always be the opposite of encoding so if you encode by doing "encrypt then base64-encode" then the decoding must be "base64-decode then decrypt". Your method is "decrypt then base64-encode" which is pretty clearly a mistake.
If you are using the returning value of the encrypt method, its not going to work.
You encrypt and then encode the result of the encryption. After that you try do decrypt the encoded value. You have to decode first and then try to decrypt.
Related
I have been trying to figure this out for days now. Encryption method works fine, but during the decryption tests I am getting the exception below. Especially I am using: AES/GCM/NoPadding . As far as I know T_LEN should be IV_LENGTH*8 as a byte array representation. The error truly shows at ExampleCryptografer.java decryption method: byte[] decryptedText = cipher.doFinal(decoded);
javax.crypto.AEADBadTagException: Tag mismatch!
at java.base/com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:623)
at java.base/com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1116)
at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1053)
at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202)
at com.example.ExampleCryptografer.decrypt(ExampleCryptografer.java:61)
at com.example.ExampleCryptograferTest.decrypt_givenEncryptedExample_ShouldSucceed(ExampleCryptograferTest.java:21)
This is how my tests looks like:
public class ExampleCryptographerTest {
private ExampleCryptographer objectUnderTest = new ExampleCryptographer("knownKeyForTest=");
#Test
public void decrypt_givenEncryptedExample_ShouldSucceed() {
String example = "afasfdafafa=";
String encodedExample = objectUnderTest.encrypt(example);
String result = objectUnderTest.decrypt(encodedExample);
assertThat(result).isNotNull();
assertThat(result.length()).isEqualTo(48);
}
#Test
public void encrypt_givenExample_ShouldSucceed() {
String example = "afasfdafafa=";
String result = objectUnderTest.encrypt(example);
assertThat(result).isNotNull();
assertThat(result.length()).isEqualTo(48);
}
#Test
public void decrypt_givenEncryptedExampleWithOtherKey_ShouldFail() {
String example = "afasfdafafa=";
String encodedExample = new ExampleCryptographer("otherKeyForTest=").encrypt(example);
Throwable throwable = catchThrowable(() -> objectUnderTest.decrypt(encodedExample));
assertThat(throwable)
.isInstanceOf(IllegalArgumentException.class);
}
#Test(expected = InvalidKeyException.class)
public void encrypt_givenInvalidKey_ShouldFail() {
new ExampleCryptographer("invalid").encrypt("test");
}
}
and finally the actual code:
public class ExampleCryptographer {
private static final String ALGORITHM = "AES";
private final Key key;
private static final int T_LEN = 96;
private static final int IV_LENGTH = 12;
private final Base64 base64 = new Base64(76, null, true);
#SneakyThrows
public ExampleCryptographer(#Value("${myKey}") String secretKey) {
this.key = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
}
#SneakyThrows
public String encrypt(#NonNull String text) {
byte[] iv = new byte[IV_LENGTH];
(new SecureRandom()).nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec ivSpec = new GCMParameterSpec(T_LEN, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] ciphertext = cipher.doFinal(text.getBytes(UTF_8));
byte[] encrypted = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, encrypted, 0, iv.length);
System.arraycopy(ciphertext, 0, encrypted, iv.length, ciphertext.length);
return base64.encodeAsString(encrypted);
}
#SneakyThrows
public String decrypt(#NonNull String encryptedText) {
byte[] decoded = base64.decode(encryptedText);
byte[] iv = Arrays.copyOfRange(decoded, 0, IV_LENGTH);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec ivSpec = new GCMParameterSpec(T_LEN, iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decryptedText = cipher.doFinal(decoded);
return new String(decryptedText);
}
}
Can anyone help? I have been reading many about, and still cannot find anything wrong.
T_LEN is the size OF THE AUTHENTICATION TAG in bits. It should be large enough that the risk of (successful) forgery does not exceed that acceptable to your data owners, but is not related to the IV in any way. If you don't have an analysis that less is sufficient, and you aren't in a resource-constrained environment (and JavaSE never is), just use the max of 128.
Your main problem is in encrypt you reasonably concatenate IV+ciphertext (which for Java includes the tag), but in decrypt you use the first bytes as the IV and the whole buffer as the ciphertext when it should be Arrays.copyOfRange(decoded,IV_LENGTH,decoded.length).
Also, AES key must be exactly 16, 24, or 32 bytes and should be random bits, which cannot be reliably represented directly in a Java String. Generally you should use byte[] and if you need to pass or store it as a string encode to (and decode from) hex or base64.
Finally, on encrypt you encode with getBytes() as UTF-8, but on decrypt you decode with new String using the default encoding, which varies from one JVM to another and often depends on the environment and often isn't UTF-8 in which case this may return 'mojibake' (effectively garbage) not the data you encrypted.
Oh, and AEADBadTagException is not a subclass of IllegalArgumentException.
the current decryption algorithm I wrote goes as follows,
public String decrypt(String enc) throws Exception
{
Key key = k;
Cipher crypt = Cipher.getInstance("AES");
crypt.init(Cipher.DECRYPT_MODE,key);
byte[] decrypt = crypt.doFinal(enc.getBytes());
return new String(decrypt);
}
The error that I get is at the line
byte[] decrypt = crypt.doFinal(enc.getBytes());
I have seen similar questions as this posted, but I am using a 128 bit key, so I am pretty certain there is no padding.
This is how I generate the key
public static SecretKey getKey() throws Exception
{
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
return keyGen.generateKey();
}
Additionally, decoding using base64 gives the same exact error
public String decrypt(String enc) throws Exception
{
Key key = k;
Cipher crypt = Cipher.getInstance("AES");
crypt.init(Cipher.DECRYPT_MODE,key);
byte[] decrypt = crypt.doFinal(Base64.getMimeDecoder().decode(enc));
return new String(decrypt);
}
public String decrypt(String enc)
The problem has already happened by the time you get here. The problem is that you are passing around ciphertext in a String. String isn't a container for binary data. Use a byte[].
I have a problem with decrypting a message using AES. At the end when I expect a message, e.g.
ala123
Instead of that I receive sth like:
...6�b}\7�k�8�vFP�8~%��_zժF��FW��O_e���ó������������ala123
The message I pass to encryption is built as:
cipher key is SHA-256 from AES_TOKEN
cipher IV is some characters, which are then stored in the message (at the beginnig)
decrypted message is wrapped up into Base64
The question is why at the end I eventually receive my expected message, but with a lot of rubbish chars at the beggining?
My encryption code is:
private static final String AES_TOKEN = "my_very_secret_token";
// encrypted is base64 string
public String decrypt(String encrypted) throws Exception {
byte[] decrypted = Base64.getDecoder().decode(encrypted);
return new String(aesDecrypt(decrypted), "UTF-8");
}
private byte[] aesDecrypt(byte[] message) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] token = MessageDigest.getInstance("SHA-256").digest(AES_TOKEN.getBytes());
SecretKeySpec secretKey = new SecretKeySpec(token, "AES");
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOf(message, 16));
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
return cipher.doFinal(message);
}
It looks like you aren't removing the IV from the beginning of message after reading it in to iv. That would explain the garbage being at the start of the decrypted message.
I am working a chat in android, here I am using the next methods for key generation, encrypt and decrypt messages. The problem is that when I send a message for example "hola" in the other side I get "holgAAAAAAAAAAAAAAA". Could you help to fix this?.
private byte[] K;
public void setK(){
KeyGenerator KeyGen=KeyGenerator.getInstance("AES");
KeyGen.init(128);
SecretKey key=KeyGen.generateKey();
K = key.getEncoded();
}
public String encrypt(byte[] input){
try {
IvParameterSpec iv = new IvParameterSpec(Base64.decode("Hola".getBytes(), Base64.DEFAULT));
SecretKeySpec key = new SecretKeySpec(K, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
ctLength += cipher.doFinal(cipherText, ctLength);
return Base64.encodeToString(cipherText, Base64.DEFAULT);
} catch (Exception e) {
Log.e(JUAN, "failed to encrypt ", e);
}
return null;
}
public String decrypt(byte[] input){
try {
IvParameterSpec iv = new IvParameterSpec(Base64.decode("Hola".getBytes(), Base64.DEFAULT));
SecretKeySpec key = new SecretKeySpec(K, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] plainText = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, plainText, 0);
ctLength += cipher.doFinal(plainText, ctLength);
return Base64.encodeToString(plainText, Base64.DEFAULT);
} catch (Exception e) {
Log.e(JUAN, "failed to decrypt ", e);
}
return null;
}
EDIT
Here is my calling, for example to encrypt "Hola".
encrypt(Base64.decode("Hola".getBytes(), Base64.DEFAULT));
decrypt(Base64.decode(ciphertext, Base64.DEFAULT));
There are multiple problems with your code:
Your input and output types of the decryption function are reversed. If you encrypt a byte[], you should get one out when you decrypt it. If your ciphertext is a Base64 String then the decryption method should take such a String and not a byte[].
String encrypt(byte[] plaintext) {
...
return Base64.encodeToString(cipher.doFinal(plaintext), Base64.DEFAULT);
}
byte[] encrypt(String ciphertext) {
...
return cipher.doFinal(Base64.decode(ciphertext.getBytes("UTF-8"), Base64.DEFAULT));
}
You're passing a single plaintext and ciphertext into their respective method, but then use cipher.update() and cipher.doFinal(). This is not necessary. You should use a single cipher.doFinal() call without a previous buffer. Encryption example:
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherText = cipher.doFinal(plaintext);
Since "Hola" is supposed to be what the user typed in, it doesn't make any sense to decode from Base 64. Not all strings that someone types in are valid Base 64 encoded so that they can be decoded. You shouldn't decode the input at all, but pass it directly into the encrypt() function.
Using String#getBytes() is unsafe, because it uses the default Charset of the system. The decryption might not succeed when a different default Charset is used on the receiving system. You should specify the Charset yourself and get the String from the byte[] after decryption:
String ciphertext = encrypt(plaintext.getBytes("UTF-8"));
String recoveredPlaintext = new String(decrypt(ciphertext), "UTF-8");
You're not using your static IV.
Security Issues:
You're using ECB mode. Don't do this! It's not semantically secure. Use at least CBC mode with a random IV. The IV doesn't have to be hidden, so you can simply prepend it to the ciphertext.
You're not authenticating the ciphertext. Your system might be vulnerable to the padding oracle attack. You should either use an encrypt-then-MAC approach with a strong MAC like HMAC-SHA256 or use an authenticated mode of operation for AES like GCM or EAX.
Use for example this library by Isaac Potoczny-Jones of which is compatible with Android. It supports AES-CBC with a random IV and ciphertext authentication with HMAC-SHA256.
Your code is OK assuming that the parameter input in your public String decrypt(byte[] input) method is successfully Base64 decoded from the cipher text by the caller (because your encrption returns Base64 encoded cipher string). But, in the decrypt() method you are creating a byte array plainText by getOutputSize() method. That makes plainText an array of size of multiple of AES Block Size(16). For your case, plainText is a 16 byte array. So after decrypting and removing the paddings from cipher text the plainText contains the decrypted text with some zeroes, those zeroes are then encoded into AAA...As.
So use
return Base64.encodeToString(plainText, 0, ctLength, Base64.DEFAULT);
instead of
return Base64.encodeToString(plainText, Base64.DEFAULT);
Note: You are using ECB mode, so your IvParameterSpec is useless. Use CBC mode instead.
Edition: Your calling is not OK. Try this
//Encryption side
String text = "hola, hi, anything u want";
byte[] plainText = text.getBytes("UTF-8");
String base64 = encrypt(plainText);
// Decryption side
byte[] cipherText = Base64.decode(base64, Base64.DEFAULT);
String plainEncodedText = decrypt(cipherText);
byte[] plainTextAsByte = Base64.decode(plainEncodedText, Base64.DEFAULT);
String plainTextAgain = new String(plainTextAsByte , "UTF-8");
Now print the plainTextAgain and hope this will work!
When running below programme i am getting this exception. Not able to figure out what the issue as AES allows the 128 -256 bit key?
Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 29 bytes
at com.sun.crypto.provider.AESCipher.engineGetKeySize(DashoA13*..)
at javax.crypto.Cipher.b(DashoA13*..)
Getting exception at line 20
Here is the programme
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class AESEncryptionDecryptionTest {
private static final String ALGORITHM = "AES";
private static final String myEncryptionKey = "ThisIsSecurityKey";
private static final String UNICODE_FORMAT = "UTF8";
public static String encrypt(String valueToEnc) throws Exception {
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGORITHM);
c.init(Cipher.ENCRYPT_MODE, key); //////////LINE 20
byte[] encValue = c.doFinal(valueToEnc.getBytes());
String encryptedValue = new BASE64Encoder().encode(encValue);
return encryptedValue;
}
public static String decrypt(String encryptedValue) throws Exception {
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGORITHM);
c.init(Cipher.DECRYPT_MODE, key);
byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedValue);
byte[] decValue = c.doFinal(decordedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
}
private static Key generateKey() throws Exception {
byte[] keyAsBytes;
keyAsBytes = myEncryptionKey.getBytes(UNICODE_FORMAT);
Key key = new SecretKeySpec(keyAsBytes, ALGORITHM);
return key;
}
public static void main(String[] args) throws Exception {
String value = "password1";
String valueEnc = AESEncryptionDecryptionTest.encrypt(value);
String valueDec = AESEncryptionDecryptionTest.decrypt(valueEnc);
System.out.println("Plain Text : " + value);
System.out.println("Encrypted : " + valueEnc);
System.out.println("Decrypted : " + valueDec);
}
}
AES allows 128, 192 or 256 bit key length. That is 16, 24 or 32 byte. Try taking just the first 16 bytes of your mEncryptionKey as the keyAsBytes.
Edit:
An after though occurred to me. A habit I have formed, and one which I recommend, is to take a SHA hash of a password/passphrase, and use that as the source bytes of your key. Taking a hash guarantees the key data will be the correct size, irrespective of the length of the password/passphrase. Your current implementation of using the String bytes has two problems;
It will break your key generation if someone uses a short password.
Two different passwords for which the first 16 bytes are the same will create the same key.
Both of these problems are eliminated by using a hash.
Take a look at the buildKey() method in this class; https://github.com/qwerky/DataVault/blob/master/src/qwerky/tools/datavault/DataVault.java
The key uses randomness as input, but there are stiill requirements for how it is composed. The SecretKeySpec constructor you used is for loading an already generated key into memory. Instead, use KeyGenerator.
KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM);
kg.init(128);
SecretKey k = kg.generateKey();
Also note that AES-128 is now actually thought to be weaker than AES-256. It probably isn't drastically different but the benefit from the longer key size may be outweighed by simplifications elsewhere (fewer rounds).