AES encrypted and decrypted values differ - java

In an Android app, I am trying to consume a WebAPI with an encrypted value in the JSON. I have a C# script which handles the encryption and I have adapted it to Android. The encryption method in short is adding a byte array a few variables, randomly generated IV, randomly generated salt and hmacsalt, adding the ciphertext which is generated from a Json String using AES/CBC/Pkcs7Padding and generating hmac and also adding that to the byte array. Converting this byte array to base64 gives me the encrypted string. I think everything works fine in this process since the WebAPI is able to decrypt my string. What the problem is the string is decrypted mostly correct but there is a date field and the date appears to be completely wrong.
I've been trying to adapt the C# to Android and what I am not sure about is how to specify the key length.
The key generators in both C# and Android are as follows.
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, pbkdf2_iterations).GetBytes(pbkdf2_keyLength);
Here the pbkdf2_iterations = 100 and pbkdf2_keyLength =32;
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, 100, 32);
byte[] key = factory.generateSecret(pbeKeySpec).getEncoded();
Here when initiating the PBEKeySpec, when I pass 32 for the key length, the byte[]key becomes 4 bytes and there is an exception saying unsupported key size 4 bytes but the WebAPI is able to decrypt my string with the date error. When I pass 256 for the key length, to make the byte[] 32 bytes, the WebAPI is not able to decrypt the string.
I am quite confused about what is the right way to do this.
To give an example about the faulty decrypted string, my plaintext to be encrypted is;
{"Username":"username","Password":"password","DateCreated":"2019-10-25T14:46:01.441Z"}
but the decrypted string is;
{"Username":"username","Password":"password","DateCreated":"2019-03-04T09:29:54.3516562Z"}
In this case, I suppose the encryption is correct but have no clue how the date value and format changes.
Any opinions about what may be wrong would be greatly appreciated.
Edit:
JSonObject Creation code
The Json data is created statically.
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
String dateString = formatter.format(new java.util.Date());
JsonObject o = new JsonObject();
o.addProperty("Username", "username");
o.addProperty("Password","password");
o.addProperty("DateCreated", dateString);
String jsonString = o.toString();
And jsonString is passed for the encryption. I can see that its value is just the way I want.

Related

Cipher.doFinal() returning part of decrypted string for a long string

I am experiencing an issue when decrypting a string using sunjce :
javax.crypto.Cipher cipher =
javax.crypto.Cipher.getInstance("AES/GCM/NoPadding", new BouncyCastleProvider());
GCMParameterSpec spec = new GCMParameterSpec(Constants.GCM_TAG_BYTES * 8, nonce);
cipher.init(javax.crypto.Cipher.DECRYPT_MODE, dataKey, spec);
cipher.update(ciphertext);
return cipher.doFinal();
if I pass the whole ciphertext to doFinal it works correctly but if I call it correctly it only returns partial string. FOr instance for the input
String jsonExample = "{\"dataType\":\"STRING\",\"strValue\":\"000000\"}";
The decrypted bytes only contain "000000" but if I use
return cipher.doFinal(ciphertext);
and remove the update so it correctly prints the original string. What might be the reason? if I pass an empty byte array to doFinal after the update it also results in the same data loss. I want to know the logic behind it, it passes for small texts but for texts of this size it simply does not work.
this is my input
String jsonExample = "{\"dataType\":\"STRING\",\"strValue\":\"000000\"}";
This is how I am printing the decrypted string
String decryptedString = new String(decrypted, StandardCharsets.UTF_8);
This is how I am passing the input string as bytes to the encrypt function
text = jsonExample.getBytes(StandardCharsets.UTF_8)
this is how I am calling encrypt
GCMParameterSpec spec = new GCMParameterSpec(Constants.GCM_TAG_BYTES * 8, nonce);
try {
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, dek, spec);
byte[] ciphertext = cipher.doFinal(text);
When I use cipher.update(ciphertext) during decryption followed by cipher.doFinal() or cipher.doFInal(new byte[0]) it only returns
"000000" after I use the returned byte[] to String decryptedString = new String(decrypted, StandardCharsets.UTF_8);
But if I directly call cipher.doFInal(cipherText) during decryption the result string I get is the original string.
You're not using SunJCE, you're using the BouncyCastle provider. (You are using the Sun/Oracle API -- JCA, Java Crypto Architecture -- if that's what you meant to say, but not the provider SunJCE.)
Most Ciphers in JCA return partial data from each or any update call (in either encrypt or decrypt direction), thus in general if you use update you must concatenate that value (or those values if multiple calls) plus any value returned from doFinal before using the result for anything that needs it to be complete, such as decoding UTF8. (Typically the easiest way is to .write them all to a ByteArrayOutputStream, or .put them all to a ByteBuffer, but there are other options.) However the SunJCE provider does NOT do this for GCM, in the decryption direction only, apparently because the spec (SP800-38D) calls for the plaintext not to be released if the authentication fails, which can only be determined at .doFinal time, and then it returns all the plaintext.
The Bouncy provider does 'stream' GCM decryption, arguably in violation of the spec but consistent with usual and traditional JCA behavior, so most of the data is returned from the update call with only the last few bytes from doFinal and you need to concatenate these as above -- or, as you've found, just don't use update at all: if the data all fit in one buffer when encrypting, it should also fit when decrypting.
The Bouncy provider must buffer an amount of data equal to the tag so that it can remove and verify said tag; the "small texts" where your code works -- because decrypt .update returns nothing -- will be ones up to but not exceeding your tag size, which is apparently 8 bytes.

I'm having trouble decrypting a string in java [duplicate]

This question already has answers here:
javax.crypto.IllegalBlockSizeException : Input length must be multiple of 16 when decrypting with padded cipher [duplicate]
(3 answers)
Closed 3 years ago.
I'm having some trouble decrypting strings. The error I am recieving is:
"javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher"
Here is what I am trying to achieve.
-The user sets a password when creating an account. In this case Taylor.
-This password manager class will translate this string into Gibberish (This is what it produces : "I^ÇÔµoü|& ÄŠóÁ").
-Im then storing this gibberish in a text file.
-From there when the password Taylor is entered this stored gibberish gets decrypted and then compared to the string entered. if its correct the user can access the application.
Thank you for helping.
As a side note i'm not sure if I've initialized the key correctly either :/
This is also my first time playing around with encryption. Im not sure if its really really cool or really really frustrating.
public static void Decrypt(String encryptedText) {
try
{
//we are using the same key to decrypt the string as we used to encrypt it.
String key = "AbCd1234aBcD4321";
// Here we are taking the 128 bit key we just created and expanding it
Key aesKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, aesKey);
//decrypt the text
byte[] encrypted = cipher.doFinal(encryptedText.getBytes());
String decrypted = new String(cipher.doFinal(encrypted));
System.out.println(decrypted);
}
catch(Exception e)
{
e.printStackTrace();
}
Your issue is this line:
byte[] encrypted = cipher.doFinal(encryptedText.getBytes());
You can't convert arbitrary binary information to a string and expect it to convert back correctly. It simply doesn't work that way.
UTF-8 is structured binary data in the same way that an MP3 file is. Not every sequence of bytes produces a valid MP3 file, not every sequence of bytes produces a valid UTF-8 string.
Encrypted data is, by definition, binary data. You should be storing it as such.

Encrypting in NetSuite Suitescript and decrypting in java application

I need to encrypt a string using SuiteScript, send it to a web service written in Java, and decrypt it there.
Using SuiteScript I'm able to encrypt and decrypt without any issue. But when I use the same key in java, I get different errors.
var x = "string to be encrypted";
var key = 'EB7CB21AA6FB33D3B1FF14BBE7DB4962';
var encrypted = nlapiEncrypt(x,'aes',key);
var decrypted = nlapiDecrypt(encrypted ,'aes',key);
^^works fine^^
The code in Java
final String strPassPhrase = "EB7CB21AA6FB33D3B1FF14BBE7DB4962"; //min 24 chars
SecretKeyFactory factory = SecretKeyFactory.getInstance("DESede");
SecretKey key = factory.generateSecret(new DESedeKeySpec(strPassPhrase.getBytes()));
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.DECRYPT_MODE, key);
String encrypted = "3764b8140ae470bda73f7ebed3c33b0895f70c3497c85f39043345128a4bc3b3";
String decrypted = new String(cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted)));
System.out.println("Text Decryted : " + decrypted);
With the above code, I get an exception javax.crypto.BadPaddingException: Given final block not properly padded
The key was generated using openssl
openssl enc -aes-128-ecb -k mypassphrase -P
it looks like you are encrypting with AES, and decrypting with DES. I think the ciphertext needs to be decrypted with the same symmetric algorithm that you used to encrypt.
Looks like currently you have to use Suitescript to decrypt messages if it was encrypted using SuiteScript.
See suiteanswers: 35099
The workaround suggested is to use an external javascript library to encrypt/decrypt. We ended up using OpenJS on the javascript, but on the java side had to make sure the defaults were adjusted according to what is setup on the javascript side. The Java APIs were more flexible in this regard than the javascript ones.

How to generate the same AES key in Java(Android) as in .Net?

I need to generate an AES key in Java (Android) from salt and password given from .Net WebService. I need to have the same key as the key generated in .net with the same password and salt (using Rfc2898DeriveBytes and AesManaged()).
Here is my code in Android:
char[] passwordAsCharArray = password.toCharArray();
PBEKeySpec pbeKeySpec = new PBEKeySpec(passwordAsCharArray, salt, 1000, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKeySpec secretKey = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
Here is code in .net:
byte[] keyBytes = Encoding.Unicode.GetBytes(key);
Rfc2898DeriveBytes derivedKey = new Rfc2898DeriveBytes(key, keyBytes);
AesManaged rijndaelCSP = new AesManaged();
rijndaelCSP.BlockSize = 128;
rijndaelCSP.KeySize = 256;
rijndaelCSP.Key = derivedKey.GetBytes(rijndaelCSP.KeySize / 8);
rijndaelCSP.IV = derivedKey.GetBytes(rijndaelCSP.BlockSize / 8);
ICryptoTransform decryptor = rijndaelCSP.CreateDecryptor();
When I compare both keys they are different. Any ideas how to generate on Android the same key as in .Net? (I know that the key which have been generated in .net is correct).
Number of iterations in .Net is 1000, salt and password are also the same as in Android.
Ok, it turned out that I dont need exactly the same key (as a byte array). I needed this to decrypt a file (in Java) which have been encrypted in .Net - with this key it gaves me Bad Padding Exception so I think the key was different and that causes the problem, but all I needed to do was to generate IV like a key - that solved my problem. Thanks for response!
It looks like you used the "key" (which should be a password) as a salt in your .NET code, whereas the Java part uses a specified salt. Furthermore, you specified the Unicode character set for decoding your salt, which is weird, the salt should be a random octet string (== byte array) from the beginning.
I would recommend you transform your password and random salt to byte arrays first, compare both using a hexadecimal representation (on your console, or in your debugger) and only then use those as input parameters for the PBKDF2 function in each. I would recommend an UTF-8 encoding for your password.
Always specify all parameters in cryptography, try not to use default, e.g. for the iteration count. If your input is off by a single bit, the output will be completely incorrect, and there is no way to tell which parameter was responsible.
It looks like the Java and .NET PBKDF2 "primitive" is identical on both platforms, there is working code out on the internet.

RSA decryption of valid data padded data fails (BadPaddingException)

I am facing a very peculiar problem when using RSA encryption/decryption in Java.
Example code:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.genKeyPair();
Cipher enc = Cipher.getInstance("RSA");
enc.init(Cipher.ENCRYPT_MODE, kp.getPublic());
String CipherText = new String(enc.doFinal(PlainText.getBytes()));
System.out.println("CipherText: ") + CipherText);
Cipher dec = Cipher.getInstance("RSA");
dec.init(Cipher.DECRYPT_MODE, kp.getPrivate());
PlainText = new String(dec.doFinal(CipherText.getBytes()));
System.out.println("PlainText: " + PlainText);
As everyone can plainly see: I encrypt the plaintext using the public key, after which I decrypt the ciphertext using the private key.
This code crashes with the following message:
Exception in thread "main" javax.crypto.BadPaddingException: Data must start with zero
I also tried to explicitly use "RSA/ECB/NoPadding", and this fails on decoding period. (Eg the decoded ciphertext doesn't match the original plaintext).
Last but not least, I have tried to perform this when using my own PKCS1.5 padding function ala the PKCS1.5 specs:
EMB = 00 || 02 || RD || 00 || MD
EMB is encoded messageblock of length k
Where RD are 8 random nonzero bytes
MD is max length k = 11, and optionally padded with zero bytes to make EMB length k.
After two days of testing I can only conclude that the RSA algo in Java is flawed or simply not performing what I expect it to perform.
Any suggestions or fixes to the above code are very welcome, as I am completely stumped on why the above code will not simply work as expected.
Don't do this:
String CipherText = new String(enc.doFinal(PlainText.getBytes()));
Two reasons:
It's almost never a good idea to call String.getBytes() without specifying an encoding. Do you really want the result to depend on the system default encoding?
It's definitely never a good idea to treat the result of a binary encryption operation (i.e. opaque binary data) as an encoded string. Encode it in Base64 or hex instead.
You can use Apache Commons Codec to perform the base64 encode/decode operations, or this standalone public domain encoder/decoder.

Categories