3

I am decrypting a text in dart which is encrypted in java. Here is the code of java which is used for encryption.

package aes;

import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class Aes {

    public static void main(String[] args) {
        try {

            String keyString = "1234567890123456";//length of key is 16
            Cipher desCipher = Cipher.getInstance("AES");
            byte[] key = keyString.getBytes("UTF-8");
            MessageDigest sha = MessageDigest.getInstance("SHA-1");
            key = sha.digest(key);
            key = Arrays.copyOf(key, 16); // use only first 128 bit

            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
            desCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);

            String plainText = "abcd";

            byte[] text = plainText.getBytes("UTF-8");
            byte[] textencrypted = desCipher.doFinal(text);
            System.out.println("encrypted: " + new String(textencrypted));

        } catch (Exception ex) {
            Logger.getLogger(Aes.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

}

Cipher desCipher = Cipher.getInstance("AES"); here mode of encryption is not defined. I found that when mode is not defined it uses AES/ECB/PKCS5Padding. Also IV is not used during encryption.

I am migrating the java android application to flutter. data on the server is encrypted using this above java code. Now I can't change the all the in use data from the server. I have to decrypt it in flutter to show the data in flutter app.

I am using encrypt: ^4.1.0 package for decryption in dart.

  Encrypted encryptedText = Encrypted.fromUtf8('ßȶ8)\œå7£');
  final key = Key.fromUtf8('1234567890123456');
  final iv = IV.fromLength(16);

  final encrypter = Encrypter(AES(key, mode: AESMode.ecb));

  final decrypted = encrypter.decrypt(encryptedText, iv: iv);

  print(decrypted); 

but this code gives error as follows

E/flutter (18070): [ERROR:flutter/lib/ui/ui_dart_state.cc(171)] Unhandled Exception: Invalid argument(s): Input data length must be a multiple of cipher's block size
E/flutter (18070): #0      PaddedBlockCipherImpl.process (package:pointycastle/padded_block_cipher/padded_block_cipher_impl.dart:60:9)
E/flutter (18070): #1      AES.decrypt (package:encrypt/src/algorithms/aes.dart:55:22)
E/flutter (18070): #2      Encrypter.decryptBytes (package:encrypt/src/encrypter.dart:25:17)
E/flutter (18070): #3      Encrypter.decrypt (package:encrypt/src/encrypter.dart:31:17)
E/flutter (18070): #4      _MyHomePageState.sha1 (package:flutter_decrypt_video/main.dart:196:33)
E/flutter (18070): #5      _MyHomePageState.build (package:flutter_decrypt_video/main.dart:85:5)
E/flutter (18070): #6      StatefulElement.build (package:flutter/src/widgets/framework.dart:4681:28)
E/flutter (18070): #7      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4564:15)
E/flutter (18070): #8      StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4737:11)
E/flutter (18070): #9      Element.rebuild (package:flutter/src/widgets/framework.dart:4280:5)
E/flutter (18070): #10     ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4543:5)
E/flutter (18070): #11     StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4728:11)
E/flutter (18070): #12     ComponentElement.mount (package:flutter/src/widgets/framework.dart:4538:5)
E/flutter (18070): #13     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3508:14)
E/flutter (18070): #14     Element.updateChild (package:flutter/src/widgets/framework.dart:3266:18)
E/flutter (18070): #15     SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:5892:14)
E/flutter (18070): #16     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3508:14)
E/flutter (18070): #17     Element.updateChild (package:flutter/src/widgets/framework.dart:3266:18)
E/flutter (18070): #18     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4589:16)
E/flutter (18070): #19     Element.rebuild (package:flutter/src/widgets/framework.dart:4280:5)
E/flutter (18070): #20     ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4543:5)
E/flutter (18070): #21     ComponentElement.mount (package:flutter/src/widgets/framework.dart:4538:5)
E/flutter (18070): #22     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3508:14)
E/flutter (18070): #23     Element.updateChild (package:flutter/src/widgets/framework.dart:3266:18)
E/flutter (18070): #24     SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:5892:14)
E/flutter (18070): #25     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3508:14)
E/flutter (18070): #26     Element.updateChild (package:flutter/src/widgets/framework.dart:3266:18)
E/flutter (18070): #27     SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:5892:14)
E/flutter (18070): #28     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3508:14)
E/flutter (18070): #29     Element.updateChild (package:flutter/src/widgets/framework.dart:3266:18)
E/flutter (18070): #30     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4589:16)
E/flutter (18070): #31     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4737:11)
E/flutter (18070): #32     Element.rebuild (package:flutter/src/widgets/framework.dart:4280:5)
E/flutter (18070): #33     ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4543:5)
E/flutter (18070): #34     StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4728:11)
E/flutter (18070): #35     ComponentElement.mount (package:flutter/src/widgets/framework.dart:4538:5)
E/flutter (18070): #36     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3508:14)
E/flutter (18070): #37     Element.updateChild (package:flutter/src/widgets/framework.dart:3266:18)
E/flutter (18070): #38     SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:5892:14)
E/flutter (18070): #39     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3508:14)
E/flutter (18070): #40     Element.updateChild (package:flutter/src/widgets/framework.dart:3266:18)
E/flutter (18070): #41     SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:5892:14)
E/flutter (18070): #42     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3508:14)
E/flutter (18070): #43     Element.updateChild (package:flutter/src/widgets/framework.dart:3266:18)
E/flutter (18070): #44     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4589:16)
E/flutter (18070): #45     StatefulElement.performRebuild (package:flutter/src/widgets/fram

If anyone know how to do this please put your answer.

In java code which is used for encryption SHA-1 hashing algorithm is used to get 160 bit of key and then it uses first 128 bits for encryption, may it causes any problem?

If anyone knows any another package which can solve my issue please let me know.

If anyone want any further information please let me know.

Thanks in advance !!

Shubham
  • 41
  • 5
  • In the Java code the key is derived by SHA-1, which is missing in the Dart Code. Therefore different keys are used, so that decryption fails. Furthermore, in the Java code the ciphertext is encoded with a charset encoding, which generally corrupts the data. Use a binary-to-text encoding like Base64. Also, in the Java code algorithms and encodings should be fully specified. Btw, which default encoding is used (i.e. what encoding is used for `new String(textencrypted)`)? Note, ECB is insecure. – Topaco Oct 27 '20 at 08:00
  • [_encrypt_](https://pub.dev/packages/encrypt) is a wrapper for some _Pointy Castle_ functionalities. [_Pointy Castle_](https://pub.dev/packages/pointycastle) itself is more extensive and could be an alternative. – Topaco Oct 27 '20 at 08:15
  • thanks!! also i had that doubt about SHA-1 so I already tried to apply SHA1 in dart but when I compared bytes of key after applying SHA1 in java and dart, those both outputs are not same.. some bytes in java are in negative but in dart all are positive. I tried packages pointly Castle and crypto for that, both are giving same output but does not match with bytes which are calculated by java code. @Topaco If you have any code snippet for that please share. – Shubham Oct 27 '20 at 09:24
  • It is best to compare the hex encoded data. Furthermore, if you have narrowed the problem down to the key derivation, then you should edit your question and post your dart code for the key derivation, preferably together with sample data. Also post the result (for the key derivation) of the Java code. Then it might be possible to identify the problem. – Topaco Oct 27 '20 at 09:39
  • Okay I ll post another question for key derivation. But once please see the exception thrown by flutter. It says that input data length must be multiple of cipher block size. Key is definitely wrong but with this key algorithm can decrypt it and can show some unreadable text but it is giving exception. And the "ßȶ8)\œå7£" is the encryption of "shubham" word i.e. encrypted message is valid one. @Topaco – Shubham Oct 27 '20 at 11:22
  • AES/ECB only processes plaintexts/ciphertexts whose length is an integer multiple of the block length (16 bytes). The encoded ciphertext (i.e. the number of bytes) probably does not meet this condition. You should use binary-to-text encoding like Base64 in both codes and not a charset encoding like UTF-8. – Topaco Oct 27 '20 at 13:15
  • Have a look at my answer please. – Topaco Oct 27 '20 at 13:15

1 Answers1

5

In the Java code, the ciphertext is converted into a string using a charset encoding (like UTF-8). This generally corrupts the data. Here a binary-to-text encoding like base64 should be used.

The Java code posted in the question provides with the following changes:

import java.util.Base64;
...
Cipher desCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
...
String plainText = "The quick brown fox jumps over the lazy dog";
...
System.out.println("encrypted: " + Base64.getEncoder().encodeToString(textencrypted));
...

the following Base64 encoded ciphertext:

encrypted: Mj48sRIlEidcVHVHIw8i8PPXUQiZB3toykI7ODbzXvbrFyOO957Euy0mzgfbVGVF

This ciphertext can be decrypted using the Pointy Castle package with the following Dart code:

import 'dart:convert';
import 'dart:typed_data';
import 'package:pointycastle/api.dart';
import 'package:pointycastle/block/aes_fast.dart';
import 'package:pointycastle/block/modes/ecb.dart';
import 'package:pointycastle/padded_block_cipher/padded_block_cipher_impl.dart';
import 'package:pointycastle/paddings/pkcs7.dart';
...
Digest sha1 = Digest("SHA-1");
Uint8List key = sha1.process(utf8.encode('1234567890123456')).sublist(0, 16);
Uint8List ciphertext = base64.decode('Mj48sRIlEidcVHVHIw8i8PPXUQiZB3toykI7ODbzXvbrFyOO957Euy0mzgfbVGVF');
ECBBlockCipher cipher = ECBBlockCipher(AESFastEngine());
PaddedBlockCipher paddedCipher = new PaddedBlockCipherImpl(new PKCS7Padding(), cipher);
PaddedBlockCipherParameters<CipherParameters, CipherParameters> paddingParams = new PaddedBlockCipherParameters(KeyParameter(key), null);
paddedCipher.init(false, paddingParams);
Uint8List plainText = paddedCipher.process(ciphertext);
print(utf8.decode(plainText));

Please note the following: ECB is an insecure mode. More secure are CBC (see here for a dart example) or GCM, where the latter allows encryption as well as data authentication. Also the derivation of a key from a password with SHA-1 is insecure. Here a reliable key derivation function, e.g. PBKDF2, should be used.

Edit:
Converting a ciphertext into a string with a charset like UTF-8 will damage the data. You can find many posts on Stackoverflow, e.g. this one, which explains the problem in detail. This means that the Java code posted in your question won't work reliably if the ciphertext is converted with UTF-8.

However, there are charsets like ISO-8859-1 that do not corrupt the data. It is possible that such a charset is used on the server. This cannot be determined from the posted code, because no charset is specified during decoding (i.e. in new String(textencrypted)), so the default charset of the respective platform is applied, see here. Therefore, to check this possibility, you have to determine which default charset is used on the server.
Another way to analyze which encoding is used would be to check (or post) the code that is used to decrypt the ciphertext created by the server.

Most reliable is the use of encodings that are dedicated to convert binary data into a string, so called binary-to-text encodings, such as Base64, see here, which is why I used Base64 in the posted example.


You can verify the effects of a conversion using UTF-8 or ISO-8859-1 with the following Java code. The data from the posted example above is used as ciphertext:

convertDataWith(StandardCharsets.UTF_8);
convertDataWith(StandardCharsets.ISO_8859_1);
...
private static void convertDataWith(Charset charset) {
    String ciphertextBeforeB64 = "Mj48sRIlEidcVHVHIw8i8PPXUQiZB3toykI7ODbzXvbrFyOO957Euy0mzgfbVGVF";    // Data from posted example
    byte[] ciphertextBefore = Base64.getDecoder().decode(ciphertextBeforeB64);
    String ciphertextCharset = new String(ciphertextBefore, charset);                                   // Convert to String with specified charset
    byte[] ciphertextAfter = ciphertextCharset.getBytes(charset);                                       // Convert from String with specified charset
    String ciphertextAfterB64 = Base64.getEncoder().encodeToString(ciphertextAfter);
    System.out.println("Ciphertext BEFORE conversion: " + ciphertextBeforeB64);
    System.out.println("Ciphertext AFTER conversion:  " + ciphertextAfterB64);  
}

with the output:

Ciphertext BEFORE conversion: Mj48sRIlEidcVHVHIw8i8PPXUQiZB3toykI7ODbzXvbrFyOO957Euy0mzgfbVGVF
Ciphertext AFTER conversion:  Mj4877+9EiUSJ1xUdUcjDyLvv73vv73vv71RCO+/vQd7aO+/vUI7ODbvv71e77+977+9FyPvv73vv73vv73Euy0m77+9B++/vVRlRQ==

Ciphertext BEFORE conversion: Mj48sRIlEidcVHVHIw8i8PPXUQiZB3toykI7ODbzXvbrFyOO957Euy0mzgfbVGVF
Ciphertext AFTER conversion:  Mj48sRIlEidcVHVHIw8i8PPXUQiZB3toykI7ODbzXvbrFyOO957Euy0mzgfbVGVF

As explained above, UTF-8 corrupts the data.

Topaco
  • 40,594
  • 4
  • 35
  • 62
  • Thank you so much !! It works perfectly... But is there any solution to use existing UTF-8 encoding instead of base64 ? Because existing all the data is encrypted using UTF-8 encoding as you can see in encryption java code, it does not use base64. so is there any solution for that ? @Topaco – Shubham Oct 27 '20 at 17:22
  • @Shubham - _But is there any solution to use existing UTF-8 encoding?_ Unfortunately not. Please see the Edit section of my answer. – Topaco Oct 27 '20 at 20:38
  • 1
    I'm able to use UTF-8 encoding in decryption by writing bytes of ciphertext in file and then reading bytes from that file for decryption. Now i don't need to change existing data on the server. I know it is not recommended but now I can't change 80GB of encrypted data. And thank you so much for helping me.. :) @Topaco – Shubham Oct 28 '20 at 08:08
  • you saved my day, thank you very much for the Pointy castle solution. thanks – Nabil Ait Brahim Jan 12 '22 at 00:05