Iam encrypting data using the following code
final SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(),
"AES");
final String myIV = "89ABCDEF01234567";
Cipher c = null;
try {
try {
c = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
c.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(
myIV.getBytes()));
byte[] encrypted = c.doFinal(msgfromEB.getBytes(),0,msgfromEB.getBytes().length);
and iam decyrpting like this
c.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(
myIV.getBytes()));
byte[] decryptedBytes = c.doFinal(encrypted ,0,encrypted .length);
System.out.println("decrypted string is"+new String(decryptedBytes));
it is working iam able to encrypt and decrypt properly
But if iam converting into a string like this
String myString = new String (encrypted);
and again get the byte array like this
byte[] newbytearray = myString.getBytes();
Now iam trying to decrypt
c.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(
myIV.getBytes()));
byte[] decryptedBytes = c.doFinal(newbytearray ,0,newbytearray .length);
System.out.println("decrypted string is"+new String(decryptedBytes));
now iam getting an error
javax.crypto.IllegalBlockSizeException: last block incomplete in decryption
If iam using base64 convertion it is working fine, but I don't want to do it as it increases the length. Any other alternative to over come this?
The alternative is to transfer bytes instead of Strings.
When you're doing new String(bytes), you're asking to use your platform's default encoding to interpret bytes and sequences of bytes as characters. The problem is that all the bytes and sequences of bytes do not represent valid characters. The algorithm is thus lossy. That's a bit like transforming a color picture to black and white, and then trying to go from black and white to color. It doesn't work. Hence the need for base64.
An additional problem is that you don't specify any explicit encoding, which means that the sender and the receiver could use different encodings if they're not on the exact same platform.
Related
This question already has answers here:
Why does my encrypted string looks like consisting of only question marks?
(2 answers)
Closed 7 years ago.
For a project a try to encrypt a pwd with DES algorithm.
When I try this
private static String key = "my8bcode"; /*Key 8 bytes or 56 bit supported by algo OF*/
private static byte[] byteKey = key.getBytes();
public static void main(String[] args) throws Exception {
String ss = "yuyuvdzdsfdsfsdsdsdsdsa";
byte[] plainText = ss.getBytes();//Conversion en byte
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(byteKey, "DES");
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); // Request the use of the DES algorithm, using the ECB mode (Electronic CodeBook) and style padding PKCS-5.
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] myCipherText = cipher.doFinal(plainText);
System.out.println(new String(myCipherText, "UTF8"));
System.out.println(myCipherText.length);
System.out.println("\nStart decryption");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] newPlainText = cipher.doFinal(myCipherText);
System.out.println(new String(newPlainText, "UTF8"));
} catch (Exception e) {
e.printStackTrace();
}
I have no problems, but to go further, i try to do this in two separate steps with this code :
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(byteKey, "DES");
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); //Request the use of the DES algorithm, using the ECB mode (Electronic CodeBook) and style padding PKCS-5.
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] myCipherText = cipher.doFinal(plainText);
byte[] test = (new String(myCipherText, "UTF8")).getBytes();
System.out.println("\nStart decryption");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] newPlainText = cipher.doFinal(test);
System.out.println(new String(newPlainText, "UTF8"));
} catch (Exception e) {
e.printStackTrace();
}
It doesn't work, i have this message in my console :
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8
Indeed, the length of test[] is 7 and not 8. How is possible ?
Why did I lose a byte during my transcription?
Help me please to understand and to solve my problem ;)
byte[] and String should be kept apart. The main error is that a random sequence of bytes (the encrypted text) might easily be incorrect UTF-8. UTF-8 is a multi-byte format, where high bits mark a multi-byte sequence in a specified way, 10xxxxxx being a continuation byte for instance.
However to encrypt a String (Unicode) it is very appropriate to use UTF-8. A single byte encoding would be lossy; say if the text contained both Greek and Bulgarian.
byte[] data = text.getBytes(StandardCharsets.UTF_8);
text = new String(data, StandardCharsets.UTF_8);
These are the overloaded versions from the String constructor and getBytes method, with charset specified. Otherwise it is platform dependent, hence disastrous in communication to another computer.
But as soon as you have binary data, byte[], do not convert it to (Unicode) String, which is a (costly) conversion, redundant and error prone.
Hence one often sees an additional conversion of the binary data to Base64, receiving a longer ASCII string.
I just do that (delete UTF 8 specifications) and after lots of test it's working :
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(byteKey, "DES");
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); //Demande l'utilisation de l'algorithme DES, en utilisant le mode ECB (Electronic CodeBook) et le style de padding PKCS-5.
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] myCipherText = cipher.doFinal(plainText);
byte[] test = (new String(myCipherText)).getBytes();
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] newPlainText = cipher.doFinal(test);
System.out.println(new String(newPlainText));
} catch (Exception e) {
e.printStackTrace();
}
Thanks a lot
I am working on a file encryption/decryption app. I am using a simple .txt file for testing. When I select the file from within the app and choose to encrypt, the entire file data is encrypted. However, when I decrypt only part of the file data gets decrypted. For some reason the first 16 bytes/characters doesn't get decrypted.
test_file.txt contents: "This sentence is used to check file encryption/decryption results."
encryption result: "¾mÁSTÐÿT:Y„"O¤]ÞPÕµß~ëqrÈb×ßq²¨†ldµJ,O|56\e^-’#þûÝû"
decryption result: "£ÿÒÜÑàh]VÄþ„- used to check file encryption/decryption results."
There aren't any errors in the logcat.
What am I doing wrong?
Method to encrypt file:
public void encryptFile(String password, String filePath) {
byte[] encryptedFileData = null;
byte[] fileData = null;
try {
fileData = readFile(filePath);//method provided below
// 64 bit salt for testing only
byte[] salt = "goodsalt".getBytes("UTF-8");
SecretKey key = generateKey(password.toCharArray(), salt);//method provided below
byte[] keyData = key.getEncoded();
SecretKeySpec sKeySpec = new SecretKeySpec(keyData, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, sKeySpec);
encryptedFileData = cipher.doFinal(fileData);
saveData(encryptedFileData, filePath);//method provided below
}
catch (Exception e) {
e.printStackTrace();
}
}
Method to read file content:
public byte[] readFile(String filePath) {
byte[] fileData;
File file = new File(filePath);
int size = (int) file.length();
fileData = new byte[size];
try {
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
inputStream.read(fileData);
inputStream.close();
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
return fileData;
}
Method to generate secret key:
private SecretKey generateKey(char[] password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
// Number of PBKDF2 hardening rounds to use. Larger values increase computation time. You
// should select a value that causes computation to take >100ms.
final int iterations = 1000;
// Generate a 256-bit key
final int outputKeyLength = 256;
SecretKeyFactory secretKeyFactory;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// Use compatibility key factory -- only uses lower 8-bits of passphrase chars
secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1And8bit");
}
else {
// Traditional key factory. Will use lower 8-bits of passphrase chars on
// older Android versions (API level 18 and lower) and all available bits
// on KitKat and newer (API level 19 and higher).
secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
}
KeySpec keySpec = new PBEKeySpec(password, salt, iterations, outputKeyLength);
return secretKeyFactory.generateSecret(keySpec);
}
Method to save encrypted/decrypted data to the file:
private void saveData(byte[] newFileData, String filePath) {
File file = new File(filePath);
try {
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));
outputStream.write(newFileData);
outputStream.flush();
outputStream.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
Method to decrypt file:
public void decryptFile(String password, String filePath) {
byte[] decryptedFileData = null;
byte[] fileData = null;
try {
fileData = readFile(filePath);
byte[] salt = "goodsalt".getBytes("UTF-8");//generateSalt();
SecretKey key = generateKey(password.toCharArray(), salt);
byte[] keyData = key.getEncoded();
SecretKeySpec sKeySpec = new SecretKeySpec(keyData, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, sKeySpec);
decryptedFileData = cipher.doFinal(fileData);
saveData(decryptedFileData, filePath);
}
catch (Exception e) {
e.printStackTrace();
}
}
This line of code encrypts the file:
//simple password for testing only
encryptor.encryptFile("password", "storage/emulated/0/Download/test_file.txt");
This line decrypts the file:
encryptor.decryptFile("password", "storage/emulated/0/Download/test_file.txt");
Edit: Thanks to DarkSquirrel42 and Oncaphillis. You guys are awesome!
Adding this line of code to both encrypt and decrypt functions solved my problem.
//note: the initialization vector (IV) must be 16 bytes in this case
//so, if a user password is being used to create it, measures must
//be taken to ensure proper IV length; random iv is best and should be
//stored, possibly alongside the encrypted data
IvParameterSpec ivSpec = new IvParameterSpec(password.getBytes("UTF-8"));
and then,
cipher.init(Cipher.XXXXXXX_MODE, sKeySpec, ivSpec);
your problem has something to do with the cipher's mode of operation ... cbc, or cipher block chaining mode
in general CBC is simple ... take whatever the output of your previous encryiption block was, and xor that onto the current input before encrypting it
for the first block we obviously have a problem... there is no previous block ... therefore we introduce something called IV ... an initialisation vector ... a block ength of random bytes ...
now ... as you can imagine, you will need the same IV when you want to decrypt ...
since you don't save that, the AES implementation will give you a random IV every time ...
therefore you don't have all information to decrypt block 1 ... which is the first 16 bytes in case of AES ...
when handling CBC mode data, it's allways a good choice to simply prepend the used IV in your cypertext output ... the IV shall just be random ... it is no secret ...
Like #ÐarkSquirrel42 already points out the en/decrytion routine for CBC seems to interpret the first 16 bytes as an initialisation vector. This worked for me:
// got to be random
byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
IvParameterSpec ivspec = new IvParameterSpec(iv);
cipher.init(Cipher.XXXXX_MODE, sKeySpec,ivspec);
I'm currently running into problems decrypting my data. The base64 of the encoded string is being stored in the database. So, I'm printing out the encoded string and then trying to run it back through with "DECRYPT" instead of "ENCRYPT". However, I never get a value that the Decrypter method likes, it always gives me an error about parameters or the value not being 16 bytes.
public class crypto {
public static void main(String [] args) {
String s = args[0];
String s1 = args[1];
String ivkey = "thisisasecretkey";
byte[] ivraw = ivkey.getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(ivraw, "AES");
if (s.equalsIgnoreCase("ENCRYPT")) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(s1.getBytes());
System.out.println(new String(Base64.encodeBase64(encrypted)));
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(s1.getBytes());
System.out.println(new String(Base64.decodeBase64(encrypted)));
} catch (Exception e) {
e.printStackTrace();
}
}
return;
};
}
command:crypto "ENCRYPT" "password"
output: 5eQvSzPG1TE2AybgCmeV6A==
command:crytpo "DECRYPT" "5eQvSzPG1TE2AybgCmeV6A=="
output: java.security.InvalidKeyException: Parameters missing
I'm aware of the security flaws, that's not what I'm asking about and I would prefer answers/comments not get cluttered with best practices.
You should do base 64 decoding, and you should do that before decrypting.
You are not including the initialization vector (IV).
AES in CBC mode has both a 16 byte IV and the 16 byte symmetric key.
String IV = "AAAAAAAAAAAAAAAA"; // generate this randomly
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(IV.getBytes()));
byte[] encrypted = cipher.doFinal(s.getBytes());
Edit: as it turns out, encryption does not require a IV to be provided (as owlstead pointed out), but decryption does. The best bet would be to be explicit and use IV in both encryption and decryption. Change your decryption function to include the IV, and you will run into the other error in your code that owlstead pointed out.
I have an application developed on BlackBerry JDE 5.0.0 that encrypts a String using DES algorithm with ECB mode. After the encryption, the result is encoded by base64 encoding. But whenever I compare the result that i get from my encryption method with the result that i get on the online encryptor engine, it always give different result on the several last character. I tried to decrypt the result that i get form my encryption method with the online encriptor engine and it looks like the result is not the valid one. So how can I fix that different result on the several last character?
Here my encryption method code:
public String encryptDESECB(String text) throws MessageTooLongException
{
byte[] input = text.getBytes();
byte[] output = new byte[8];
byte[] uid = null;
uid = "431654625bd37673e3b00359676154074a04666a".getBytes();
DESKey key = new DESKey(uid);
try {
DESEncryptorEngine engine = new DESEncryptorEngine(key);
engine.encrypt(input, 0, output, 0);
String x= BasicAuth.encode(new String(output));
System.out.println("AFTER ENCODE"+x);
return new String(x);
} catch (CryptoTokenException e) {
return "NULL";
} catch (CryptoUnsupportedOperationException e) {
return "NULL";
}
}
The String that i want to encrypt is "00123456"
The Result that i get from my encryption method is:YnF2BWFV/8w=
The Result that i get from online encryptor engine (http://www.tools4noobs.com/online_tools/encrypt/) : YnF2BWFV9sw=
The Result that i get from android (With the same encryption algorithm & Method) : YnF2BWFV9sw=
Here's the code on Android:
public static String encryptDesECB(String data) {
try {
DESKeySpec keySpec = newDESKeySpec("431654625bd37673e3b00359676154074a04666a".getBytes("UTF8"));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey key = keyFactory.generateSecret(keySpec);
// ENCODE plainTextPassword String
byte[] cleartext = data.getBytes("UTF8");
Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
Logger.log(Log.INFO, new String(cipher.doFinal(cleartext)));
String encrypedPwd = Base64.encodeToString(cipher.doFinal(cleartext), Base64.DEFAULT);
Logger.log(Log.INFO, encrypedPwd);
return encrypedPwd;
} catch (Exception e) {
Logger.log(e);
return null;
}
}
Can anyone help me with this?
This is most likely caused by padding, as DES works with 8 byte blocks.
For more information check out this link:
http://www.tero.co.uk/des/explain.php#Padding
As long as you can properly decrypt the content you'll be fine.
I found my mistake. It turn out my BasicAuth Class isn't the correct one for encoding the encrypted string. Now I'm using the correct one Base64 Class for the encoding, and it turn out fine.
I have a problem with with
javax.crypto.Cipher
When I write this lines of code
Cipher cipher;
byte[] bytes = null;
try
{
cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, generateAESKey128b(key));
bytes = cipher.doFinal(input.getBytes("UTF-8"));
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
catch (NoSuchPaddingException e)
{
e.printStackTrace();
}
catch (InvalidKeyException e)
{
e.printStackTrace();
}
catch (UnsupportedEncodingException e)
{
e.printStackTrace();
}
catch (IllegalBlockSizeException e)
{
e.printStackTrace();
}
catch (BadPaddingException e)
{
e.printStackTrace();
}
The console give me this error
javax.crypto.IllegalBlockSizeException
Input length must be multiple of 16 when
decrypting with padded cipher
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
at javax.crypto.Cipher.doFinal(DashoA13*..)
at it.unitn.se.gym.backend.utils.Security.AES128Decode(Security.java:109)
at it.unitn.se.gym.backend.utils.Security.decode_AES128_Base64(Security.java:96)
at it.unitn.se.gym.backend.WebService.main(WebService.java:42)
Exception in thread "main" java.lang.NullPointerException
at it.unitn.se.gym.backend.utils.Security.decode_AES128_Base64(Security.java:97)
at it.unitn.se.gym.backend.WebService.main(WebService.java:42)
The first 2 lines of code are correct but when I pass the attribute "text", of type byte[], to the doFinal function, it give me the error.
Can someone tell me why?
SOLVED:
Okay, problem solved
byte[] encrypted = UniversalBase64Encoder.decode(input);
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, generateAESKey128b(key));
byte[] originalBytes = cipher.doFinal(encrypted);
This is the right code that I wrote
The problem is that you're trying to decrypt a string that was not encrypted, and in doing so are violating an assumption of the decryption algorithm (that its input size is always a multiple of 16).
Here's a block of code that encrypts and then decrypts a string. Notice that when the encrypted string is printed, it's 16 bytes long, even though the input string is not. The encryption algorithm pads the input string out to make it a multiple of 16 bytes before encrypting it. That 16-byte-long encrypted string is now a valid input for decryption.
This assumption (that the result of encrypting will be an even size) is quite standard. It not only makes the decryption/encryption algorithm easier to write, but it also prevents an attacker from knowing the length of the thing you encrypted.
byte[] keyBytes = new byte[16];
keyBytes[0] = 1;
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
String input = "hello";
Cipher cipher;
byte[] bytes = null;
cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
bytes = cipher.doFinal(input.getBytes("UTF-8"));
System.out.println("Encoded: "+Arrays.toString(bytes));
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decoded = cipher.doFinal(bytes);
System.out.println("Decoded: "+new String(decoded, "UTF-8"));