2

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.

4
  • 1
    Two 'quick shots': On PHP-side you provide a 32 char long key to the encryption function 'aes-128-cbc'. When using the '128' key any longer key then 16 chars will get truncated, so you should truncate the key on PHP AND on C# side [shorten your substring parameters to 16] as well. Secondly: PHP has PKCS7Padding as default so you need it on C# side as well [don't know the default there]. BTW: sending the key & iv along with the encrypted message means sending cleartext and is UNSECURE. Commented Sep 2, 2020 at 15:13
  • 2
    In addition: openssl_encrypt encodes the data with Base64 by default, i.e. the third part of the data (the actual ciphertext) must be Base64 decoded: var encBytes = Convert.FromBase64String(encString);. AesCryptoServiceProvider uses PKCS7 by default, i.e. the padding is OK. Commented Sep 2, 2020 at 15:29
  • I reduced the key length to 16 bytes on both sides but I get the same message and when I use Base64 decoding it throws an Exception that says: The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters. Commented Sep 2, 2020 at 15:41
  • I got it so I used 16 bytes for the key and for the vector and encrypted message that I encoded in base 64, then just decoded the base64 message and now the decryption works. Thank you guys. Commented Sep 2, 2020 at 15:53

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

To support your answer I have use base64_encode on PHP side only for ciphertext and now it works.
@spzvtbg - After your last comment the answer was actually not necessary anymore, but I had posted it shortly before. But it can't hurt either ;-) Glad that it works.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.