private static byte[] encryptData(ByteArrayOutputStream data, byte[] symmetricKey) throws EncryptionException {
try {
SecretKey secKey = new SecretKeySpec(symmetricKey, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secKey);
return cipher.doFinal(data.toByteArray());
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException |
InvalidKeyException |
BadPaddingException e) {
throw new EncryptionException(e);
}
}
I have a situation where I need to encrypt data using .NET and decrypt the same data using JAVA. Essentially, I need to rewrite the above encryption method in .NET.
public byte[] Encrypt(byte[] key, byte[] plainText)
{
using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider())
{
using (ICryptoTransform encryptor = aesProvider.CreateEncryptor(key, magicIV))
{
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(plainText, 0, plainText.Length);
}
byte[] cipherText = ms.ToArray();
return cipherText;
}
}
}
}
The above code I used somewhere mandates IV which JAVA is not asking for. What is the IV used in JAVA code?
I tried many links which didn't work.
Symmetric Encryption between .NET and Java
Please help
If your current Java decryption code also does not ask for an IV (and your decryption returns the same data you encrypted) then Cipher.getInstance("AES") is returning an object using the ECB block mode.
.NET symmetric algorithms default to the CBC block mode, which requires an IV.
You have a couple of options:
Set aesProvider.Mode = CipherMode.ECB before calling CreateEncryptor.
You probably don't want this, see the penguin at https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Common_modes.
Pass aesProvider.IV to the IV parameter of CreateEncryptor. The IV property will make a cryptographically random value on the first read if it's not set.
You will need to pass this data to the decryption routine, which should then use "AES/CBC/PKCS5Padding", and set the IV however one does that in Java.
One common method of transport is to simply prepend the data to the ciphertext, then just pick off the first 16 bytes at decryption time.
DO NOT use a fixed value for an IV, because it's then almost the same as ECB.
Related
Environment: Java 8 / SpringBoot v2.0.2
I'm trying to fix a security issue in the code by changing Cipher instance from just "AES" to "AES/GCM/NoPadding". However, the existing test for the REST endpoint which has an encrypted path parameter value which uses this encrypt method fails.
Here is my encrypt method,
public String encrypt(final Long transactionId) {
Assert.notNull(transactionId, "Transaction Id Should Not Be null");
String encryptedText = "";
try
{ final byte[] encodedSecretKey = Base64.decodeBase64(encryptKey);
final SecretKeySpec secretKey = new SecretKeySpec(encodedSecretKey, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(1, secretKey);
final byte[] contentAsBytes = transactionId.toString().getBytes();
byte[] contentAsByteCypherText = cipher.doFinal(contentAsBytes);
byte[] iv = cipher.getIV();
byte[] message = new byte[NUMBER_OF_IV_BYTES + contentAsByteCypherText.length];
System.arraycopy(iv, SRC_POSITION, message, DEST_POSITION, NUMBER_OF_IV_BYTES);
System.arraycopy(contentAsByteCypherText, SRC_POSITION, message, NUMBER_OF_IV_BYTES, contentAsByteCypherText.length);
encryptedText = Base64.encodeBase64URLSafeString(message);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
LOGGER.error("Failed to encrypt Transaction ID ", e);
throw new BlahException("Failed to encrypt transaction id", e);
}
return encryptedText;
}
My Test
public void testJourney(final Long transactionId) throws Exception {
final String request = loadExpectedContent(transactionId);
mockRestServiceServer.reset();
mockRestServiceServer.expect(requestTo("/spring/rest/transaction/" + webClient.encrypt(transactionId)))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON).body(request));
final CompletableFuture<String> completable = webClient.getWebJourney(transactionId);
mockRestServiceServer.verify();
final String response = completable.get();
Assert.assertNotNull(response);
LOGGER.info(LoggingUtils.format(LoggingUtils.keyValue("Request", transactionId),
LoggingUtils.keyValue("Response", LoggingUtils.parse(response))));
}
In this test the encrypt method is hit twice.
when trying to create the expect
inside webClient.getWebJourney(transactionId)
The difference is when Cipher.getInstance("AES/GCM/NoPadding"); is used those two times it returns two different values failing the test. But when Cipher.getInstance("AES"); is used (without IV) it returns same value both times.
My question is how can I test this REST endpoint with "AES/GCM/NoPadding"?
To test it you don't want to get the same ciphertext, you want to get the same cleartext after decryption. With GCM you want a test that encrypts something twice and doesn't get the same result.
You should not re-use IV values (with GCM the same IV and cleartext results in the same ciphertext).
So this:
byte[] iv = cipher.getIV();
Will return a secure random generated array of 12 bytes, hence the ciphertext is different each time.
Calling Cipher.getInstance("AES") without IV is definitely not what you want to do:
it's likely defaulting to ECB, which is old and badly broken (have a look at the penguin in the "encrypted" image.
not using an IV is why you get the same result - the idea of the IV is to start with some randomness to avoid repeating patterns that can be used to find the key.
I would change the test to perform encryption, then decrypt (in the test) and ensure the ciphertext is the same.
I would like to encrypt a String with RSA encryption. My public/private keys were generated and stored in DB. In android, I use this code:
public static String encryptRSAToString(String text, String strPublicKey) {
byte[] cipherText = null;
String strEncryInfoData="";
try {
KeyFactory keyFac = KeyFactory.getInstance("RSA");
KeySpec keySpec = new X509EncodedKeySpec(Base64.decode(strPublicKey.trim().getBytes(), Base64.DEFAULT));
Key publicKey = keyFac.generatePublic(keySpec);
// get an RSA cipher object and print the provider
final Cipher cipher = Cipher.getInstance("RSA");
// encrypt the plain text using the public key
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
cipherText = cipher.doFinal(text.getBytes());
strEncryInfoData = new String(Base64.encode(cipherText,Base64.DEFAULT));
} catch (Exception e) {
e.printStackTrace();
}
return strEncryInfoData.replaceAll("(\\r|\\n)", "");
}
For debug purpose, I try to call 2 times this method with the same parameters and String result were similar (as expected).
I want to generate the same encrypted String in java. However, "android.util.Base64" class is not available in Java, so I've tried with the default Base64 class:
public static String encryptRSAToString(String text, String strPublicKey) {
byte[] cipherText = null;
String strEncryInfoData="";
try {
KeyFactory keyFac = KeyFactory.getInstance("RSA");
KeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(strPublicKey.trim().getBytes()));
Key publicKey = keyFac.generatePublic(keySpec);
// get an RSA cipher object and print the provider
final Cipher cipher = Cipher.getInstance("RSA");
// encrypt the plain text using the public key
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
cipherText = cipher.doFinal(text.getBytes());
strEncryInfoData = new String(Base64.encodeBase64(cipherText));
} catch (Exception e) {
e.printStackTrace();
}
return strEncryInfoData.replaceAll("(\\r|\\n)", "");
}
But the String generated in Android and the one in java are different.
Generated in Android side :
Ky2T4j1JdI081ZESVJgxZXEf/xmtpehfv/EwpVvKQxUu1JI8lwXP2Rc66jHZRc0P846ZYuF3C9YEmWoKbXGXk2MBuT5KVxa2yoTbwZlMmhVOX3X3Efq0VyaO5zZ4qavIq036cA3MzvQbUAb678UdbALW/CjRCsOdeH+hSCzNQ+0=
Generated in JAVA side :
XhSLxfiJUUdZW5kWh0MEPSrqoROBBhNC/krfTx+sdnXML3WegYbMzSvNnPgB8+8Z9joEUBMmoeBI1OhTF6qPFL1EEixkFYAkGaryEFxvN/aFI75kEUj71OHNzAHAuvS+h+9Nssx9psSZ5gc2OoLQH0QtbGDyXB4p+qUGFCde4tY=
Does someone know how to solve my issue ?
thank you
It looks like you've been undone by relying on defaults. Never do that if you hope for interoperability.
Here are the two examples of mistakenly relying on defaults in your code that I've found.
final Cipher cipher = Cipher.getInstance("RSA");
The tranformation string is supposed to be of the form "algorithm/mode/padding" but you've left off the mode and padding specifications. As a result you got default values for those. The defaults are evidently different on Android and Oracle Java. You should always fully specify the transformation, for example:
final Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING");
Another bad example is
cipherText = cipher.doFinal(text.getBytes());
In text.getBytes() you are relying on the no-args getBytes() method which uses the default charset for the platform. But this default charset differs on different platforms, and thus this is not portable. In almost all cases I've run across you should specify the UTF-8 charset. So the correct line would thus be
cipherText = cipher.doFinal(text.getBytes("UTF-8"));
and the correct string constructor to use to recreate the original string in the decrypt method is the String(byte [] data, String charsetName).
I can´t comment yet so I answer.
It is possible that different default configurations are being used. Check this question: Is there any difference between Apache's Base64.encodeBase64 and Android's Base64.encode with Base64.Default flag?
There are deviations of different cipher and hash implementations. I would suggest using OpenSSL as a common implementation.
I was wondering, is there any difference, if I init AES cipher, with and without IvParameterSpec?
With IvParameterSpec
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
Without IvParameterSpec
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
I tested with some sample test data, their encryption and decryption result yield the same.
However, since I'm not the security expert, I don't want to miss out anything, and create a potential security loop hole. I was wondering, which is the correct way?
A bit of background (I'm sorry if you already know this, it's just worth making sure we're using the same terminology):
AES is a block cipher, an encryption algorithm that operates on 128-bit blocks.
CBC is a block cipher mode, a way of using a block cipher to encrypt large amounts of data.
Block cipher modes need an initialisation vector (IV), which is a block of initialisation data, usually the same size as the block size of the underlying cipher.
(The Wikipedia on block cipher modes - http://en.wikipedia.org/wiki/Block_cipher_mode - is really good, and makes it clear why you need an IV.)
Different block modes impose different requirements on the IV selection process, but they all have one thing in common:
You must never encrypt two different messages with the same IV and key.
If you do, an attacker can usually get your plaintext, and sometimes your key (or equivalently useful data).
CBC imposes an additional constraint, which is that the IV must be unpredictable to an attacker - so artjom-b's suggestion of using a SecureRandom to generate it is a good one.
Additionally, as artjob-b points out, CBC only gives you confidentiality. What that means in practice is that your data is kept secret, but there's no guarantee that it arrives in one piece. Ideally, you should use an authenticated mode, such as GCM, CCM, or EAX.
Using one of these modes is a really, really good idea. Encrypt-then-MAC is unwieldy even for the experts; avoid it if you can. (If you have to do it, remember that you must use different keys for encryption and MAC.)
By default when you encrypt - your cipher will generate a random IV. You must use exactly that specific IV when you decrypt that data.
The good news is that IV is not a secret thing - you can store it in public. The main idea is to keep it different for every encrypt-decrypt operation.
Most of the times you will need to encrypt-decrypt various data and storing each IV for each piece of data is a pain.
That's why IV is often stored along with the encrypted data in a single string, as a fixed size prefix.
So that when you decrypt your string - you definitely know that first 16 bytes (in my case) are your IV, the rest of the bytes - are the encrypted data and you need to decrypt it.
Your payload (to store or send) will have the following structure:
[{IV fixed length not encrypted}{encrypted data with secret key}]
Let me share my encrypt and decrypt methods, I'm using AES, 256 bit secret key, 16 bit IV, CBC MODE and PKCS7Padding.
As Justin King-Lacroix stated above you better use GCM, CCM, or EAX block modes. Do not use ECB!
Result of encrypt() method is safe & ready to store in DB or send anywhere.
Note a comment where you can use custom IV - just replace new SecureRandom() with new IvParameterSpec(getIV()) (you can input there your static IV but this is strongly NOT recommended)
private Key secretAes256Key is a class field with a secret key, it is initialized in the constructor.
private static final String AES_TRANSFORMATION_MODE = "AES/CBC/PKCS7Padding"
the encrypt() method:
public String encrypt(String data) {
String encryptedText = "";
if (data == null || secretAes256Key == null)
return encryptedText;
}
try {
Cipher encryptCipher = Cipher.getInstance(AES_TRANSFORMATION_MODE);
encryptCipher.init(Cipher.ENCRYPT_MODE, secretAes256Key, new SecureRandom());//new IvParameterSpec(getIV()) - if you want custom IV
//encrypted data:
byte[] encryptedBytes = encryptCipher.doFinal(data.getBytes("UTF-8"));
//take IV from this cipher
byte[] iv = encryptCipher.getIV();
//append Initiation Vector as a prefix to use it during decryption:
byte[] combinedPayload = new byte[iv.length + encryptedBytes.length];
//populate payload with prefix IV and encrypted data
System.arraycopy(iv, 0, combinedPayload, 0, iv.length);
System.arraycopy(encryptedBytes, 0, combinedPayload, iv.length, encryptedBytes.length);
encryptedText = Base64.encodeToString(combinedPayload, Base64.DEFAULT);
} catch (NoSuchAlgorithmException | BadPaddingException | NoSuchPaddingException | IllegalBlockSizeException | UnsupportedEncodingException | InvalidKeyException e) {
e.printStackTrace();
}
return encryptedText;
}
And here is the decrypt() method:
public String decrypt(String encryptedString) {
String decryptedText = "";
if (encryptedString == null || secretAes256Key == null)
return decryptedText;
}
try {
//separate prefix with IV from the rest of encrypted data
byte[] encryptedPayload = Base64.decode(encryptedString, Base64.DEFAULT);
byte[] iv = new byte[16];
byte[] encryptedBytes = new byte[encryptedPayload.length - iv.length];
//populate iv with bytes:
System.arraycopy(encryptedPayload, 0, iv, 0, 16);
//populate encryptedBytes with bytes:
System.arraycopy(encryptedPayload, iv.length, encryptedBytes, 0, encryptedBytes.length);
Cipher decryptCipher = Cipher.getInstance(AES_TRANSFORMATION_MODE);
decryptCipher.init(Cipher.DECRYPT_MODE, secretAes256Key, new IvParameterSpec(iv));
byte[] decryptedBytes = decryptCipher.doFinal(encryptedBytes);
decryptedText = new String(decryptedBytes);
} catch (NoSuchAlgorithmException | BadPaddingException | NoSuchPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | InvalidKeyException e) {
e.printStackTrace();
}
return decryptedText;
}
Hope this helps.
When no IvParameterSpec is provided then the Cipher should initialize a random IV itself, but it seems that in your case, it doesn't do this (new byte[16] is an array filled with 0x00 bytes). It seems the Cipher implementation is broken. In that case you should always provide a new random IV (necessary for semantic security).
This is usually done this way:
SecureRandom r = new SecureRandom(); // should be the best PRNG
byte[] iv = new byte[16];
r.nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));
When you then send or store the ciphertext, you should prepend the IV to it. During decryption you only need to slice the IV off the front of the ciphertext to use it. It doesn't need to be kept secret, but it should be unique.
Note that CBC mode alone only gives you confidentiality. If any type of manipulation of ciphertexts (malicious or non-malicious) is possible then you should use an authenticated mode like GCM or EAX. Those will also give you integrity in addition to confidentiality. If you don't have access to those (SpongyCastle has them), you could use a message authentication code (MAC) in an encrypt-then-MAC scheme, but it is much harder to implement correctly.
I'm trying to create AES encryption/decryption methods, but I cant seem to get my original input without using AES/ECB/NoPadding. Now I am trying to use AES/CBC/PKCS7Padding. I have confirmed that reading and writing the byte to/from the file works fine. With PKCS7 padding I get a BadPaddingException from
cipher.doFinal(encrypted)
in the decrypt method. Without padding, no exceptions - however the output is scrambled. I've spent time going through other posts about this exact same issue but I cant seem to find a solution that fits my problem.
How do I unscramble that output?
protected boolean encrypt(String place, String encrypt) {
try {
// encrypt the text
cipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] encrypted = cipher.doFinal(encrypt.getBytes());
Context context = HomeViewActivity.hva.getApplicationContext();
FileOutputStream writer = context.openFileOutput(file_name.get(place.toUpperCase()), Context.MODE_PRIVATE);
writer.write(encrypted);
writer.close();
return true; //successfully wrote encrypted string to file
}catch(Exception e) {
e.printStackTrace();
}
return false;
}
protected String decrypt(String place){
String decrypted = null;
try{
Context context = HomeViewActivity.hva.getApplicationContext();
// decrypt the text
cipher.init(Cipher.DECRYPT_MODE, aesKey);
FileInputStream reader = context.openFileInput(file_name.get(place.toUpperCase()));
byte[] encrypted = new byte[reader.available()];
reader.read(encrypted);
reader.close();
decrypted= new String(cipher.doFinal(encrypted));
}catch(FileNotFoundException e){return null;}
catch(IllegalBlockSizeException |
BadPaddingException |
InvalidKeyException |
NoSuchAlgorithmException |
IOException |
NoSuchPaddingException e){e.printStackTrace();}
return decrypted;
}
Edit 1: Made the encrypted array read in from file the appropriate size
Edit 2: initialized key and cipher in constructor instead of each method
The problem is that ECB doesn't use an IV and that CBC - and most other modes of operation do use an IV value. Java randomizes the IV value when it is not explicitly given, which means that the plaintext after decryption is not correct.
For AES CBC mode this means that the first 16 bytes of the plaintext - the initial block - contains random characters. As the blocks after the initial block contain the normal plaintext you won't get a BadPaddingException in the code.
The normal solution to this problem is to prefix the IV value to the ciphertext or write it to the underlying stream first. Needless to say you have to retrieve the IV during decryption and skip the IV value during decryption by altering the offset in a buffer or by advancing a stream (for files you may not want to copy the entire ciphertext).
I want to encrypt and decrypt integers with AES but can't get it going.
To test the basic cryptographic process I wrote a simple method that takes input data, encrypts and decrypts it with the same parameters and returns the result.
Here is my failing JUnit test case that checks whether the input and the output data are equal.
#Test
public void test4() throws UnsupportedEncodingException {
Random random = new Random();
SecretKey secretKey = Tools.generateKey("secretKey".getBytes("UTF-8"));
byte[] initializationVector = Tools.intToByteArray(random.nextInt());
// ensuring that the initialization vector has the correct length
byte[] ivHash = Tools.hashMD5(initializationVector);
int value = random.nextInt();
byte[] input = Tools.intToByteArray(value);
byte[] received = Tools.enDeCrypt(input, secretKey, ivHash);
assertEquals(data.hashCode(), received.hashCode());
}
Method generateKey:
public static SecretKeySpec generateKey(byte[] secretKey) {
try {
// 256 bit key length
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(secretKey);
byte[] key = md.digest();
return new SecretKeySpec(key, "AES");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
Method for int -> byte[] conversion:
public static byte[] intToByteArray(int a) {
// block size is 128 bit, thus I append zeros
byte[] intByte = ByteBuffer.allocate(4).putInt(a).array();
byte[] longerIntByte = new byte[16];
for (int i = 0; i < 4; i++) {
longerIntByte[i] = intByte[i];
}
for (int i = 4; i < longerIntByte.length; i++) {
longerIntByte[i] = 0;
}
return longerIntByte;
}
Here is the code for encryption and decryption:
public static byte[] enDeCrypt(byte[] data, SecretKey secretKey,
byte[] initialisationVector) {
try {
IvParameterSpec ivSpec = new IvParameterSpec(initialisationVector);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
byte[] encrypted = cipher.doFinal(data);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
} catch (NoSuchAlgorithmException | NoSuchPaddingException
| InvalidKeyException | InvalidAlgorithmParameterException
| IllegalBlockSizeException | BadPaddingException e) {
throw new RuntimeException(e);
}
}
assertEquals(data.hashCode(), received.hashCode()) is very unlikely to pass unless data and received refer to the same object (since byte arrays inherit the identity hash code method from Object). I don't see where data comes from, but that is probably not the case here. You should use Arrays.equals(data, received).
Apart from that, there are various cryptographic issues here:
Random is not "random enough" for cryptographic purposes; you should use SecureRandom.
Key derivation using plain SHA-256 is dubious. You should consider using a key derivation algorithm that is specifically designed for this, like PBKDF2.
AES with 256-bit keys is not always better than 128-bit keys. Check this page. In this case it's completely bogus since passphrases rarely even reach 128 bits of entropy.
Random IVs – good, but why jump through hoops when you could just directly use SecureRandom.nextBytes(). Hashing the IV doesn't add anything useful.
There's no reason to do manual zero padding when you could instead let the library handle it. Just specify PKCS5Padding instead of NoPadding.