I have some problem with decrypting text:
My backend often throws IllegalBlockSizeException during decryption:
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
When I try again (1-3 times) finally it can decrypt the SAME text successfully and send the response to FE.
As I noticed it usually happens when I try to decrypt many (about 100) Strings in short time (2 requests arrive from FE, 15 record queried from db / request, 2 encrypted fields / record)
My server runs on a Raspberry Pi B+ with Raspbian. The problem is not present on "normal" PC.
The encryption class:
package bookmarks.common.encryption.base;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.net.util.Base64;
import lombok.extern.slf4j.Slf4j;
#Slf4j
public class DefaultEncryptor {
private static final int SIZE = 16;
private static final String ALGORITHM = "AES";
private static final Base64 BASE_64 = new Base64();
private final Key key;
private final Cipher cipher;
public DefaultEncryptor(String password) {
byte[] key = createKey(password);
this.key = new SecretKeySpec(key, ALGORITHM);
try {
cipher = Cipher.getInstance(ALGORITHM);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
log.error("Error creating encryptor.", e);
throw new RuntimeException(e);
}
}
private byte[] createKey(String password) {
if (password.length() < SIZE) {
int missingLength = SIZE - password.length();
StringBuilder passwordBuilder = new StringBuilder(password);
for (int i = 0; i < missingLength; i++) {
passwordBuilder.append(" ");
}
password = passwordBuilder.toString();
}
return password.substring(0, SIZE).getBytes(StandardCharsets.UTF_8);
}
public String encrypt(String text) {
try {
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal(text.getBytes(StandardCharsets.UTF_8));
byte[] base64 = BASE_64.encode(encrypted);
return new String(base64, StandardCharsets.UTF_8);
} catch (InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
log.error("Error encryping value.", e);
throw new RuntimeException(e);
}
}
public String decrypt(String text) {
try {
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] base64 = BASE_64.decode(text.getBytes(StandardCharsets.UTF_8));
byte[] decrypted = cipher.doFinal(base64);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
log.error("Error decrypting value.", e);
throw new RuntimeException(e);
}
}
}
Any ideas what can cause the problem?
Using only AES cipher without IV implies using AES/ECB/PKCS5Padding. This cipher and mode requires the input to decrypt being multiple of block size (128 bit)
Input length must be multiple of 16 when decrypting with padded cipher
The cipher complains about the input being not multiple of 16
As I noticed it usually happens when I try to decrypt many (about 100) Strings in short time
What came to my mind (as James commented, it is my educated guess), the cipher object is not thread safe. With high confidence I'd say either
the input is passed as not complete (you may want to log the input byte array length)
there are multiple threads reusing the same cipher object
The problem was the static constant BASE_64, what is not Thread-safe. When multiple requests arrived at the same time, spring-boot processed them parallel, and the base64 decoding returned corrupt result, and of course invalid data cannot be decrypted.
Related
I have been struggling with this for a couple of days now. I'm required to consume an API that takes an encrypted parameter. The API was written in C#. The encryption requested is the following:
Algorithm: AES
Cipher mode: CBC
Padding mode: PKCS7
Block size: 128
Key size: 256
Key: String --> The key is generated by converting a provided string to a byte array of size 32: Encoding.ASCII.GetBytes(…). The API states that the String is generated by them using MD5 hashing function of a string.
IV: IV array is generated by converting a provided string to a byte array of size 16: Encoding.ASCII.GetBytes(…).
Representation of encrypted string: Base64
After searching and trying so many things that were suggested online, I'm still unable to produce the same encrypted value (Specially that PKCS7 is not supported by default and PKCS5 should be working the same, but it's not). Here are some things that I've tried:
1) Using bouncy castle jar to use PKCS7
2) Adding JCE compliance to be able to remove the limit on key and block sizes.
After contacting them, they sent me an android snippet that's working (which if I run in plain java 8 complains about the provider (NoSuchAlgorithmException: Cannot find any provider supporting AES/CBC/PKCS7Padding)):
public static String encrypt(String value) {
String plainText = value;
String escapedString;
try {
byte[] key = ENCRYPT_KEY.getBytes("UTF-8");
byte[] ivs = ENCRYPT_IV.getBytes("UTF-8");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
AlgorithmParameterSpec paramSpec = new IvParameterSpec(ivs);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, paramSpec);
escapedString = Base64.encodeToString(cipher.doFinal(plainText.getBytes("UTF-8")), Base64.DEFAULT).trim();
return escapedString;
} catch (Exception e) {
e.printStackTrace();
return value;
}
}
Please any help would be really appreciated.
Here's a code snippet from what I tried:
package com.melhem.TestJava;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class StringFunc {
final static String key = "API_KEY_32_CHARs";
final static String iv = "API_IV_16_CHARs";
final static String algorithm = "AES/CBC/PKCS7Padding";
private static Cipher cipher = null;
private static SecretKeySpec skeySpec = null;
private static IvParameterSpec ivSpec = null;
public static void main(String[] args) {
System.out.println(encrypt("STRING_TO_ENCODE"));
}
private static void setUp(){
try{
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
skeySpec = new SecretKeySpec(key.getBytes("ASCII"), "AES");
ivSpec = new IvParameterSpec(iv.getBytes("ASCII"));
cipher = Cipher.getInstance(algorithm);
}catch(NoSuchAlgorithmException | NoSuchPaddingException ex){
ex.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static String encrypt(String str){
try{
// Integer strL = (int) Math.ceil(str.length() / 8.0);
// Integer strB = strL*8;
// str = padRight(str, ' ', strB);
setUp();
try {
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivSpec);
System.out.println("Block size: " + cipher.getBlockSize() * 8);
System.out.println("Algorithm name: " + cipher.getAlgorithm());
System.out.println("Key size: " + skeySpec.getEncoded().length * 8);
} catch (InvalidAlgorithmParameterException ex) {
ex.printStackTrace();
return "";
}
byte[] enc = cipher.doFinal(str.getBytes("ASCII"));
String s = new String(Base64.getEncoder().encode(enc));
s = s.replace("+", "__plus__");
s = s.replace("/", "__slash__");
return s;
}catch(InvalidKeyException | IllegalBlockSizeException | BadPaddingException ex){
ex.printStackTrace();
return "";
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return "";
}
}
public static String padRight(String msg, char x, int l) {
String result = "";
if (!msg.isEmpty()) {
for (int i=0; i<(l-msg.length()); i++) {
result = result + x;
}
result = msg + result;
}
return result;
}
}
Java Cipher package only supports PKCS#7 padding with AES/CBC/PKCS5Padding. This is not a good naming since PKCS#5 padding supports 8-byte block sizes as DES and PKCS#7 supports up to 255 bytes. For Java use this;
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
The #5 and #7 are not interchangeable for the most modern block ciphers as AES is a 128-bit block cipher. See the question on Crypto.StackExchange.
and, for using AES with 256-bit key size;
Java standard cipher library limited to 128-bit key size. You must go and download Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 6
In my Java code, I'm trying to encrypt a String using RSA, with a public key. The String is a Base64 encoded String that represents an Image (Image was converted to String). It will be decrypted using a private key.
During the Encryption, I first got an exception "javax.crypto.IllegalBlockSizeException: Data must not be longer than 190 bytes". So, I processed the String (plaintext) in blocks of 189 which then resolved it.
During the Decryption, I got another exception "javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes". So, I processed the byte[] (ciphertext), by converting it to a String first, in blocks of 256 which then resolved it as well.
Again, during my decryption process, I end up getting a "javax.crypto.BadPaddingException: Decryption error" Exception, which I have been unable to resolve.
Upon the recommendation of experts on this site, I used "OAEPWithSHA-256AndMGF1Padding". I even tried using No Padding, after other padding methods, to see if the Exception would go away, but it did not work. What have I done wrong?
I was able to identify that the Exception was thrown at the line - decryptedImagePartial = t.rsaDecrypt(cipherTextTrimmed.getBytes(), privateKey);
- which is in the decryption portion of the main method.
Please bear with me if my coding practices are poor. I'd really prefer to just find out the error behind the exception for now.
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
public class Tester
{
public KeyPair buildKeyPair() throws NoSuchAlgorithmException
{
final int keySize = 2048;
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(keySize);
return keyPairGenerator.genKeyPair();
}
public byte[] encrypt(PublicKey publicKey, String message) throws Exception
{
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(message.getBytes());
}
public String decrypt(PrivateKey privateKey, byte [] encrypted) throws Exception
{
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(encrypted));
}
public byte[] rsaEncrypt(String watermarkMsg, PublicKey publicKey) throws Exception
{
byte[] cipherText = encrypt(publicKey, watermarkMsg);
return cipherText;
}
public String rsaDecrypt(byte[] cipherText, PrivateKey privateKey) throws Exception
{
String plainText = decrypt(privateKey, cipherText);
return plainText;
}
public static void main(String args[]) throws NoSuchAlgorithmException
{
Tester t = new Tester();
String inputImageFilePath = "<file_path_here";
String stringOfImage = null;
byte[] encryptedImage = null;
byte[] encryptedImagePartial = null;
KeyPair keyPair = t.buildKeyPair();
PublicKey pubKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate()
//-----------IMAGE TO STRING CONVERSION----------------
//The imagetostring() function retrieves the image at the file path and converts it into a Base64 encoded String
try
{
stringOfImage = t.imagetostring(inputImageFilePath);
}
catch(Exception e)
{
System.out.println(e.toString());
}
//-----------ENCRYPTION OF STRING----------------
//The encryption is done in blocks of 189, because earlier I got an exception - "javax.crypto.IllegalBlockSizeException: Data must not be longer than 190 bytes"
try
{
String plaintext = stringOfImage;
String plaintextTrimmed = "";
System.out.println(stringOfImage);
encryptedImage = new byte[15512]; //The size is given as 15512 because the length of the particular string was found to be 15512
while(plaintext!="")
{
if(plaintext.length()>189)
{
plaintextTrimmed = plaintext.substring(0, 189);
plaintext = plaintext.substring(189);
}
else
{
plaintextTrimmed = plaintext;
plaintext = "";
}
encryptedImagePartial = t.rsaEncrypt(plaintextTrimmed, pubKey);
encryptedImage = t.concatenate(encryptedImage, encryptedImagePartial);
System.out.println(encryptedImage.length);
}
}
catch(Exception e)
{
System.out.println(e.toString());
}
t.byteDigest(encryptedImage);
//-----------DECRYPTION OF STRING--------------
//The decryption is done in blocks of 189, because earlier I got an exception - "javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes"
try
{
// The ciphertext is located in the variable encryptedImage which is a byte[]
String stringRepOfCipherText = new String(encryptedImage); String cipherTextTrimmed = "";
String decryptedImagePartial;
String decryptedImage = "";
while(stringRepOfCipherText!="")
{
if(stringRepOfCipherText.length()>189)
{
cipherTextTrimmed = stringRepOfCipherText.substring(0, 189);
stringRepOfCipherText = stringRepOfCipherText.substring(189);
}
else
{
cipherTextTrimmed = stringRepOfCipherText;
stringRepOfCipherText = "";
}
decryptedImagePartial = t.rsaDecrypt(cipherTextTrimmed.getBytes(), privateKey);
decryptedImage = decryptedImage + decryptedImagePartial;
}
}
catch(BadPaddingException e)
{
System.out.println(e.toString());
}
catch(Exception e)
{
System.out.println(e.toString());
}
}
}
Also, I noticed a few other examples where KeyFactory was used to generate the keys. Could anyone also tell me the difference between using KeyFactory and what I have used?
You can not cut the ciphertext into arbitrary chunks!
Since you specifically asked for plain RSA without symmetric algorithms involved (which I strongly recommend against!), this is what you need to do:
Find out the maximum payload size for your RSA configuration.
Split your plaintext into chunks of this size
Encrypt each chunk individually and do not simply concatenate them and discard chunk boundaries!
During decryption:
Pass each ciphertext chunk to the decrypt function using the original size it has after encryption. Do not append any data and do not create "substrings".
Concatenate the resulting plaintexts.
Ideally you should use a hybrid encryption scheme:
generate an encryption key (encKey)
encrypt your image using a symmetric algorithm with encKey
encrypt encKey using pubKey with RSA
Symmetric ciphers can be used in different modes of operation, that avoid such length limitations.
First of all, it makes absolutely no sense to first encode the image to base 64. The input of modern ciphers consist of bytes, and images are already bytes. You may want to base 64 encode the ciphertext if you want to store that a string.
The input block size is indeed 190 bytes. You can see a table for RSA / OAEP here (don't forget to upvote!). I'm not sure why you would want to use 189 in that case; my code is however generalized. The output block size is simply the key size for RSA as it is explicitly converted to the key size in bytes (even if it could be smaller).
During decryption you convert the ciphertext to a string. However, string decoding in Java is lossy; if the decoder finds a byte that doesn't represent a character then it is dropped silently. So this won't (always work), resulting for instance in a BadPaddingException. That's OK though, we can keep to binary ciphertext.
So without further ado, some code for you to look at. Note the expansion of the ciphertext with the 66 bytes per block and the poor performance of - mainly - the decryption. Using AES with RSA in a hybrid cryptosystem is highly recommended (and not for the first time for this question).
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import javax.crypto.Cipher;
public class Tester {
private static final int KEY_SIZE = 2048;
private static final int OAEP_MGF1_SHA256_OVERHEAD = 66;
public static KeyPair buildKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
public static void main(String args[]) throws Exception {
KeyPair keyPair = Tester.buildKeyPair();
RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// assumes the bitLength is a multiple of 8 (check first!)
int keySizeBytes = pubKey.getModulus().bitLength() / Byte.SIZE;
byte[] image = new byte[1000];
Arrays.fill(image, (byte) 'm');
// --- encryption
final Cipher enc;
try {
enc = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("OAEP with MGF-1 using SHA-256 not available in this runtime", e);
}
enc.init(Cipher.ENCRYPT_MODE, pubKey);
int fragmentsize = keySizeBytes - OAEP_MGF1_SHA256_OVERHEAD;
ByteArrayOutputStream ctStream = new ByteArrayOutputStream();
int off = 0;
while (off < image.length) {
int toCrypt = Math.min(fragmentsize, image.length - off);
byte[] partialCT = enc.doFinal(image, off, toCrypt);
ctStream.write(partialCT);
off += toCrypt;
}
byte[] ct = ctStream.toByteArray();
// --- decryption
Cipher dec = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
dec.init(Cipher.DECRYPT_MODE, privateKey);
ByteArrayOutputStream ptStream = new ByteArrayOutputStream();
off = 0;
while (off < ct.length) {
int toCrypt = Math.min(keySizeBytes, ct.length - off);
byte[] partialPT = dec.doFinal(ct, off, toCrypt);
ptStream.write(partialPT);
off += toCrypt;
}
byte[] pt = ptStream.toByteArray();
// mmmm...
System.out.println(new String(pt, StandardCharsets.US_ASCII));
}
}
I want to encrypt a text using Arduino and decrypt it using Java. I tried this code from this link but without success.
I am using this Arduino library for encryption on the Arduino and the Java Cryptographic Extension (JCE) framework for the Java side.
This the Arduino code:
#include <AESLib.h> //replace the ( with < to compile (forum posting issue)
#include <Base64.h>
void setup() {
Serial.begin(9600);
uint8_t key[] = {50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50};
//expressed in 16 unsigned in characters, be careful not to typecast this as a char in a decrypter
//16- 50's (uint8) is the way to express 16 2's in ASCII, the encryption matches to what will show up on http://aesencryption.net/
char data[] = "0123456789012345";
//The message to encrypt, 16 chars == 16 bytes, no padding needed as frame is 16 bytes
char encryptedData[100];
int *size;
Serial.print("Message:");
Serial.println(data);
aes128_enc_single(key, data);
Serial.print("encrypted:");
Serial.println(data);
int inputLen = sizeof(data);
int encodedLen = base64_enc_len(inputLen);
char encoded[encodedLen];
base64_encode(encoded, data, inputLen);
Serial.print("encrypted(base64):"); //used
Serial.println(encoded);
Serial.println("***********Decrypter************");
int input2Len = sizeof(encoded);
int decodedLen = base64_dec_len(encoded, input2Len);
char decoded[decodedLen];
base64_decode(decoded, encoded, input2Len);
Serial.print("encrypted (returned from Base64):");
Serial.println(decoded);
Serial.print("decrypted:");
Serial.println(decoded);
}
void loop() {
}
This is the Java code:
package main;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
public class ForTest {
public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
String message= "0123456789012345";//Message to encode
String key = "2222222222222222";
// 128 bit key -this key is processed as ASCII values
System.out.println("Processing 3.0 AES-128 ECB Encryption/Decryption Example");
System.out.println("++++++++++++++++++++++++++++++++");
System.out.println("Original Message: " + message);
System.out.println("Key: " + key);
System.out.println("key in bytes: "+key.getBytes("UTF-8"));
System.out.println("==========================");
//Encrypter
SecretKeySpec skeySpec_encode = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher_encode = Cipher.getInstance("AES/ECB/NoPadding");
// Cipher cipher_encode = Cipher.getInstance("AES/ECB/PKCS5PADDING"); //AES-CBC with IV encoding, ECB is used without the IV, example shown on http://aesencryption.net/
cipher_encode.init(Cipher.ENCRYPT_MODE, skeySpec_encode);
byte[] encrypted = cipher_encode.doFinal(message.getBytes());
System.out.println("Encrypted String (base 64): "
+ DatatypeConverter.printBase64Binary(encrypted));
//encode without padding: Base64.getEncoder().withoutPadding().encodeToString(encrypted));
//encode with padding: Base64.getEncoder().encodeToString(encrypted));
String base64_encrypted = DatatypeConverter.printBase64Binary(encrypted);
//Decrypter
SecretKeySpec skeySpec_decode = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher_decode = Cipher.getInstance("AES/ECB/NoPadding");
// Cipher cipher_decode = Cipher.getInstance("AES/ECB/PKCS5PADDING");
cipher_decode.init(Cipher.DECRYPT_MODE, skeySpec_decode);
System.out.println("length: "+"Ouril+UTDF8htLzE".length());
byte[] decrypted_original = cipher_decode.doFinal(DatatypeConverter.parseBase64Binary("Ouril+UTDF8htLzEhiRj7wA="));
String decrypt_originalString = new String(decrypted_original);
System.out.println("Decrypted String: " + decrypt_originalString);
}
}
In Java when I try to decrypt the encoded String by Arduino I get this:
Processing 3.0 AES-128 ECB Encryption/Decryption Example
++++++++++++++++++++++++++++++++
Original Message: 0123456789012345
Key: 2222222222222222
key in bytes: [B#2a139a55
==========================
Encrypted String (base 64): Ouril+UTDF8htLzEhiRj7w==
length: 16
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes
at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1016)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:960)
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 main.ForTest.main(ForTest.java:46)
Any ideas?
Thanks!
I was able to get this working after like a week - working Arduino documentation on integration with other systems is crap : )
Working Arduino code:
#include "mbedtls/aes.h"
#include <Arduino.h>
#include <HTTPClient.h>
#include <base64.h>
void makeUpdateAPICall()
{
if (WiFi.status() == WL_CONNECTED)
{
HTTPClient http;
// Your Domain name with URL path or IP address with path
http.begin(serverName);
// Specify content-type header
http.addHeader("Content-Type", "text/plain");
http.addHeader("Authorization", "Bearer XXXXXXXX [whatever your web token is]");
http.addHeader("X-Content-Type-Options", "nosniff");
http.addHeader("X-XSS-Protection", "1; mode=block");
//AES Encrypt
esp_aes_context aesOutgoing;
unsigned char key[32] = "1234567812345678123456781234567" ;
key[31] = '8'; // we replace the 32th (index 31) which contains '/0' with the '8' char.
char *input = "Tech tutorials x";
unsigned char encryptOutput[16];
mbedtls_aes_init(&aesOutgoing);
mbedtls_aes_setkey_enc(&aesOutgoing, key, 256);
int encryptAttempt = mbedtls_aes_crypt_ecb(&aesOutgoing, MBEDTLS_AES_ENCRYPT, (const unsigned char *)input, encryptOutput);
USE_SERIAL.println();
USE_SERIAL.println("MBEDTLS_AES_EBC encryption result:\t ");
USE_SERIAL.print(encryptAttempt); //0 means that the encrypt/decrypt function was successful
USE_SERIAL.println();
mbedtls_aes_free(&aesOutgoing);
int encryptSize = sizeof(encryptOutput) / sizeof(const unsigned char);
USE_SERIAL.println("Size of AES encrypted output: ");
USE_SERIAL.println(encryptSize);
//Base 64 Encrypt
int inputStringLength = sizeof(encryptOutput);
int encodedLength = Base64.decodedLength((char *)encryptOutput, inputStringLength);
char encodedCharArray[encodedLength];
Base64.encode(encodedCharArray, (char *)encryptOutput, inputStringLength);
//Send to server
USE_SERIAL.print("Sending to server.");
int httpResponseCode = http.POST(encodedCharArray);
String payload = "{}";
if (httpResponseCode > 0)
{
//Retrieve server response
payload = http.getString();
}
// Free resources
http.end();
}
WiFi.disconnect();
}
Working Java code:
public static String decrypt(String strToDecrypt, String key) {
byte[] encryptionKeyBytes = key.getBytes();
Cipher cipher;
try {
cipher = Cipher.getInstance("AES/ECB/NoPadding");
SecretKey secretKey = new SecretKeySpec(encryptionKeyBytes, "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt.getBytes("UTF-8"))));
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
Working on the return process now.
You call the Java side with this code:
final String decryptedText = AES.decrypt(encryptedStr, "12345678123456781234567812345678");
System.out.println("Decrypted AES ECB String: ");
System.out.println(decryptedText);
Wanted to provide this for any poor slob who finds him/herself in the same boat : )
Hope this helps!
I'm trying to generate an AES key, encrypt it and decrypt it using RSA.
It kind of works, except that after decrypting the data and encoding with Base64 I get a pile of "A" letters before my actual string(the base64-encoded AES key). I guess these were zeros in byte.
The "RSA/ECB/NoPadding" parameters are mandatory. What am I doing wrong ? I need it to return the original string/bytes.
package szyfrator;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.tools.bzip2.CBZip2OutputStream;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import com.sun.org.apache.xml.internal.security.utils.Base64;
public class Cryptography {
private static byte[] aesKey;
private static String base64AESKey;
private static byte[] encryptedAESKey;
private static String base64AESEncryptedKey;
private static byte[] aesKeyTransformed;
public static void main(String args[]){
Cryptography.generateAESkey();
Cryptography.encryptAESKey(new File("G:\\HASHBABYHASH\\public.txt"));
Cryptography.decryptAESKey(new File("G:\\HASHBABYHASH\\private.txt"));
System.out.println("String: " + Base64.encode(Cryptography.getAesKey()) + "\r\n");
System.out.println("Encrypted string: " + Cryptography.getBase64EncryptedKey() + "\r\n");
System.out.println("Decrypted String: " + Base64.encode(Cryptography.getAesKeyTransformed()) + "\r\n");
}
public static void generateAESkey(){
try {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey secretKey = keyGen.generateKey();
byte[] keyBytes = secretKey.getEncoded();
base64AESKey = Base64.encode(keyBytes);
aesKey = keyBytes;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
public static void encryptAESKey(File publicKeyFile){
try {
FileInputStream input = new FileInputStream(publicKeyFile);
byte[] decoded = Base64.decode(IOUtils.toByteArray(input));
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(decoded);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(publicSpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
encryptedAESKey = cipher.doFinal(aesKey);
base64AESEncryptedKey = Base64.encode(encryptedAESKey);
input.close();
}catch (Exception e) {
e.printStackTrace();
}
}
public static void decryptAESKey(File privateKeyFile){
try {
FileInputStream input = new FileInputStream(privateKeyFile);
byte[] decoded = Base64.decode(IOUtils.toByteArray(input));
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
aesKeyTransformed = cipher.doFinal(encryptedAESKey);
input.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
Here is the result:
String: xVwH7Nbz84emVoH0J31sRHC+B669T9wCUVlTDhYgXiI=
Encrypted string: INTA8rx46hX6bZbDIl4iiWsUGO4ywCW0Aee1reqQ3wR5X7He5ztLHvyZoa0WZmUGYbYwprNGffRI
OVJFxczMHkxUfHU1WWCTzcfNylD+sWObIYrbyc13aZi9OL/r1GXuaGtkIgTJyqv0QPHfIri7iaH3
Lr/F4EIcyphJM3E2reQ=
Decrypted String: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxVwH7Nbz84emVoH0J31sRHC+
B669T9wCUVlTDhYgXiI=
In RSA some data is encoded into a large number and calculated upon. NoPadding (unpadded or textbook RSA) means that you're fully responsible for the proper encoding of the message. All of the calculations are done against a large modulus (should be at least 2048 bit nowadays). Since Java assumes big-endian numbers, your message is encoded into the least significant bytes automatically, but the decryption returns the decoded message in the same size of the modulus, because it cannot know whether the leading zero-bytes where intentional or not.
In order to make this calculation correct and secure it is necessary to apply padding. The old-style PKCS#1 v1.5 padding is not considered secure nowadays, but it only has 11 bytes of overhead (only 2048/8-11=245 bytes can be encrypted with a key of 2048 bit). The newer PKCS#1 v2.1 padding (OAEP) is considered secure and should be used here. It does have an overhead of 42 bytes if SHA-1 is used.
The "RSA/ECB/NoPadding" parameters are mandatory.
This is really bad, because it is very insecure: Which attacks are possible against raw/textbook RSA?
If you're not willing to simply change the cipher string to Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");, you will have to remove the leading zeros yourself. The problem is of course that this "zero-padding" mode is ambiguous and if the plaintext begins with a 0x00 byte, you will not be able to distinguish it from a padding byte and will have to remove it, thus breaking your plaintext. If the plaintext is an AES key as in your case, there is a 0.3% chance that it begins with a 0x00 byte and thus breaks the key. You will have to make sure that the key is actually correct and fill up with zero bytes if it has not the correct length.
Here is how you can remove leading zero bytes:
byte[] unpadZeros(byte[] in) {
int i = 0;
while(in[i] == 0) i++;
return Arrays.copyOfRange(in, i, in.length);
}
If you know that you're decrypting an AES key, then it's possible to make the unpadding no produce wrong data:
byte[] unpadZerosToGetAesKey(byte[] in) {
int i = 0;
while(in[i] == 0) i++;
int len = in.length - i;
if (len <= 16) len = 16;
else if (len <= 24) len = 24;
else len = 32;
return Arrays.copyOfRange(in, in.length - len, in.length);
}
I'm having trouble creating an encrypted string using AES/CBC/PKCS5Padding with a 128-bit key. I have code to decrypt an encrypted string. I have an example encrypted string from another system that decrypts successfully, but when I try to create my own encrypted string it is not padded properly for some reason. When I decrypt my encrypted string it only shows the characters after the 16 byte.
All of the examples I find either assume the encryption happens first then decryption happens right after that with variables set during encryption or they are randomly generating a key, but in my case i want to use a known key.
I am really stuck so any help would be greatly appreciated, thank you very much for your time and efforts!
Example:
Original Text: 01234567891234565
Encrypted: zmb16qyYrdoW6akBdcJv7DXCzlw0qU7A2ea5q4YQWUo=
Key length: 16
Decrypted: 5 (this is the last digit in the Original Text String)
Sample Code:
package com.company.encrypt.tests;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class TestEncryptDecrypt {
private static final String characterEncoding = "UTF-8";
private static final String cipherTransformation = "AES/CBC/PKCS5Padding";
private static final String aesEncryptionAlgorithm = "AES";
public static void main(String[] args) throws Exception {
String key1 = "1234567812345678";
String text = "01234567891234565";
System.out.println("Original Text: " + text);
String encrypted = encrypt(text, key1);
System.out.println("Encrypted: " + encrypted);
String decrypted = decrypt(encrypted, key1);
System.out.println("Decrypted: " + decrypted);
}
public static String decrypt(String encryptedText, String key) throws Exception {
String plainText = null;
int keyLength = key.length();
System.out.println("Key length: " + String.valueOf(keyLength));
byte[] encryptedTextBytes = Base64.decodeBase64(encryptedText.getBytes());
byte[] keyBytes = key.getBytes();
byte[] initialVector = Arrays.copyOfRange(encryptedTextBytes, 0, keyLength);
byte[] trimmedCipherText = Arrays.copyOfRange(encryptedTextBytes, keyLength, encryptedTextBytes.length);
try {
Cipher cipher = Cipher.getInstance(cipherTransformation);
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, aesEncryptionAlgorithm);
IvParameterSpec ivParameterSpec = new IvParameterSpec(initialVector);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] clearText;
clearText = cipher.doFinal(trimmedCipherText);
plainText = new String(clearText, characterEncoding);
} catch(NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException
| InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return plainText;
}
public static String encrypt(String plainText, String encryptionKey) throws Exception {
SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), aesEncryptionAlgorithm);
Cipher cipher = Cipher.getInstance(cipherTransformation);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] plainTextBytes = plainText.getBytes("UTF-8");
byte[] encrypted = cipher.doFinal(plainTextBytes);
return new String(Base64.encodeBase64(encrypted));
}
}
I've noticed that in the decrypt() function, you separated the encrypted array into two parts: first 16 bytes, and the rest. You used the first 16 bytes as the IV for decryption, however, you did not prepend the 16 byte IV to the beginning of the encrypted message in encrypt(). This results in the first 16 bytes of the plaintext to be lost. I presume you assumed that doFinal() automatically does that for you, but it doesn't.
To fix this, before returning the encrypted message, prepend the IV, which can be retrieved using cipher.getIV(). You can accomplish this using the ArrayUtils.addAll() from Apache Commons Lang library, or simply write your own function to do it. Another thing to note is that the IV will always be the block size, which is 16 bytes for AES, no matter the key size.
Hope this answer helps!