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
The following has to be changed/optimized in the Dart code:
iv.base64
.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:
getBytes(StandardCharsets.UTF_8))
, otherwise the default encoding will be used (which might not be wanted).