How to sign a string with rsa-sha256 by using private key? - c#

I have to sign a string with privateKey generated by OpenSSL on each API call, the privateKey is received from a database and changes for each user.
I've read that I should use RSACryptoService and add privateKey as parameter to achieve this. However I get error "Invalid data" in ImportParameters
This is the code:
string privateKey = "-----BEGIN RSA PRIVATE KEY-----
MIIJ....."
RSAParameters rsap = new RSAParameters
{
Modulus = Encoding.ASCII.GetBytes(privateKey)
};
rsa.ImportParameters(rsap);
byte[] encryptedData = rsa.Encrypt(Encoding.UTF8.GetBytes(StringToSign), false);
string base64Encrypted = Convert.ToBase64String(encryptedData);
In the documentation of the service it is stated that it is required to sign the string with RSA-SHA256. Here is the code to sign that string in Node.JS:
const signature = crypto.createSign('RSA-SHA256').update(string).sign(privateKey, 'base64')
However, I wasn't able to find anything similar in c#.

In the linked answer by #SmileDeveloper How to read a PEM RSA private key from .NET there was linked the source for OpenSSLKey and by reusing some of functions linked in the source code i managed to sign my string by using my PEM privateKey as string to sign another string.
The used code to do so is the following:
// encoding my privateKey from string to byte[] by using DecodeOpenSSLPrivateKey function from OpenSSLKey source code
byte[] pemprivatekey = DecodeOpenSSLPrivateKey(privateKey);
// enconding my string to sign in byte[]
byte[] byteSign = Encoding.ASCII.GetBytes(Sign);
// using DecodeRSAPrivateKey function from OpenSSLKey source code to get the RSACryptoServiceProvider with all needed parameters
var rsa = DecodeRSAPrivateKey(pemprivatekey);
// Signing my string with previously get RSACryptoServiceProvider in SHA256
var byteRSA = rsa.SignData(byteSign, CryptoConfig.MapNameToOID("SHA256"));
// As required by docs converting the signed string to base64
string Signature = Convert.ToBase64String(byteRSA);
While here are all the functions reused from OpenSSLKey used above:
public static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
{
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
// --------- Set up stream to decode the asn.1 encoded RSA private key ------
MemoryStream mem = new MemoryStream(privkey);
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
int elems = 0;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
if (twobytes != 0x0102) //version number
return null;
bt = binr.ReadByte();
if (bt != 0x00)
return null;
//------ all private key components are Integer sequences ----
elems = GetIntegerSize(binr);
MODULUS = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
E = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
D = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
P = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
Q = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DP = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DQ = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
IQ = binr.ReadBytes(elems);
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAparams = new RSAParameters();
RSAparams.Modulus = MODULUS;
RSAparams.Exponent = E;
RSAparams.D = D;
RSAparams.P = P;
RSAparams.Q = Q;
RSAparams.DP = DP;
RSAparams.DQ = DQ;
RSAparams.InverseQ = IQ;
RSA.ImportParameters(RSAparams);
return RSA;
}
catch (Exception)
{
return null;
}
finally
{
binr.Close();
}
}
public static byte[] DecodeOpenSSLPrivateKey(String instr)
{
const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----";
const String pemprivfooter = "-----END RSA PRIVATE KEY-----";
String pemstr = instr.Trim();
byte[] binkey;
if (!pemstr.StartsWith(pemprivheader) || !pemstr.EndsWith(pemprivfooter))
return null;
StringBuilder sb = new StringBuilder(pemstr);
sb.Replace(pemprivheader, ""); //remove headers/footers, if present
sb.Replace(pemprivfooter, "");
String pvkstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
try
{ // if there are no PEM encryption info lines, this is an UNencrypted PEM private key
binkey = Convert.FromBase64String(pvkstr);
return binkey;
}
catch (System.FormatException)
{ //if can't b64 decode, it must be an encrypted private key
//Console.WriteLine("Not an unencrypted OpenSSL PEM private key");
}
StringReader str = new StringReader(pvkstr);
//-------- read PEM encryption info. lines and extract salt -----
if (!str.ReadLine().StartsWith("Proc-Type: 4,ENCRYPTED"))
return null;
String saltline = str.ReadLine();
if (!saltline.StartsWith("DEK-Info: DES-EDE3-CBC,"))
return null;
String saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim();
byte[] salt = new byte[saltstr.Length / 2];
for (int i = 0; i < salt.Length; i++)
salt[i] = Convert.ToByte(saltstr.Substring(i * 2, 2), 16);
if (!(str.ReadLine() == ""))
return null;
//------ remaining b64 data is encrypted RSA key ----
String encryptedstr = str.ReadToEnd();
try
{ //should have b64 encrypted RSA key now
binkey = Convert.FromBase64String(encryptedstr);
}
catch (System.FormatException)
{ // bad b64 data.
return null;
}
//------ Get the 3DES 24 byte key using PDK used by OpenSSL ----
SecureString despswd = GetSecPswd("Enter password to derive 3DES key==>");
//Console.Write("\nEnter password to derive 3DES key: ");
//String pswd = Console.ReadLine();
byte[] deskey = GetOpenSSL3deskey(salt, despswd, 1, 2); // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes
if (deskey == null)
return null;
//showBytes("3DES key", deskey) ;
//------ Decrypt the encrypted 3des-encrypted RSA private key ------
byte[] rsakey = DecryptKey(binkey, deskey, salt); //OpenSSL uses salt value in PEM header also as 3DES IV
if (rsakey != null)
return rsakey; //we have a decrypted RSA private key
else
{
Console.WriteLine("Failed to decrypt RSA private key; probably wrong password.");
return null;
}
}
private static byte[] GetOpenSSL3deskey(byte[] salt, SecureString secpswd, int count, int miter)
{
IntPtr unmanagedPswd = IntPtr.Zero;
int HASHLENGTH = 16; //MD5 bytes
byte[] keymaterial = new byte[HASHLENGTH * miter]; //to store contatenated Mi hashed results
byte[] psbytes = new byte[secpswd.Length];
unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
//UTF8Encoding utf8 = new UTF8Encoding();
//byte[] psbytes = utf8.GetBytes(pswd);
// --- contatenate salt and pswd bytes into fixed data array ---
byte[] data00 = new byte[psbytes.Length + salt.Length];
Array.Copy(psbytes, data00, psbytes.Length); //copy the pswd bytes
Array.Copy(salt, 0, data00, psbytes.Length, salt.Length); //concatenate the salt bytes
// ---- do multi-hashing and contatenate results D1, D2 ... into keymaterial bytes ----
MD5 md5 = new MD5CryptoServiceProvider();
byte[] result = null;
byte[] hashtarget = new byte[HASHLENGTH + data00.Length]; //fixed length initial hashtarget
for (int j = 0; j < miter; j++)
{
// ---- Now hash consecutively for count times ------
if (j == 0)
result = data00; //initialize
else
{
Array.Copy(result, hashtarget, result.Length);
Array.Copy(data00, 0, hashtarget, result.Length, data00.Length);
result = hashtarget;
//Console.WriteLine("Updated new initial hash target:") ;
//showBytes(result) ;
}
for (int i = 0; i < count; i++)
result = md5.ComputeHash(result);
Array.Copy(result, 0, keymaterial, j * HASHLENGTH, result.Length); //contatenate to keymaterial
}
//showBytes("Final key material", keymaterial);
byte[] deskey = new byte[24];
Array.Copy(keymaterial, deskey, deskey.Length);
Array.Clear(psbytes, 0, psbytes.Length);
Array.Clear(data00, 0, data00.Length);
Array.Clear(result, 0, result.Length);
Array.Clear(hashtarget, 0, hashtarget.Length);
Array.Clear(keymaterial, 0, keymaterial.Length);
return deskey;
}
public static byte[] DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV)
{
MemoryStream memst = new MemoryStream();
TripleDES alg = TripleDES.Create();
alg.Key = desKey;
alg.IV = IV;
try
{
CryptoStream cs = new CryptoStream(memst, alg.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipherData, 0, cipherData.Length);
cs.Close();
}
catch (Exception exc)
{
Console.WriteLine(exc.Message);
return null;
}
byte[] decryptedData = memst.ToArray();
return decryptedData;
}
private static SecureString GetSecPswd(String prompt)
{
SecureString password = new SecureString();
Console.ForegroundColor = ConsoleColor.Gray;
Console.Write(prompt);
Console.ForegroundColor = ConsoleColor.Magenta;
while (true)
{
ConsoleKeyInfo cki = Console.ReadKey(true);
if (cki.Key == ConsoleKey.Enter)
{
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine();
return password;
}
else if (cki.Key == ConsoleKey.Backspace)
{
// remove the last asterisk from the screen...
if (password.Length > 0)
{
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
Console.Write(" ");
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
password.RemoveAt(password.Length - 1);
}
}
else if (cki.Key == ConsoleKey.Escape)
{
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine();
return password;
}
else if (Char.IsLetterOrDigit(cki.KeyChar) || Char.IsSymbol(cki.KeyChar))
{
if (password.Length < 20)
{
password.AppendChar(cki.KeyChar);
Console.Write("*");
}
else
{
Console.Beep();
}
}
else
{
Console.Beep();
}
}
}
private static int GetIntegerSize(BinaryReader binr)
{
byte bt = 0;
byte lowbyte = 0x00;
byte highbyte = 0x00;
int count = 0;
bt = binr.ReadByte();
if (bt != 0x02) //expect integer
return 0;
bt = binr.ReadByte();
if (bt == 0x81)
count = binr.ReadByte(); // data size in next byte
else
if (bt == 0x82)
{
highbyte = binr.ReadByte(); // data size in next 2 bytes
lowbyte = binr.ReadByte();
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
count = BitConverter.ToInt32(modint, 0);
}
else
{
count = bt; // we already have the data size
}
while (binr.ReadByte() == 0x00)
{ //remove high order zeros in data
count -= 1;
}
binr.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte
return count;
}

The problem is with this code:
RSAParameters rsap = new RSAParameters
{
Modulus = Encoding.ASCII.GetBytes(privateKey)
};
You are reading privateKey as a byte array encoded in a string. But your private key is not in that format, it is in PEM format. Check this answer: How to read a PEM RSA private key from .NET. for reading a PEM key in C#. Maybe you should decrypt the base64 in your pem string aswell.

Related

AES Encryption in C# and Decryption in JS using crypto-js

UseCase:
I am developing a service that is in C# and I have to send an encrypted string to a different system that is already coded in JS using CryptoJS which will be decrypting the string I am going to be sending.
The receiving system is not going to make any changes to its implementation. They have shared the JS code they are using to DECRYPT along with the required credentials that I would need for my encryption.
JS code of both Encrypt and Decrypt from the receiver
var message = "hello world!!!";
function generateKey(salt, passPhrase, keySize, iterationCount) {
const key = CryptoJS.PBKDF2(passPhrase, CryptoJS.enc.Hex.parse(salt), {
keySize: keySize / 32,
iterations: iterationCount
});
return key;
}
function Encrypt(cipherText) {
const salt = "DUMMYSALT-VALUE-of-Size-32CHARACTERS";
const iv ="DUMMYIV-VALUE-of-Size-32CHARACTERS";
const passPhrase = "SomeSecretPassPhrase";
const keySize = 128
const iterationCount = 1
const key = generateKey(salt, passPhrase, keySize, iterationCount);
const encrypted = CryptoJS.AES.encrypt(cipherText, key, {
iv: CryptoJS.enc.Hex.parse(iv)
});
return encrypted.ciphertext.toString();
}
function Decrypt(cipherText, pass = "SomeSecretPassPhrase") {
const salt = "DUMMYSALT-VALUE-of-Size-32CHARACTERS";
const iv ="DUMMYIV-VALUE-of-Size-32CHARACTERS";
const passPhrase = "SomeSecretPassPhrase";
const keySize = 128
const iterationCount = 1
const key = generateKey(salt, passPhrase, keySize, iterationCount);
const cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Hex.parse(cipherText)
});
const decrypted = CryptoJS.AES.decrypt(cipherParams, key, {
iv: CryptoJS.enc.Hex.parse(iv)
});
return decrypted.toString(CryptoJS.enc.Utf8);
}
var encrypted = Encrypt(message);
//equivalent to CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(message), key);
var decrypted = Decrypt(encrypted);
$('#1').text("Encrypted: "+encrypted.toString(CryptoJS.enc.Utf8));
$('#2').text("Decrypted: "+decrypted.toString(CryptoJS.enc.Utf8));
Output
Encrypted: 24a935f11a3e0b35dd6604cd7dbee292
Decrypted: hello world!!!
Question: While I try to port this over the C# and try to encrypt a string using the below, my encrypted value is different and also the JS code is unable to decrypt it.
my code to encrypt:
var MyKey = GenerateKey("DUMMYSALT-VALUE-of-Size-32CHARACTERS", "SomeSecretPassPhrase");
var EncryptedString = EncryptString(MyKey, "hello world!!!");
public byte[] GenerateKey(string saltVal, string passphrase)
{
byte[] salt = ConvertHexStringToByteArray("DUMMYSALT-VALUE-of-Size-32CHARACTERS");
int iterations = 1;
var rfc2898 = new Rfc2898DeriveBytes(passphrase, salt, iterations);
byte[] key = rfc2898.GetBytes(16);
return key;
}
public string EncryptString(byte[] key, string stringToEncrypt )
{
AesManaged aesCipher = new AesManaged();
aesCipher.KeySize = 128;
aesCipher.BlockSize = 128;
aesCipher.Mode = CipherMode.CBC;
aesCipher.Padding = PaddingMode.PKCS7;
aesCipher.Key = key;
aesCipher.IV = ConvertHexStringToByteArray("DUMMYPRESETIV-VALUE-of-Size-32CHARACTERS");
byte[] b = System.Text.Encoding.UTF8.GetBytes(stringToEncrypt);
ICryptoTransform encryptTransform = aesCipher.CreateEncryptor();
byte[] ctext = encryptTransform.TransformFinalBlock(b, 0, b.Length);
System.Console.WriteLine("IV:" + Convert.ToBase64String(aesCipher.IV));
System.Console.WriteLine("Cipher text: " + Convert.ToBase64String(ctext));
return Convert.ToBase64String(ctext);
}
public byte[] ConvertHexStringToByteArray(string hexString)
{
int NumberChars = hexString.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16);
return bytes;
}
I am not sure if am doing the correct equivalent of the JS method CryptoJS.enc.Hex.parse()
,

Encrypt a file in c# and decrypt in flutter

I have encrypted a file in c# code using RijndaelManaged which is available in System.Security.Cryptography. This file needs to be transferred to a mobile app developed using dart/flutter and I need it to be decrypted using dart code and present it to the user. How can this be done?
Below shown is the code to do the encryption in c#:
string password = keyPhrase; // Your Key Here
UnicodeEncoding UE = new UnicodeEncoding();
byte[] key = UE.GetBytes(password);
string cryptFile = outputFile;
FileStream fsCrypt = new FileStream(cryptFile, FileMode.Create);
RijndaelManaged RMCrypto = new RijndaelManaged();
CryptoStream cs = new CryptoStream(fsCrypt,
RMCrypto.CreateEncryptor(key, key),
CryptoStreamMode.Write);
FileStream fsIn = new FileStream(inputFile, FileMode.Open);
int data;
while ((data = fsIn.ReadByte()) != -1)
cs.WriteByte((byte)data);
fsIn.Close();
cs.Close();
fsCrypt.Close();
Thank you
I ran into the same problem. After many hours, a solution was found. My code is based on this question1 and question2 Code on C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
var m_strPassPhrase = "YYYYYYYYYYYYYYYYYYY";
var p_strSaltValue = "XXXXXXXXXXXXXXXXX";
var m_strPasswordIterations = 2;
var m_strInitVector = "ZZZZZZZZZZZZZZZZ";
var plainText = "myPassword";
var blockSize = 32;
var saltValueBytes = Encoding.ASCII.GetBytes(p_strSaltValue);
var password = new Rfc2898DeriveBytes(m_strPassPhrase, saltValueBytes, m_strPasswordIterations);
var keyBytes = password.GetBytes(blockSize);
var symmetricKey = new RijndaelManaged();
var initVectorBytes = Encoding.ASCII.GetBytes(m_strInitVector);
var encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);
var memoryStream = new System.IO.MemoryStream();
var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
var cipherTextBytes = memoryStream.ToArray();
memoryStream.Close();
cryptoStream.Close();
var cipherText = Convert.ToBase64String(cipherTextBytes);
Console.WriteLine(cipherText);
Console.WriteLine("\n end");
}
}
}
For flutter you can use pointycastle
Code on Dart(use decryptString and cryptString methods):
import 'dart:convert';
import 'package:pointycastle/block/aes_fast.dart';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';
import 'package:pointycastle/key_derivators/pbkdf2.dart';
import 'package:pointycastle/paddings/pkcs7.dart';
import 'package:pointycastle/pointycastle.dart';
const KEY_SIZE = 32; // 32 byte key for AES-256
const ITERATION_COUNT = 2;
const SALT = "XXXXXXXXXXXXXXXXX";
const INITIAL_VECTOR = "ZZZZZZZZZZZZZZZZ";
const PASS_PHRASE = "YYYYYYYYYYYYYYYYYYY";
Future<String> cryptString(String text) async {
String encryptedString = "";
final mStrPassPhrase = toUtf8(PASS_PHRASE);
encryptedString =
AesHelper.encrypt(mStrPassPhrase, toUtf8(text), mode: AesHelper.CBC_MODE);
return encryptedString;
}
Future<String> decryptString(String text) async {
String decryptedString = "";
final mStrPassPhrase = toUtf8(PASS_PHRASE);
decryptedString =
AesHelper.decrypt(mStrPassPhrase, toUtf8(text), mode: AesHelper.CBC_MODE);
return decryptedString;
}
///MARK: AesHelper class
class AesHelper {
static const CBC_MODE = 'CBC';
static const CFB_MODE = 'CFB';
static Uint8List deriveKey(dynamic password,
{String salt = '',
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);
Pbkdf2Parameters params =
new Pbkdf2Parameters(saltBytes, iterationCount, derivedKeyLength);
KeyDerivator keyDerivator =
new PBKDF2KeyDerivator(new HMac(new SHA1Digest(), 64));
keyDerivator.init(params);
return keyDerivator.process(password);
}
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}) {
String salt = toASCII(SALT);
Uint8List derivedKey = deriveKey(password, salt: salt);
KeyParameter keyParam = new KeyParameter(derivedKey);
BlockCipher aes = new AESFastEngine();
var ivStr = toASCII(INITIAL_VECTOR);
Uint8List iv =
createUint8ListFromString(ivStr);
BlockCipher cipher;
ParametersWithIV params = new ParametersWithIV(keyParam, iv);
switch (mode) {
case CBC_MODE:
cipher = new CBCBlockCipher(aes);
break;
case CFB_MODE:
cipher = new CFBBlockCipher(aes, aes.blockSize);
break;
default:
throw new ArgumentError('incorrect value of the "mode" parameter');
break;
}
cipher.init(true, params);
Uint8List textBytes = createUint8ListFromString(plaintext);
Uint8List paddedText = pad(textBytes, aes.blockSize);
Uint8List cipherBytes = _processBlocks(cipher, paddedText);
return base64.encode(cipherBytes);
}
static String decrypt(String password, String ciphertext,
{String mode = CBC_MODE}) {
String salt = toASCII(SALT);
Uint8List derivedKey = deriveKey(password, salt: salt);
KeyParameter keyParam = new KeyParameter(derivedKey);
BlockCipher aes = new AESFastEngine();
var ivStr = toASCII(INITIAL_VECTOR);
Uint8List iv = createUint8ListFromString(ivStr);
Uint8List cipherBytesFromEncode = base64.decode(ciphertext);
Uint8List cipherIvBytes =
new Uint8List(cipherBytesFromEncode.length + iv.length)
..setAll(0, iv)
..setAll(iv.length, cipherBytesFromEncode);
BlockCipher cipher;
ParametersWithIV params = new ParametersWithIV(keyParam, iv);
switch (mode) {
case CBC_MODE:
cipher = new CBCBlockCipher(aes);
break;
case CFB_MODE:
cipher = new CFBBlockCipher(aes, aes.blockSize);
break;
default:
throw new ArgumentError('incorrect value of the "mode" parameter');
break;
}
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);
Uint8List textBytes = unpad(paddedText);
return new String.fromCharCodes(textBytes);
}
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;
}
}
///MARK: HELPERS
Uint8List createUint8ListFromString(String s) {
Uint8List ret = Uint8List.fromList(s.codeUnits);
return ret;
}
String toUtf8(value) {
var encoded = utf8.encode(value);
var decoded = utf8.decode(encoded);
return decoded;
}
String toASCII(value) {
var encoded = ascii.encode(value);
var decoded = ascii.decode(encoded);
return decoded;
}
The default mode of Rijndael in .Net is 128 bit block size - compatible with AES. Unless you are using a non-standard block size, prefer .Net's AesManaged.
You haven't specified which padding or mode you are using. The .Net default seems to be CBC, so we'll assume that. It's not clear whether it defaults to a certain padding mode.
(Note that you are using the key both as the IV and the key. The IV should be unique for each invocation of the encryption routine. TLDR - the way you are using AesManaged is insecure - don't use this code in real life.)
Also, you are decoding the key from a string. The key length of AES must be exactly 128 or 256 bits (or one of the more unusual ones). Unless you have chosen your string well, it is unlikely to UTF-8 encode to an exact key length. Also, by using a string you are only using bytes in the key that happen to be characters. Typically, to use a string as a password you would convert it to a key using a key derivation algorithm (e.g. PBKDF2) rather than just UTF-8 encoding it.
With all that said, if your password is exactly 16 (or 32 long) and your file is an exact multiple of 16 bytes (if it is not, you need to decide how to pad it) you should be able to decrypt it like this:
import 'dart:convert';
import 'dart:io';
import 'package:pointycastle/export.dart';
main() async {
var key = utf8.encode('abcdefghijklmnop');
var cipher = CBCBlockCipher(AESFastEngine())
..init(false, ParametersWithIV<KeyParameter>(KeyParameter(key), key));
var cipherText = await File('encryptedFile').readAsBytes();
var plainText = cipher.process(cipherText);
await File('decryptedFile').writeAsBytes(plainText, flush: true);
}

AES GCM encryption and decryption: PHP VS C# BouncyCastle

I am currently working on transforming my C# AES-GCM cryptography code to PHP. However, after some research, the text encrypted by my PHP system cannot be decrypted by the C# one. I want to know if there is any difference from both codes:
C# with BouncyCastle:
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using System;
using System.IO;
using System.Text;
//the helper for all AES methods
public class AESHelper {
private const int KEY_BIT_SIZE = 256;
private const int MAC_BIT_SIZE = 128;
private const int NONCE_BIT_SIZE = 128;
private readonly SecureRandom random;
private static AESHelper instance;
public static AESHelper Instance //property of this class. Create an instance if it is not created yet
{
get
{
if (instance == null)
instance = new AESHelper();
return instance;
}
}
public AESHelper()
{
random = new SecureRandom();
}
//decrypt with strings
public string Decrypt(string message, string key, int nonSecretPayloadLength = 0)
{
if (string.IsNullOrEmpty(message))
throw new ArgumentException("Message required!", "message");
var decodedKey = Convert.FromBase64String(key);
var cipherText = Convert.FromBase64String(message);
var plainText = DecryptWithKey(cipherText, decodedKey, nonSecretPayloadLength);
return Encoding.UTF8.GetString(plainText);
}
//encrypt with strings
public string Encrypt(string text, string key, byte[] nonSecretPayload = null)
{
if (string.IsNullOrEmpty(text))
throw new ArgumentException("Text required!", "text");
var decodedKey = Convert.FromBase64String(key);
var plainText = Encoding.UTF8.GetBytes(text);
var cipherText = EncryptWithKey(plainText, decodedKey, nonSecretPayload);
return Convert.ToBase64String(cipherText);
}
//create new key
public string NewKey()
{
var key = new byte[KEY_BIT_SIZE / 8];
random.NextBytes(key);
return Convert.ToBase64String(key);
}
//decrypt with byte array
private byte[] DecryptWithKey(byte[] message, byte[] key, int nonSecretPayloadLength = 0)
{
if (key == null || key.Length != KEY_BIT_SIZE / 8)
throw new ArgumentException(String.Format("Key needs to be {0} bit!", KEY_BIT_SIZE), "key");
if (message == null || message.Length == 0)
throw new ArgumentException("Message required!", "message");
using (var cipherStream = new MemoryStream(message))
using (var cipherReader = new BinaryReader(cipherStream))
{
var nonSecretPayload = cipherReader.ReadBytes(nonSecretPayloadLength);
var nonce = cipherReader.ReadBytes(NONCE_BIT_SIZE / 8);
var cipher = new GcmBlockCipher(new AesEngine());
var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce, nonSecretPayload);
cipher.Init(false, parameters);
var cipherText = cipherReader.ReadBytes(message.Length - nonSecretPayloadLength - nonce.Length);
var plainText = new byte[cipher.GetOutputSize(cipherText.Length)];
try
{
var len = cipher.ProcessBytes(cipherText, 0, cipherText.Length, plainText, 0);
cipher.DoFinal(plainText, len);
}
catch (InvalidCipherTextException)
{
return null;
}
return plainText;
}
}
//encrypt with byte array
private byte[] EncryptWithKey(byte[] text, byte[] key, byte[] nonSecretPayload = null)
{
if (key == null || key.Length != KEY_BIT_SIZE / 8)
throw new ArgumentException(String.Format("Key needs to be {0} bit!", KEY_BIT_SIZE), "key");
nonSecretPayload = nonSecretPayload ?? new byte[] { };
var nonce = new byte[NONCE_BIT_SIZE / 8];
random.NextBytes(nonce, 0, nonce.Length);
var cipher = new GcmBlockCipher(new AesEngine());
var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce, nonSecretPayload);
cipher.Init(true, parameters);
var cipherText = new byte[cipher.GetOutputSize(text.Length)];
var len = cipher.ProcessBytes(text, 0, text.Length, cipherText, 0);
cipher.DoFinal(cipherText, len);
using (var combinedStream = new MemoryStream())
{
using (var binaryWriter = new BinaryWriter(combinedStream))
{
binaryWriter.Write(nonSecretPayload);
binaryWriter.Write(nonce);
binaryWriter.Write(cipherText);
}
return combinedStream.ToArray();
}
}
}
Here is the PHP system:
<?php
echo '<pre>';
$hash_string = 'qIANSOwtdfF4y5Yk33ZLE5s6KwKBAeu6qzJRG84Sjjo=';
echo "password : ";
var_dump($hash_string);
echo '<hr>';
$decode_string = base64_decode($hash_string);
$app_cc_aes_key = substr($decode_string, 0, 32);
$cipher = 'aes-256-gcm';
$iv_len = openssl_cipher_iv_length($cipher);
echo "app_cc_aes_key : ";
var_dump($app_cc_aes_key);
echo '<br>';
echo "cipher :";
var_dump($cipher);
echo '<hr>';
$data = '7bc9d6ae-982f-11e9-bc42-526af7764f64';
echo "data : {$data}";
echo '<hr>';
$tag_length = 16;
$iv = openssl_random_pseudo_bytes($iv_len);
$tag = "";
$encrypt = openssl_encrypt($data, $cipher, $app_cc_aes_key, OPENSSL_RAW_DATA, $iv, $tag, "", $tag_length);
$encrypt_text = base64_encode($iv.$tag.$encrypt);
echo "encrypt :";
var_dump($encrypt);
echo '<br>';
echo "encrypt_text :";
var_dump($encrypt_text);
echo '<hr>';
$decoded_text = base64_decode($encrypt_text);
$iv = substr($decoded_text, 0, $iv_len);
$tag = substr($decoded_text, $iv_len, $tag_length);
$ciphertext = substr($decoded_text, $iv_len + $tag_length);
$decrypt_text = openssl_decrypt($ciphertext, $cipher, $app_cc_aes_key, OPENSSL_RAW_DATA, $iv, $tag);
echo "decrypt_text : {$decrypt_text}";
echo '<hr>';
?>
Can anyone tell me if there is something missing or different in the PHP code that makes them done differently? Or if there is some internal difference between the PHP functions and the BouncyCastle functions that make them different?
In the C#-code, the data are concatenated in the following order during encryption:
nonSecretPyload nonce cipherText
Here cipherText consists of two parts, the encrypted message and the authentication tag. Appending the tag to the encrypted message is done automatically by GcmBlockCipher#DoFinal.
In the PHP-code, the data are concatenated in the following order during encryption:
$iv $tag $encrypt
Here $iv is the counterpart to nonce. In contrast to GcmBlockCipher#DoFinal, the PHP-method openssl_encrypt returns only the encrypted message ($encrypt). The authentication tag is returned in a separate variable (6th openssl_encrypt-parameter $tag). Therefore, $tag and $encrypt correspond in reverse order to cipherText. The additional authenticated data, i.e. the counterpart to nonSecretPyload are not considered in the PHP-code at all.
It is immediately apparent that the orders of the individual components in the two codes are different. This means that a message encrypted in the C#-code cannot be decrypted in the PHP-code (and vice versa). For this to be possible, the order in the PHP-code must be changed as follows:
$aad $iv $encrypt $tag
Here $aad is the counterpart to nonSecretPyload. The order (as well the consideration of the additional authenticated data) must be adapted both in the encryption part and in the decryption part.
In addition, different IV lengths are used: In the C#-code 16 bytes, in the PHP-code 12 bytes (the latter because openssl_cipher_iv_length('aes-256-gcm') returns 12), where 12 bytes is actually the recommended length. For compatibility, a uniform IV length must be used in both codes!

PHP's openssl_sign in C# using strings

Is there an easy way to do this in C# ?
openssl_sign($input, $output, $privateKey, OPENSSL_ALGO_SHA1)
$input is a string to be signed
$output - If the call was successful the signature is returned in this variable. It is a byte[]
$privateKey is a PEM private key (string)
Reference: http://php.net/manual/en/function.openssl-sign.php
I'm getting the input string from an XML, it is flattenned, like this:
<foo><bar1>qux1</bar1><bar2>qux2</bar2></foo>
I'm getting the privateKey string from the content of the <RSASK> tag in an XML:
<?xml version="1.0"?>
<bla1>foo</bla1>
<bla2>bar</bla2>
<RSASK>-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAMo8SSYPCvFBDgCqFv8o8UzznvcXO6FwUKmbDFogNIA5yQKTJc8i
VIYt6oXLfes+q5i+4oQWb6MJgTIZvcjmCL8CAQMCQQCG0ttutLH2K16rHA9UxfYz
TRSkuifA9YsbasdLmwCMAJVWbScsJcXdecXfWJJVcFTo5fFf6PIigPACHUZlSo8xb
AiEA9vjXy5u74OBS3ekQdMuKrUv5fi6z+EChhhCclVtFwhMCIQDRoMyppTxAmDDY
24QpFZQm9orgXgeqcg0vVoLCcaqUJQIhAKSl5TJn0pXq4elGCviHscjdUP7JzVAr
FuC1vbjng9a3AiEAi8CIcRjS1brLOz0Cxg5ixKRclZQFHEwIyjmsgaEcYsMCIEFT
oGduMC3vOMtKHEo8SAfDFeirfubo+FAZteQ0pyFF
-----END RSA PRIVATE KEY-----
</RSASK>
It wasn't easy at all:
First, I needed to convert the input string to a byte[]
ASCIIEncoding ByteConverter = new ASCIIEncoding();
byte[] inputBytes = ByteConverter.GetBytes(input);
Compute its SHA1 Hash
byte[] inputHash = new SHA1CryptoServiceProvider().ComputeHash(inputBytes);
Remove the beginning and end of the RSA PRIVATE KEY and convert it to byte[]
byte[] privateKeyBytes = Convert.FromBase64String(privateKey
.Replace("-----BEGIN RSA PRIVATE KEY-----", string.Empty)
.Replace("-----END RSA PRIVATE KEY-----", string.Empty)
.Replace("\n", string.Empty));
Then I had to create a RSACryptoServiceProvider from privateKeyBytes but I had to use a complicated class found on Internet:
RSACryptoServiceProvider rsa = RSAUtils.DecodeRSAPrivateKey(privateKeyBytes);
// ^
// Found on Internet
Then I finally could sign
byte[] output = rsa.SignHash(inputHash, "SHA1");
Code found on the Internet:
public class RSAUtils
{
public static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
{
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
// --------- Set up stream to decode the asn.1 encoded RSA private key ------
MemoryStream mem = new MemoryStream(privkey);
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
int elems = 0;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
if (twobytes != 0x0102) //version number
return null;
bt = binr.ReadByte();
if (bt != 0x00)
return null;
//------ all private key components are Integer sequences ----
elems = GetIntegerSize(binr);
MODULUS = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
E = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
D = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
P = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
Q = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DP = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DQ = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
IQ = binr.ReadBytes(elems);
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
CspParameters CspParameters = new CspParameters();
CspParameters.Flags = CspProviderFlags.UseMachineKeyStore;
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(1024, CspParameters);
RSAParameters RSAparams = new RSAParameters();
RSAparams.Modulus = MODULUS;
RSAparams.Exponent = E;
RSAparams.D = D;
RSAparams.P = P;
RSAparams.Q = Q;
RSAparams.DP = DP;
RSAparams.DQ = DQ;
RSAparams.InverseQ = IQ;
RSA.ImportParameters(RSAparams);
return RSA;
}
catch (Exception ex)
{
return null;
}
finally
{
binr.Close();
}
}
private static int GetIntegerSize(BinaryReader binr)
{
byte bt = 0;
byte lowbyte = 0x00;
byte highbyte = 0x00;
int count = 0;
bt = binr.ReadByte();
if (bt != 0x02) //expect integer
return 0;
bt = binr.ReadByte();
if (bt == 0x81)
count = binr.ReadByte(); // data size in next byte
else
if (bt == 0x82)
{
highbyte = binr.ReadByte(); // data size in next 2 bytes
lowbyte = binr.ReadByte();
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
count = BitConverter.ToInt32(modint, 0);
}
else
{
count = bt; // we already have the data size
}
while (binr.ReadByte() == 0x00)
{ //remove high order zeros in data
count -= 1;
}
binr.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte
return count;
}
}
Testing that it works.
Php Test
$input = "<DD><RE>97975000-5</RE><TD>33</TD><F>27</F><FE>2003-09-08</FE><RR>8414240-9</RR><RSR>JORGE GONZALEZ LTDA</RSR><MNT>502946</MNT><IT1>Cajon AFECTO</IT1><CAF version=\"1.0\"><DA><RE>97975000-5</RE><RS>RUT DE PRUEBA</RS><TD>33</TD><RNG><D>1</D><H>200</H></RNG><FA>2003-09-04</FA><RSAPK><M>0a4O6Kbx8Qj3K4iWSP4w7KneZYeJ+g/prihYtIEolKt3cykSxl1zO8vSXu397QhTmsX7SBEudTUx++2zDXBhZw==</M><E>Aw==</E></RSAPK><IDK>100</IDK></DA><FRMA algoritmo=\"SHA1withRSA\">g1AQX0sy8NJugX52k2hTJEZAE9Cuul6pqYBdFxj1N17umW7zG/hAavCALKByHzdYAfZ3LhGTXCai5zNxOo4lDQ==</FRMA></CAF><TSTED>2003-09-08T12:28:31</TSTED></DD>";
$privateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBANGuDuim8fEI9yuIlkj+MOyp3mWHifoP6a4oWLSBKJSrd3MpEsZdczvL0l7t/e0IU5rF+0gRLnU1Mfvtsw1wYWcCAQMCQQCLyV9FxKFLW09yWw7bVCCdxpRDr7FRX/EexZB4VhsNxm/vtJfDZyYle0Lfy42LlcsXxPm1w6Q6NnjuW+AeBy67AiEA7iMi5q5xjswqq+49RP55o//jqdZL/pC9rdnUKxsNRMMCIQDhaHdIctErN2hCIP9knS3+9zra4R+5jSXOvI+3xVhWjQIhAJ7CF0R0S7SIHHKe04NUURf/7RvkMqm108k74sdnXi3XAiEAlkWk2vc2HM+a1sCqQxNz/098ketqe7NuidMKeoOQObMCIQCkFAMS9IcPcMjk7zI2r/4EEW63PSXyN7MFAX7TYe25mw==\n-----END RSA PRIVATE KEY-----";
openssl_sign($input, $output, $privateKey, OPENSSL_ALGO_SHA1);
echo(base64_encode($output));
The PHP output is:
pqjXHHQLJmyFPMRvxScN7tYHvIsty0pqL2LLYaG43jMmnfiZfllLA0wb32lP+HBJ/tf8nziSeorvjlx410ZImw==
C# Test
string input = "<DD><RE>97975000-5</RE><TD>33</TD><F>27</F><FE>2003-09-08</FE><RR>8414240-9</RR><RSR>JORGE GONZALEZ LTDA</RSR><MNT>502946</MNT><IT1>Cajon AFECTO</IT1><CAF version=\"1.0\"><DA><RE>97975000-5</RE><RS>RUT DE PRUEBA</RS><TD>33</TD><RNG><D>1</D><H>200</H></RNG><FA>2003-09-04</FA><RSAPK><M>0a4O6Kbx8Qj3K4iWSP4w7KneZYeJ+g/prihYtIEolKt3cykSxl1zO8vSXu397QhTmsX7SBEudTUx++2zDXBhZw==</M><E>Aw==</E></RSAPK><IDK>100</IDK></DA><FRMA algoritmo=\"SHA1withRSA\">g1AQX0sy8NJugX52k2hTJEZAE9Cuul6pqYBdFxj1N17umW7zG/hAavCALKByHzdYAfZ3LhGTXCai5zNxOo4lDQ==</FRMA></CAF><TSTED>2003-09-08T12:28:31</TSTED></DD>";
string privateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBANGuDuim8fEI9yuIlkj+MOyp3mWHifoP6a4oWLSBKJSrd3MpEsZdczvL0l7t/e0IU5rF+0gRLnU1Mfvtsw1wYWcCAQMCQQCLyV9FxKFLW09yWw7bVCCdxpRDr7FRX/EexZB4VhsNxm/vtJfDZyYle0Lfy42LlcsXxPm1w6Q6NnjuW+AeBy67AiEA7iMi5q5xjswqq+49RP55o//jqdZL/pC9rdnUKxsNRMMCIQDhaHdIctErN2hCIP9knS3+9zra4R+5jSXOvI+3xVhWjQIhAJ7CF0R0S7SIHHKe04NUURf/7RvkMqm108k74sdnXi3XAiEAlkWk2vc2HM+a1sCqQxNz/098ketqe7NuidMKeoOQObMCIQCkFAMS9IcPcMjk7zI2r/4EEW63PSXyN7MFAX7TYe25mw==\n-----END RSA PRIVATE KEY-----";
ASCIIEncoding ByteConverter = new ASCIIEncoding();
byte[] inputBytes = ByteConverter.GetBytes(input);
byte[] inputHash = new SHA1CryptoServiceProvider().ComputeHash(inputBytes);
byte[] privateKeyBytes = Convert.FromBase64String(privateKey
.Replace("-----BEGIN RSA PRIVATE KEY-----", string.Empty)
.Replace("-----END RSA PRIVATE KEY-----", string.Empty)
.Replace("\n", string.Empty));
RSACryptoServiceProvider rsa = RSAUtils.DecodeRSAPrivateKey(privateKeyBytes);
byte[] output = rsa.SignHash(inputHash, "SHA1");
Console.WriteLine(Convert.ToBase64String(output));
The C# output is:
pqjXHHQLJmyFPMRvxScN7tYHvIsty0pqL2LLYaG43jMmnfiZfllLA0wb32lP+HBJ/tf8nziSeorvjlx410ZImw==

Encrypting in C# and Decrypting with Crypto++ does not work

Encryption :
public static byte[] EncryptAES(Message msg)
{
byte[] encText; // This will keep the encrypted text
byte[] encLength; // This will keep the length of the encrypted text
byte[] finalEncText = null; // This keeps the encLength + encText (#####[encText] / [encLength][encText])
// Building the plaintext message :
string plainText = msg.MessageCode.ToString();
if (msg.Parameters != null)
foreach (string parameter in msg.Parameters)
plainText += parameter;
// Encrypting the plaintext :
encText = EncryptAES(plainText);
string encLen = encText.Length.ToString();
string fittedEncLen = MessageSender.FitStringIntoSize(encLen, Globals.MESSAGE_LENGTH_LEN); // Fit the length of the encrypted text into a certain size
encLength = Encoding.ASCII.GetBytes(fittedEncLen); // convert the length into byte[]
finalEncText = new byte[encLength.Length + encText.Length];
System.Buffer.BlockCopy(encLength, 0, finalEncText, 0, encLength.Length);
System.Buffer.BlockCopy(encText, 0, finalEncText, encLength.Length, encText.Length); // Copy the byte arrays into the new byte array
return finalEncText;
}
private static byte[] EncryptAES(string text)
{
// This function encrypts a plaintext message using the aes key we have from the server
if (AesKey == null || IV == null) // If we dont have an aes key / iv, dont encrypt
return Encoding.ASCII.GetBytes(text);
byte[] encryptedText;
try
{
Aes aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.Zeros;
aes.Key = Encoding.ASCII.GetBytes(AesKey);
aes.IV = Encoding.ASCII.GetBytes(IV);
ICryptoTransform cryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using (MemoryStream memStream = new MemoryStream())
{
using (CryptoStream crypotStream = new CryptoStream(memStream, cryptor, CryptoStreamMode.Write))
{
using (StreamWriter writerStream = new StreamWriter(crypotStream))
{
writerStream.Write(text);
}
encryptedText = memStream.ToArray();
}
}
aes.Dispose();
}
catch
{
// In case of an error while encrypting, dont encrypt
encryptedText = Encoding.ASCII.GetBytes(text);
}
return encryptedText;
}
[The added fittedEncLen is basically a prefix of fixed length of 5 chars, that contains the length of the encrypted message following it, before decrypting the server reads those 5 chars and then it decrypts the encrypted part]
Sending the message to the server [TCPClient] [C#] :
public int Send(Message message)
{
/*
* Encrpyts the message and then sends it to the network stream.
*
* Return code:
* 0 on success.
* -1 on failure.
*/
byte[] msg = Cryptography.EncryptAES(message); // Encrypt the message
// Sending message
try
{
this._networkStream.Write(msg, 0, msg.Length);
this._networkStream.Flush();
}
catch
{
return -1;
}
return 0;
}
Receiving [C++] :
wstring Helper::getWideStringPartFromSocket(SOCKET sc, int bytesNum)
{
// This function reads the message from the socket, using wide string
std::wstringstream cls;
cls << getPartFromSocket(sc, bytesNum, 0);
return cls.str();
}
char* Helper::getPartFromSocket(SOCKET sc, int bytesNum, int flags)
{
if (bytesNum == 0)
return "";
char* data = new char[bytesNum + 1];
int res = recv(sc, data, bytesNum, flags);
if (res == INVALID_SOCKET)
{
string s = "Error while recieving from socket: ";
s += to_string(sc);
throw exception(s.c_str());
}
data[bytesNum] = 0;
return data;
}
BufferedString* Helper::makeBufferedString(SOCKET sc)
{
/*
The socket contains <length of encrypted message (unencrypted)> <encrypted message>.
This function will read the length of the unencrypted message, read
the encrypted message, decrypt it, store it in a BufferedString
object and return the object.
Length of length number: MESSAGE_LENGTH_LEN.
*/
int sizeOfMessage = Helper::getIntPartFromSocket(sc, MESSAGE_LENGTH_LEN);
if (sizeOfMessage == 0)
return NULL;
wstring wideString = getWideStringPartFromSocket(sc, sizeOfMessage);
string decrypted = "";
if (wideString.length() < sizeOfMessage)
{
std::wstringstream cls;
cls << wideString;
cls << getWideStringPartFromSocket(sc, sizeOfMessage - wideString.length());
wideString = cls.str();
}
SocketEncryptionKeychain* keyChain = SocketEncryptionKeychain::getKeychain(sc);
if (keyChain != nullptr) // If the socket has a keychain, decrypt the message
decrypted = Cryptography::decryptAES(wideString, keyChain->getKey(), keyChain->getIV()); // Try to decrypt the message
else // If the keychain is null, just convert the widestring to a string
decrypted = wideStringToString(wideString);
return new BufferedString(decrypted);
}
SocketEncryptionKeychain basically contains the AES Key and IV for each socket
BufferedString is a class that contains the string, and you can read from it like you read from a socket [its a buffer that once you read from it, what you read is deleted] [basically a string buffer, nothing special]
Decrypting [C++]:
string Cryptography::decryptAES(wstring cipherText, byte aesKey[], byte iv[])
{
if (aesKey == nullptr || iv == nullptr) // If the key or iv are null, dont decrypt
return Helper::wideStringToString(cipherText);
string plaintext;
try
{
// Decrypt :
byte* cipher = wideStringToByteArray(cipherText); // Convert the wide string to byte*
CryptoPP::AES::Decryption aesDecryption(aesKey, 32);
CryptoPP::CBC_Mode_ExternalCipher::Decryption ecbDecryption(aesDecryption, iv);
CryptoPP::StreamTransformationFilter stfDecryptor(ecbDecryption, new CryptoPP::StringSink(plaintext), StreamTransformationFilter::ZEROS_PADDING);
stfDecryptor.Put(cipher, cipherText.length());
stfDecryptor.MessageEnd();
Helper::safeDelete(cipher);
}
catch (CryptoPP::InvalidCiphertext& ex)
{
// In case of an error don't decrypt
plaintext = Helper::wideStringToString(cipherText);
}
return plaintext;
}
byte* Cryptography::wideStringToByteArray(wstring text)
{
// This function translates the wstring into a byte*
byte* bytes = new byte[text.length()]; // Convert the wstring to byte*
for (int i = 0; i < text.length(); i++)
{
bytes[i] = text[i];
}
return bytes;
}
[Helper::safeDelete is a function that just deletes the pointer and sets it as null]
The decryption only fails once in a while
You may have other problems, but here's one:
using (CryptoStream crypotStream = new CryptoStream(memStream, cryptor, CryptoStreamMode.Write))
{
using (StreamWriter writerStream = new StreamWriter(crypotStream))
{
writerStream.Write(text);
}
encryptedText = memStream.ToArray();
}
You drained the CryptoStream's output before telling the CryptoStream it was done. So you've possibly lost up to 16 bytes.
You need to either:
call FlushFinalBlock() on crypotStream(sic).
don't call memStream.ToArray() until after the using for the CryptoStream has exited.
So, the problem was while parsing the char* to wstring in the function
The problem in this function is the way I parse it:
wstring Helper::getWideStringPartFromSocket(SOCKET sc, int bytesNum)
{
// This function reads the message from the socket, using wide string
std::wstringstream cls;
cls << getPartFromSocket(sc, bytesNum, 0);
return cls.str();
}
I used a wstringstream, and the encrypted text can sometimes contain null-terminating character.
So instead of using a wstringstream I used this:
wstring Helper::getWideStringPartFromSocket(SOCKET sc, int bytesNum)
{
// This function reads the message from the socket, using wide string
char* readBuffer = getPartFromSocket(sc, bytesNum, 0);
return wstring(&readBuffer[0], &readBuffer[bytesNum]);
}
and then it does not cut the message at null-character

Categories