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?
Related
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.
I am currently playing with Cipher to create a solution using a key that is always the same. I know this is not the most secure solution but it is what I have been asked to do. I am supposed to use AES256 and EBC, but I can not encrypt correctly. The problem is that I've got unknown characters.
private static String encrypt(String text) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException
{
String keyString = AESEncryption.convertToUTF8("8DJE7K01U8B51807B3E17D21");
text = AESEncryption.convertToUTF8(text);
byte[]keyValue = Base64.getEncoder().encode(keyString.getBytes(StandardCharsets.UTF_8));
Key key = new SecretKeySpec(keyValue, "AES");
Cipher c1 = Cipher.getInstance("AES/ECB/PKCS5Padding");
c1.init(Cipher.ENCRYPT_MODE, key);
byte[] encodedText =Base64.getEncoder().encode(text.getBytes(StandardCharsets.UTF_8));
System.out.println("Encoded text: "+new String(encodedText,StandardCharsets.UTF_8));
byte[] encVal = c1.doFinal(encodedText);
System.out.println("Encoded val: "+new String(encVal,StandardCharsets.UTF_8));
return new String(encVal);
}
Edit: Sorry first time asking. I will give you the full scope. Afterwards I try to decrypt with the following code(I know that I have repeated code, I will clean it) But when I decrypt the output obtained by the encrypt method I recieve the following error. The message I am trying to encrypt and decrypt is "Hola"
public static String desEncrypt(String text) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException
{
String keyString = AESEncryption.convertToUTF8("8DJE7K01U8B51807B3E17D21");
byte[] keyValue = Base64.getEncoder().encode(keyString.getBytes(StandardCharsets.UTF_8));
Key key = new SecretKeySpec(keyValue, "AES");
Cipher c1 = Cipher.getInstance("AES/ECB/PKCS5Padding");
c1.init(Cipher.DECRYPT_MODE, key);
byte[] encodedText = Base64.getDecoder().decode(text.getBytes(StandardCharsets.UTF_8));
byte[] encVal = c1.doFinal(encodedText);
System.out.println(new String(encodedText));
return new String(encVal,StandardCharsets.UTF_8);
}
And the error:
Encoded text: aG9sYWNraXNqbWRlaXJncw==
Encoded val: ???D>??|??i9???Fd?\Zz?A?-
Exception in thread "main" java.lang.IllegalArgumentException: Illegal base64 character -3d
at java.util.Base64$Decoder.decode0(Unknown Source)
at java.util.Base64$Decoder.decode(Unknown Source)
at AESEncryption.desEncrypt(AESEncryption.java:63)
at AESEncryption.main(AESEncryption.java:79)
Thank you very much, and forgive for not providing all the info needed
Your code makes no sense: converting a String to UTF8 and getting back a String makes no sense: a String contains characters. Not bytes.
Encoding a key to base64 doesn't make much sense either. Encoding the plain text to base64 is useless, too.
You need base64 encoding when you have random, binary bytes, and you want to transform them to printable english characters. Only then.
So the process should be:
transform your key to bytes (using String.getBytes(UTF_8)). Unless the String key is in fact a base64-encoded byte array, in which case you need to base64 decode it;
transform the plain text to bytes (using String.getBytes(UTF_8));
encrypt the bytes you got from step 2, using the key obtained from step 1. You obtain completely "random" bytes. These bytes don't represent characters encoded in your platform default charset. So transforming them to a String using new String(bytes) doesn't make any sense, and is a lossy transformation.
Just return the result as a byte array, or if you really want printable characters, base64-encode the bytes, and return the string you obtain from this base64 encoding.
To decrypt, use the reverse process:
Use the same thing as in step 1 above to get the key
take the bytes obtained from step4 above (if you chose to return a byte array), or base64-decode the string, to obtain the original random binary bytes
decrypt the bytes obtained from step 2, using the key obtained from step 1. You get a byte array representing the UTF8-encoded characters of the original plain text.
Use new String(decryptedBytes, UTF_8) to transform this byte array to a String.
I'm trying to convert from using Chilkat's proprietary decryption library to Apache's commons crypto. I have 2 example encrypted inputs I'm working with. The first is 16 bytes and the second is 96 bytes. The first one works great, but on the second one the CryptoCipher doesn't appear to be consuming the last 16 bytes.
Here's some example code of the setup and decryption and the output:
Properties properties = new Properties();
CryptoCipher crypt = CryptoCipherFactory.getCryptoCipher("AES/CBC/PKCS5Padding", properties);
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hashedKeyBytes = digest.digest("SHARED_SECRET".getBytes(
StandardCharsets.UTF_8));
MessageDigest ivDigest = MessageDigest.getInstance("MD5");
byte[] ivBytes = ivDigest.digest("SHARED_SECRET".getBytes(StandardCharsets.UTF_8));
final SecretKeySpec key = new SecretKeySpec(hashedKeyBytes, "AES");
IvParameterSpec iv = new IvParameterSpec(ivBytes);
crypt.init(Cipher.DECRYPT_MODE, key, iv);
ByteBuffer encBuffer = ByteBuffer.allocateDirect(enc.length);
System.out.println("--" + enc.length);
encBuffer.put(enc);
encBuffer.flip();
System.out.println("encln " + encBuffer.limit());
ByteBuffer decoded = ByteBuffer.allocateDirect(bufferSize);
CryptoCipher crypt = init();
System.out.println("consume " + crypt.update(encBuffer, decoded));
System.out.println("finish " + crypt.doFinal(encBuffer, decoded));
decoded.flip();
return asString(decoded);
This produces these 2 outputs for the 2 inputs:
Short input:
--16
encln 16
consume 0
finish 13
Long input:
--96
encln 96
consume 80
finish 3
As you can see it's only consuming 80 bytes out of the input... Since the shorter input produces the correct output as compared to what Chilkat produced, I'm not sure where to approach this to get it to work with the longer input.
The number returned by crypt.update() and crypt.doFinal(..) is the number of bytes decrypted, not the number of bytes consumed by the operation. As your data is padded (or at least you specify it as PKCS5Padded), your encrypted data will always be a bit bigger than the decrypted version. With PSCS5 and AES the padding will add 1 to 16 bytes of padding to the nearest multiplum of 16 bytes which is the block size of AES.
In the the first example your 13 bytes of clear data have 3 bytes of padding giving 16 bytes of encrypted data (or one full AES block). In the second example you have 83 bytes of clear data and 13 bytes of padding (giving 6 AES blocks of 16 bytes).
In the following snippet I try to print encrypted array in a simple string format.
KeyGenerator keyGenerator = KeyGenerator.getInstance("Blowfish");
SecretKey secretKey = keyGenerator.generateKey();
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
String input = "password";
byte encrypted[] = cipher.doFinal(input.getBytes());
String s = new String(encrypted);
System.out.println(s);
But what I get is `┐╫Y²▓ô┴Vh¬∙:╪⌡¶ . Why is it ? How can I print it in the proper string format ?
You could use Base64 encoding from common-codec.
KeyGenerator keyGenerator = KeyGenerator.getInstance("Blowfish");
SecretKey secretKey = keyGenerator.generateKey();
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
String input = "password";
byte encrypted[] = cipher.doFinal(input.getBytes());
System.out.println(new String(Base64.encodeBase64(encrypted)));
Output:
8KA8ahr6INnY4qqtzjAJ8Q==
Encode the bytes in Base64 encoding (How do I convert a byte array to Base64 in Java?)
Or Hex: How to convert a byte array to a hex string in Java?
System.out.println( Hex.encodeHexString( bytes ) );
Most cryptographic algorithms (including blowfish) deal with binary data meaning that it will take binary data in and split out binary data that has been transformed by the algorithm (with the provided specs).
Binary data, as you know is != to string data, however binary data can be represented as string data (using hex, base64, etc).
If we look at your example code we can see this line:
byte encrypted[] = cipher.doFinal(input.getBytes());
This is what it is doing step by step:
It first converts string data into a binary data equivalent using the platform's default charset (NOT RECOMMENDED, but irrelevant).
It is passing the binary data (in form of a byte array) to the method doFinal().
The doFinal() method is processing this byte array via the specifications specified in the statements prior to this line (Blowfish, encryption).
The doFinal() statement is returning a byte array which represents the processed (encrypted, in your case) data.
The fact that the data originally came from a string is no longer relevant because of the nature of the encryption operation does not account for the source or type of the data. The encrypted byte array now contains data that may not be valid charset encoded string. Trying to use a character set to decode the string would most likely result in garbage output as the binary data is no longer a valid string.
However, binary data can be represented directly by outputting the VALUE of the actual bytes rather than what the charset equivalent mapping is (e.g A byte may have the value of 97, which represented in hex is: 0x61 but decoded via ASCII results in the character 'a').
Consider this code to output your encrypted data in hex:
KeyGenerator keyGenerator = KeyGenerator.getInstance("Blowfish");
SecretKey secretKey = keyGenerator.generateKey();
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
String input = "password";
byte encrypted[] = cipher.doFinal(input.getBytes());
StringBuilder str = new StringBuilder();
for(byte b:encrypted){
str.append(String.format("%02x", b));
}
String encData = str.toString();
System.out.println(encData);
P.S: Don't use getBytes() without any arguments! Supply your own charset like UTF-8. Do as follows:
byte encrypted[] = cipher.doFinal(input.getBytes(Charset.forName("UTF-8")));
You can try with:
new String(bytes, StandardCharsets.UTF_8)
I am trying to encrypt a message, which works and returns it as a byte array. I then convert this byte array to a string, in order to send via a tcp network message. On the other end, I convert the string back to a byte array, however the resulting array is larger and I can't figure out why. I think it may be something to do with the encoding as if I use "MacRoman", I do not have this problem, however the program needs to be able to run on systems which do not support this encoding, so I decided to use UTF-8.
String message="222233332221";
//Encrypt message
byte[] encryptedMsg = encryptString(message, temp.loadCASPublicKey());
System.out.println("ENCRYPTED MESSAGE byte Length: "+encryptedMsg.length);
//Convert to String in order to send
String stringMessage = new String(encryptedMsg);
System.out.println("ENCRYPTED MESSAGE String Length: "+stringMessage.length());
//Convert String back to Byte[] and decrpt
byte[] byteMessage = stringMessage.getBytes("UTF-8");
System.out.println("ENCRYPTED MESSAGE byte Length: "+byteMessage.length);
Outputs:
ENCRYPTED MESSAGE byte Length: 256
ENCRYPTED MESSAGE String Length: 235
ENCRYPTED MESSAGE byte Length: 446
Can any one please point me in the right direction as to why the resulting byte array is 446 bytes not 256 bytes.
The encryptString part is as follows. I believe this returns a byte array in UTF-8?
private static byte[] encryptString(String message, Key publicKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherData = cipher.doFinal(message.getBytes("UTF-8"));
return cipherData;
}
Managed to fix it using Base64.
byte[] encryptedMsg = Base64.encodeBase64(encryptString(message, temp.loadCASPublicKey()));
System.out.println("ENCRYPTED MESSAGE byte Length: "+encryptedMsg.length);
//Convert to String in order to send
String stringMessage = new String(encryptedMsg, "UTF-8");
System.out.println("ENCRYPTED MESSAGE String Length: "+stringMessage.length());
//Convert String back to Byte[] and decrpt
byte[] byteMessage = Base64.decodeBase64(stringMessage.getBytes("UTF-8"));
System.out.println("ENCRYPTED MESSAGE byte Length: "+byteMessage.length);
It's an encoding issue.
1) You have a byte array. It contains bytes
2) You convert it to a string. As soon as you do this, you have a UTF16 encoded String. So you have taken the bytes and changed them to characters.
3) You now convert those characters back to bytes. But if the original bytes were not UTF8 or UTF16, you might not have the same number of bytes. If the default encoding of the platform is MacRoman, then in step 3 you are translating your UTF16 String into bytes, but treating the characters as MacRoman.
I guess there is a good reason of doing encryption manually, however just in case... Do you consider using SSLSocket instead?