Java AES/GCM/NoPadding encryption fails with special characters - java

I've followed a few examples and when trying to implement AES/GCM/NoPadding referenced here: https://www.strongauth.com/samplecode/GCM.java I am unable to encrypt any text that contains special characters (i.e. ø).
Ultimately it fails inside of doFinal with
javax.crypto.ShortBufferException: Output buffer must be (at least) 30 bytes long
but it seems like I must be doing something wrong. What am I missing?
Simple POC:
public class Example {
private static final String CIPHER_TRANSFORM = "AES/GCM/NoPadding";
public static void main(String[] args) {
String key = generateKey("AES", 256, "seed");
encryptText("text containing a ø character", key, "TOKENTOKENTOKENTOKEN", "AES");
}
private static String generateKey(String alg, int size, String seed) {
try {
SecureRandom securerandom = SecureRandom.getInstance("SHA1PRNG");
securerandom.setSeed(seed.getBytes("UTF-8"));
KeyGenerator kg = KeyGenerator.getInstance(alg);
kg.init(size, securerandom);
SecretKey sk = kg.generateKey();
return new String(Base64.getEncoder().encode(sk.getEncoded()), "UTF-8");
}
catch (UnsupportedEncodingException | NoSuchAlgorithmException ex) {
System.err.println(ex);
}
return null;
}
private static String encryptText(String PLAINTEXT, String PLAINTEXTKEY, String TOKEN, String alg) {
try {
// Create SecretKey & Cipher
SecretKeySpec sks = new SecretKeySpec(Base64.getDecoder().decode(PLAINTEXTKEY), alg);
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORM);
// Setup byte arrays
byte[] input = PLAINTEXT.getBytes("UTF-8");
byte[] tkb = TOKEN.getBytes("UTF-8");
byte[] iv = new byte[12];
System.arraycopy(tkb, 4, iv, 0, 12);
cipher.init(Cipher.ENCRYPT_MODE, sks, new GCMParameterSpec(128, iv));
cipher.updateAAD(tkb);
byte[] opbytes = new byte[cipher.getOutputSize(PLAINTEXT.length())];
// Perform crypto
int ctlen = cipher.update(input, 0, input.length, opbytes);
ctlen += cipher.doFinal(opbytes, ctlen);
byte[] output = new byte[ctlen];
System.arraycopy(opbytes, 0, output, 0, ctlen);
return new String(Base64.getEncoder().encode(output), "UTF-8");
}
catch (InvalidAlgorithmParameterException | UnsupportedEncodingException |
IllegalBlockSizeException | BadPaddingException | InvalidKeyException |
NoSuchAlgorithmException | NoSuchPaddingException | ShortBufferException ex) {
System.err.println(ex);
}
return null;
}
}

Your issue is this line:
byte[] opbytes = new byte[cipher.getOutputSize(PLAINTEXT.length())];
The length of a string in UTF-8 runes is not always the same as the length of the underlying byte array. You should be using the length of input here, not PLAINTEXT.

Related

Image encryption throws IllegalArgumentException

I am creating a program that encrypts data. De and encryption works perfectly fine. But when decrypting an image I get an error. I am very greatful for any help!
When trying to decrypt my image using the cat_encrypt method like this:
String encrypted = encrypt.cat_encrypt(getFileContent(jf.getSelectedFile()), pass);
Files.writeString(Path.of(jf.getSelectedFile().getPath()), encrypted);
I get the following error:
java.lang.IllegalArgumentException: Input byte array has incorrect ending byte at 54808
Here is my encryption method:
public static String cat_encrypt(String text, String pass) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
Key key = new SecretKeySpec(messageDigest.digest(pass.getBytes(UTF_8)), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal(text.getBytes(UTF_8));
byte[] encoded = Base64.getEncoder().encode(encrypted);
return new String(encoded, UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |
BadPaddingException e) {
throw new RuntimeException("Cannot encrypt", e);
}
}
And this is my decryption method:
public static String cat_decrypt(String text, String pass) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
Key key = new SecretKeySpec(messageDigest.digest(pass.getBytes(UTF_8)), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(DECRYPT_MODE, key);
byte[] decoded = Base64.getDecoder().decode(text.getBytes(UTF_8));
byte[] decrypted = cipher.doFinal(decoded);
return new String(decrypted, UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |
BadPaddingException e) {
try {
sendError((e.getClass().getSimpleName()));
}catch (Exception sE){e.printStackTrace();}
throw new RuntimeException("Cannot decrypt", e);
}
}
I call the decrypt method like this:
String decrypted = encrypt.cat_decrypt(getFileContent(jf.getSelectedFile()), pass);
The full error code:
Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Input byte array has incorrect ending byte at 54808
at java.base/java.util.Base64$Decoder.decode0(Base64.java:876)
at java.base/java.util.Base64$Decoder.decode(Base64.java:566)

Why do I get "BadPaddingException: Message is larger than modulus" when decrypting data stored as a bit array from text file?

I'm using the RSA algorithm to encrypt some numbers, then store those numbers in a bit array within a text file. The numbers are encrypted and added to the text file just fine, but when I try and decrypt them using the method below, I get "BadPaddingException: Message is larger than modulus" this error in my console. I'm pretty new to encryption and this is really throwing me off.
Cipher cipher;
KeyPair pair;
public byte[] encryptData(String data) throws BadPaddingException, IllegalBlockSizeException {
try{
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
pair = keyPairGen.generateKeyPair();
PublicKey publicKey = pair.getPublic();
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
cipher.update(data.getBytes());
cipher.doFinal();
}
catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException ex){
ex.printStackTrace();
}
return cipher.doFinal();
}
writing data into a file
String nums = request.getParameter("numbers");
String password = (String) session.getAttribute("password");
String filename = (password +".txt");
File dir = new File("/volume");
dir.mkdir();
File myfile = new File(dir, filename);
System.out.println("filename: " + filename);
FileOutputStream output;
try {
if (myfile.isFile()){
output = new FileOutputStream(myfile, true);
}
else{
output = new FileOutputStream(myfile);
}
byte[] encryptednums = encryptData(nums);
output.write(encryptednums);
output.close();
} catch (BadPaddingException | IllegalBlockSizeException | IOException e) {
e.printStackTrace();
}
Reading the file
public byte[] bytesFileReader(File filename){
try {
return Files.readAllBytes(Paths.get(String.valueOf(filename)));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
Decryption
HttpSession session = request.getSession();
KeyPair pair = (KeyPair) session.getAttribute("keypair");
Cipher cipher = (Cipher) session.getAttribute("cipher");
String password = (String) session.getAttribute("password");
String filename = (password +".txt");
File dir = new File("/volume");
dir.mkdir();
File myfile = new File(dir, filename);
byte[] encryptedBytes = bytesFileReader(myfile);
try {
cipher.init(Cipher.DECRYPT_MODE, pair.getPrivate());
byte[] decipheredText = cipher.doFinal(encryptedBytes);
System.out.println(new String(decipheredText, StandardCharsets.UTF_8));
} catch (InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
e.printStackTrace();
}
}
Probably worth noting that this is all taking place across two java servlets.
There is one main issue and some minor issues in your code. Let's start will a minor one: when converting a string to a byte array and vice versa all conversions have to use a fixed encoding (see your encryptData method):
cipher.update(data.getBytes());
The main issue is the plaintext size limitation when doing RSA encryption. You can calculate the maximum plaintext size
with this formula
1) RSA keysize divided by 8 - e.g. 2048 / 8 = 256 bytes
2) PKCS1 padding takes 11 bytes
3) maximum of plaintext bytes: 256 - 11 = 245 bytes
In my example code I'm using a RSA keypair with size of (UNSECURE) 512 bits = 64 bytes, minus 11 bytes for padding there are 53 bytes that can get encrypted. The first round will run like expected, the next encryption with 54 bytes of data runs into the
IllegalBlockSizeException: Data must not be longer than 53 bytes
As your code has only the methods but does not show what data is written I can only assume that you try to decrypt all written data in one run - there is a high chance for failure.
The reason why my code runs into the error (and yours not) is the update- and double (!) final-call (beware I left out the catching that you maybe need):
change:
cipher.update(data.getBytes());
cipher.doFinal();
return cipher.doFinal();
to:
return cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
output:
BadPaddingException: Message is larger than modulus
encrypting 12345678901234567890123456789012345678901234567890123
encryptedData length: 64
decryptedData: 12345678901234567890123456789012345678901234567890123
encrypting 123456789012345678901234567890123456789012345678901234
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Data must not be longer than 53 bytes
Security warning: this code uses an UNSECURE 512 bit long RSA key (use minimum 2048 bit) and encrypts with RSA padding PKCS1 that is vulnerable - please use OEAP padding!.
code:
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.nio.charset.StandardCharsets;
import java.security.*;
public class Main {
static KeyPair keyPair;
public static void main(String[] args) throws NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException, NoSuchPaddingException {
System.out.println("BadPaddingException: Message is larger than modulus");
// https://stackoverflow.com/questions/64722370/why-do-i-get-badpaddingexception-message-is-larger-than-modulus-when-decrypti
keyPair = generateKeyPair(512); // ### do not use RSA keys with 512 bits length, minimum 2048 bits
// 512 bit = 64 byte - 11 byte for padding = maximal 53 bytes data to encrypt
System.out.println("\nencrypting 53 chars");
String data53Chars = "12345678901234567890123456789012345678901234567890123";
//String data53Chars = "12345678901234567890";
System.out.println("encrypting " + data53Chars);
byte[] encryptedData = encryptData(data53Chars);
System.out.println("encryptedData length: " + encryptedData.length);
String decryptedData = decryptData(encryptedData);
System.out.println("decryptedData: " + decryptedData);
// now 54 bytes
System.out.println("\nencrypting 54 chars");
String data54Chars = "123456789012345678901234567890123456789012345678901234";
System.out.println("encrypting " + data54Chars);
encryptedData = encryptData(data54Chars);
System.out.println("encryptedData length: " + encryptedData.length);
decryptedData = decryptData(encryptedData);
System.out.println("decryptedData: " + decryptedData);
}
public static byte[] encryptData(String data) throws BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
PublicKey publicKey = keyPair.getPublic();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
}
public static String decryptData(byte[] encryptedBytes) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException {
byte[] decipheredText = new byte[0];
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
decipheredText = cipher.doFinal(encryptedBytes);
return new String(decipheredText, StandardCharsets.UTF_8);
}
static KeyPair generateKeyPair(int keyLength) throws NoSuchAlgorithmException {
final String KEY_ALGORITHM = "RSA";
KeyPairGenerator keygen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keygen.initialize(keyLength, new SecureRandom());
KeyPair keyPair = keygen.generateKeyPair();
return keyPair;
}
}

AES/CFB decryption

i am trying to decrypt using AES/CFB mode by using below code,
final static public String ENCRYPT_KEY = "4EBB854BC67649A99376A7B90089CFF1";
final static public String IVKEY = "ECE7D4111337A511F81CBF2E3E42D105";
private static String deCrypt(String key, String initVector, String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skSpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");
Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skSpec, iv);
byte[] original = cipher.doFinal(encrypted.getBytes());
return new String(original);
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
and throw below error,
Wrong IV length: must be 16 bytes long.
Above ENCRYPT_KEY and IVKEY are valid one. can any one help in that ?
You're calling "ECE7D4111337A511F81CBF2E3E42D105".getBytes("UTF-8"); which will result in byte[] of size 32, not to mention a completely wrong IV.
You need to parse the String into a byte[] instead, for example by borrowing the DatatypeConverter from javax.xml.bind.
IvParameterSpec iv = new IvParameterSpec(
javax.xml.bind.DatatypeConverter.parseHexBinary(initVector));

Generating and using two keys for encryption and decryption in Java

I am working on a Java application which requires I use two keys
generated from different Strings for encrypting and decrypting. One
String is coming from user and other is master key. I looked on net
and found a few references regarding it. I would really like some
help in knowing how to implement this. I will show what I have now.
So as you can see from code, I used some code from other stackoverflow post and modified it a bit. I just don't know how to generate the 2 keys from 2 Strings and from where I can get the SecretKey desKey used for decryption.
Code :
public class Encryption {
public void doStuff() {
String plaintext = "abc";
SecretKey k1 = generateDESkey();
SecretKey k2 = generateDESkey();
String firstEncryption = desEncryption(plaintext, k1);
String decryption = desDecryption(firstEncryption, k2);
String secondEncryption = desEncryption(decryption, k1);
System.out.println(firstEncryption);
System.out.println(decryption);
System.out.println(secondEncryption);
}
public static SecretKey generateDESkey() {
KeyGenerator keyGen = null;
try {
keyGen = KeyGenerator.getInstance("DESede");
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(Test.class
.getName()).log(Level.SEVERE, null, ex);
}
try {
assert keyGen != null;
keyGen.init(112); // key length 56
return keyGen.generateKey();
} catch (NullPointerException ex){
return null;
}
}
public static String desEncryption(String strToEncrypt, SecretKey desKey) {
try {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, desKey);
return Base64.encode(cipher.doFinal(strToEncrypt.getBytes()));
} catch (NoSuchAlgorithmException | NoSuchPaddingException |
IllegalBlockSizeException | BadPaddingException |
InvalidKeyException ex) {
Logger.getLogger(Test.class
.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
public static String desDecryption(String strToDecrypt, SecretKey desKey) {
try {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, desKey);
return new String(cipher.doFinal(Base64.decode(strToDecrypt)));
} catch (NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException
| InvalidKeyException | NoSuchPaddingException ex) {
Logger.getLogger(Test.class
.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
}
If there is any confusion or doubts about it. Kindly let me know.
Answer for the code before it was changed.
You're trying to do DESede with only two keys instead of three.
That might generally work, but not as you've written. The problem is the padding. In the second step, you try to decrypt the ciphertext with an other key than with what it was encrypted, so the decryption will fail more than 255 out of 256 times, because the padding will be wrong (also because you use Base64 encoding where it is not necessary).
If you really want to do it, you will have to decrypt without padding and without Base64 encoding. The good thing is that the unencoded ciphertext is already a multiple of the blocksize, so there is no stopping you to use "DES/ECB/NoPadding".
public static void main(String[] args) {
// First I would like to create keys by giving Strings
SecretKey k1 = generateDESkey();
SecretKey k2 = generateDESkey();
// encryption
byte[] firstEncryption = desEncryption("plaintext".getBytes("UTF-8"), k1, false);
byte[] decryption = desDecryption(firstEncryption, k2, true);
byte[] secondEncryption = desEncryption(decryption, k1, true);
// decryption
byte[] firstDecryption = desDecryption(secondEncryption, k1, true);
byte[] encryption = desEncryption(firstDecryption, k2, true);
byte[] secondDecryption = desDecryption(encryption, k1, false);
System.out.println(new String(secondDecryption)); // plaintext
}
public static byte[] desEncryption(byte[] strToEncrypt, SecretKey desKey, boolean noPadding) {
try {
Cipher cipher = Cipher.getInstance(noPadding ? "DES/ECB/NoPadding" : "DES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, desKey);
return cipher.doFinal(strToEncrypt);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static byte[] desDecryption(byte[] strToDecrypt, SecretKey desKey, boolean noPadding) {
try {
Cipher cipher = Cipher.getInstance(noPadding ? "DES/ECB/NoPadding" : "DES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, desKey);
return cipher.doFinal(strToDecrypt);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
This is actually an equivalent implementation of DESede with two keys when the general key is constructed in this way:
SecretKey k1 = generateDESkey();
SecretKey k2 = generateDESkey();
byte[] edeKeyBytes = new byte[24];
System.arraycopy(k1.getEncoded(), 0, edeKeyBytes, 0, 8);
System.arraycopy(k2.getEncoded(), 0, edeKeyBytes, 8, 8);
System.arraycopy(k1.getEncoded(), 0, edeKeyBytes, 16, 8);
edeKey = new SecretKeySpec(edeKeyBytes, "DESede");
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, edeKey);
System.out.println(Base64.encode(cipher.doFinal("plaintext".getBytes("UTF-8"))));
DESede uses three keys which we will call k1, k2 and k3. All of them are concatenated into a single byte array. In your case k1 is used a second time in place of k3.

How do I encrypt/decrypt a string of text using 3DES in java? [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How do I use 3des encryption/decryption in Java?
How do I encrypt/decrypt a string of text using 3DES in java?
I found my answer. Duplicate question that didn't show up when I asked this one.
How do I use 3des encryption/decryption in Java?
From an old code:
public void testSymCypher(SecretKey k, String str)
throws BadPaddingException, IllegalBlockSizeException,
InvalidAlgorithmParameterException, InvalidKeyException,
NoSuchAlgorithmException, NoSuchPaddingException
{
Cipher cip = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cip.init(Cipher.ENCRYPT_MODE,k);
byte[] ciphered = cip.doFinal(str.getBytes());
byte iv[] = cip.getIV();
// printing the ciphered string
printHexadecimal(ciphered);
IvParameterSpec dps = new IvParameterSpec(iv);
cip.init(Cipher.DECRYPT_MODE,k,dps);
byte[] deciphered = cip.doFinal(ciphered);
// printing the deciphered string
printHexadecimal(deciphered);
}
Notice than other usage of DESede are available in Java JDK 6:
DESede/CBC/NoPadding (168)
DESede/CBC/PKCS5Padding (168)
There is also ECB mode available (but be carreful to not use it twice !!), you don't need to use iv part in this case:
DESede/ECB/NoPadding (168)
DESede/ECB/PKCS5Padding (168)
To generate key for DESede:
KeyGenerator generatorDes = KeyGenerator.getInstance("DESede");
SecretKey skaes = generatorDes.generateKey();
Finally I recommand reading this document from SUN if you need to work on Java and Cryptography
We use this little helper class for password-based DES encryption from String to Hex String and back - not sure how to get this working with 3DES though:
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
public class DesHelper {
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DesHelper.class);
static final byte[] SALT = { (byte) 0x09, /* snip - randomly chosen but static salt*/ };
static final int ITERATIONS = 11;
private Cipher _ecipher;
private Cipher _dcipher;
public DesHelper(final String passphrase) {
try {
final PBEParameterSpec params = new PBEParameterSpec(SALT, ITERATIONS);
final KeySpec keySpec = new PBEKeySpec(passphrase.toCharArray());
final SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES")
.generateSecret(keySpec);
_ecipher = Cipher.getInstance(key.getAlgorithm());
_dcipher = Cipher.getInstance(key.getAlgorithm());
_ecipher.init(Cipher.ENCRYPT_MODE, key, params);
_dcipher.init(Cipher.DECRYPT_MODE, key, params);
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
public String encrypt(final String string) {
try {
// Encode the string into bytes using utf-8
final byte[] bytes = string.getBytes("UTF-8");
// Encrypt
final byte[] enc = _ecipher.doFinal(bytes);
// Encode bytes to base64 to get a string
return bytesToHex(enc);
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
public String decrypt(final String str) {
try {
// Decode base64 to get bytes
final byte[] dec = hexToBytes(str);
// Decrypt
final byte[] utf8 = _dcipher.doFinal(dec);
// Decode using utf-8
return new String(utf8, "UTF8");
} catch (final Exception e) {
log.info("decrypting string failed: " + str + " (" + e.getMessage() + ")");
return null;
}
}
private static String bytesToHex(final byte[] bytes) {
final StringBuilder buf = new StringBuilder(bytes.length * 2);
for (final byte b : bytes) {
final String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
buf.append("0");
}
buf.append(hex);
}
return buf.toString();
}
private static byte[] hexToBytes(final String hex) {
final byte[] bytes = new byte[hex.length() / 2];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
}
return bytes;
}
}
You would use this class like this:
public static void main(final String[] args) {
final DesHelper h = new DesHelper("blabla");
System.out.println(h.decrypt(h.encrypt("foobar")));
}
I wrote an article on this sometimes back. Please visit the following link in my blog that has a working, completed code with explanations and diagram.
View My Triple DES Encryption Article, Code Here
Hopefully you will find it helpful.
You may also consider using a stream cipher (e.g., OFB or CTR mode on top of a 3DES block encryption), so that you don't have to deal with padding the string to a multiple of the cipher blocksize.

Categories

Resources