Java AES encryption and decryption using a predefined 128bit key - java

The following code in java attempts to decrypt a string encoded in a QR code, encrypted in C# code. it seems to fail to decrypt the string. is there an easy way of doing this???
//string encrypted contains the string of the encoded characters.
String encrypted = intent.getStringExtra("SCAN_RESULT");
//converting the string into a byte array
byte[] byteEncrypted = encrypted.getBytes();
//instantiating the AES cipher object
Cipher cipher = Cipher.getInstance("AES");
//Predefined public-key
byte[] skey = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
//creating a secretKeySpec
SecretKeySpec skeyspec = new SecretKeySpec(skey, "AES");
//initializing the cipher to Decrypt
cipher.init(Cipher.DECRYPT_MODE, skeyspec);
final byte[] decrypt = cipher.doFinal(byteEncrypted);
//decrypting the string
String contents = new String(decrypt, "UTF-8");

For starters, you generally can't convert a cipher text stored as text, and convert it directly to bytes with a call to getBytes().
AES cipher text contains bytes with values from 0 to 255; I know of no character set encoding that maps all 256 values to a character, and even if there is one, it's unlikely to be your platform default encoding, and you aren't specifying it in your text-to-byte conversion.
The most common byte-to-text transformation for cipher text is Base-64 encoding. If that's what you are using here, you'll have to find or write a base-64 decoding utility.
You should also specify a complete transformation when creating the Cipher instance; otherwise, a provider specific default is used, and that might not match the sender's choices.
Since you don't show any IV, you might be using ECB as the mode. For most messages, this is insecure. It can only be safe if your message is a large random number (like a session identifier).
Most likely the padding is PKCS #5 padding (called PKCS7Padding in .NET), but you might have no padding, or some home-brew padding algorithm.
Assuming ECB and PKCS #5 padding, your cipher creation should look like this:
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

Related

Converting .Net decryption to Java

Currently I'm working on a project where they use AES encryption with RFC2898 derived bytes. This the decryption method that I've provided. Now I need to implement it in java.
private string Decrypt(string cipherText)
{
string EncryptionKey = "MAKV2SPBNI657328B";
cipherText = cipherText.Replace(" ", "+");
byte[] cipherBytes = Convert.FromBase64String(cipherText);
using (Aes encryptor = Aes.Create())
{
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] {
0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76
});
encryptor.Key = pdb.GetBytes(32);
encryptor.IV = pdb.GetBytes(16);
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(cipherBytes, 0, cipherBytes.Length);
cs.Close();
}
cipherText = Encoding.Unicode.GetString(ms.ToArray());
}
}
return cipherText;
}
This is what I go so far:
String EncryptionKey = "MAKV2SPBNI657328B";
String userName="5L9p7pXPxc1N7ey6tpJOla8n10dfCNaSJFs%2bp5U0srs0GdH3OcMWs%2fDxMW69BQb7";
byte[] salt = new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76};
try {
userName = java.net.URLDecoder.decode(userName, StandardCharsets.UTF_8.name());
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec pbeKeySpec = new PBEKeySpec(EncryptionKey.toCharArray(), salt, 1000);
Key secretKey = factory.generateSecret(pbeKeySpec);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] result = cipher.doFinal(userName.getBytes("UTF-8"));
System.out.println(result.toString());
} catch (Exception e) {
System.out.println(e.getMessage());
}
But I'm getting errors as below:
Key length not found
java.security.spec.InvalidKeySpecException: Key length not found
There are some issues in the Java code: The number of bits to be generated must be specified, besides the key the IV must be derived, the IV must be applied for decryption, the ciphertext must be Base64 decoded and Utf-16LE must be used when decoding the plaintext. In detail:
When instantiating PBEKeySpec, the number of bits to be generated must be specified in the 4th parameter. Since both, key (256 bits) and IV (128 bits) are derived in the C# code, 384 (= 256 + 128) must be applied:
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec pbeKeySpec = new PBEKeySpec(encryptionKey.toCharArray(), salt, 1000, 384);
The first 256 bits are the key, the following 128 bits the IV:
byte[] derivedData = factory.generateSecret(pbeKeySpec).getEncoded();
byte[] key = new byte[32];
byte[] iv = new byte[16];
System.arraycopy(derivedData, 0, key, 0, key.length);
System.arraycopy(derivedData, key.length, iv, 0, iv.length);
The IV must be passed in the 3rd parameter of the Cipher#init-call using an IvParameterSpec instance:
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec);
The ciphertext must be Base64-decoded before it can be decrypted:
byte[] result = cipher.doFinal(Base64.getDecoder().decode(userName));
From the decrypted byte array a string has to be created using Utf-16LE encoding (which corresponds to Encoding.Unicode in C#):
System.out.println(new String(result, StandardCharsets.UTF_16LE)); // Output: Mohammad Al Mamun
Note that for CBC mode, it's important that a key/IV combination is only used once for security reasons. For the C# (or Java) code here, this means that for the same password, different salts must be used for each encryption, see here.

Why does AES 128 encryption expanding the data?

I used following code to encrypt the data. My input is 16 bytes and key is 16 bytes but the output I am getting (encrypted data ) is 32 bytes. Why?
public static byte[] encrypt(byte[] plainText, byte[] key) {
try {
byte[] passwordKey128 = Arrays.copyOfRange(key, 0, 16);
SecretKeySpec secretKey = new SecretKeySpec(passwordKey128, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] cipherText = cipher.doFinal(plainText);
// String encryptedString = Base64.getEncoder().encodeToString(cipherText);
return cipherText;
What can be the reason? Does AES add some data?
You obtain Cipher object through the Cipher.getInstance(transformation) method where the transformation is of the form:
"algorithm/mode/padding" or
"algorithm"
When you do this the implementation searches through the list of crypto providers in the system and determine if any implementation supports this. If you don't specify the mode and padding, its up to the crypto provider to decide what default mode and padding to use.
According to this, For example, the SunJCE defaults to ECB as the default mode, and PKCS5Padding.
As PKCS5Padding always adds at least one byte, it pushes your 16 bytes over the limit of the block and creates the need for two blocks.

Java NIO + AES Encryption from Client to Server - ByteBuffer issue

I'm quite a newbie regarding encryption and NIO,
I have the following code for client:
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] encrypted = cipher.doFinal(msg.getBytes());
System.out.println("Encrypted info: " + encrypted);
String send = encrypted.toString();
bytebuf = ByteBuffer.allocate(48);
bytebuf.clear();
bytebuf.put(send.getBytes());
bytebuf.flip();
while(bytebuf.hasRemaining()) {
nBytes += client.write(bytebuf);
}
and the following code for server:
// Server receives data and decrypts
SocketChannel socket = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
nBytes = socket.read(buf);
String data = new String(buf.array()).trim();
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secret);
byte[] decrypted = cipher.doFinal(data.getBytes());
System.out.println("Decrypted Info: " + new String(decrypted));
When a message is sent from the Client to the Server, "HELLO" for example is encrypted to [B#34d74aa5 and on the Server side I get *Data packet found as [B#34d74aa5.
Till here everything looks fine, but I get the following exception:
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
I suspect that I have some issue with the way the data is coming out of the buffer on the server side?
Any ideas on this?
UPDATE:
**Based on Erickson's answer this is the final solution
javax.crypto.BadPaddingException: Given final block not properly padded
Client Code:
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
IvParameterSpec ivspec = new IvParameterSpec(iv);
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret, ivspec);
byte[] encrypted = cipher.doFinal(msg.getBytes(StandardCharsets.UTF_8));
String text = DatatypeConverter.printBase64Binary(encrypted);
System.out.println("Encrypted info: " + text);
bytebuf = ByteBuffer.allocate(32);
bytebuf.clear();
bytebuf.put(text.getBytes());
bytebuf.flip();
while(bytebuf.hasRemaining()) {
nBytes += client.write(bytebuf);
}
Server Code:
LOGGER.info("Confirming write");
String data = new String(buf.array());
LOGGER.info("Data packet found as {}", data);
/*******************************************************/
byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
IvParameterSpec ivspec = new IvParameterSpec(iv);
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, ivspec);
byte[] encrypted = DatatypeConverter.parseBase64Binary(data);
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Decrypted Info: " + new String(decrypted, StandardCharsets.UTF_8));
Your cipher text, encrypted, is a byte[], and invoking toString() on an array doesn't render the array content, it returns type ([B) and hash code (#34d74aa5) information as described by Object.toString().
You can't just use new String(encrypted) either. When a byte array is decoded to text, the decoder will replace any invalid byte sequences with the replacement character, \uFFFD (�). Thus, information is lost and subsequent decryption will fail.
Use an encoding like base-64 to convert byte sequences to printable characters instead. Don't junk up your code with third-party libraries for this; you can use javax.xml.bind.DatatypeConverter.
/* Client: */
byte[] encrypted = cipher.doFinal(msg.getBytes(StandardCharsets.UTF_8));
String text = DatatypeConverter.printBase64Binary(encrypted);
…
/* Server: */
byte[] encrypted = DatatypeConverter.parseBase64Binary(data);
byte[] decrypted = Cipher.doFinal(encrypted);
System.out.println(new String(decrypted, StandardCharsets.UTF_8);
You should also be explicit in selecting your mode and padding (like "AES/CBC/PKCS5Padding") because there's no guarantee the recipient will use the same provider, or that the same provider will use the same defaults over time. Same goes for specifying character encodings, like UTF-8.
The AES scheme is a "block cipher" it works on fixed-size blocks of data. You are creating a "raw" Cipher instance, which will expect you to make sure that every byte array that you pass to the cipher is aligned to the cipher's "native" block length. That's usually not what you want to do.
An additional problem that you are exposing yourself to in using the cipher "raw", although it's not causing an actual error, is that if you were to pass it the same block of data on separate occasions, each time, that block would be encrypted identically, therefore giving an attacker clues as to the structure of the data. Again, that's usually not what you want to do in a practical application.
So usually, you need to specify two extra things: a padding scheme, which determines what happens when sections of data are not exactly aligned to a block size, and a block mode, which determines what scheme the cipher will use to avoid identical input blocks being encrypted to identical output blocks. The block mode generally needs initialising with a "starting state" called the initialisation vector (you could use a default state of "all zero", but that's less secure).
So you need to do two things:
You need to initialise you cipher with a padding scheme and block
mode, e.g. "AES/CBC/PKCS5PADDING"
For additional security, you would also usually set up (and transmit
before the data) a random initialisation vector. See this example for more
information.
You are converting the ciphertext, which is a byte[], to a String here:
byte[] encrypted = cipher.doFinal(msg.getBytes());
String send = encrypted.toString();
This is incorrect. You also cannot do new String(byte[]) because the byte[] is random, not a stream of character data in the platform default encoding assumed by new String(byte[]). You should convert the byte[] data to a String by using a hex or base64 encoding (I recommend Apache Commons Codec) e.g.
hexEncodedCipherText = new String(Hex.encodeHex(binaryCipherText))
On the server-side, use the opposite operation to convert the hex or base64 encoded data back to a byte[] before decryption e.g.
binaryCipherText = Hex.decodeHex(hexEncodedCipherText.toCharArray());
UPDATE:
The updated question is not working during decryption because of the incorrect use of the initialization vector. You don't specify an IV during encryption, which means Java will generate a random one. You need to obtain this random IV from the cipher by calling cipher.getIV() after the encryption (or specify it explicitly, though generating a random one is more secure). Then, during the decryption, create the IvParameterSpec using the IV created during encryption. In addition, you will need to encode/decode the IV in the same manner as the ciphertext, since it is also binary data.
UPDATE 2:
I see you have updated your question with the IV, but you are using a null IV. Generally, this is only "safe" when you have a unique key for every message you send. If your key is fixed or re-used for any significant length of time, you should generate a unique IV for each encryption/decryption. Otherwise, you are leaving yourself open to cryptanalysis based on multiple ciphertexts encrypted with the same key and IV.

Cipher.init() required for each message?

Assume two clients are exchanging secure messages back and forth.
Must this block be run every time for each message, or can any step(s) be done just once at start:
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
output = cipher.doFinal(content);
I guess to lend some context- although I don't (yet) understand the internals completely, it is my understanding that for security purposes it's important to change the IV for each message. So I think the answer to this question will depend on whether that step happens under the hood at the doFinal() stage or init()....?
You are correct: to be safe you need to use a new,random, IV for each message. This means you either need to recreate the cipher or randomly set the IV yourself for each subsequent message. The former is probably safer since if you change ciphers or modes, there maybe some other state you need to randomly set as well and reinitializing the cipher should handle all of it.
If you don't do this, you end up with the same rather bad bug SSL had with IV reuse.
Cipher.doFinal does not reset the cipher to a random IV. In fact, its far worse than that, it appears to reset the internal state to the same IV you started with. As shown by this code.
Cipher f = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] keyBytes = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
f.init(Cipher.ENCRYPT_MODE, key);
byte[] iv = f.getIV();
System.out.println(Arrays.toString(f.doFinal("hello".getBytes())));
System.out.println(Arrays.toString(f.getIV()));
System.out.println(Arrays.toString(f.doFinal("hello".getBytes())));
System.out.println(Arrays.toString(f.getIV()));
System.out.println( Arrays.equals(f.getIV(), iv)); // true

encryption using provided key using DES with padding

I want to encrypt a frame using DES using a given key.
The padding style I am using is PKCS5Padding. This pads the string with 02 02 if 2 bytes are to be added and with 03 03 03 if 3 bytes are to be added to make multiple of 8.
But my requirement is to pad a string with my particular bytes. e.g if 2 bytes are to be added then 30 30 and 3 bytes are to be added then 30 30 30 (in hex 0's value is 30). Also, I must know how many padded bytes have been added.
Which padding technique should I follow and how can I use it?
Below is my code for encryption:
byte[] keyValue = new byte[]{(byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x16,(byte) 0x05, (byte) 0x12};
myKeySpec = new DESKeySpec(keyValue);
mySecretKeyFactory = SecretKeyFactory.getInstance("DES");
key = mySecretKeyFactory.generateSecret(myKeySpec);
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
IvParameterSpec iv2 = new IvParameterSpec(new byte[8]);
cipher.init(Cipher.ENCRYPT_MODE, key, iv2);
byte[] plainText = function.HexStringToByteArray(payloadRecv);
byte[] encryptedText = cipher.doFinal(plainText);
Do not select PKCS5Padding in the cipher specification. Select NoPadding. You'll have to add the padding onto the data yourself prior to encrypting it. After decrypting it (also using no padding), you'll have to inspect the last byte to know how many bytes of padding to remove and remove it yourself.
Basically, just code exactly what you described.

Categories