DES encryption plain vs. cipher length - java

I am using Java to make a toy program that encrypts a message using DES encryption. The message I want to encrypt is:
String msg="This is a secret message";
Which I convert to bytes as:
byte [] msgBytes=msg.getBytes();
And send it to encrypt function that works as follows:
//encryption function
public static String encryptMsg(byte [] msgBytes, SecretKey myDesKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException
{
Cipher desCipher;
// Create the cipher
desCipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
desCipher.init(Cipher.ENCRYPT_MODE, myDesKey);
byte[] textEncrypted = desCipher.doFinal(msgBytes);
// converts to base64 for easier display.
byte[] base64Cipher = Base64.encode(textEncrypted);
return new String(base64Cipher);
} //end encryptMsg
Then, I display the cipher, the cipher and plaintext lengths and I get:
Encrypted Message: FDCU+kgWz25urbQB5HbFtqm0HqWHGlGBHlwwEatFTiI=
Original msg length: 24
Encrypted msg length: 44
Can you please clarify to me why the cipher length is 44 while the original message length is 24?
EDIT:
Kindly, I need the answer with clarification. The cipher always ends with =. Could this be because of the padding? Can you explain to me why/how the cipher is resulted with this length? And always ends with =?
Is my code correct or there is a mistake? I have doubts in the encoding part.

There are several things going on:
msg.getBytes() returns the bytes representing an encoding of the string using the "platform's default charset" (e.g. could be UTF-8 or UTF-16 or ..): specify the encoding manually to avoid confusion! In any case, see msgBytes.length to get the true plain text length.
DES, being a block cypher, will have output padded along a block size boundary - but this will always be larger than the plain text (refer to msgBytes.length) length when using PKCS#5 because the plain text is always padded with [1,8] bytes. To see what the true encrypted size is, see textEncrypted.length.
The encrypted bytes are encoded using base-64 and this process - which is independent of the encryption - inflates the number of bytes required by about 33% (as only 6 bits per character/byte are used). The Java base-64 implementation also adds padding which is where the trailing "=" character is introduced.
As long as you (or someone else with the correct algorithm and cipher key) can retrieve the initial string - by performing the inverse of each step in reverse order, then it works. If a particular step does not have an inverse/reverse operation or cannot be "undone", then something is wrong; but this also means that every step can be individually tested.
To the numbers!
msg.getBytes() returns an ASCII/UTF-8 encoded sequence (if it used UTF-16 or another another "wide" encoding then the numbers below would be too large)
Therefore, msgBytes.length is 24
And since msgBytes.length mod 8 is 0, the plain text is padded with 8 bytes that have the value of 0x08 (per CKCS#5)
Thus, textEncrypted.length is 32 (24 data + 8 padding)
Due to base-64 encoding, 32 bytes * 1.33 ~ 43 characters
And with base-64 padding (=), the final result is 44 characters!

The result of a DES encryption will always be a multiple of 8 bytes. The input is also padded to a multiple of 8 bytes according to the padding algorithm specified.
The base 64 encoding encodes each 3 bytes into 4 characters (3x8 = 4x6 = 24), and ensures the output length is a multiple of 4 by padding with = characters.
So, the 44 characters output corresponds to 33 bytes, but the = at the end indicates that in fact there were only 32 bytes. Which is fine, since 24 bytes clear data with PKCS5 padding becomes 32 bytes.

Related

Problem in hiding exact size of cipherText inside image, later extract it and then decrypt it

I am having a problem in storing exact size of encrypted message inside an image.
What I want to do is that I want to encrypt a message with AES cipher and then I want to hide each byte of message in a random pixel by replacing only one component(either red or green or blue component) of pixel with the message byte.
I have done that but I am unable to store with the same size of ciphertext.
First I have done encryption of message with the help of a key using AES/CBC/PKCS5Padding using Java Cryptographic Extension.
byte[] cipherText = encrypt(message, keyBytes);
Then I had to convert cipherText to UTF-8 byte array and then store inside image. If not I will not be able to decrypt it later. If I don't encode properly, I cannot decrypt it after extraction. The problem here is that the size of messageBytes > size of cipherText.
It takes more space than the encrypted message i.e cipherText.
Example: if cipherText is 32 bytes, I get 148 bytes of messageBytes.
But I want to store the byte array of size cipherText and be able to extract it later.
String encryptedMessage = Arrays.toString(cipherText);
byte[] messageBytes = encryptedMessage.getBytes(StandardCharsets.UTF_8);
Now 148 bytes are stored in the image.
Then I have stored each byte inside one of the component(r or g or b) of random pixel.
for(int i=0; i<messageBytes.length; i++) {
storeInsidePixel(image, messageBytes[i], secureRandom)
}
In the extraction part, I retrieved the messageBytes back and converted to encryptedMessage to get the hidden cipherText.
String encryptedMessage = new String(byteArray, StandardCharsets.UTF_8);
String newString = encryptedMessage.substring(1, encryptedMessage.length()-1);
String[] stringArray = newString.split(", ");
byte[] cipherText = new byte[stringArray.length];
for(int i=0; i<stringArray.length; i++) {
cipherText[i] = (byte) (Integer.parseInt(stringArray[i]));
}
Then I decrypted it using the same cipher and key.
String output = decrypt(cipherText, keyBytes);
I want to store the byte array of size cipherText and be able to extract it with same size and decrypt it later.
If I don't encode to utf-8 and store it, I don't get the same output because if the cipherText stored in image is 1040 bytes and later extract it, I will get the same byte values but the size of byte array is just 205 bytes and decryption fails.
Is there any to store exactly 1040 bytes of cipherText in image and extract the same 1040 bytes of cipherText and be able to decrypt it?
Is there any way to get it like that?
Then I had to convert cipherText to UTF-8 byte array and then store inside image. If not I will not be able to decrypt it later. If I don't encode properly, I cannot decrypt it after extraction.
No, here is where you go wrong. Your cipherText should be binary already (as shown by byte[] cipherText = encrypt(message, keyBytes);) , and image formats are binary as well. So you should never have to encode it again. Just directly use cipherText.
The problem here is that the size of messageBytes > size of cipherText. It takes more space than the encrypted message i.e cipherText. Example: if cipherText is 32 bytes, I get 148 bytes of messageBytes.
...
String encryptedMessage = Arrays.toString(cipherText);
Yeah, well, duh. If you do Arrays.toString then you will get a textual representation of the array, including separators and whatnot, all which you can do without.
If I don't encode to utf-8 and store it, I don't get the same output because if the cipherText stored in image is 1040 bytes and later extract it, I will get the same byte values but the size of byte array is just 205 bytes and decryption fails.
We cannot see why you get a smaller value back here, but this is what needs to be solved - and it should not be solved by encoding / decoding. Feel free to post a follow-up question with a minimal reproducible example.
Is there any to store exactly 1040 bytes of cipherText in image and extract the same 1040 bytes of cipherText and be able to decrypt it? Is there any way to get it like that?
There are two problems here: length indication -> you need some way to indicate the size of the ciphertext. Furthermore, if you use the same key for multiple messages then you would need to store the IV as well.
For detecting the size: you can e.g. prefix a 16 bit (short) length indicator if your ciphertext doesn't exceed 65536 bytes. Or, for CBC, you could use it to count the number of blocks as the unpadding will then reveal the exact size. This would let you store 16 times larger ciphertext.
With a 2 byte size indicator and 16 byte IV you'd grow your ciphertext by 18 bytes. Since your ciphertext is already up to 16 bytes larger than the plaintext this may be a small enough size increase for you to handle.

Generate a 16 digit unique Hexadecimal value from given string of numbers

How would I go about doing that? I tried using SHA-1 and MD5 but the output is too long for my requirements and truncation would not make it unique.
Input : String containing numbers e.g. (0302160123456789)
Received output : 30f2bddc3e2fba9c05d97d04f8da4449
Desired Output: Unique number within range (0000000000000000 - FFFFFFFFFFFFFFFF) and 16 characters long
Any help/ pointers are greatly appreciated.
How big is your input domain? If it is bigger than your output domain, then the Pigeon Hole principle applies and you can't get unique output by definition.
If the input domain is smaller or equal to the output domain, then you can easily accomplish this with a Pseudo-Random Permutation (PRP) that block ciphers provide.
The output of 16 hexits is equivalent to 8 bytes and equivalent to 64 bit. DES (and Triple DES) is a block cipher that has this block size.
Parse the input string to a byte array in a compact fashion. If the input always consists of numerical digits, you can use Ebbe M. Pedersen's approach with
byte[] plaintext = new BigInteger("0302160123456789").toByteArray();
Then you can generate some random, but fixed key of 24 bytes for Triple DES and instantiate the cipher with:
Cipher c = Cipher.getInstance("DESede/ECB/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DESede"));
byte[] ciphertext = c.doFinal(plaintext);
Use some kind of Hex converter to get the representation you want.
You can "hash" numbers up to 36028797018963968 with this. If you want larger numbers (up to 9223372036854775808), then you need to use "DESede/ECB/NoPadding" and pad yourself with some padding bytes.
are you going to receive more than FFFFFFFFFFFFFFFF different strings?
if not then it's a simple problem of generating integers: the first string will get 0 the next 1 etc; you just keep a list of the strings and check if something the same appears.
You could just convert your number to hex using BigInteger like this:
String id = new BigInteger("0302160123456789").toString(16);
System.out.println(id);
That gives:
112d022d2ed15

generate random characters in my case

I need to generated random 32 characters string with the use of SecureRandom class. I tried with generating 32 byte array then use Base64 encoding:
byte[] bytes = new byte[32];
new SecureRandom().nextBytes(bytes);
new String(Base64.encodeBase64(bytes));
But this code generate a string with more than 32 characters. How can I get random 32 characters while still using SecureRandom class ?
Try to encode 22 to 24 bytes instead.
When encoding this amount, the resulting Base64 encoded string should contain exactly 32 characters although some of them might be = marks based on whether its 22 or 23 bytes due to padding.
If you don't want the = marks, just encode 24 bytes and no padding will be added.
If you are more interested on how the padding or Base64 encoding works, the current wikipedia article is quite detailed.
e.g. change your code accordingly:
byte[] bytes = new byte[24];
new SecureRandom().nextBytes(bytes);
new String(Base64.encodeBase64(bytes)); // Should be 32 characters in length.
We can achieve this using single line of code. Use org.apache.commons.lang.RandomStringUtilsfrom commons-lang library.
Code :
RandomStringUtils.randomAlphabetic(32);

Why does Java crypto AES pad my plaintext message which is exactly 16bytes?

For example, if a message is 16-byte long:
String message = "AAAAAAAAAAAAAAAA"
byte[] plaintext = message.getBytes();//plaintext.length = 16
the output cyphtertext would be 32-byte long, which indicates there are 2 blocks here, and the second block has been padded.
Whereas, if a message is 15-byte long or shorter:
String message = "AAAAAAAAAAAAAAA"
byte[] plaintext = message.getBytes();//plaintext.length = 15
the output cyphertext would also be 16-byte long.
Since AES's block is 16-byte long, why 16-byte plaintext message would be divided into two blocks and be padded?
Thanks!
It actually depends on the encryption mode. Some modes do not require padding at all, but the modes that require padding do require that messages of a multiple of the block length include an additional block full of padding bytes, otherwise it becomes ambiguous if the data is padded or not.
Since some messages will be padded, the algorithm will always look at the last bytes and interpret them as padding information. So all messages must be padded, even those that are of a suitable length in the first place.
For example, if your padding scheme adds bytes which values is the number of missing bytes to fill the block, and your message ends with 01 or 02 02, 03 03 03, etc. You wouldn't know if it's padding or data.

Java - Converting string into DES key

I have been given a key as a string and an encrypted file using DES. That is all I know. I don't know how the key was encoded.
There is also a des.exe that I can use to decrypt, this is all I found on the Internet: http://knowledge-republic.com/CRM/2011/07/how-to-decrypt-extract-recreate-thecus-storage-firmware/
Using des.exe, the only command it works with is "-D", not "-d".
My goal is to use Java to do the same thing. I copied and pasted this from somewhere
String key = "blah";
DESKeySpec dks = new DESKeySpec(key.getBytes());
SecretKeyFactory skf = SecretKeyFactory.getInstance("DES");
SecretKey desKey = skf.generateSecret(dks);
System.out.println(desKey);
Cipher cipher = Cipher.getInstance("DES"); // DES/ECB/PKCS5Padding for SunJCE
if (mode == Cipher.DECRYPT_MODE) {
cipher.init(Cipher.DECRYPT_MODE, desKey);
CipherOutputStream cos = new CipherOutputStream(os, cipher);
doCopy(is, cos);
}
and it doesn't work.
What are some other options in converting a string into a key?
Should probably add I'm a complete newb at cryptography.
The SunOS man page for des (which seems to be what your des.exe is based on?) indicates that they key is generated like this:
The DES algorithm requires an 8 byte key whose low order bits are assumed to be odd-parity bits. The ASCII key supplied by the user is zero padded to 8 bytes and the high order bits are set to be odd-parity bits. The DES algorithm then ignores the low bit of each ASCII character, but that bit's information has been preserved in the high bit due to the parity.
It also mentions that the initial IV is always zero'd out, no matter what mode you are running in
The CBC mode of operation always uses an initial value of all zeros
for the initialization vector, so the first 8 bytes of a file are
encrypted the same whether in CBC or ECB mode.
It also mentions that the padding used is such that the last byte is always a value from 0-7, indicating the number of padding bytes used. This is similar to PKCS5Padding, so perhaps that would work
Since the CBC and ECB modes of DES require units of 8 bytes to be
encrypted, files being encrypted by the des command have 1 to 8 bytes
appended to them to cause them to be a multiple of 8 bytes. The last
byte, when decrypted, gives the number of bytes (0 to 7) which are to
be saved of the last 8 bytes. The other bytes of those appended to the
input are randomized before encryption.
Based on the options you indicated you are using, it sounds like you are using DES/CBC/PKCS5Padding for the cipher.
I think that just leaves determining how to actually derive the key. I found this sample code on exampledepot which might work for you. I think you would just need to convert your string password into 8 bytes (1 byte per character, so no UTF encodings) then stuff it through the code in the example to derive the key. Its worth a shot anyway.
DES keys are 7 (apparently SunJCE uses 7?) or 8 bytes. Check if the string you have been provided is 7 or 8 bytes. If so, then the chances are good it's the raw key. If not, it could be encoded in some fashion. A giveaway for hex encoding would be a prefix of 0x or suffix of h, and all characters would be in the range 0-9,A-F. You can certainly convert from hex yourself or use some code on the web, but I usually use an Apache commons lib (http://commons.apache.org/codec/apidocs/org/apache/commons/codec/binary/Hex.html).
That said, this is really speculation and I'm not sure we can jump to the conclusion that it's a problem with the key alone. Do you have any other info on the purported encryption algorithm? If the executable you cited works with "-d" then it seems like the encryption is plain DES in CBC mode:
-b : encrypt using DES in ecb encryption mode, the defaut is cbc mode.
(there are multiple possible modes, see http://download.oracle.com/javase/1.4.2/docs/guide/security/jce/JCERefGuide.html#AppA)
I would try setting your cipher to "DES/CBC".
Then again, I'm not sure how to interpret this:
Default is tripple cbc
You may be able to use this snippet to tell what ciphers are available on your system: http://www.java2s.com/Code/Java/Security/ListAllProviderAndItsAlgorithms.htm
I had the same issue with C#. I solved it in the end. You can have a look at my answer here: DES Initialization Vector in C#
Generally, what des.exe does, is that it computes a checksum using DES. So each encryption step is using the previous result instead of advancing in the output array.

Categories