I am trying to implement private key encryption within my application.
However, this key that I have to encrypt is of the List type and when I try to encrypt I get the following error:
Expected a value of type 'String', but got one of type 'List <int>'
how can I encrypt my key of type List correctly without getting errors so that I can later pass the parameter as type Uint8List to my method:
AccountEntity.account (account, Uint8List.fromList (privateKey));
this is my code:
var privateKey = await account.keyPair.extractPrivateKeyBytes();
privateKey = AesEncryptionDecryption
.encryptKey(
privateKey).bytes ;
final entity =
AccountEntity.account(account, Uint8List.fromList(privateKey));
while this is the class which contains the encryption methods, as API I used encrypt: ^ 5.0.1 from pubdev :
class AesEncryptionDecryption{
static final key = encrypt.Key.fromLength(32);
static final iv = encrypt.IV.fromLength(16);
static final encrypter = encrypt.Encrypter(encrypt.AES(key));
static encryptAES(text) {
final encrypted = encrypter.encrypt(text, iv: iv);
return encrypted;
}
static encryptKey(text) {
final encrypted = encrypter.encryptBytes(text, iv: iv);
return encrypted as List<int>;
}
static String decryptAES(String base64Text) {
print(base64Text);
String decrypted = encrypter.decrypt(
Encrypted.fromBase64(base64Text),
iv: iv,
);
print(decrypted);
return decrypted;
}
does anyone know how to help me? thank you very much
Related
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:
The JSON to be sent is encrypted as follows: "AES/CBC/ZeroBytePadding", IV is generated according to SHA1PRNG with a length of 16 bytes.
The encrypted bytes are Base64 encoded. This results in the encryptedJson.
The hmac is generated from base64 encoded IV and the encryptedJson with "HmacSHA256".
A json will be generated: {"value":encryptedJson,"iv":initialisationVector,"mac":hmac}
This json will be base64 encoded and sent as an encrypted payload.
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:
The Java code uses Zero padding. PointyCastle and the encrypt package (a PointyCastle wrapper) do not support Zero padding (to my knowledge). A possible approach for the Dart code is to disable the default PKCS#7 padding in combination with a custom implementation for Zero padding.
The Java code applies the Base64 encoded data for the HMAC, while the Dart code uses the raw data. This has to be changed.
The Base64 encoding of the IV is obtained more efficiently with 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:
Typically, an AES key is a randomly generated byte sequence and not a string. If the key is to be derived from a passphrase/string, a reliable key derivation like PBKDF2 is to be used.
Zero padding is unreliable, so the reliable PKCS#7 padding that most libraries use by default should be applied. If the Java code had used PKCS#7 padding, porting would have been easier.
For encoding/decoding the charset should be specified (e.g. getBytes(StandardCharsets.UTF_8)), otherwise the default encoding will be used (which might not be wanted).
Using the same key for encryption and integrity checking for AES/HMAC is not a pressing security issue, but should be avoided as a preventive measure, see here.
The code is partially inefficient, e.g. when concatenating the Base64 encoded data instead of the raw data to determine the HMAC.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed last year.
Improve this question
I have two functions witch cipher and decrypt data with AES-GCM algorithm but using cryptography package. I don't think its bad, it helped me a lot, but I want to translate them so I can use pointycastle package for all my different encryption and decryption algorithms.
Functions using cryptography package are:
Future<CipherDataHolder> cipher(String clearValue) async {
final algorithm = AesGcm.with256bits(nonceLength: 12); //128bits MAC default
final SecretKey secretKey = await algorithm.newSecretKey();
final List<int> nonce = algorithm.newNonce();
final secretBox = await algorithm.encrypt(
utf8.encode(clearValue),
secretKey: secretKey,
nonce: nonce,
);
String cipherText = base64.encode(secretBox.concatenation());
String passphrase = base64.encode(await secretKey.extractBytes());
return CipherDataHolder(ciphertext: cipherText, passphrase: passphrase);
}
Future<String> decrypt(CipherDataHolder secretData) async {
final Uint8List cipherData = base64.decode(secretData.ciphertext);
final Uint8List ciphertext = cipherData.sublist(12, cipherData.length - 16);
final Uint8List iv = cipherData.sublist(0, 12);
final Uint8List mac = cipherData.sublist(cipherData.length - 16);
List<int> passphrase = base64.decode(secretData.passphrase);
final SecretKey secretKey = SecretKey(passphrase);
final SecretBox secretBox = SecretBox(ciphertext, nonce: iv, mac: Mac(mac));
final List<int> clearValue = await AesGcm.with256bits().decrypt(secretBox, secretKey: secretKey);
return utf8.decode(clearValue);
}
CipherDataHolder is just a class to hold the values
class CipherDataHolder {
CipherDataHolder({required this.ciphertext, required this.passphrase});
String ciphertext;
String passphrase;
}
/////////////////////////////////////////////////////////////////////
UPDATE
/////////////////////////////////////////////////////////////////////
Here is what I have tried till now to translate those functions
Functions using pointycastle package are:
Future<CipherDataHolder> cipherWithGCMyPointyCastle({required String clearValue}) async {
final Uint8List key = genKey(); //32 bytes key
final Uint8List nonce = generateRandomNonce(); //12 bytes nonce
final List<int> plainTextBytes = utf8.encode(clearValue);
final cipher = pointy.GCMBlockCipher(pointy.AESEngine())
..init(
true, // encrypt
pointy.AEADParameters(
pointy.KeyParameter(key), // the 256 bit (32 byte) key
128, //Mac length
nonce, // the 12 byte nonce
Uint8List(0), // empty extra data
));
//Last 16 is mac data, rest is plaintext Bytes
Uint8List cipherTextBytes = cipher.process(Uint8List.fromList(plainTextBytes));
//Concatenate nonce + cipherText + mac bytes
String cipherText = base64.encode(concatenateCipherData(nonceBytes: cipher.nonce, cipherTextBytes: cipherTextBytes.sublist(0, cipherTextBytes.length - 16), macBytes: cipher.mac));
return CipherDataHolder(ciphertext: cipherText, passphrase: base64.encode(key));
}
Future<String> decryptWithGCMyPointyCastle(CipherDataHolder secretData) async {
final Uint8List cipherData = base64.decode(secretData.ciphertext);
final Uint8List ciphertext = cipherData.sublist(12, cipherData.length - 16); //Rest between 12 and last 16
final Uint8List nonce = cipherData.sublist(0, 12); //First 12 bytes
final Uint8List mac = cipherData.sublist(cipherData.length - 16); //last 16 bytes
List<int> passphrase = base64.decode(secretData.passphrase);
final cipher = pointy.GCMBlockCipher(pointy.AESEngine())
..init(
false, // decrypt
pointy.AEADParameters(
pointy.KeyParameter(Uint8List.fromList(passphrase)),
128,
nonce,
Uint8List(0),
));
BytesBuilder bb = BytesBuilder();
bb.add(ciphertext);
bb.add(mac);
Uint8List ciphertextWithTag = bb.toBytes();
return String.fromCharCodes(cipher.process(ciphertextWithTag));
}
I still have some doubts and I don't know if I'm doing things right. But cipher and decrypt are working now and when I cipher data I'm getting similar results with cryptography package.
Main problem now is that when I cipher with pointycastle, for example this value: abc123|##¢∞¬÷“”≠
Results with cryptography: abc123|##¢∞¬÷“”≠
Results with pointycastle: abc123|##¢â¬÷âââ
I understand it can be some kind of codification problem, but I don't see where :(
Where could be the problem or What am I doing wrong?
These are the auxiliar functions
Uint8List concatenateCipherData({required List<int> nonceBytes, required List<int> cipherTextBytes, required List<int> macBytes}) {
int n = cipherTextBytes.length + nonceBytes.length + macBytes.length;
Uint8List result = Uint8List(n);
int i = 0;
result.setAll(i, nonceBytes);
i += nonceBytes.length;
result.setAll(i, cipherTextBytes);
i += cipherTextBytes.length;
result.setAll(i, macBytes);
return result;
}
Uint8List generateRandomNonce() {
final _sGen = Random.secure();
final _seed = Uint8List.fromList(List.generate(32, (n) => _sGen.nextInt(256)));
pointy.SecureRandom sec = pointy.SecureRandom("Fortuna")..seed(pointy.KeyParameter(_seed));
return sec.nextBytes(12);
}
Uint8List genKey() {
final _sGen = Random.secure();
final _seed = Uint8List.fromList(List.generate(32, (n) => _sGen.nextInt(256)));
pointy.SecureRandom sec = pointy.SecureRandom("Fortuna")..seed(pointy.KeyParameter(_seed));
return sec.nextBytes(32);
}
All the pointcastle ciphers are instantiated in basically the same way. Here's the way to instantiate AES/GCM, with examples for the inputs and outputs.
final keyBytes = Uint8List(32); // dummy key - replace with 256 bit key
final nonce = Uint8List(12); // dummy nonce - replace with random value
final plainTextBytes = Uint8List(5); // dummy input - 5 bytes (5 is just an example)
final cipher = GCMBlockCipher(AESEngine())
..init(
true, // encrypt (or decrypt)
AEADParameters(
KeyParameter(keyBytes), // the 256 bit (32 byte) key
16 * 8, // the mac size (16 bytes)
nonce, // the 12 byte nonce
Uint8List(0), // empty extra data
));
final cipherTextBytes = cipher.process(plainTextBytes);
print(cipherTextBytes.length); // prints 21 = 16 (mac) + 5 (plain text length)
I am already having encryption code in java. Now I want to consume APIs from my server. Even after trying various tutorials and sample codes I am not able to successfully decrypt the hash.
I know fixed salt and IV is not recommended at all. But for simplicity and to understand the issue I have kept salt and IV to "00000000000000000000000000000000";
Hash After Encryption from Java = "XjxCg0KK0ZDWa4XMFhykIw==";
Private key used = "Mayur12354673645"
Can someone please help me to decrypt above string using dart.
JAVA Code
public String encrypt(String salt, String iv, String passphrase,
String plaintext) {
try {
SecretKey key = generateKey(salt, passphrase);
byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, iv, plaintext
.getBytes("UTF-8"));
return base64(encrypted);
} catch (UnsupportedEncodingException e) {
throw fail(e);
}
}
public String decrypt(String salt, String iv, String passphrase,
String ciphertext) {
try {
SecretKey key = generateKey(salt, passphrase);
byte[] decrypted = doFinal(Cipher.DECRYPT_MODE, key, iv,
base64(ciphertext));
return new String(decrypted, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw fail(e);
}
}
private SecretKey generateKey(String salt, String passphrase) {
try {
SecretKeyFactory factory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), hex(salt),
iterationCount, keySize);
SecretKey key = new SecretKeySpec(factory.generateSecret(spec)
.getEncoded(), "AES");
return key;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private byte[] doFinal(int encryptMode, SecretKey key, String iv,
byte[] bytes) {
try {
cipher.init(encryptMode, key, new IvParameterSpec(hex(iv)));
return cipher.doFinal(bytes);
} catch (Exception e) {
e.printStackTrace();
throw fail(e);
}
}
My Dart Code
import 'package:pointycastle/block/aes_fast.dart';
import 'package:pointycastle/block/modes/cbc.dart';
import 'package:pointycastle/digests/sha1.dart';
import 'package:pointycastle/key_derivators/pbkdf2.dart';
import 'package:pointycastle/macs/hmac.dart';
import 'package:pointycastle/paddings/pkcs7.dart';
import 'package:pointycastle/pointycastle.dart';
import 'dart:convert';
import 'dart:typed_data';
import 'package:convert/convert.dart';
import 'dart:developer';
import 'package:pointycastle/random/fortuna_random.dart';
const KEY_SIZE = 16;
const ITERATION_COUNT = 5;
class EncryptionHandler {
static const CBC_MODE = 'CBC';
static Uint8List deriveKey(dynamic password,
{String salt = '0000000000000000',
int iterationCount = ITERATION_COUNT,
int derivedKeyLength = KEY_SIZE}) {
if (password == null || password.isEmpty) {
throw new ArgumentError('password must not be empty');
}
if (password is String) {
password = createUint8ListFromString(password);
}
Uint8List saltBytes = createUint8ListFromString(salt);
String hexSalt = formatBytesAsHexString(saltBytes);
KeyDerivator keyDerivator =
new PBKDF2KeyDerivator(new HMac(new SHA1Digest(), 64));
Pbkdf2Parameters params =
new Pbkdf2Parameters(saltBytes, iterationCount, derivedKeyLength);
keyDerivator.init(params);
return keyDerivator.process(password);
}
Uint8List createUint8ListFromHexString(String hex) {
var result = new Uint8List(hex.length ~/ 2);
for (var i = 0; i < hex.length; i += 2) {
var num = hex.substring(i, i + 2);
var byte = int.parse(num, radix: 16);
result[i ~/ 2] = byte;
}
return result;
}
static String formatBytesAsHexString(Uint8List bytes) {
var result = new StringBuffer();
for (var i = 0; i < bytes.lengthInBytes; i++) {
var part = bytes[i];
result.write('${part < 16 ? '0' : ''}${part.toRadixString(16)}');
}
return result.toString();
}
static Uint8List pad(Uint8List src, int blockSize) {
var pad = new PKCS7Padding();
pad.init(null);
int padLength = blockSize - (src.length % blockSize);
var out = new Uint8List(src.length + padLength)..setAll(0, src);
pad.addPadding(out, src.length);
return out;
}
static Uint8List unpad(Uint8List src) {
var pad = new PKCS7Padding();
pad.init(null);
int padLength = pad.padCount(src);
int len = src.length - padLength;
return new Uint8List(len)..setRange(0, len, src);
}
static String encrypt(String password, String plaintext,
{String mode = CBC_MODE}) {
Uint8List derivedKey = deriveKey(password);
KeyParameter keyParam = new KeyParameter(derivedKey);
BlockCipher aes = new AESFastEngine();
var rnd = FortunaRandom();
rnd.seed(keyParam);
Uint8List iv = createUint8ListFromString("0000000000000000");
BlockCipher cipher;
ParametersWithIV params = new ParametersWithIV(keyParam, iv);
cipher = new CBCBlockCipher(aes);
cipher.init(true, params);
Uint8List textBytes = createUint8ListFromString(plaintext);
Uint8List paddedText = pad(textBytes, aes.blockSize);
Uint8List cipherBytes = _processBlocks(cipher, paddedText);
Uint8List cipherIvBytes = new Uint8List(cipherBytes.length + iv.length)
..setAll(0, iv)
..setAll(iv.length, cipherBytes);
return base64.encode(cipherIvBytes);
}
static String decrypt(String password, String ciphertext) {
log('Password: $password');
Uint8List derivedKey = deriveKey(password);
log('derivedKey: $derivedKey');
KeyParameter keyParam = new KeyParameter(derivedKey);
log('keyParam: $keyParam');
BlockCipher aes = new AESFastEngine();
Uint8List cipherIvBytes = base64.decode(ciphertext);
log('cipherIvBytes: $cipherIvBytes');
Uint8List iv = createUint8ListFromString("0000000000000000");
// Uint8List iv = new Uint8List(aes.blockSize)
// ..setRange(0, aes.blockSize, cipherIvBytes);
log('iv: $iv');
BlockCipher cipher;
ParametersWithIV params = new ParametersWithIV(keyParam, iv);
log('params: $params');
cipher = new CBCBlockCipher(aes);
log('cipher: $cipher');
cipher.init(false, params);
int cipherLen = cipherIvBytes.length - aes.blockSize;
Uint8List cipherBytes = new Uint8List(cipherLen)
..setRange(0, cipherLen, cipherIvBytes, aes.blockSize);
Uint8List paddedText = _processBlocks(cipher, cipherBytes);
log('cipher: $paddedText');
Uint8List textBytes = paddedText;
// Uint8List textBytes = unpad(paddedText);
return new String.fromCharCodes(textBytes);
}
static Uint8List createUint8ListFromString(String s) {
var ret = new Uint8List(s.length);
for (var i = 0; i < s.length; i++) {
ret[i] = s.codeUnitAt(i);
}
return ret;
}
static Uint8List _processBlocks(BlockCipher cipher, Uint8List inp) {
var out = new Uint8List(inp.lengthInBytes);
for (var offset = 0; offset < inp.lengthInBytes;) {
var len = cipher.processBlock(inp, offset, out, offset);
offset += len;
}
return out;
}
}
The code can be simplified by using existing Dart libraries for the conversion binary to hex and vice versa. PointyCastle also supports the (PKCS7) padding, so that a custom implementation is not necessary, which also reduces the code. On the Internet you can find several dart implementations for AES/CBC/PKCS7Padding in combination with PBKDF2 that use PointyCastle, e.g. here and here.
A possible Dart implementation for decryption using the pointycastle and convert package is e.g. (for simplicity without exception handling):
import 'dart:typed_data';
import "package:pointycastle/export.dart";
import 'package:convert/convert.dart';
import 'dart:convert';
...
static Uint8List decrypt(Uint8List ciphertext, Uint8List key, Uint8List iv) {
CBCBlockCipher cipher = new CBCBlockCipher(new AESFastEngine());
ParametersWithIV<KeyParameter> params = new ParametersWithIV<KeyParameter>(new KeyParameter(key), iv);
PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null> paddingParams = new PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>(params, null);
PaddedBlockCipherImpl paddingCipher = new PaddedBlockCipherImpl(new PKCS7Padding(), cipher);
paddingCipher.init(false, paddingParams);
return paddingCipher.process(ciphertext);
}
static Uint8List generateKey(Uint8List salt, Uint8List passphrase){
KeyDerivator derivator = new PBKDF2KeyDerivator(new HMac(new SHA1Digest(), 64));
Pbkdf2Parameters params = new Pbkdf2Parameters(salt, 5, 16);
derivator.init(params);
return derivator.process(passphrase);
}
With the posted test data:
String saltHex = '00000000000000000000000000000000';
String ivHex = '00000000000000000000000000000000';
String passphraseUtf8 = 'Mayur12354673645';
String ciphertextBase64 = "XjxCg0KK0ZDWa4XMFhykIw==";
Uint8List salt = hex.decode(saltHex);
Uint8List passphrase = utf8.encode(passphraseUtf8);
Uint8List key = generateKey(salt, passphrase);
Uint8List ciphertext = base64.decode(ciphertextBase64);
Uint8List iv = hex.decode(ivHex);
Uint8List decrypted = decrypt(ciphertext, key, iv);
print(utf8.decode(decrypted)); // This is working
the ciphertext can be decrypted to: This is working.
An alternative to PointyCastle is the cryptography package, which allows even a more compact implementation in the current case:
import 'package:cryptography/cryptography.dart';
import 'package:convert/convert.dart';
import 'dart:convert';
import 'dart:typed_data';
...
static Uint8List decrypt(Uint8List ciphertext, Uint8List key, Uint8List iv) {
SecretKey secretKey = new SecretKey(key);
Nonce nonce = new Nonce(iv);
Uint8List decrypted = aesCbc.decryptSync(ciphertext, secretKey: secretKey, nonce: nonce);
return decrypted;
}
static Uint8List generateKey(Uint8List salt, Uint8List passphrase){
Pbkdf2 pbkdf2 = Pbkdf2(macAlgorithm: new Hmac(sha1), iterations: 5, bits: 128);
return pbkdf2.deriveBitsSync(passphrase, nonce: Nonce(salt));
}
Note that in practice IV and salt must be generated randomly for each encryption (which you already mentioned in your question). Apart from that the iteration count of 5 is generally much too low.
I have to implement a dart algorithm in order to encrypt a password and send it to a server for authentication from a flutter app (it's barely a week since I'm on flutter/dart). This has already been implemented in an android app and angular web app (not from me), which both produce the same encrypted password even though with different algorithms.
Java code for android:
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.CryptoPrimitive;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
public class AesUtil {
private final int keySize;
private final int iterationCount;
private final Cipher cipher;
public AesUtil(int keySize, int iterationCount) {
this.keySize = keySize;
this.iterationCount = iterationCount;
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw fail(e);
}
}
public String decrypt(String salt, String iv, String passphrase, String ciphertext) {
try {
SecretKey key = generateKey(salt, passphrase);
byte[] decrypted = doFinal(Cipher.DECRYPT_MODE, key, iv, base64(ciphertext));
return new String(decrypted, "UTF-8");
} catch (UnsupportedEncodingException e) {
return null;
} catch (Exception e) {
return null;
}
}
private byte[] doFinal(int encryptMode, SecretKey key, String iv, byte[] bytes) {
try {
cipher.init(encryptMode, key, new IvParameterSpec(hex(iv)));
return cipher.doFinal(bytes);
} catch (InvalidKeyException
| InvalidAlgorithmParameterException
| IllegalBlockSizeException
| BadPaddingException e) {
return null;
}
}
private SecretKey generateKey(String salt, String passphrase) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), hex(salt), iterationCount, keySize);
SecretKey key = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
return key;
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
return null;
}
}
public static byte[] base64(String str) {
return Base64.decode(str);
}
public static String base64(byte[] bytes) {
return new String(Base64.encode(bytes));
}
public static byte[] hex(String str) {
try {
return Hex.decode(str);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
public static String hex(byte[] bytes) {
return Hex.encode(bytes).toString();
}
private IllegalStateException fail(Exception e) {
return null;
}
public String random(int length) {
byte[] salt = new byte[length];
new SecureRandom().nextBytes(salt);
return hex(salt);
}
public String encrypt(String salt, String iv, String passphrase, String plaintext) {
try {
SecretKey key = generateKey(salt, passphrase);
byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, iv, plaintext.getBytes("UTF-8"));
return base64(encrypted);
} catch (UnsupportedEncodingException e) {
throw fail(e);
}
}
public String encriptToAES(String password) {
//`
//for some reasons key, iv and salt are hardcoded string
//
final String key = "hardcoded_key_16_characters";
final String iv = "hardcoded_iv_32_characters";
final String salt = "hardcoded_salt_32_characters";
String ciphertext = encrypt(salt, iv, key, password);
String preEncr = new String(Base64.encode((iv + "::" + salt + "::" + ciphertext).getBytes()));
return preEncr;
}
}
And this is the java line of code which generate the encrypted password:
AesUtil(128, 1000).encriptToAES(password)
JS code for angular:
import * as CryptoJS from 'crypto-js';
export class AesUtil{
private keySize;
private iterationCount;
constructor(keySize, iterationCount) {
this.keySize = keySize / 32;
this.iterationCount = iterationCount;
};
generateKey(salt, passPhrase) {
var key = CryptoJS.PBKDF2(
passPhrase,
CryptoJS.enc.Hex.parse(salt),
{ keySize: this.keySize, iterations: this.iterationCount });
return key;
}
encrypt(salt, iv, passPhrase, plainText) {
var key = this.generateKey(salt, passPhrase);
var encrypted = CryptoJS.AES.encrypt(
plainText,
key,
{ iv: CryptoJS.enc.Hex.parse(iv) });
return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
}
decrypt(salt, iv, passPhrase, cipherText) {
var key = this.generateKey(salt, passPhrase);
var cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(cipherText)
});
var decrypted = CryptoJS.AES.decrypt(
cipherParams,
key,
{ iv: CryptoJS.enc.Hex.parse(iv) });
return decrypted.toString(CryptoJS.enc.Utf8);
}
}
And this code does the trick:
const iv = CryptoJS.lib.WordArray.random(128/8).toString(CryptoJS.enc.Hex);
const salt = CryptoJS.lib.WordArray.random(128/8).toString(CryptoJS.enc.Hex);
const aesUtil = newAesUtil(128,1000);
const cipherText = aesUtils.encrypt(salt,iv,'hardcoded_key_16_characters',password);
const aesPassword = (iv+ '::' + salt + '::'+ cipherText);
const encryptedPassword = btoa(aesPassword);
For what I can understand I need an AES128 encryption method, where to set a specific number of iterations, which accepts iv and salt and eventually encrypt the result with Base64.
I have already tried all kinds of dart encryption packages on pub.dev and snippets without success.
I was relying on this one in particular, but even with hardcoded parameters the result password doesn't match and I'm not able to figure out why. Does anyone know on which elements I have to focus in order to achieve the same result in flutter? For example, which aes128 'plugin' should I use? Static or random iv and salt? All kind of things, I'm pretty much lost. Thanks
UPDATE:
import 'dart:typed_data';
import 'dart:convert';
import 'package:cryptography/cryptography.dart';
final password = "justatest";
final key = "0123456789abcdef";
final iv = "0123456789abcdefghijklmnopqrstuv";
final salt = "abcdefghijklmnopqrstuvwzyz012345";
encryptAESCryptoJS() {
final pbkdf2 = Pbkdf2(
macAlgorithm: Hmac(sha1),
iterations: 1000,
bits: 128,
);
final Uint8List hashBytes = pbkdf2.deriveBitsSync(
utf8.encode(key),
nonce: Nonce(utf8.encode(salt)),
);
}
I have one small doubt as i am new to AES.
I encrypted a string using one certificate with some password lets say , 'xxx'.
Now i duplicated the certificate by changing the password of it.
When i try to decrypt the encrypted string with the duplicated cert, it says Bad padding exception.Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded
However, when i use the original cert, it decrypts properly.
Could anyone please guide me on it?
public SecretKey retrieveKey(String password, byte[] certFile) throws Exception {
try {
String alias = null;
certPass = password;
char[] pass = certPass.toCharArray();
KeyStore keyStore = KeyStore.getInstance("jceks");
InputStream inputStream = new ByteArrayInputStream(certFile);
keyStore.load(inputStream, pass);
Enumeration enumeration = keyStore.aliases();
while (enumeration.hasMoreElements()) {
alias = (String) enumeration.nextElement();
}
Certificate cert = keyStore.getCertificate(alias);
Key key = cert.getPublicKey();
aesSecretKey = new SecretKeySpec(key.getEncoded(), algorithm);
byte[] encoded = aesSecretKey.getEncoded();
byte[] encryptionKey = Arrays.copyOfRange(encoded, encoded.length - 16, encoded.length);
aesSecretKey = new SecretKeySpec(encryptionKey, algorithm);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw e;
}
return aesSecretKey;
}
You should use RSA to wrap / unwrap the AES key. The public key is not identical to the AES key, so the following code is certainly incorrect:
Key key = cert.getPublicKey();
aesSecretKey = new SecretKeySpec(key.getEncoded(), algorithm);