Java AES Encryption with CBC and PKCS7Padding - java

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

Related

Trying to decript in java the oracle procedure DBMS_OBFUSCATION_TOOLKIT.DESDECRYPT

I'm trying to adapt this code to java in order to reduce the number of calls to DB:
--set serveroutput on
declare
l_aux NUMBER;
l_cle RAW(9) := utl_raw.cast_to_raw('example21');
l_crypt_raw VARCHAR2(200);
l_crypt_str VARCHAR2(200);
p_txt_desencrip varchar2(200):='8387F8937F5F842F805C44B88429D2CD';
BEGIN
l_crypt_raw := utl_raw.cast_to_raw(utl_raw.cast_to_varchar2( p_txt_desencrip));
DBMS_OBFUSCATION_TOOLKIT.DESDECRYPT ( input => p_txt_desencrip
, key => l_cle
, decrypted_data => l_crypt_raw
);
l_crypt_str := utl_raw.cast_to_varchar2(l_crypt_raw);
l_aux := LENGTH(l_crypt_str);
l_crypt_str := RPAD(l_crypt_str,l_aux-ASCII(SUBSTR(l_crypt_str,l_aux)));
DBMS_OUTPUT.PUT_LINE('Decypted message->' || l_crypt_str);
END;
I have been seen all attepts of solving this task, but anyway my main problem is that my key have 9 characters. Here is my java code:
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import com.sun.mail.util.BASE64DecoderStream;
import com.sun.mail.util.BASE64EncoderStream;
public class Test{
private static Cipher ecipher;
private static Cipher dcipher;
private static SecretKey key;
public static void main(String[] args) {
try {
String clave = "example21";
// generate secret key using DES algorithm
SecretKey key2 = new SecretKeySpec(clave.getBytes(), 0, 9, "DES");
ecipher = Cipher.getInstance("DES");
dcipher = Cipher.getInstance("DES");
// initialize the ciphers with the given key
ecipher.init(Cipher.ENCRYPT_MODE, key2);
dcipher.init(Cipher.DECRYPT_MODE, key2);
String encrypted = encrypt("text to encrypt");
System.out.println(encrypted);
String decrypted = decrypt(encrypted);
System.out.println("Decrypted: " + decrypted);
}catch (NoSuchAlgorithmException e) {
System.out.println("No Such Algorithm:" + e.getMessage());
return;
}
catch (NoSuchPaddingException e) {
System.out.println("No Such Padding:" + e.getMessage());
return;
}
catch (InvalidKeyException e) {
System.out.println("Invalid Key:" + e.getMessage());
return;
}
}
public static String encrypt(String str) {
try {
// encode the string into a sequence of bytes using the named charset
// storing the result into a new byte array.
byte[] utf8 = str.getBytes("UTF8");
byte[] enc = ecipher.doFinal(utf8);
// encode to base64
enc = BASE64EncoderStream.encode(enc);
return new String(enc);
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String decrypt(String str) {
try {
// decode with base64 to get bytes
byte[] dec = BASE64DecoderStream.decode(str.getBytes());
byte[] utf8 = dcipher.doFinal(dec);
// create new string based on the specified charset
return new String(utf8, "UTF8");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
I've tried differents ways and algorithms but I allways get the error "Invalid Key:Wrong key size"...
Any have a suggestion of what I should try?
Thanks in advance!
Firstly, the DBMS_OBFUSCATION_TOOLKIT is deprecated and should not be used.
Secondly, DES was broken more than 20 years ago and should not be used anymore.
As for your question. The documentation for
DBMS_OBFUSCATION_TOOLKIT.DESDECRYPT have the following to say about the key parameter:
If the key length is missing or is less than 8 bytes, then the
procedure raises the error ORA-28234 "Key length too short." Note that
if larger keys are used, extra bytes are ignored. So a 9-byte key will
not generate an exception.
So Java just validates the key a bit more.

Random IllegalBlockSizeException when decrypting

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.

javax.crypto.BadPaddingException during RSA Decryption

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));
}
}

Nodejs Encrypted string not matching java : AES-256-CBC

Hi I have written nodejs encryption and java encryption by using same algorithm on both the side. But Java and NodeJS are returning different encrypted string. Please help me here.
//Here is my Java Code
import java.io.UnsupportedEncodingException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public enum AESUtil {
;
private static final String ENCRYPTION_KEY = "RwcmlVpg";
private static final String ENCRYPTION_IV = "4e5Wa71fYoT7MFEX";
public static String encrypt(String src) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, makeKey(), makeIv());
Base64.Encoder encoder = Base64.getEncoder();
return encoder.encodeToString(cipher.doFinal(src.getBytes()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String decrypt(String src) {
String decrypted = "";
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, makeKey(), makeIv());
Base64.Decoder decoder = Base64.getDecoder();
decrypted = new String(cipher.doFinal(decoder.decode(src)));
} catch (Exception e) {
throw new RuntimeException(e);
}
return decrypted;
}
static AlgorithmParameterSpec makeIv() {
try {
return new IvParameterSpec(ENCRYPTION_IV.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
static Key makeKey() {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] key = md.digest(ENCRYPTION_KEY.getBytes("UTF-8"));
return new SecretKeySpec(key, "AES");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
}
//Below is the code to test above code
public class AESMain {
/**
* #param args
*/
public static void main(String[] args) {
String src = "Hello,CryptWorld";
String encrypted = AESUtil.encrypt(src);
String decrypted = AESUtil.decrypt(encrypted);
System.out.println("src: " + src);
System.out.println("encrypted: " + encrypted);
System.out.println("decrypted: " + decrypted);
}
}
Response from the above code is
src: Hello,CryptWorld
encrypted: rh7ro9NH1XZeLX95paLETDgYxRbnDoOIrxarO0Sy73s=
decrypted: Hello,CryptWorld
//Node JS Code
var Encrypt, crypto;
crypto = require("crypto");
Encrypt = module.exports = (function() {
var b64dec, b64enc, cipher, decrypt, encrypt, iv, key;
key = crypto.createHash("sha256").update("RwcmlVpg").digest();
iv = '4e5Wa71fYoT7MFEX';
cipher = function(mode, data) {
var encipher, encoded;
encipher = crypto[mode]("aes-256-cbc", key, iv);
encoded = encipher.update(data);
encoded += encipher.final();
return encoded;
};
encrypt = function(data) {
return b64enc(cipher("createCipheriv", data));
};
decrypt = function(data) {
return cipher("createDecipheriv", b64dec(data));
};
b64enc = function(data) {
var b;
b = new Buffer(data, "binary");
return b.toString("base64");
};
b64dec = function(data) {
var b;
b = new Buffer(data, "base64");
return b.toString("binary");
};
return {
encrypt: encrypt,
decrypt: decrypt
};
})();
var expected = Encrypt.encrypt("Hello,CryptWorld");
console.log("expected " + expected);
The Response from Node JS is
expected /R79/f1H/XZeLX95/f39TDgY/Rb9Dv39/Rb9O0T9/Xs=
The node js version is v6.10.1 and JDK version 1.8.0_77.
I really don't know what I am missing.
I'm not a javascript or node.js expert, but I think the problem is that the cipher.update() and cipher.final() are returning instances of Buffer, not string. Therefore you must use Buffer.concat(...) to concatenate them, i.e
cipher = function (mode, data) {
var encipher, encoded;
encipher = crypto[mode]("aes-256-cbc", key, iv);
cipher1 = encipher.update(data);
cipher2 = encipher.final();
return Buffer.concat([cipher1, cipher2]);
};
Additionally, you should never use this String.getBytes() method nor this String(byte[]) constructor in any code that strives to achieve portability or interoperability. Instead, always specify the charset explicitly. I would recommend UTF_8 exclusively, e.g. so use String.getBytes(StandardCharsets.UTF_8) and new String(byte[], StandardCharsets.UTF_8).
My first hint would be to make sure that you exactly the same:
character encoding
line endings
in both of your programs, for the text being encrypted and for the keys. Try printing the text buffers and keys as hex and compare those before you even do any encryption. If they differ then there's your problem. If they're the same then it may be a problem with the encryption itself.
Note that Node uses UTF-8 by default while Java uses UCS-2 internally as far as I know. I see you're making some attempts to convert the encoding but double check the results in both envirements for bot the keys and the clear text just before the encryption step.
Also make sure that you're not encrypting base64 representation of the string instead of the string itself, or that if you use base64 for the key then you do it consistently.
Also, print both keys before the encryption. You're building them programmatically so make sure you know what they are at the end.
If you make sure that you have the same keys, the same messages, the same encoding, line endings and representation (hex, base64 etc.) then use some online tool for AES encryption like:
http://aesencryption.net/
http://aes.online-domain-tools.com/
https://www.browserling.com/tools/aes-encrypt
and compare which of your program does the job correctly.

AES Encryption : Encrypt using Arduino and decrypt using Java

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!

Categories