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

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.

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.

AES encrypted and decrypted values differ

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.

Byte Array to String and back again are not the same even with UTF-8 encoding (also Base64)

SOLVED
Apparently the code below works, but Eclipse had some sort of issue and I restarted it to fix it. (After all these hours of debugging...)
I am writing a small encryption mechanism for stuff in my app. It is not connected to a service that provides tokens, so I have to store some sensative data locally, which I want to be encrypted. I figured a good place would be in SharedPreferences, where I can store some data that is encrypted, anad the user must provide a 'key' that is used for unlocking the data (it is part of the algorithm, so that part is never truley stored).
The issue is that I make an encrypted piece of data (which is returned to me as a byte[], which I convert to Base64 (also tried UTF-8), and store in SharedPreferences. For testing purposes right now I am immediately reading back the string out of shared preferences and attempting to decrypt it with the same 'key' used to encrypt, but it's throwing some exceptions, and the byte arrays for storing and the one retreived (and converted to bytes) are not the same.
I'm using the Crypto Example given in the accepted answer of the following question:
Java string encrypt
My code is as follows:
SharedPreferences crypto = getActivity().getSharedPreferences("cryptodb",
Context.MODE_PRIVATE);
String uuid = createLocalUUID(); //used to prevent moving data to a different device (security risk)
if (uuid != null) {
try {
SecretKey secret = Cryptography.generateKey(passPhrase,
uuid.getBytes("UTF-8")); //This key generator is the same as the one used for decryption below
byte[] encrypted = Cryptography.encryptMsg(uuid, secret);
SharedPreferences.Editor editor = crypto.edit();
String putdata = Base64.encodeBytes(encrypted);
editor.putString("pass_check", putdata); //decrypt this back to the "stored" UUID to show that this is the correct passphrase
// Commit the edits!
editor.commit();
//Test decrypting the validator object
String validation = crypto.getString("pass_check", "FAILURE"); //get string. Must provide fallback string
String result = Cryptography.decryptMsg(Base64.decode(validation), secret); //fails
} catch (Exception e) {
// too many exceptions to catch.
e.printStackTrace();
}
}
The Cryptography class I am using works, as it encrypts and decrypts if I don't put and get the string from SharedPreferences. I have also tried storing the data as UTF-8 rather than Base64, but that same issue still appears.
The 'strings' that I put and read back are the same (I tested in code), but when I compare the byte arrays (using Arrays.compare(), it returns that they are different. So I am not sure what is going on...
Any help is appreciated.

Decrypting a hardcoded file as byte[]

Well this is actually a two-parter...
First I need to
read the contents of the file
crypt them into a byte[]
write the byte[] in a file or whatever...
Then the result from #2 or #3 will go into another project. I'm trying to protect our PEM/DER keys.
For decryption, I need to
read the contents of the crypted file as a byte[]
decrypt them into a byte[]
write the decrypted data to a file OR use it instead of a file
Now, I have some basic crypting code
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128); // 192 and 256 bits may not be available
SecretKey secretKey = keyGenerator.generateKey();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// By initializing the cipher in CBC mode, an "initialization vector" has been randomly
// generated. This initialization vector will be necessary to decrypt the encrypted data.
// It is safe to store the initialization vector in plain text for later use. You can obtain
// it's bytes by calling iv.getIV().
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
IvParameterSpec iv = cipher.getParameters().getParameterSpec(IvParameterSpec.class);
// IvParameterSpec iv = new IvParameterSpec(IV); //used for the hardcoded one
byte[] encryptedData = cipher.doFinal(data);
and decrypting one as well
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
byte[] decryptedData = cipher.doFinal(encryptedData);
System.out.println("decrypted: " + new String(decryptedData));
and the question is:
Given a use-case scenario where one would rarely encrypt something and would distribute crypted keys that are to be decrypted at runtime, what do I need to save apart from the cyphertext?
I know I need to save the IV, but when I did decryption wasn't quite good - which leads me to believe that I need to save the secretKey as well.
Could anyone give me any tips, pointers or general security hints to a better solution? If I need to save the key, the IV and the encrypted data, where should I store them? Maybe hardcode the key and store the IV along the encrypted data? Maybe hardcode both the IV and the key and just store encrypted data in the files?
This isn't about theoretical safety, think of this as the biggest nuissance and inconvenience you can cause to someone that is trying to steal your keys. We all know there's no way I can perfectly hide them.
I pretty much need what this guy started with Decrypting an encrypted file and executing in Java
However if there's a better way of feeding secure data into a PemKeyReader, i'm all ears.
Sharing the key and encrypting something are two completely different things. How to share keys
Having said this, AES with 128bit is fairly strong encryption algorithm than 3DES So what you can do is keep PKI infrastructure in place to exchange AES keys and then Encrypt and Decrypt using them.
Why not RSA? RSA needs to be minimum 512 bit to consider it as strongest and if you increase more bits then it increases time required for encryption and decryption.
SO AES is fast and safe.
Use SecretKeySpec to create key from byte[]
public static void main(String[] args) throws Exception
{
// Initialise secret key with predefined byte array [] like below. I
// have used simple string to array method to generate 16 byte array.
// AES Key must be minimum 16 bytes.
// Now you can put this byte array some where is .SO file.
// Generate new Key using this byte []
// Then you can generate a key using device specific information at
// first boot up.
// Use second key to encrypt data and first key to encrypt the second
// key
// I Hope it clears all the doubts
SecretKey key = new SecretKeySpec("ABCDEFGHIJKLMNOP".getBytes(), "AES");
System.out.println(Arrays.toString(key.getEncoded()));
// Initialise Cipher with AES Algorithm
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// Set The Encrypt Mode
cipher.init(Cipher.ENCRYPT_MODE, key);
// Encrypt some bytes
byte[] encrypted = cipher.doFinal("ABCDEFGH".getBytes());
// Print it to vefiry
System.out.println(Arrays.toString(encrypted));
// Get The IV
byte[] iv = cipher.getIV();
System.out.println(iv.length);
// Now why storing you can create structure like [16 IV][Encrypted Data]
// And while decrypting you can read first [16] bytes IV and then
// decrypt remaining bytes
//byte[] iv = new byte[16];
// System.arraycopy(encrypted, 0, iv, 0, 16)
//Copy remaining bytes to decrypt
// set cipher to decrypt mode
cipher.init(Cipher.DECRYPT_MODE, key,new IvParameterSpec(iv));
// decrypt it
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println(new String(decrypted));
}
Now write an algorithm which will generate byte[] from some random data like device name, user name, random seed etc.
You can add more protection to algorithm source code by writing that algorithm in C and create.SO file and get byte [] using Native calls.
What are the advantages of doing all this?
Event if your so is hacked it will need real time environment to run create key out of it.
Even if some one does crack it the damage will be limited i.e. 1 device
Hacker will have to repeat same with each device which is highly impossible to do.
The I/O aspect of your question is best addressed by reading the "Byte Streams" and "Buffered Streams" sections of the Oracle Java tutorial. You can accumulate the bytes in memory by writing them to a ByteArrayOutputStream, and then using the toByteArray() method to get the bytes as a byte[].

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