Symmetric encryption between C# and PHP

Viewed 385

At the moment I'm stucked on the symmetrical encryption between PHP and C#, no matter how I rewrite my script I always get either an error message or the encrypted text even more encrypted. I have been trying allmost every suggestion that is offered on the internet for 3 days without success, I hope someone can help me to finish the encryption and decryption process. You can find examples of my scripts below.

This is how I build and send the message containing the Key, IV and Encrypted text:

function alphaNumeric () : string {

    $number = rand(32, 127);
    return $number >= 48 && $number <= 57 
        || $number >= 65 && $number <= 90 
        || $number >= 97 && $number <= 122
        ? chr($number)
        : alphaNumeric();
}

function randomBytes (int $length, string $byteString = '') : string {

    return $length > 0
        ? randomBytes($length - 1, $byteString.alphaNumeric())
        : $byteString;
}

$key        = randomBytes(16);
$iv         = randomBytes(16);
$data       = 'This text should be encrypted in PHP and decrypted in C#!';
$encrypted  = openssl_encrypt($data, 'aes-128-cbc', $key, 1, $iv);
$message    = $key.$iv.$encrypted;

file_put_contents('message.txt', $message);
echo $message;
die;

this is what I send from PHP and what I receive again in C#:

UeWeXUAnu98RKTkMiBGLWpMNy4CRKJErOqTTUfJWrtXziFTELGG+647lw/XT846dj8tlNMITLVBg2cKS3dFINeKot4zlb+gVpfq4oIb/M3a8n3a9XWaeIOrHpNedZmMrYiZoCQ==

UeWeXUAnu98RKTkMiBGLWpMNy4CRKJErOqTTUfJWrtXziFTELGG+647lw/XT846dj8tlNMITLVBg2cKS3dFINeKot4zlb+gVpfq4oIb/M3a8n3a9XWaeIOrHpNedZmMrYiZoCQ==

and at the end this is the c# code which should decrypt the message:

    public static void Main()
    {
        var client = new HttpClient();
        var requestUri = "http://localhost/message.php";

        while (Console.ReadLine() == string.Empty)
        {
            var response = client.GetAsync(requestUri).Result;

            if (!response.IsSuccessStatusCode)
            {
                continue;
            }

            var content = response.Content.ReadAsStringAsync().Result;

            if (string.IsNullOrWhiteSpace(content) || content.Length < 48)
            {
                continue;
            }

            File.WriteAllText("../../../message.txt", content);

            var keyString = content.Substring(0, 16);
            var keyBytes = Encoding.UTF8.GetBytes(keyString);

            var ivString = content.Substring(16, 16);
            var ivBytes = Encoding.UTF8.GetBytes(ivString);

            var encString = content.Substring(32);
            var encBytes = Encoding.UTF8.GetBytes(encString);

            Console.WriteLine($"{keyBytes.Length}: {keyString}");
            Console.WriteLine($"{ivBytes.Length}: {ivString}");
            Console.WriteLine($"{encBytes.Length}: {encString}");

            try
            {
                var plainText = Decrypt(encBytes, keyBytes, ivBytes);

                Console.WriteLine(plainText);
            }
            catch (Exception e)
            {
                Console.WriteLine($"Error: {e.Message}");
            }
        }
    }

    static string Decrypt(byte[] encrypted, byte[] key, byte[] iv)
    {
        using var alg = AesCryptoServiceProvider.Create();

        //alg.IV = iv;
        //alg.Key = key;
        //alg.KeySize = 128;
        //alg.BlockSize = 256;
        //alg.Mode = CipherMode.CBC;
        alg.Padding = PaddingMode.PKCS7;

        var decryptor = alg.CreateDecryptor(key, iv);
        using var ms = new MemoryStream(encrypted);
        using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
        using var sr = new StreamReader(cs);

        return sr.ReadToEnd();
    }

this is the message I'm currently getting: enter image description here Thanks in advance.

1 Answers

There are the following issues in the C# code:

  • In the PHP code a 32 bytes key is generated, but because of the specified AES-128 (aes-128-cbc), only the first 16 bytes are taken into account. Accordingly, in the C# code only the first 16 bytes of the key may be considered and not the full 32 bytes (see first comment).
  • In the PHP code openssl_encrypt returns the ciphertext Base64 encoded by default, so this part of the ciphertext must be Base64 decoded in the C# code and not UTF8 encoded (see second comment).
  • AesCryptoServiceProvider uses CBC mode and PKCS7 padding by default, so both do not need to be explicitly specified in the C# code.

The following C# code decrypts the ciphertext encrypted with the PHP code:

string content = "UeWeXUAnu98RKTkMiBGLWpMNy4CRKJErOqTTUfJWrtXziFTELGG+647lw/XT846dj8tlNMITLVBg2cKS3dFINeKot4zlb+gVpfq4oIb/M3a8n3a9XWaeIOrHpNedZmMrYiZoCQ==";

var keyString = content.Substring(0, 16);
var keyBytes = Encoding.UTF8.GetBytes(keyString);

var ivString = content.Substring(32, 16);
var ivBytes = Encoding.UTF8.GetBytes(ivString);

var encString = content.Substring(48);
var encBytes = Convert.FromBase64String(encString);

using var alg = AesCryptoServiceProvider.Create();

alg.IV = ivBytes;
alg.Key = keyBytes;

var decryptor = alg.CreateDecryptor(keyBytes, ivBytes);
using var ms = new MemoryStream(encBytes);
using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
using var sr = new StreamReader(cs);

string decrypted = sr.ReadToEnd();
Console.WriteLine(decrypted);

Please consider with regard to the PHP-Code, that it is inconsistent when a 32 bytes key is generated for AES-128. Instead, a 16 bytes key should be generated. Alternatively, you can switch to AES-256 (aes-256-cbc). And also keep in mind the hint in the first comment: A key must generally not be sent with the ciphertext, because any attacker could easily decrypt the data.

Related