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.
Related
I have a String which is encrypted with RSA algorithm, When i was trying to decrypt it's working fine.The decryption output will be in byte[].
My question is when i trying to convert the byte[] to a new String(decrypted String) it's getting different length .
If i try with a random string the length is remains same.But if i decrypt the
generated aesKey(AES) the length of the byte[] is changing while conversion of new String(decrypted String).
Why i am getting different length incase of aesKey decryption???
Is there any difference between "random string" and "generated aeskey"
String ==
"t8xypyI6gKlKTkt4Qec7FCor4EpukZXqYQcIDm6YvbtRB9+YBrX0CqyoHOHN91T8RBQS/JD2osbf4ao9Y"SgNbzhfDa2NpJKMEIBWH4TNlF4Ngb8yWdSm3hz3l8FdeFUIy3pyCxkLjU8n4VAxsmgoIQbgd7DJuPiSMZBA9/IVlcCfo/tZjMtSkezITtoT5aVvLxLaTsp08UREdalvXxb5USKi3cAEdqR9TmLJxB004IMv5Eiuvdmcc3fJzO6mnwiHPuGKArd9LjjiqbPQ75uc8NDOFrvleLc5KwSuThS5Xx7tR1qfoX6qefh6SD7FRk5UzyCEnv+eD+mCQ588Jam1A==";
****The above string is encrypted form of the aesKey(generated by KeyGenerator)
If i decrypt this string by using RSA----
private String decrypt(String text, Key privatekey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
byte[] dectyptedText = null;
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
cipher.init(Cipher.DECRYPT_MODE, privatekey);
dectyptedText = cipher.doFinal(Base64.getDecoder().decode(text));
System.out.println(dectyptedText.length); //32
System.out.println(new String(dectyptedText).length()); //30
System.out.println(new String(dectyptedText).getBytes().length); //60
return new String(dectyptedText);
}
above, The length is changing in byte string conversions.
Suppose if i encrypt and decrypt with a normal string the lengths are not changing????why????
Likely, the decryptedText contains funny bytes. The documentation says:
public String(byte[] bytes)
Constructs a new String by decoding the specified array of bytes using the platform's default charset. The length of the new String is a function of the charset, and hence may not be equal to the length of the byte array.
The behavior of this constructor when the given bytes are not valid in the default charset is unspecified. The CharsetDecoder class should be used when more control over the decoding process is required.
I am aware of a question very similar to this (How do I encrypt in Python and decrypt in Java?) but I have a different problem.
My problem is, I am not able to decrypt in Java correctly. Despite using the correct key and IV, I still get garbage characters after decryption. I don't have any compile/run-time errors or exceptions in Java so I believe I am using the right parameters for decryption.
Python Encryption Code -
from Crypto.Cipher import AES
import base64
key = '0123456789012345'
iv = 'RandomInitVector'
raw = 'samplePlainText'
cipher = AES.new(key,AES.MODE_CFB,iv)
encrypted = base64.b64encode(iv + cipher.encrypt(raw))
Java Decryption Code -
private static String KEY = "0123456789012345";
public static String decrypt(String encrypted_encoded_string) throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
String plain_text = "";
try{
byte[] encrypted_decoded_bytes = Base64.getDecoder().decode(encrypted_encoded_string);
String encrypted_decoded_string = new String(encrypted_decoded_bytes);
String iv_string = encrypted_decoded_string.substring(0,16); //IV is retrieved correctly.
IvParameterSpec iv = new IvParameterSpec(iv_string.getBytes());
SecretKeySpec skeySpec = new SecretKeySpec(KEY.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
plain_text = new String(cipher.doFinal(encrypted_decoded_bytes));//Returns garbage characters
return plain_text;
} catch (Exception e) {
System.err.println("Caught Exception: " + e.getMessage());
}
return plain_text;
}
Is there anything obvious that I am missing?
The Cipher Feedback (CFB) mode of operation is a family of modes. It is parametrized by the segment size (or register size). PyCrypto has a default segment size of 8 bit and Java (actually OpenJDK) has a default segment size the same as the block size (128 bit for AES).
If you want CFB-128 in pycrypto, you can use AES.new(key, AES.MODE_CFB, iv, segment_size=128). If you want CFB-8 in Java, you can use Cipher.getInstance("AES/CFB8/NoPadding");.
Now that we have that out the way, you have other problems:
Always specify the character set you're using, because it can change between different JVMs: new String(someBytes, "UTF-8") and someString.getBytes("UTF-8"). When you do, be consistent.
Never use a String to store binary data (new String(encrypted_decoded_bytes);). You can copy the bytes directly: IvParameterSpec iv = new IvParameterSpec(Arrays.copyOf(encrypted_decoded_bytes, 16)); and cipher.doFinal(Arrays.copyOfRange(encrypted_decoded_bytes, 16, encrypted_decoded_bytes.length)).
In Java, you're assuming that the IV is written in front of the ciphertext and then encoded together, but in Python, you're never doing anything with the IV. I guess you posted incomplete code.
It is crucial for CFB mode to use a different IV every time if the key stays the same. If you don't change the IV for every encryption, you will create a multi-time pad which enables an attacker to deduce the plaintext even without knowing the key.
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 able to encrypt data however when decrypting it i am getting the following error:
Error
HTTP Status 500 - Request processing failed; nested exception is javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8 when decrypting with padded cipher
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8 when decrypting with padded cipher
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:894)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778)
javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
Here is my Encryption and Decryption code
//secret key 8
private static String strkey ="Blowfish";
UPDATED
//encrypt using blowfish algorithm
public static byte[] encrypt(String Data)throws Exception{
SecretKeySpec key = new SecretKeySpec(strkey.getBytes("UTF8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.ENCRYPT_MODE, key);
return (cipher.doFinal(Data.getBytes("UTF8")));
}
//decrypt using blow fish algorithm
public static String decrypt(byte[] encryptedData)throws Exception{
SecretKeySpec key = new SecretKeySpec(strkey.getBytes("UTF8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = cipher.doFinal(encryptedData);
return new String(decrypted);
}
If you run your encrypt and decrypt methods in a main method, it will work. But if the results of encrypt are put into a url and then the url parameter is decrypted, it will fail.
After encryption, the byte array contains values that are outside the character set of URLS (non-ascii), so this value gets encoded when it is stuffed into a url. And you you receive a corrupted version for decryption.
As an example, when I created a string from an encrypted byte array, it looked like this Ž¹Qêz¦ but if I put it into a URL it turns into Ž%0B¹Qêz¦.
The fix, as suggested in other comments, is to add a encode / decode step. After encryption, the value should be encoded to a format which contains ascii characters. Base 64 is an excellent choice. So you return encrypted and encoded value in the url. When you receive the param, first decode then decrypt, and you'll get the original data.
Here are some notes on the implementation.
Use a library like commons codec. It is my weapon of choice, this class specifically http://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/binary/Base64.html.
In the class that does encryption and decryption, have a shared instance of Base64. To instantiate it use new Base64(true); this produces url safe strings.
Your encrypt and decrypt method signatures should accept and return strings, not byte arrays.
So the last line of your encrypt would become something like return base64.encodeToString(cipher.doFinal(Data.getBytes("UTF8"))); You can now safely pass the encrypted value in a url
In your decrypt, you first step is to decode. So the first line would become something like byte[] encryptedData = base64.decodeBase64(encrypted);
I just took your code and added some base 64 stuff, the result looks like this:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class Test {
private static String strkey ="Blowfish";
private static Base64 base64 = new Base64(true);
//encrypt using blowfish algorithm
public static String encrypt(String Data)throws Exception{
SecretKeySpec key = new SecretKeySpec(strkey.getBytes("UTF8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.ENCRYPT_MODE, key);
return base64.encodeToString(cipher.doFinal(Data.getBytes("UTF8")));
}
//decrypt using blow fish algorithm
public static String decrypt(String encrypted)throws Exception{
byte[] encryptedData = base64.decodeBase64(encrypted);
SecretKeySpec key = new SecretKeySpec(strkey.getBytes("UTF8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = cipher.doFinal(encryptedData);
return new String(decrypted);
}
public static void main(String[] args) throws Exception {
String data = "will this work?";
String encoded = encrypt(data);
System.out.println(encoded);
String decoded = decrypt(encoded);
System.out.println(decoded);
}
}
Hope this answers your questions.
You can't create a String out of random (in this case encrypted) bytes like you're doing in the last line of your encrypt method - you need to create a Base64 encoded string instead (which you then need to decode back to a byte array in the decrypt method). Alternatively, just have your encrypt method return a byte array and have your decrypt method accept a byte array as its parameter.
The problem is with the way you are creating String instances out of the raw encrypted byte[] data. You need to either use binhex encoding like that provided by javax.xml.bind.DatatypeConverter via the parseHexBinary and printHexBinary methods or base 64 using the parseBase64Binary and printBase64Binary methods of the same object.
One other word of advice, never rely on the default mode and padding, always be explicit. Use something like Cipher.getInstance("Blowfish/CBC/PKCS5Padding") depending on what your needs are.
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?