Reputation: 305
Could anyone help me to figure out a decryption algorithm for data that is encrypted in PHP using AES-256-CBC. I have tried lots of different ways, but I think I am messing up when trying to replicate the method of recreating they Key/IV in Dart and keep getting exceptions such as:
RangeError (end): Invalid value: Not in inclusive range 0..16:
The PHP code that does the encryption (which cannot be changed as the encrypted strings are provided by a third party) is as follows:
function encrypt( $string, $encrypt=true) {
$secret_key = 'SuperSecretKey';
$secret_iv = 'SuperSecretBLOCK';
$output = false;
$encrypt_method = "AES-256-CBC";
$key = hash( 'sha256', $secret_key );
$iv = substr( hash( 'sha256', $secret_iv ), 0, 16 );
if($encrypt) {
$output = base64_encode( openssl_encrypt( $string, $encrypt_method, $key, 0, $iv ) );
} else {
$output = openssl_decrypt( base64_decode( $string ), $encrypt_method, $key, 0, $iv );
}
return $output;
}
For example, if the encryption routine in PHP were called to encrypt the string "This is a Test!", the result would be:
ZHArWURDY2FkelBtSGY5c1AzdTNBZz09
It is this result that I am attempting to decrypt in Dart and not having any luck!
Here is what I have so far that is resulting in the exception referenced above:
import 'package:encrypt/encrypt.dart';
import 'package:crypto/crypto.dart';
import 'dart:convert' show utf8;
String extractPayload(String payload) {
String strPwd = 'SuperSecretKey';
String strIv = 'SuperSecretBLOCK';
var iv = sha256.convert(utf8.encode(strIv));
var key = sha256.convert(utf8.encode(strPwd));
IV ivObj = IV.fromUtf8(iv.toString());
Key keyObj = Key.fromUtf8(key.toString());
final encrypter = Encrypter(AES(keyObj));
final decrypted = encrypter.decrypt(Encrypted.from64(payload), iv: ivObj);
print(decrypted);
return decrypted;
}
Any suggestions welcome, Thanks
Upvotes: 6
Views: 8779
Reputation: 49276
The PHP code has a number of unnecessary weaknesses that complicate the porting:
hash
function. It would make more sense to return the result as binary data (but for this, the third parameter would have to be explicitly set to TRUE
). The return as hexadecimal string has two disadvantages in this context:openssl_encrypt
Base64 encodes the ciphertext by default, openssl_decrypt
expects a Base64 encoded ciphertext by default. This can be disabled with the flag OPENSSL_RAW_DATA
, but this is not the case in the current code. At the same time the ciphertext is explicitly Base64 encoded during encryption, so that it is double Base64 encoded. Similarly when decrypting the ciphertext it is explicitly Base64 decoded, so that it is also double Base64 decoded. This redundancy is pointless, only inflates the ciphertext unnecessarily and reduces performance.In addition, the Dart code assumes wrong default values: The encrypt package uses SIC (or CTR) as default mode. Since CBC is specified in the PHP code, this mode must be explicitly specified in the Dart code.
The following Dart implementation decrypts the ciphertext ZHArWURDY2FkelBtSGY5c1AzdTNBZz09:
import 'package:encrypt/encrypt.dart' as EncryptPack;
import 'package:crypto/crypto.dart' as CryptoPack;
import 'dart:convert' as ConvertPack;
String extractPayload(String payload) {
String strPwd = "SuperSecretKey";
String strIv = 'SuperSecretBLOCK';
var iv = CryptoPack.sha256.convert(ConvertPack.utf8.encode(strIv)).toString().substring(0, 16); // Consider the first 16 bytes of all 64 bytes
var key = CryptoPack.sha256.convert(ConvertPack.utf8.encode(strPwd)).toString().substring(0, 32); // Consider the first 32 bytes of all 64 bytes
EncryptPack.IV ivObj = EncryptPack.IV.fromUtf8(iv);
EncryptPack.Key keyObj = EncryptPack.Key.fromUtf8(key);
final encrypter = EncryptPack.Encrypter(EncryptPack.AES(keyObj, mode: EncryptPack.AESMode.cbc)); // Apply CBC mode
String firstBase64Decoding = new String.fromCharCodes(ConvertPack.base64.decode(payload)); // First Base64 decoding
final decrypted = encrypter.decrypt(EncryptPack.Encrypted.fromBase64(firstBase64Decoding), iv: ivObj); // Second Base64 decoding (during decryption)
return decrypted;
}
The following test returns the plaintext This is a Test!
String plaintext = extractPayload("ZHArWURDY2FkelBtSGY5c1AzdTNBZz09");
print(plaintext); // This is a Test!
Upvotes: 8