flutterdartencryptionhmachmacsha256

Send an encrypted request to APi in Dart


I am trying to send an encrypted request to a specific API in dart, but without success - I don't have any experience with the Dart language.

This are the requirements:

Can anyone help me? Thanks in advance!

This is the Dart Code so far.

import 'dart:convert';
import 'dart:core';
import 'package:crypto/crypto.dart' as crypto;
import 'package:encrypt/encrypt.dart' as enc;


String encrypt(String string) {
    // json encryption
    final enc.Key key = enc.Key.fromUtf8(env.get('password'));
    final enc.IV iv = enc.IV.fromSecureRandom(IV_LENGTH);
    final enc.Encrypter encrypter = enc.Encrypter(enc.AES(key, mode: enc.AESMode.cbc));
    final encryptedJson = encrypter.encrypt(string, iv: iv);
    final String IVBase64String = base64.encode(iv.bytes);

    print('encrypted JSON: '+encryptedJson.base64);
    print('decrypted JSON: '+encrypter.decrypt(encryptedJson, iv: iv));
    

    crypto.Hmac hmacSha256 = new crypto.Hmac(crypto.sha256, key.bytes);
    crypto.Digest sha256Result = hmacSha256.convert(iv.bytes + encryptedJson.bytes);

    print('data: ' + encryptedJson.base64);
    print('iv: ' + IVBase64String);
    print('hmac: ' + sha256Result.toString());

    // Payload
    final encryptedText = "{\"value\":\""+encryptedJson.base64+"\",\"iv\":\""+IVBase64String+"\",\"mac\":\""+sha256Result.toString()+"\"}";

    print('final: ' + jsonEncode(encryptedText));
    return base64.encode(utf8.encode(encryptedText));
  }

This is the JavaExample

import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class ApiJavaSample
{
    private final Cipher cipher;
    private final SecretKeySpec key;
    private static final String TAG = "AESCrypt";
    private static final int IV_LENGTH = 16;
    private String cypher_mode = "AES/CBC/NoPadding";
    private String cypher_mode_iv = "SHA1PRNG";
    
    public static void main (String[] args)
    {
        try{
            System.out.println("encrypting");
            ApiJavaSample test = new ApiJavaSample("password");
            String encryptedString = test.encrypt("{\"coupon_key\":\"011205358365\",\"location_id\":\"2\",\"device_key\":\"test_1234\"}");
            System.out.println("encrpyted");
            System.out.println(encryptedString);
        }
        catch(Exception e)
        {
            System.out.println(e);
        }
    }
    

    public ApiJavaSample(String password) throws Exception
    {
        // hash password with SHA-256 and crop the output to 128-bit for key
        //MessageDigest digest = MessageDigest.getInstance("SHA-256");
        //digest.Updater(password.getBytes("UTF-8"));
        byte[] keyBytes = password.getBytes();

        cipher = Cipher.getInstance(cypher_mode);
        key = new SecretKeySpec(keyBytes, "AES");
    }

    
     private String hmacDigest(String msg, String algo)
    {
        String digest = null;
        try
        {
            //SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), algo);
            Mac mac = Mac.getInstance(algo);
            mac.init(key);

            byte[] bytes = mac.doFinal(msg.getBytes("UTF-8"));

            StringBuilder hash = new StringBuilder();
            for (int i = 0; i < bytes.length; i++)
            {
                String hex = Integer.toHexString(0xFF & bytes[i]);
                if (hex.length() == 1)
                {
                    hash.append('0');
                }
                hash.append(hex);
            }
            digest = hash.toString();
        }
        catch (UnsupportedEncodingException | InvalidKeyException e)
        {
            e.printStackTrace();
        }
        catch (NoSuchAlgorithmException e)
        {
            e.printStackTrace();
        }
        return digest;
    }


    public String encrypt(String plainText) throws Exception
    {

        byte[] iv_bytes = generateIv();
        AlgorithmParameterSpec spec = new IvParameterSpec(iv_bytes);
        cipher.init(Cipher.ENCRYPT_MODE, key, spec);
        int blockSize = cipher.getBlockSize();
        
        while (plainText.length() % blockSize != 0) {
            plainText += "\0";
        }
        
        
        byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
        String encryptedText = Base64.getEncoder().encodeToString(encrypted);

        String iv_base64_string = Base64.getEncoder().encodeToString(iv_bytes);

        String mac = hmacDigest(iv_base64_string + encryptedText.trim(), "HmacSHA256");

        //JSONObject encryptedJson = new JSONObject();

        //encryptedJson.put("value", encryptedText.trim());
        //encryptedJson.put("iv", iv_base64_string);
        //encryptedJson.put("mac", mac);

        String base64Encrypt = "{\"value\":\""+encryptedText.trim()+"\",\"iv\":\""+iv_base64_string+"\",\"mac\":\""+mac+"\"}";

        return Base64.getEncoder().encodeToString(base64Encrypt.getBytes());
    }


    private byte[] generateIv() throws NoSuchAlgorithmException
    {
        SecureRandom random = SecureRandom.getInstance(cypher_mode_iv);
        byte[] iv = new byte[IV_LENGTH];
        random.nextBytes(iv);

        return iv;
    }
}

Here is my test data:

Plaintext:

"{\"coupon_key\":\"382236526272\",\"location_id\":\"2\",\"device_key\":\"test_1234\"}"

Key:

33a485cb146e1153c69b588c671ab474

Solution

  • The following has to be changed/optimized in the Dart code:

    Thus, the code is to be changed as follows:

    import 'package:crypto/crypto.dart' as crypto;
    import 'package:encrypt/encrypt.dart' as enc;
    import 'package:convert/convert.dart';
    import 'dart:typed_data';
    import 'dart:convert';
    
    String encrypt(String string) {
    
      final enc.Key key = enc.Key.fromUtf8(env.get('password')); // Valid AES key           
      final enc.IV iv = enc.IV.fromSecureRandom(IV_LENGTH);      // IV_LENGTH = 16          
    
      final dataPadded = pad(Uint8List.fromList(utf8.encode(string)), 16);
      final enc.Encrypter encrypter = enc.Encrypter(enc.AES(key, mode: enc.AESMode.cbc, padding: null));
      final encryptedJson = encrypter.encryptBytes(dataPadded, iv: iv);
    
      crypto.Hmac hmacSha256 = crypto.Hmac(crypto.sha256, key.bytes);
      crypto.Digest sha256Result = hmacSha256.convert(utf8.encode(iv.base64 + encryptedJson.base64));
    
      final encryptedText = "{\"value\":\""+encryptedJson.base64+"\",\"iv\":\""+iv.base64+"\",\"mac\":\""+sha256Result.toString()+"\"}";
      return base64.encode(utf8.encode(encryptedText));
    }
    
    Uint8List pad(Uint8List plaintext, int blockSize){
      int padLength = (blockSize - (plaintext.lengthInBytes % blockSize)) % blockSize;
      if (padLength != 0) {
        BytesBuilder bb = BytesBuilder();
        Uint8List padding = Uint8List(padLength);
        bb.add(plaintext);
        bb.add(padding);
        return bb.toBytes();
      }
      else {
        return plaintext;
      }
    }
    

    Test (using a static IV to allow comparison between the ciphertexts of the two codes):

    Key:        enc.Key.fromUtf8("5432109876543210")
    IV:         enc.IV.fromUtf8("0123456789012345")
    Plaintext:  "{\"coupon_key\":\"011205358365\",\"location_id\":\"2\",\"device_key\":\"test_1234\"}"  
    Result:     eyJ2YWx1ZSI6InNRTjJ0OWc5ZWY2RzdNV2RsOFB3emlXSlQwclNxUWJ2ZnN0eCtpMmNtSTQyUXJjUGRNV0JLbTlRZ2kxdmM0dElna2NOZEJsOVpEM0JlYTFPZ1kxaHNSeklSbHM1TnlaN0s1T2NqMTEzdkdvPSIsIml2IjoiTURFeU16UTFOamM0T1RBeE1qTTBOUT09IiwibWFjIjoiMzkwYzlhMzAxMjAxYjc1MWUxNjBhM2JlZTdmZGU5YzE5ZDY0MzJlNTBjOTJhNTg0ODBhMTJkNTYyNWRkYWMyNSJ9
    

    After the changes, both codes return the above result for the above input data.


    Security: