DES Encryption after Base64 encoding gives different result - java

I have a Java BMS application with Java clients running on RPi. However, I like to add Arduino units to the system as they are simpler, but ideally using the same communication protocol.
I encrypt a JSON with DES and then encode it with Base64.
DESKeySpec keySpec = new DESKeySpec(encryptionKey.getBytes("UTF8"));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey key = keyFactory.generateSecret(keySpec);
byte[] cleartext = data.getBytes("UTF8");
Cipher cipher = Cipher.getInstance("DES"); // cipher is not thread safe
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] finalByte = cipher.doFinal(cleartext);
return Base64.encodeBase64String(finalByte);
For the following JSON this results in the bytes (as HEX):
json: {"result":"SUCCESS","message":"(none)", "actions":[{"action":"campAlarm","value":"false"},{"action":"warningBeep","value":"false"},{"action":"gpio_21","value":0}]}
Encrypted: 1e64fd8c074b2eda044f2ed820dab06949e5a2c5602918b13779e906c2733c1719965a456e2127fef0c910cbbbfcd137c535c9423cc14e7e757ddbe9f74ea307d7584eed404e31ff4cb069c40b2464eff5a6705666900a5706950b87df995e8252ed2dfb1070287c080a3527b3c40a2749c2982d033279b827cde81829d2a8d7568808caa6604a32ef41fe9b1a2fdbd3e79d5107938e6b179100c7f542cfe8cb433750ea38954bd9
Base64 encoded: HmT9jAdLLtoETy7YINqwaUnlosVgKRixN3npBsJzPBcZllpFbiEn/vDJEMu7/NE3xTXJQjzBTn51fdvp906jB9dYTu1ATjH/TLBpxAskZO/1pnBWZpAKVwaVC4ffmV6CUu0t+xBwKHwICjUns8QKJ0nCmC0DMnm4J83oGCnSqNdWiAjKpmBKMu9B/psaL9vT551RB5OOaxeRAMf1Qs/oy0M3UOo4lUvZ
However, I am running into a difficulty with this particular json string (it doesn't happen with all). There are extra zeros (reading the HEX) in the server encrypted data before Base64 encoding, which are not there after decoding with Base64 on Arduino (result is the string with the Base64 encoded data):
char encoded[result.length()];
result.toCharArray(encoded, result.length());
// Convert back.
int decodedLength = Base64.decodedLength(encoded, sizeof(encoded));
char decodedChar[decodedLength];
Base64.decode(decodedChar, encoded, sizeof(encoded));
Serial.print("Decoded: "); des.printArray((byte*) decodedChar, decodedLength);
for (int i = 0; i < decodedLength; i += 8) {
byte intermitInput[8];
for (int j = 0; j < 8; j++) {
intermitInput[j] = (byte) decodedChar[i + j];
}
Serial.print(i);Serial.print(" ");
des.decrypt((byte*)decodedChar + i, intermitInput, (byte*)key); // Re-use the decodedChar array as it is already initiated (saving money)
}
Serial.println("Finished decription.");
decodedChar[decodedLength] = '\0';
Serial.print("Decripted: "); des.printArray((byte*) decodedChar, (int) sizeof(decodedChar));
Serial.print("Decripted result:\t");
Serial.println(decodedChar);
This results in:
Base64 decoded: 1e64fd8c74b2eda44f2ed820dab06949e5a2c5602918b13779e96c2733c1719965a456e2127fef0c910cbbbfcd137c535c9423cc14e7e757ddbe9f74ea37d7584eed404e31ff4cb069c4b2464eff5a670566690a57695b87df995e8252ed2dfb1070287c8a3527b3c4a2749c2982d33279b827cde81829d2a8d756888caa6604a32ef41fe9b1a2fdbd3e79d517938e6b17910c7f542cfe8cb433750ea38954bbf
DES decrypted: 7b22726573756c74223a2253554343455353222c226d657373616765223a22286e6f6e6529222c2022616374696f6e73223a5b7b22616374696f6e223a2263616d70416c61726d222c2276616c7565223a2266616c7365227d2c7b22616374696f6e223a227761726e696e6742656570222c2276616c7565223a2266616c7365227d2c7b22616374696f6e223a226770696f5f3231222c2276616c7565223a30d8e9d029b7c75ed1
json: {"result":"SUCCESS","message":"(none)", "actions":[{"action":"campAlarm","value":"false"},{"action":"warningBeep","value":"false"},{"action":"gpio_21","value":0⸮⸮⸮)⸮⸮^⸮
I guess it is because of the 'missing' '0' (in the HEX) after the Base64 decoding that the last 8 characters if the json are off. This is only a guess, as I don't know the reason those '0' HEX 'characters' are missing. (eg 1e64fd8c07 vs 1e64fd8c7)
Is there anybody know how this happens? The interesting thing is that the string is decrypted correctly apart from the last group of 8 bytes. The string is 163 characters, so is padded upon encoding.

Related

AES encrypt any java object to a base64 string

I'm trying to encrypt any java object (in this example an Integer, but Date should also work) to a base64 string using the Cipher class.
Basically I convert the given object into a byte array using ByteArrayOutputStream and encrypt this byte array with Cipher. See below
for (Integer i = 0; i < 10; i++) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput oos = new ObjectOutputStream(bos);
oos.writeObject(i);
oos.flush();
byte[] data = bos.toByteArray();
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec("&E(H+MbQeThWmZq4".getBytes("UTF-8"), "AES"));
String base64output = Base64.getEncoder().encodeToString(cipher.doFinal(data));
System.out.println(i + " - " + base64output);
}
The output
0 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa94LOaOdEXeZZm8qNoELOLdj
1 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97aK6ELffW8n7vEkNAbC9RW
2 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97mJ1m8lVtjwfGbHbMO2rxu
3 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa942rroZJbe2KN0/t8ukOkWd
4 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97rbkvF4HLzuvGTm4JMJw+2
5 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa94zvSlIQe8RQI8t5/H74ShO
6 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97tNLWZHmR0rNkDXZtVWA2Y
7 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa94lr84KZ6MnUsPOFyJIfDTB
8 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97e6ihJ8SXmz9sy9XXwWeAz
9 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97neBL2tLG2TXgCI/wDuyMo
seems strange to me because of the same prefix BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa9
for every encrypted object. In this example, I'm using the same key for every object but this should not be the reason for this issue.
I've also tested this example with Strings and Dates instead of Integers. Encoding Dates into byte arrays and encrypting them with the same method also lead
into this issue having an identical prefix for all Date objects, while encoding Strings with the same method seem to work fine. Every encoded and encrypted
String leads to another encrypted base64 String. See below
Outputs for encrypted Dates: (also with identical prefix)
0 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JRj1HrbSaioOqhbM2uZi2r0
1 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JQ0q0kophfAfiPxe0U+sb1R
2 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JTeTKnbYsLo6TjfuQF9PYIk
3 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JSrDPGtepg4HWUL6VeBtWg7
4 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JS7dlSsNjnY011F2BooNnKW
5 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JStO2xPQvT76/k+xMdaDBpQ
6 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JQqz4J3yO8G9taHi7b/Zefl
7 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JR8/fOAiuGM8tO8zMcju4Xk
8 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JSMDHi6UyD5QQY1jRXNCErc
9 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JRfKstfsC8dPYuPfd9f2B+B
Outputs for encrypted Strings: (works as expected)
0 - TNpI3oLRzH5id6c/yRJlQQ==
1 - yMkm+ZuYWs4EnISo56Zljw==
2 - 03i1Lv01Nn2sGDGmtpRAIg==
3 - 5skvWbkcVXfT2TScaGxNfQ==
4 - 0p9qg5U+DqAnCBdyji+L9Q==
5 - gD5xPtAMy34xC90hKCQeWA==
6 - oQwKUhuxC5X/f6U9G9la8Q==
7 - 72cvCiLks3DDaTLAQvoVfw==
8 - wQu7Ug5RHg5egbNTI0YXQw==
9 - x1BQVwy3r6MP3SDLl/mktw==
Any idea?
Edit:
Even when I use CBC or another encryption method like DES or Blowfish the same issue occurs. I expect that every byte array from ByteArrayOutputStream should be encrypted into a completely different base64 string even if they have an identical prefix with ~90% of their length.
Using object serialization before encryption is not a great idea. Either you encrypt the data for transport protection, in which case TLS makes much more sense. Or you are encrypting for longer time storage, in which case serialization is dangerous as the serialization format could change. Heck, you might want to change the entire language / runtime in the future.
I'd suggest you generate your own protocol. In that case you can for instance simply encode an integer to 4 bytes using ByteBuffer#putInt(int) or by using DataOutputStream#writeInt(int). That way your integer just takes the minimum amount of 4 bytes (as an unsigned 32 bit big endian value). For very complex methods you may even look at ASN.1 structures and encodings (which are implemented in Bouncy Castle, among other libraries).
A Java Date is really just a long internally, which can be perfectly stored in 8 bytes. Another option is to encode it to an (UTC) date string and store that using US ASCII compatible encoding (StandardCharsets.US_ASCII).
Beware that ECB mode is very dangerous. For instance, imagine that values above 0x00FFFFFF are uncommon and that you don't want to leak the presence of such values. Also imagine that the most significant byte is the last byte of a block that is filled with header bytes otherwise. In that case it is very easy to distinguish blocks with e.g. 0x01 from blocks with 0x00 which should be more common in this situation. So you immediately leak information about your plaintext.
This problem is just as prominent in CBC mode if you use a static IV rather than a random (or at least fully unpredictable) IV value. You have to use a random IV for each CBC encryption to be secure. You can store the IV together with the ciphertext. Commonly for CBC the 16 byte IV is simply prefixed to the ciphertext. However, preferably you should be using the authenticated GCM mode with a 12 byte random nonce instead.
It's a bit of a shame that Java allows reuse of the Cipher instances at all - as it for instance doesn't allow the Cipher to destroy key material after usage. That it defaults to an insecure mode where the IV is repeated is doubly shameful. You'll have to take care of the IV issue yourself.
Example of using GCM and ByteBuffer:
public static void main(String[] args) throws Exception {
// input, a date and message
Date date = new Date();
String message = "hello world";
// AES-128 key (replace by a real 256 bit key in your case)
SecretKey aesKey = new SecretKeySpec(new byte[128 / Byte.SIZE], "AES");
// default nonce sizes for GCM, using a constant should be preferred
int nonceSize = 96;
int tagSize = 128;
String cts;
try (StringWriter stringWriter = new StringWriter(); PrintWriter out = new PrintWriter(stringWriter)) {
for (Integer i = 0; i < 10; i++) {
byte[] randomNonce = createRandomIV(nonceSize);
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, randomNonce);
byte[] encodedMessage = message.getBytes(StandardCharsets.UTF_8);
ByteBuffer encodedNumberDateAndMessage = ByteBuffer.allocate(Integer.BYTES + Long.BYTES + encodedMessage.length);
encodedNumberDateAndMessage.putInt(i);
encodedNumberDateAndMessage.putLong(date.getTime());
encodedNumberDateAndMessage.put(encodedMessage);
// for reading we need to flip the buffer
encodedNumberDateAndMessage.flip();
ByteBuffer encryptedNumberDateAndMessage =
ByteBuffer.allocate(nonceSize / Byte.SIZE + encodedNumberDateAndMessage.limit() + tagSize / Byte.SIZE);
encryptedNumberDateAndMessage.put(randomNonce);
Cipher gcm = Cipher.getInstance("AES/GCM/NoPadding");
gcm.init(Cipher.ENCRYPT_MODE, aesKey, gcmSpec);
gcm.doFinal(encodedNumberDateAndMessage, encryptedNumberDateAndMessage);
// not required, we'll be using array() method
// encryptedNumberDateAndMessage.flip();
// we can use the full array as there
String base64Ciphertext = Base64.getEncoder().encodeToString(encryptedNumberDateAndMessage.array());
if (i != 0) {
out.write('\n');
}
out.write(base64Ciphertext);
}
cts = stringWriter.toString();
}
System.out.println(cts);
// TODO decrypt ciphertexts in cts
// hint use BufferedReader to read lines and don't forget to strip off the IV/Nonce first
}
private static byte[] createRandomIV(int sizeInBits) {
if (sizeInBits % Byte.SIZE != 0) {
throw new IllegalArgumentException("Invalid IV size, must be a multiple of 8 bits");
}
byte[] randomNonce = new byte[sizeInBits / Byte.SIZE];
SecureRandom rbg = new SecureRandom();
rbg.nextBytes(randomNonce);
return randomNonce;
}
Which produces the seemingly random output:
LHMsZPgZOz7nEcN5adB03+twTG2/ITfPnUUy4DxdgFEBAxm3HNDg8eXVnuvo80i4WMjY
eRJuw1ynrD3GeMmFTYiQc6VxelJuz8wHZtbl+7cepteKdtzcsdIDcDHBqvfjyzZp6WXd
MOkTLt4pk+sFm6I+CH4c90lxrRmwFKmS1wbX5eRSZYy6xqEjSz6iGC1vBXkPbl3k1C5r
cB5hKbpiAeNmbZYy1vdK5vissWYlkL6h6XJEYEFZaK7M097LkVAB01nu5GtCBUjPMjrK
LHzr/iudU3BPYmrimAIugjSckzXrzm03Ucgyb8laKktbh/Um4K2nyAGE2+T1aLH6JaYX
dg9SmcPl+dolHSIQPyvMUEPyu3VLSNPbN7ErPY93sjfKVyZsaGgft/cP4kUzNWEyRgAo
PiLHu4TKZMfBlFXst1867hEywST3RBbSSQ1g9D4DOkqh3oPkvsXP5INIEANZr2BHta38
4pJITAvij26NphYf9/ry5yGm+qPAaNG0Hqrk5ruVa60+V7k0jqDozjsST8OygyvkLrgY
HI6I3UHgzBNjskSJeo9fS3Cw3oKY8tneFbChtLz35DbcASOjpi7U9LKTL39lBTOBaZkG
jRycn4uSfT6JlDk3jn64wTL07I7bHvTSPSbWVG7XdKeSgOibW7FiCtTXojDPi8iywD58
which consists of the nonce, the ciphertext of an integer, a long value representing the date and the "hello world" string and finally the authentication tag, which is considered part of the ciphertext in Java.
The behavior you see is the result of you using the no-mode ECB mode in combination with similarity in the plaintext. You will have the same issue with all block ciphers (AES, Blowfish, DES).
When using CBC all that goes away if you provide IVs as needed:
public class Main {
static Random rand = new SecureRandom();
public static IvParameterSpec generateIv() {
byte[] ivBytes = new byte[16];
rand.nextBytes(ivBytes);
return new IvParameterSpec(ivBytes);
}
public static void main(String[] args) throws Exception {
for (Integer i = 0; i < 10; i++) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput oos = new ObjectOutputStream(bos);
oos.writeObject(i);
oos.flush();
byte[] data = bos.toByteArray();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = generateIv();
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec("&E(H+MbQeThWmZq4".getBytes("UTF-8"), "AES"), iv);
String base64output = Base64.getEncoder().encodeToString(cipher.doFinal(data));
System.out.println(i + " - " + base64output);
}
}
}
Output:
0 - jt3Mk13pGjeaFf1oNq4LfmQ4z/31nRG4KtZ4H3RK6k/GA1anC3/lrzSXoLsQ6jMsVEpnxU13wAu6lkZJ3it1Ei4i4EsNFixc+YX4K6cIIv4ByY5Q246jd3H0m11C2FZJ
1 - Jqd0RB6lOITqifAaWluW6jx8F8gY4btZHx12CiXtZjfnehhtk64jva4eGTQd4EpvB/5Q/ORhZCNgF3ue0/Na1R9MCsK+mULAcyANdNcLyKbXo272G21z0LPCeweXdjhu
2 - xHdCG6rWNDyLTl8zruo8u+45V/RMXkrB7K+QU5r9lpc3FzvDwpl0wmy9Yj3FOyjMulmVT1zahH+wWVrmB9gNcXy7sGyCH/anJANC396OcDyQXqNIyvOPw9mpUmmRQcwR
3 - ygIDkLtQTupkbB35SzRflE3RAMmdYGSkdGZgRctFHdZCqGt+Arb3RbvhoAiiE9PwkyLmifyllQTTSutvV/ZtlGaGMX3v4bQUZDoaSyXQd9xn+pUSJk87NDVGi37xWw1O
4 - cJYSthCHHGeCqnuBJY8YdUbptKD3XNb2nt+pyIc94vRvjquYf7atu0+bDndFnePWvrlPzFIFXVB8CuANIsDhzRSNEOOU/wOkwcAN2AdavCqlZqN0Mtqdg4vqKGWx2oAE
5 - f7/gu8fJ8jkyhRAXJkLqdnJMLjCfFSjq8ovjhlNcuDPk8N/mYlA2845PGgi74Kb/zCG1WH8NtFK06xrpn15KyUxSANxoQ6C9QnzE9sc4aZj5rUatWeekvBfbqngq3JpG
6 - PitP2MuX4/Yysso8dCl1h2VK3MKoU2YpyzvLgZ3hZX/cBzSWp9O0Eafzj6GIMvAGVaL0x0V+K2Wv4eBOLIhDczhJXvHmKvTU7ZJnAwI37JXkOecN4HJdAXfFqg2WkT5f
7 - 1Mj8WnSqgLE08qfeYC1a3nZQ1jszxbT9J+ClUy8rCYusZHiArQcCgCwrNbWbI2yVfRjYOpsuTgyq31fnuHrkVfGu6RhiRhucR0a0Dign5fSU71STKksweHQ+oYQJibnQ
8 - TgGDGlOFWyfKO50xxPTPOmSpEsmpIVtWfnXkhhAoRsbZwo6z4oAuBJQs8EibsOr/r8KY5UHRbp+q3SlDhBE3mWszybMdOVRQKyJ1lZVXpmxmjXp/W2AqitsjCTKQaHi+
9 - 4xUnNjT8P0WiPtYg6ojrrQZnF0gU0wnndNQdLfPOMxoDvWjfe5OuEcY55yDRIosdpkeItTMVN1CRL4WecFgM8mBIVlnssE4Q1GM87PWNHipGZ91+MJwdsr0yUfCsJyRv
By the way you are using a 16 byte key and get AES-128 not AES-256.
As Mark pointed out, ObjectOutputStream creates an object header, so the common prefix is because of that and because you're not using a salt and you're using the same encryption key.
These weaknesses make the encryption solution (i.e. your code) susceptible to ciphertext-only attacks, even though the algorithm itself is perfectly fine. You've just implemented it in an unsecure way.

AES Encryption in Java to match with C# Output

I am trying to do AES Encryption using JAVA, I have made multiple attempts, tried a lot of codes and did many changes to finally reach to a place where my encrypted text matches with the encrypted text generated using C# code BUT PARTIALLY. The last block of 32 bits is different. I do not have access to the C# code since it is a 3rd Party Service. Can anyone guide what am I missing?
Conditions Mentioned are to use:
Use 256-bit AES encryption in CBC mode and with PKCS5 padding to encrypt the entire query string using your primary key and initialization vector. (Do not include a message digest in the query string.) The primary key is a 64-digit hexadecimal string and the initialization vector is a 32-digit hexadecimal string.
The sample values I used are:
Aes_IV = 50B666AADBAEDC14C3401E82CD6696D4
Aes_Key = D4612601EDAF9B0852FC0641DC2F273E0F2B9D6E85EBF3833764BF80E09DD89F (my KeyMaterial)
Plain_Text = ss=brock&pw=123456&ts=20190304234431 (input)
Encrypted_Text = 7643C7B400B9A6A2AD0FCFC40AC1B11E51A038A32C84E5560D92C0C49B3B7E0 A072AF44AADB62FA66F047EACA5C6A018 (output)
My Output =
7643C7B400B9A6A2AD0FCFC40AC1B11E51A038A32C84E5560D92C0C49B3B7E0 A38E71E5C846BAA6C31F996AB05AFD089
public static String encrypt( String keyMaterial, String unencryptedString, String ivString ) {
String encryptedString = "";
Cipher cipher;
try {
byte[] secretKey = hexStrToByteArray( keyMaterial );
SecretKey key = new SecretKeySpec( secretKey, "AES" );
cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding" );
IvParameterSpec iv;
iv = new IvParameterSpec( hexStrToByteArray( ivString ) );
cipher.init( Cipher.ENCRYPT_MODE, key, iv );
byte[] plainText = unencryptedString.getBytes( "UTF-8") ;
byte[] encryptedText = cipher.doFinal( plainText );
encryptedString = URLEncoder.encode(byteArrayToHexString( encryptedText ),"UTF-8");
}
catch( InvalidKeyException | InvalidAlgorithmParameterException | UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException e ) {
System.out.println( "Exception=" +e.toString() );
}
return encryptedString;
}
I have used this for conversions.
public static byte[] hexStrToByteArray ( String input) {
if (input == null) return null;
if (input.length() == 0) return new byte[0];
if ((input.length() % 2) != 0)
input = input + "0";
byte[] result = new byte[input.length() / 2];
for (int i = 0; i < result.length; i++) {
String byteStr = input.substring(2*i, 2*i+2);
result[i] = (byte) Integer.parseInt("0" + byteStr, 16);
}
return result;
}
public static String byteArrayToHexString(byte[] ba) {
String build = "";
for (int i = 0; i < ba.length; i++) {
build += bytesToHexString(ba[i]);
}
return build;
}
public static String bytesToHexString ( byte bt) {
String hexStr ="0123456789ABCDEF";
char ch[] = new char[2];
int value = (int) bt;
ch[0] = hexStr.charAt((value >> 4) & 0x000F);
ch[1] = hexStr.charAt(value & 0x000F);
String str = new String(ch);
return str;
}
Any Suggestions, what should I do to match the outputs?
If only the last block of ECB / CBC padding is different then you can be pretty sure that a different block cipher padding is used. To validate which padding is used you can try (as Topaco did in the comments below the question) or you can decrypt the ciphertext without padding. For Java that would be "AES/CBC/NoPadding".
So if you do that given the key (and IV) then you will get the following output in hexadecimals:
73733D62726F636B2670773D3132333435362674733D3230313930333034323334343331000000000000000000000000
Clearly this is zero padding.
Zero padding has one big disadvantage: if your ciphertext ends with a byte valued zero then this byte may be seen as padding and stripped from the result. Generally this is not a problem for plaintext consisting of an ASCII or UTF-8 string, but it may be trickier for binary output. Of course, we'll assume here that the string doesn't use a null terminator that is expected to be present in the encrypted plaintext.
There is another, smaller disadvantage: if your plaintext is exactly the block size then zero padding is non-standard enough that there are two scenarios:
the padding is always applied and required to be removed, which means that if the plaintext size is exactly a number of times the block size that still a full block of padding is added (so for AES you'd have 1..16 zero valued bytes as padding);
the padding is only applied if strictly required, which means that no padding is applied if the plaintext size is exactly a number of times the block size (so for AES you'd have 0..15 zero valued bytes as padding).
So currently, for encryption, you might have to test which one is expected / accepted. E.g. Bouncy Castle - which is available for C# and Java - always (un)pads, while the horrid PHP / mcrypt library only pads where required.
You can always perform your own padding of course, and then use "NoPadding" for Java. Remember though that you never unpad more than 16 bytes.
General warning: encryption without authentication is unfit for transport mode security.

Coverting java code to php with AES and Cipher

I am finding it difficult to convert a piece of code to php from java.
I searched on the internet about the meaning of each line of code written in my java code example but I didn't find any.
I want to understand what each line does in this particular example.
This is what I tried.
function my_aes_encrypt($key, $data) {
if(16 !== strlen($key)) $key = hash('MD5', $key, true);
$padding = 16 - (strlen($data) % 16);
$data .= str_repeat(chr($padding), $padding);
return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_ECB, str_repeat("\0", 16)));
}
function my_aes_decrypt($str, $key){
$str = base64_decode($str);
$str = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_ECB);
$block = mcrypt_get_block_size('rijndael_128', 'ecb');
$pad = ord($str[($len = strlen($str)) - 1]);
$len = strlen($str);
$pad = ord($str[$len-1]);
return substr($str, 0, strlen($str) - $pad);
}
Convert from Java to PHP
//provided key
byte[] keyBinary = DatatypeConverter.parseBase64Binary("r/RloSflFkLj3Pq2gFmdBQ==");
SecretKey secret = new SecretKeySpec(keyBinary, "AES");
// encrypted string
byte[] bytes = DatatypeConverter.parseBase64Binary("IKWpOq9rhTAz/K1ZR0znPA==");
// iv
byte[] iv = DatatypeConverter.parseBase64Binary("yzXzUhr3OAt1A47g7zmYxw==");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
String msisdn = new String(cipher.doFinal(bytes), "UTF-8");
It would be great if you guys let me know the details of each line written in Java.
The functionalities of the Java- and the PHP-code differ significantly. First of all, the Java-code contains only the decryption part, whereas the PHP-part contains both, the encryption- and decryption part. Contrary to the Java-code, in the PHP-my_aes_decrypt-method the insecure ECB-mode (https://crypto.stackexchange.com/questions/20941/why-shouldnt-i-use-ecb-encryption) seems to be used instead of the CBC-mode and thus, no IV is involved. Less important, but nonetheless different, the key doesn't seem to be base64-encoded because it's not decoded anywhere. In addition, in the PHP-code deprecated methods like mcrypt_encrypt (http://php.net/manual/de/function.mcrypt-encrypt.php) or cryptographic weak algorithms like MD5 (https://en.wikipedia.org/wiki/MD5) are used.
If I get it right, the Java code is the reference code and you need the PHP-counterpart. Thus, I focus on the Java-code and ignore the differing and outdated PHP-code completely.
In the Java-code, the key, the data and the IV, all base64-encoded, become decoded and then, the encrypted data are decrypted using these decoded data.
A possible PHP-counterpart for the decryption could be:
<?php
$keyBinary = base64_decode('r/RloSflFkLj3Pq2gFmdBQ=='); // decode base64-encoded key in a string (internally, PHP strings are byte arrays)
$bytes = base64_decode('IKWpOq9rhTAz/K1ZR0znPA=='); // decode base64-encoded encrypted data in a string
$iv = base64_decode('yzXzUhr3OAt1A47g7zmYxw=='); // decode base64-encoded IV in a string
$msisdn = openssl_decrypt($bytes, 'AES-128-CBC', $keyBinary, OPENSSL_RAW_DATA, $iv); // decrypt data using AES-128, CBC-mode and PKCS7-Padding (default-padding)
// - when OPENSSL_RAW_DATA is specified raw data are returned, otherwise base64-encoded data (= default)
// - when OPENSSL_ZERO_PADDING is specified no padding is used, otherwise PKCS7-padding (= default)
// - The value XXX in AES-XXX-CBC is determined by the length of the key in Bit used in the Java-code,
// e.g. for a 32 Byte (256 Bit)-key AES-256-CBC has to be used.
print $msisdn."\n"; // Output: 1234567 // print decrypted data
The desired explanation for the Java-code can be found in the comments:
//provided key
byte[] keyBinary = DatatypeConverter.parseBase64Binary("r/RloSflFkLj3Pq2gFmdBQ=="); // decode base64-encoded key in a byte-array
SecretKey secret = new SecretKeySpec(keyBinary, "AES"); // create AES-key from byte-array (currently 16 Byte = 128 Bit long)
// encrypted string
byte[] bytes = DatatypeConverter.parseBase64Binary("IKWpOq9rhTAz/K1ZR0znPA=="); // decode base64-encoded encrypted data in a byte-array
// iv
byte[] iv = DatatypeConverter.parseBase64Binary("yzXzUhr3OAt1A47g7zmYxw=="); // decode base64-encoded IV in a byte-array
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // create cipher-instance for using AES in CBC-mode with PKCS5-Padding (Java counterpart to PKCS7)
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv)); // initialize cipher-instance for decryption with specified AES-key and IV (the latter created from corresponding byte-array)
String msisdn = new String(cipher.doFinal(bytes), "UTF-8"); // decrypt data using AES-128 (128 determined by length of used key in Bit), CBC-mode and PKCS5-Padding,
// and put them in a UTF-8 string
System.out.println(msisdn); // Output: 1234567 // print decrypted data
The PHP-encryption part could be:
<?php
$keyBinary = base64_decode('r/RloSflFkLj3Pq2gFmdBQ==');
$msisdn = '1234567'; // plain text
$iv = openssl_random_pseudo_bytes(16); // generate random IV
//$iv = base64_decode('yzXzUhr3OAt1A47g7zmYxw=='); // use this line for tests with your base64-encoded test-IV yzXzUhr3OAt1A47g7zmYxw==
$bytes = openssl_encrypt($msisdn, 'AES-128-CBC', $keyBinary, OPENSSL_RAW_DATA, $iv); // encrypt data using AES-128, CBC-mode and PKCS7-Padding (default-padding)
$ivBase64 = base64_encode($iv); // base64-encode IV
$bytesBase64 = base64_encode($bytes); // base64-encode encrypted data
print $ivBase64."\n".$bytesBase64."\n"; // print base64-encoded IV and encrypted data

How to turn 64 character string into key for 256 AES encryption

public static byte[] decryptByte(byte[] blahh, byte[] keyExample) throws Exception
{
Cipher cipher = null;
try
{
cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(Base64.decodeBase64(blah));
}
catch(Exception e)
{
e.printStackTrace();
}
return null;
}
String keyExample = "99112277445566778899AABBCCDDEEFF0123456789ABCDEF0123456789ABCDEF";
byte[] key = keyExample.getBytes();
byte[] barrayMessage = {123,45,55,23,64,21,65};
byte[] result = decryptByte(barrayMessage, key);
Exception thrown: java.security.InvalidKeyException: Invalid AES key length: 64 bytes
When you call String.getBytes() (JDK documentation) you encodes characters of the given string into a sequence of bytes using the platform's default charset.
What you are actually need to do is to convert each hexadecimal (also base 16) number (represented by two characters from 0 to 9 and A to F e.g. 1A, 99, etc.) into its corresponding numerical (byte) value e.g. "FF" -> -1 byte.
Sample code is as follows:
import static java.lang.Character.digit;
...
private static byte[] stringToBytes(String input) {
int length = input.length();
byte[] output = new byte[length / 2];
for (int i = 0; i < length; i += 2) {
output[i / 2] = (byte) ((digit(input.charAt(i), 16) << 4) | digit(input.charAt(i+1), 16));
}
return output;
}
...
String keyExample = "99112277445566778899AABBCCDDEEFF0123456789ABCDEF0123456789ABCDEF";
byte[] key = stringToBytes(keyExample);
byte[] barrayMessage = {123,45,55,23,64,21,65};
byte[] result = decryptByte(barrayMessage, key);
Please bear in mind that because we convert each two characters into a single byte, the proposed method assumes your input will have even number of characters (also the input is not null and empty).
If that method is going to be used internally that form is acceptable but if you make it as a part of library visible to others, it would be good to put some checks and throw exception on invalid input.
You should try and decode your key using a hexadecimal decoder instead of calling getBytes().

RSA and Base64 encoding too many bytes

I am trying to implement RSA encryption with Base64 encoding. The sequence is:
String -> RSA encrypt -> Base64 encoder -> network -> Base64 decoder* -> RSA decrypt > String
I'm sending the base64 encoded string with a over the network and read it as a string on the other side, after all Base64 is text, right?
Now For Some Reason when I decode the Base64, I am getting more bytes out than which I originally sent.
On the sender side, my RSA string is 512 bytes. After Base64 encoding its 1248 long (this varies each time).
On the receiver side, my Base64 encoded received string is still 1248 long but when I decode it then I suddenly get 936 bytes. Then I can not decipher it with RSA because the ciper.doFinal method hangs.
I am assuming this has something todo with byte to unicode string conversion but I cannot figure out in which step this happens and how I can fix it.
Sender side code:
cipher = Cipher.getInstance("RSA/NONE/OAEPWithSHA256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey());
byte[] base64byes = loginMessage.getBytes();
byte[] cipherData = cipher.doFinal(base64byes);
System.out.println("RSA: " + cipherData.length); //is 512 long
//4. Send to scheduler
Base64PrintWriter base64encoder = new Base64PrintWriter(out);
base64encoder.writeln(new String(cipherData)); //send string is 1248 long
base64encoder.flush();
Receiver side code:
System.out.println("Base 64: " + encodedChallenge.length()); //1248 long
byte[] base64Message = encodedChallenge.getBytes();
byte[] rsaEncodedMessage = Base64.decode(base64Message);
System.out.println("RSA: " + rsaEncodedMessage.length); //936 long
cipher = Cipher.getInstance("RSA/NONE/OAEPWithSHA256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
cipherData = cipher.doFinal(rsaEncodedMessage); //hangs up
System.out.println("Ciper: " + new String(cipherData));
P.S. Base64PrintWriter is a PrintWriter that I have decorated to convert every output to base64 before writing it out.
There is something wrong with your encoding. Using base 64 instead of base 256 means an increase of 8-bit/6-bits required or 1/3 similarly decoding results in a drop of 1/4 e.g. 1248 * 6 / 8 = 936
The problem appears to be that you are converting 8-bit data into 16-bit string before encoding. This requires 512 * 16 / 6 bytes = ~1365.
You need a Base64 stream which takes bytes instead of chars/Strings.
Perhaps using Base64.encode() is what you need?

Categories

Resources