Decrypt AES/CBC/PKCS5Padding Encryption in Dart - flutter

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.

Related

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:
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.

How to encrypt data in one app and decrypt it in different Windows app with RSA keys tied to local system?

I have a setup where I need to encrypt blob of data in one app and decrypt it in different app.
I built a sample app that creates a named CngKey object. Then create a RSACng using CngKey object. Then use RSACng object to do encryption/decryption. What I found is that the key changes across restarts of the application even though it is loaded using the name it was created with. I am lost trying to understand the relation between CngKey and RSACng objects.
Below is snippet of code that describes what I am trying to do:
using System;
using System.IO;
using System.Security.Cryptography;
namespace TPMCrypto
{
class Program
{
static byte[] data = { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 };
static byte[] privateKey;
private static byte[] encrypted;
private static byte[] decrypted;
static void Main(string[] args)
{
const string MyKey = "MyRSAKey";
CngKey cngKey = null;
string cmd = args.Length > 0 ? args[0] : "";
try
{
CngKeyCreationParameters cng = new CngKeyCreationParameters
{
KeyUsage = CngKeyUsages.AllUsages,
KeyCreationOptions = CngKeyCreationOptions.MachineKey,
Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider
};
if (!CngKey.Exists(MyKey, CngProvider.MicrosoftSoftwareKeyStorageProvider, CngKeyOpenOptions.MachineKey))
{
Console.WriteLine("Creating rsaKey");
cngKey = CngKey.Create(CngAlgorithm.Rsa, MyKey, cng);
}
else
{
Console.WriteLine("Opening rsaKey");
cngKey = CngKey.Open(MyKey, CngProvider.MicrosoftSoftwareKeyStorageProvider, CngKeyOpenOptions.MachineKey);
}
RSACng rsaKey = new RSACng(cngKey)
{
KeySize = 2048
};
privateKey = rsaKey.Key.Export(CngKeyBlobFormat.GenericPrivateBlob);
string prvResult = ByteArrayToHexString(privateKey, 0, privateKey.Length);
Console.WriteLine("\nPrivate key - length = " + privateKey.Length + "\n" + prvResult + "\n");
const string FILE_PATH = #"\temp\tpmtests\encryptedblob.dat";
// Encrypt / decrypt
if (cmd == "readfromfile")
{
Directory.CreateDirectory(Path.GetDirectoryName(FILE_PATH));
encrypted = File.ReadAllBytes(FILE_PATH);
}
else if (cmd == "deletekey")
{
cngKey.Delete();
return;
}
else
{
encrypted = Encrypt(rsaKey, data);
Console.WriteLine("The encrypted blob: ");
Console.WriteLine(ByteArrayToHexString(encrypted, 0, encrypted.Length));
File.WriteAllBytes(FILE_PATH, encrypted);
}
decrypted = Decrypt(rsaKey, encrypted);
bool result = ByteArrayCompare(data, decrypted);
if (result)
Console.WriteLine("Encrypt / decrypt works");
else
Console.WriteLine("Encrypt / decrypt fails");
}
catch (Exception e)
{
Console.WriteLine("Exception " + e.Message);
}
finally
{
if (cngKey != null)
cngKey.Dispose();
}
Console.ReadLine();
}
static bool ByteArrayCompare(byte[] a1, byte[] a2)
{
if (a1.Length != a2.Length)
return false;
for (int i = 0; i < a1.Length; i++)
if (a1[i] != a2[i])
return false;
return true;
}
public static string ByteArrayToHexString(byte[] bytes, int start, int length)
{
string delimitedStringValue = BitConverter.ToString(bytes, start, length);
return delimitedStringValue.Replace("-", "");
}
public static byte[] Sign512(byte[] data, byte[] privateKey)
{
CngKey key = CngKey.Import(privateKey, CngKeyBlobFormat.GenericPrivateBlob);
RSACng crypto = new RSACng(key);
return crypto.SignData(data, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
}
public static bool VerifySignature512(byte[] data, byte[] signature, byte[] publicKey)
{
CngKey key = CngKey.Import(publicKey, CngKeyBlobFormat.GenericPublicBlob);
RSACng crypto = new RSACng(key);
return crypto.VerifyData(data, signature, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
}
public static byte[] Encrypt(byte[] publicKey, byte[] data)
{
CngKey key = CngKey.Import(publicKey, CngKeyBlobFormat.GenericPublicBlob);
RSACng crypto = new RSACng(key);
var result = Encrypt(crypto, data);
return result;
}
public static byte[] Encrypt(RSACng crypto, byte[] data)
{
if (null == crypto)
return null;
var result = crypto.Encrypt(data, RSAEncryptionPadding.OaepSHA512);
return result;
}
public static byte[] Decrypt(byte[] privateKey, byte[] data)
{
CngKey key = CngKey.Import(privateKey, CngKeyBlobFormat.GenericPrivateBlob);
RSACng crypto = new RSACng(key);
var result = Decrypt(crypto, data);
return result;
}
public static byte[] Decrypt(RSACng aKey, byte[] data)
{
if (null == aKey)
return null;
var result = aKey.Decrypt(data, RSAEncryptionPadding.OaepSHA512);
return result;
}
}
}
I am aware of dpapi and how to do this using it. I don't want to use it for this, please don't point me in that direction. I am using CNG flavor of crypto to force C# use NCryptXYZ crypto calls and the desire is to secure the keys in TPM.
Ah, looking at your code again, you've made a goof.
RSACng rsaKey = new RSACng(cngKey)
{
KeySize = 2048
};
Setting the KeySize property on an RSACng does one of two things:
If get_KeySize == value, ignore the input, do nothing.
Else, detach from the current key and the next time the key is used, generate a new key of get_KeySize at the time.
So you're opening an existing key, then discarding it, and generating a new ephemeral key. (Which you could see by checking rsaKey.Key.Name, it won't match your input).
Presumably you did this as a way to create the key with the right size in the first place, but you're too late. The correct way is
CngKeyCreationParameters cng = new CngKeyCreationParameters
{
KeyUsage = CngKeyUsages.AllUsages,
KeyCreationOptions = CngKeyCreationOptions.MachineKey,
Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider,
Parameters =
{
new CngProperty("Length", BitConverter.GetBytes(2048), CngPropertyOptions.Persist),
},
};

Flutter how to use AES GCM 256 algorithm to cipher and decrypt using pointycastle package [closed]

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)

Flutter AES128 encryption not matching with java and angular AES encryption

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)),
);
}

Integrate Signed Hash into original PDF

I am integrating a signed hash in an original PDF, and I still have an error on the validity of the signature. it's say that a pdf has been changed after signing.
below the steps: I calculate the hash then I send it for signature and finally I get the hash sign and I proceed to the integration in the original pdf
package com.example.hashdocument;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;
import com.lexpersona.commons.utils.ProcessLauncher;
import java.io.*;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.*;
public class Test2 {
private static final String SRC = "B:/Hash-et-Reconstitution/tmp/Doc_test.pdf";
private static final String DST = "B:/Hash-et-Reconstitution/tmp/Doc_test_DST.pdf";
private static final String HASH = "B:/Hash-et-Reconstitution/tmp/Doc_test_hashed.hash";
private static final String PATH_BAT = "C:/Repo_LP7/lpcommand.bat";
private static final String PIN = "123456";
private static final String CERTIFICATE = "C:/lp7command/tools/certificate.p12";
private static final String SIGNED_HASH = "B:/Hash-et-Reconstitution/tmp/doc_signed.hash";
private static byte[] readFileToByteArray(File file){
FileInputStream fis = null;
byte[] bArray = new byte[(int) file.length()];
try{
fis = new FileInputStream(file);
fis.read(bArray);
fis.close();
}catch(IOException ioExp){
ioExp.printStackTrace();
}
return bArray;
}
public static File bytesToFile(byte[] fileByte,String pathFile) {
File file = new File(pathFile);
try {
OutputStream os = new FileOutputStream(file);
os.write(fileByte);
os.close();
} catch (Exception e) {
e.printStackTrace();
}
return file;
}
public static byte[] signDocument() throws IOException {
ProcessLauncher p = new ProcessLauncher(System.out, System.err);
int exec;
exec = p.exec("cmd.exe /c "+PATH_BAT+" <nul "+ SIGNED_HASH +" "+ PIN+" "
+ HASH+" "+CERTIFICATE, null, null);
byte[] signedHash = readFileToByteArray(new File(SIGNED_HASH));
return signedHash;
}
public static void main(String[] args) throws IOException, GeneralSecurityException, DocumentException {
PdfSignatureAppearance appearance = null;
ByteArrayOutputStream os = null;
String hash_document = "";
InputStream data = null;
int contentEstimated = 8192;
PdfReader reader = new PdfReader(SRC);
reader.unethicalreading = true;
reader.setAppendable(true);
int pdfPagenumber = 1;
pdfPagenumber = reader.getNumberOfPages(); // Sign on last page
os = new ByteArrayOutputStream ();
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', null, true);
Calendar cal = Calendar.getInstance();
appearance = stamper.getSignatureAppearance();
appearance.setSignDate(cal);
//appearance.setAcro6Layers(false);
appearance.setReason("Signature de contrat");
appearance.setLocation("MAROC");
appearance.setImage(null);
appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
Rectangle rect = new Rectangle(300, 300, 20, 20);
appearance.setVisibleSignature(rect, pdfPagenumber, null);
HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, new Integer(contentEstimated * 2 + 2));
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.setReason(appearance.getReason());
dic.setLocation(appearance.getLocation());
dic.setContact(appearance.getContact());
dic.setDate(new PdfDate(appearance.getSignDate()));
appearance.setCryptoDictionary(dic);
appearance.preClose(exc);
data = appearance.getRangeStream();
MessageDigest messageDigest;
String provider = null;
String hashAlgorithm = DigestAlgorithms.SHA256;
if (provider == null){
messageDigest = MessageDigest.getInstance(hashAlgorithm);
}else {
messageDigest = MessageDigest.getInstance(hashAlgorithm,provider);
}
int read = 0;
byte[] buff = new byte[contentEstimated];
while ((read = data.read(buff, 0, contentEstimated)) > 0)
{
messageDigest.update(buff,0,read);
}
byte[] hashDigest = messageDigest.digest();
byte[] documentHash = org.bouncycastle.util.encoders.Hex.encode(hashDigest);
//eSign Start
hash_document = new String(documentHash, "UTF-8");
System.out.println("Document Hash :"+hash_document);
PrintStream out = new PrintStream(new FileOutputStream(HASH));
out.print(hash_document);
byte[] hashdocumentByte = signDocument();
//////////////////// ADD SIGNED BYTES/HASH TO PDF DOCUMENT.
int contentEstimated2 = 8192;
byte[] paddedSig = new byte[contentEstimated2];
byte[] signedDocByte = hashdocumentByte;
System.arraycopy(signedDocByte, 0, paddedSig, 0, signedDocByte.length);
PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
appearance.close(dic2);
try(OutputStream outputStream = new FileOutputStream(DST)) {
os.writeTo(outputStream);
}
os.close();
}
}
what do you think abous this code : First i calculate the hash and send to server A for signature
PdfReader reader = new PdfReader(SRC);
FileOutputStream os = new FileOutputStream(TEMP);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");
//appearance.setCertificate(chain[0]);
ExternalSignatureContainer external = new
ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.signExternalContainer(appearance, external, 8192);
InputStream inp = appearance.getRangeStream();
BouncyCastleDigest digest = new BouncyCastleDigest();
byte[] hash = DigestAlgorithms.digest(inp, digest.getMessageDigest("SHA256"));
System.out.println("hash to sign : "+ hash);
bytesToFile(hash, HASH);
byte[] hashdocumentByte = TEST.signed_hash(hash);
PdfReader reader2 = new PdfReader(TEMP);
FileOutputStream os2 = new FileOutputStream(DEST);
ExternalSignatureContainer external2 = new
MyExternalSignatureContainer(hashdocumentByte,null);
MakeSignature.signDeferred(reader2, "sig", os2, external2);
And in the server B where i sign the hash :
BouncyCastleProvider providerBC = new BouncyCastleProvider();
Security.addProvider(providerBC);
// we load our private key from the key store
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream(CERTIFICATE), PIN);
String alias = (String)ks.aliases().nextElement();
Certificate[] chain = ks.getCertificateChain(alias);
PrivateKey pk = (PrivateKey) ks.getKey(alias, PIN);
PrivateKeySignature signature = new PrivateKeySignature(pk, "SHA256", null);
BouncyCastleDigest digest = new BouncyCastleDigest();
Calendar cal = Calendar.getInstance();
String hashAlgorithm = signature.getHashAlgorithm();
System.out.println(hashAlgorithm);
PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", null, digest, false);
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
byte[] extSignature = signature.sign(sh);
System.out.println(signature.getEncryptionAlgorithm());
sgn.setExternalDigest(extSignature, null, signature.getEncryptionAlgorithm());
return sgn.getEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);
Your signDocument method apparently does not accept a pre-calculated hash value but seems to calculate the hash of the data you give it, in your case the (lower case) hex presentation of the hash value you already calculated.
In your first example document you have these values (all hashes are SHA256 hashes):
Hash of the byte ranges to sign:
91A9F5EBC4F2ECEC819898824E00ECD9194C3E85E4410A3EFCF5193ED7739119
Hash of "91a9f5ebc4f2ecec819898824e00ecd9194c3e85e4410a3efcf5193ed7739119".getBytes():
2F37FE82F4F71770C2B33FB8787DE29627D7319EE77C6B5C48152F6E420A3242
Hash value signed by the embedded signature container:
2F37FE82F4F71770C2B33FB8787DE29627D7319EE77C6B5C48152F6E420A3242
And in your first example document you have these values (all hashes also are SHA256 hashes):
Hash of the byte ranges to sign:
79793C58489EB94A17C365445622B7F7945972A5A0BC4C93B6444BEDFFA5A5BB
Hash of "79793c58489eb94a17c365445622b7f7945972a5a0bc4c93b6444bedffa5a5bb".getBytes():
A8BCBC6F9619ECB950864BFDF41D1B5B7CD33D035AF95570C426CF4B0405949B
Hash value signed by the embedded signature container:
A8BCBC6F9619ECB950864BFDF41D1B5B7CD33D035AF95570C426CF4B0405949B
Thus, you have to correct your signDocument method to interpret the data correctly, or you have to give it a byte array containing the whole range stream to digest.