I have to make an app in JS which encodes a message with AES and passes it to a server via AJAX. Then the server and decodes the message using Java.
My question is: How to encrypt a message in JS and be able to decrypt it in Java using AES? Knowing that the communication between java and js is already established via webservices
Client-side, I use the Crypto JS library (http://code.google.com/p/crypto-js/). Server-side I use the Cipher class provided with Java (I use the Java Play framework but it doesn't matter here).
I'm totally new to cryptography. I've made researches all day long and still can't make this work.
The problem is that the key used to encrypt and decrypt the message must be the same, and I don't know how to do this.
From my searches, I understand there is different modes to use AES. By default Java uses ECB and CryptoJS uses CBC which is a problem, but which seems not so hard to fix, by telling CryptoJS to use the ECB mode too. But then there is a padding problem, it seems the only padding available in Java and CryptoJS is no padding at all. But when I use NoPadding in Java I get an exception.
But even if I manage to fix this, the huge problem is that the key generated by CryptoJS and the one generated by Java are not the same. If I encrypt a message in Java the result is always the same, in Hex. But in crypto JS it is in Base64 and it is never the same....
I understand this is caused by the key generation which isn't the same in Java and CryptoJS (then enters the notion of IV and Salt which are blur for me).
Don't do encryption in browser JS; it's impossible to do securely.
Use SSL. Its intended purpose is to encrypt communication between a browser and a server.
If cost is your problem, there are free SSL certificates.
I recently invested days in this problem. I tried a lot a lot of libraries on the java and javascript side. This blog saved my day: http://watchitlater.com/blog/tag/aes/ .
So it can be done! Just use Gibberish AES (https://github.com/mdp/gibberish-aes) an the javascript side and bouncycastle an on the java side.
The java side encryption might look like this(leaned on the above mentioned blog):
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.spec.InvalidKeySpecException;
import java.util.Random;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import sun.misc.BASE64Encoder;
public class OpenSSLEncryption {
private static final String CIPHER_ALG = "PBEWITHMD5AND256BITAES-CBC-OPENSSL";
private static final Provider CIPHER_PROVIDER = new BouncyCastleProvider();
private static final String PREFIX = "Salted__";
private static final String UTF_8 = "UTF-8";
private String password;
private PBEKeySpec pbeSpec;
private SecretKeyFactory keyFact;
private Cipher cipher;
private Random rand = new Random();
private BASE64Encoder encoder = new BASE64Encoder();
public OpenSSLEncryption(String password) throws NoSuchAlgorithmException, NoSuchPaddingException {
this.password = password;
pbeSpec = new PBEKeySpec(password.toCharArray());
keyFact = SecretKeyFactory.getInstance(CIPHER_ALG, CIPHER_PROVIDER);
cipher = Cipher.getInstance(CIPHER_ALG, CIPHER_PROVIDER);
}
public synchronized String encrypt(String toEncrypt) throws InvalidKeySpecException, InvalidKeyException, InvalidAlgorithmParameterException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException, IOException {
byte[] salt = new byte[8];
rand.nextBytes(salt);
PBEParameterSpec defParams = new PBEParameterSpec(salt, 0);
cipher.init(Cipher.ENCRYPT_MODE, keyFact.generateSecret(pbeSpec), defParams);
byte[] cipherText = cipher.doFinal(toEncrypt.getBytes(UTF_8));
ByteArrayOutputStream baos = new ByteArrayOutputStream(cipherText.length + 16);
baos.write(PREFIX.getBytes(UTF_8));
baos.write(salt);
baos.write(cipherText);
baos.close();
return encoder.encode(baos.toByteArray());
}
}
Related
I know that there are many threads over this , but i am not able to find solution with them.
Problem Statement:
Need to encrypt data. Below is my code:
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import static java.nio.charset.StandardCharsets.UTF_8;
public class CryptoUtil {
private static final String AES = "AES/ECB/PKCS5Padding";
public String encryptMessage(final String message, final byte[] dataKey) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(dataKey, AES);
try {
Cipher cipher = Cipher.getInstance(AES);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedMessage = cipher.doFinal(message.getBytes());
return Base64.getEncoder().encodeToString(encryptedMessage);
}
catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
throw new Exception("Error while encrypting application credentials", e);
}
}
}
I need to do it using BountyCastle service provider. So i followed the steps mentioned here and this. I am using JAVA 11, but i switched to JAVA 8 to follow mentioned link.
In my code i added static block to add BountyCastle as service provider
static {
BouncyCastleProvider bouncyCastleProvider = new BouncyCastleProvider();
Security.addProvider(bouncyCastleProvider);
}
But, it is not getting added as verified providers. So i am getting the issue.
Any idea how to handle. How to add BouncyCastle as verified provider.
I am running on local machine , and need to create JAR.
Nobody has answered this, so I'll just post an answer with the comment I had above. I have not tested it so I leave that part to you.
When you do Cipher.getInstance(), it is correct to have "AES/ECB/PKCS5Padding" (for some weird definition of "correct", where Java providers believe PKCS5 padding = PKCS7 padding, but never mind that).
But when you create the key, it is not correct to have the mode and padding in there. Instead, they just want the algorithm. So try:
SecretKeySpec secretKey = new SecretKeySpec(dataKey, "AES");
The change is the quotes around "AES", rather than using AES="AES/ECB/PKCS5Padding".
Not sure if that will fix your problem, but should be a step in the right direction (I hope).
According to section 5.2 (Two GCM Functions) of the Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode (GCM) and GMAC, it mentions that for the case of GMAC, the authenticated encryption and decryption functions become the functions for generating and verifying an authentication tag on the non-confidential data.
Looking online and on the JCA reference page, I understand that GMAC is generated like this:
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5Padding");
cipher.init(ENCRYPT_MODE, encryptionKey, new GCMParameterSpec(96, iv), new SecureRandom());
cipher.updateAAD(aadData);
byte[] gmac = cipher.doFinal();
However, I have doubts regarding the GMAC verification. Is this the right way of verifying a GMAC?
Cipher decryptCipher = Cipher.getInstance("AES/GCM/PKCS5Padding");
decryptCipher.init(DECRYPT_MODE, encryptionKey, new GCMParameterSpec(96, iv), new SecureRandom());
decryptCipher.updateAAD(aadData);
decryptCipher.update(gmac);
byte[] verifiedGmac = decryptCipher.doFinal();
Where verifiedGmac size == 0?
Here is the full code with inputs and outputs:
import org.hamcrest.core.Is;
import org.junit.Test;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.SecureRandom;
import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;
import static javax.xml.bind.DatatypeConverter.parseHexBinary;
import static javax.xml.bind.DatatypeConverter.printHexBinary;
import static org.junit.Assert.assertThat;
public class TestGmac {
#Test
public void generateAndVerifyGmac() throws Exception {
final byte[] message = parseHexBinary("AAAAAAAAAAAA");
final byte[] authenticatedKey = parseHexBinary("63509E5A672C092CAD0B1DC6CE009A61");
final byte[] aadData = buildAadForAuthenticationOnly(message, authenticatedKey);
final byte[] iv = parseHexBinary("BBBBBBBBBBBBBBBBBBBBBBBB");
SecretKeySpec encryptionKey = new SecretKeySpec(parseHexBinary("55804F3AEB4E914DC91255944A1F565A"), "AES");
// Generate GMAC
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5Padding");
cipher.init(ENCRYPT_MODE, encryptionKey, new GCMParameterSpec(96, iv), new SecureRandom());
cipher.updateAAD(aadData);
byte[] gmac = cipher.doFinal();
assertThat(printHexBinary(gmac), Is.is("44C955D63799428524E97993"));
// Verify GMAC
Cipher decryptCipher = Cipher.getInstance("AES/GCM/PKCS5Padding");
decryptCipher.init(DECRYPT_MODE, encryptionKey, new GCMParameterSpec(96, iv), new SecureRandom());
decryptCipher.updateAAD(aadData);
decryptCipher.update(gmac);
byte[] verifiedGmac = decryptCipher.doFinal();
assertThat(printHexBinary(verifiedGmac), Is.is(""));
}
private byte[] buildAadForAuthenticationOnly(byte[] message, byte[] authenticatedKey) throws IOException {
ByteArrayOutputStream aaDoutputStream = new ByteArrayOutputStream();
aaDoutputStream.write(parseHexBinary("10"));
aaDoutputStream.write(authenticatedKey);
aaDoutputStream.write(message);
return aaDoutputStream.toByteArray();
}
}
Yes, that is correct. The Java API verifies the MAC, throwing an exception if it fails. It returns the encrypted plaintext message, but in your case the message is empty.
There are two design choices for creating a GCM API: handle the authentication tag separately or assume it is part of the ciphertext. Although I prefer the separate handling of ciphertext and authentication tag, Java/Oracle decided to keep to the AEAD RFC and include the authentication tag with the ciphertext (requiring extra buffering, a code size increase of about 30% and removing the posiblity to perform online decryption).
So although your code maybe feels a bit strange the handing of the MAC seems OK - if it weren't you'd get a AEADBadTagException in the call to doFinal during decryption.
Using a static key and IV certainly isn't OK, but I presume those were used for testing purposes - right?
In my IDE warning displayed as Potentially insecure random numbers on Android 4.3 and older.
Read Android Development Blog for more info
how to make it secure?
package com.example.encryptographytest;
import java.security.Security;
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.SecretKeySpec;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Base64;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String key = "1234567891234567";
String data = "example";
Log.d("CRYPTO-TEST", ""+ decrypt(encrypt(data, key), key));
Log.d("CRYPTO-TEST", ""+encrypt(data, key));
}
public static String encrypt(String input, String key){
byte[] crypted = null;
try{
SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skey);
crypted = cipher.doFinal(input.getBytes());
}catch(Exception e){
System.out.println(e.toString());
}
return new String(Base64.encode(crypted, Base64.DEFAULT));
// return new String(Base64.encodeBase64(crypted));
}
public static String decrypt(String input, String key){
byte[] output = null;
try{
SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skey);
output = cipher.doFinal( Base64.decode(input, Base64.DEFAULT));
}catch(Exception e){
System.out.println(e.toString());
}
return new String(output);
}
}
First off, Android 4.3 is inherently not secure. There are now multiple exploits including remote ones such as StageFright.
Second, I assume you were talking about some-securerandom-thoughts.html, as your link was dead. It's talking about random number generations, but I don't see that in your code. Instead I see AES encryption, which isn't random at all.
Also, looking at Potentially insecure random numbers on Android 4.3 and older, this warning may have been related to a previous version of your code, which relied on SecureRandom to initialize the KeyGenerator.
Doing a google search for 'aes not secure' brings up all bunch of results and opinions, but it seems secure enough for most people.
Having said that, doing a google for 'ecb not secure' brings up Why shouldn't I use ECB encryption?, which aptly demonstrates why it's not secure. But that's not secure on any platform, not just Android 4.3.
Hope this helps and please clarify if the warning really came from this code snippet or specify the exact line.
I am integrating with a .Net application which uses Symmetric encryption. My application is on Java. Following is the .Net code which was shared by the team which uses the Symmetric encryption;
public static string SymmetricEncrypt<T>(string value, string PrivateKey, string SALT_STRING) where T : SymmetricAlgorithm, new()
{
PasswordDeriveBytes rgb =
new PasswordDeriveBytes(PrivateKey ,
Encoding.Unicode.GetBytes(SALT_STRING));
SymmetricAlgorithm algorithm = new T();
byte[] rgbKey = rgb.GetBytes(algorithm.KeySize >> 3);
byte[] rgbIV = rgb.GetBytes(algorithm.BlockSize >> 3);
ICryptoTransform transform = algorithm.CreateEncryptor(rgbKey, rgbIV);
using (MemoryStream buffer = new MemoryStream())
{
using (CryptoStream stream =
new CryptoStream(buffer, transform, CryptoStreamMode.Write))
{
using (StreamWriter writer = new StreamWriter(stream, Encoding.Unicode))
{
writer.Write(value);
}
}
return Convert.ToBase64String(buffer.ToArray());
}
}
Looking at the .Net documentation, I could see that SymmetricEncrypt class by default uses Rijndael encryption. And also I have found a class similar to "PasswordDeriveBytes" implementation in bouncy castle which implements the "PKCS5S1" algorithm for key generation using the salt with an iteration count of 100. But still I am unable to generate the exact encryption to what is required by the .Net application. Following code is what i have tried so far;
import java.security.InvalidAlgorithmParameterException;
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 mtnsa.sorb.handler.PKCS5Test.PasswordDeriveBytes;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.generators.PKCS5S1ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.encoders.Base64;
public class EncryptionHelper {
public static void main(String[] args)throws Exception {
encrypt("FTTH","HhN01vcEEtMmwdNFliM8QYg0Y89xzBOJJG7BHARC7g", "002400000480000094000000060200000024000052534131000400000100010085525e9438e9fae122f71ec7124" +
"443bf2f9f57f5f3760b3704df168493004b9ef68413f500d54fa9fa3869b42b1e2365204826e54b618d56e7e575f2" +
"7f675f0eae3ea8458a8ee1e92dc3f4bfc34fbe23851afa9d2c28fc8cd5b124f60a03a06bfb598bc3acbd8c4380ae" +
"f02cc58bdf955d140390f740a7e115c59e3b3b5758ca");
}
public static String encrypt(final String valueToEncrpt, final String saltString, final String privateKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException{
String encrptedValue = null;
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] password = privateKey.getBytes();
byte[] salt = saltString.getBytes();
PKCS5S1ParametersGenerator generator = new PasswordDeriveBytes(new SHA1Digest());
generator.init(password, salt, 100);
byte[] keyArr = ((KeyParameter)generator.generateDerivedParameters(128)).getKey();
byte[] IvArr = ((KeyParameter)generator.generateDerivedParameters(128)).getKey();
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyArr,"AES"),new IvParameterSpec(IvArr));
byte[]test = cipher.doFinal(valueToEncrpt.getBytes());
System.out.println(new String(Base64.encode(test)));
return encrptedValue;
}
}
In the Java code i have given the sample SALT key and the PRIVATE key used for testing purposes. But still I am unable to get the exact value as generated by the .Net application. Following is an example plain text and the encrypted value which i got from the .Net application which i have failed to replicate in my Java code.
Plain text value : FTTH
Encrypted value : MjgmbdT3Vg6RW/7K1BjQ/Q==
Any help on this is much appreciated as i am currently out of ideas.
Thank you everyone for the valuable comments. I have requested the .Net module authors to change their implementation to use RFC2898DeriveBytes
I'm encrypt data using triple DES. It works fine, but I have a question.
Where can I see the initialization vector (IV)?
It's 3des encyption with BASE64Decoder.
import java.io.IOException;
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.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class Crypter {
Cipher ecipher;
Cipher dcipher;
Crypter(String as_Phrase)
throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException {
this.ecipher = Cipher.getInstance("DESede");
this.dcipher = Cipher.getInstance("DESede");
this.ecipher.init(1, getSecretKey(as_Phrase));
this.dcipher.init(2, getSecretKey(as_Phrase));
}
public String encrypt(String as_valueToEncrypt)
throws BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException, IOException {
byte[] lbarr_utf8 = as_valueToEncrypt.getBytes("UTF8");
byte[] lbarr_enc = this.ecipher.doFinal(lbarr_utf8);
return new BASE64Encoder().encode(lbarr_enc);
}
public String decrypt(String as_valueToDecrypt)
throws BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException, IOException {
byte[] lbarr_enc = new BASE64Decoder().decodeBuffer(as_valueToDecrypt);
byte[] lbarr_utf8 = this.dcipher.doFinal(lbarr_enc);
return new String(lbarr_utf8, "UTF8");
}
private SecretKey getSecretKey(String as_Phrase)
throws UnsupportedEncodingException {
return new SecretKeySpec(as_Phrase.getBytes("UTF8"), "DESede");
}
}
You can get the IV from the cipher:
ecipher.getIV();
The problem is that the IV is generated during init. Since you init in the constructor you would run into the problem of using the same IV for every encryption of different ciphertexts. It's always best to generate a new Cipher and init it for every encryption and decryption operation separately.
You use the DESede cipher which is actually DESede/ECB/PKCS5Padding. Note that the mode is ECB which doesn't use an IV. So the above call returns null. Although this is the default mode, it is not recommended. It's safer to use DESede/CBC/PKCS5Padding which actually uses an IV.
So when you decrypt in CBC mode you need to pass that IV in:
dcipher.init(2, new SecretKeySpec(key, "DESede"), new IvParameterSpec(ecipher.getIV()));
To reduce the burden of passing the IV around you could then append the IV to the front of the ciphertext before encoding it and slice it off before decrypting the ciphertext. DES is a 64-bit cipher so your IV will be 8 bytes long.
Cipher has getIV() method, which returns initialization vector.