0

I'm doing some encryption methods in my client/server application using C# .NET Framework and PHP. The encryption method is AES-256-GCM where it can be very simple in PHP. The .NET code I copy from here with a little modification. The .NET version resulted different value.

In PHP version I can write like this

<?php
   
$dynamicSecretKey = "2192B39425BBD08B6E8E61C5D1F1BC9F428FC569FBC6F78C0BC48FCCDB0F42AE";
$iv = "E1E592E87225847C11D948684F3B070D";
$plainText = 'Test only. PHP and C# encryption';
$tag = null;

$content = openssl_encrypt($plainText, 'AES-256-GCM', $dynamicSecretKey, OPENSSL_RAW_DATA,$iv, $tag);

echo bin2hex($tag)."\n";

$cipherText = base64_encode($content.$tag);

echo $cipherText."\n";

$cipherTextWithTag = base64_decode($cipherText);
echo bin2hex($cipherTextWithTag);
echo "\n";

$tag = substr($cipherTextWithTag , -16, 16);
$cipherText = substr($cipherTextWithTag, 0, - 16);

$decryptedText = openssl_decrypt($cipherText, 'AES-256-GCM', $dynamicSecretKey, OPENSSL_RAW_DATA, $iv,$tag);

echo $decryptedText;

?>

In C# I try to make it the same

private RunTest()
{
    //$dynamicSecretKey = "2192B39425BBD08B6E8E61C5D1F1BC9F428FC569FBC6F78C0BC48FCCDB0F42AE";
    string dynamicSecretKey = "3777217A25432A462D4A614E645267556B58703272357538782F413F4428472B";
    //$iv = "E1E592E87225847C11D948684F3B070D";
    string iv = "E1E592E87225847C11D94868";
    //$plainText = 'Test only. PHP and C# encryption';
    string plainText = "Test only. PHP and C# encryption";
    //$tag = null;
    byte[] tag = null;

    //$content = openssl_encrypt($plainText, 'AES-256-GCM', $dynamicSecretKey, OPENSSL_RAW_DATA,$iv, $tag);
    var content = AesGcm.Openssl_Encrypt(plainText, dynamicSecretKey, iv);
    //$cipherText = base64_encode($content.$tag);
    string cipherText = AesGcm.Base64Encode(AesGcm.Combine(content.cipherText, content.tag));
    //bool same = cipherText.Equals("+u3YJpiHMy6p0mDxlquYq7utAOk7r6ppKGcnYAVD9iMPSe7fhWNNoMNto6Z6p0tM");

    //$cipherTextWithTag = base64_decode($cipherText);
    string cipherTextWithTag = AesGcm.Base64Decode(cipherText); //RESULTED UNREAD CHARACTERS

    //$tag = substr($cipherTextWithTag, -16, 16);
    //tag = cipherTextWithTag.Substring(cipherTextWithTag.Length - 16, 16);
    //$cipherText = substr($cipherTextWithTag, 0, -16);
    //cipherText = cipherTextWithTag.Substring(0, cipherTextWithTag.Length - 16);
    string decryptedText = AesGcm.Openssl_Decrypt(cipherText, dynamicSecretKey, iv, AesGcm.ByteArrayToHex(content.tag)); //ERROR
}

internal static class AesGcm
{
    internal static (byte[] ciphertext, byte[] nonce, byte[] tag) Encrypt(string plaintext, byte[] key, byte[] iv)
    {
        const int nonceLength = 12; // in bytes
        const int tagLenth = 16; // in bytes

        var plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
        var bcCiphertext = new byte[plaintextBytes.Length + tagLenth];

        var cipher = new GcmBlockCipher(new AesEngine());
        var parameters = new AeadParameters(new KeyParameter(key), tagLenth * 8, iv);
        cipher.Init(true, parameters);

        var offset = cipher.ProcessBytes(plaintextBytes, 0, plaintextBytes.Length, bcCiphertext, 0);
        cipher.DoFinal(bcCiphertext, offset);

        // Bouncy Castle includes the authentication tag in the ciphertext
        var ciphertext = new byte[plaintextBytes.Length];
        var tag = new byte[tagLenth];
        Buffer.BlockCopy(bcCiphertext, 0, ciphertext, 0, plaintextBytes.Length);
        Buffer.BlockCopy(bcCiphertext, plaintextBytes.Length, tag, 0, tagLenth);

        return (ciphertext, iv, tag);
    }

    private static string Decrypt(byte[] ciphertext, byte[] key, byte[] iv, byte[] tag)
    {
        var plaintextBytes = new byte[ciphertext.Length];

        var cipher = new GcmBlockCipher(new AesEngine());
        var parameters = new AeadParameters(new KeyParameter(key), tag.Length * 8, iv);
        cipher.Init(false, parameters);

        var bcCiphertext = ciphertext.Concat(tag).ToArray();

        var offset = cipher.ProcessBytes(bcCiphertext, 0, bcCiphertext.Length, plaintextBytes, 0);
        cipher.DoFinal(plaintextBytes, offset);

        return Encoding.UTF8.GetString(plaintextBytes);
    }

    internal static (byte[] cipherText, byte[] tag) Openssl_Encrypt(string plaintext, string key, string iv)
    {
        var plainTextBytes = Encoding.UTF8.GetBytes(plaintext);
        var bKey = HexToByteArray(key);
        var bIv = HexToByteArray(iv);
        var result = Encrypt(plaintext, bKey, bIv);
        return (result.ciphertext, result.tag);
    }

    internal static string Openssl_Decrypt(string ciphertext, string key, string iv, string tag)
    {
        var bKey = HexToByteArray(key);
        var bIv = HexToByteArray(iv);
        var bTag = HexToByteArray(tag);
        var bCipherText = Convert.FromBase64String(ciphertext);

        var result = Decrypt(bCipherText, bKey, bIv, bTag);
        return Base64Decode(result);
    }

    public static byte[] HexToByteArray(string hex)
    {
        return Enumerable.Range(0, hex.Length)
                         .Where(x => x % 2 == 0)
                         .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                         .ToArray();
    }

    public static string ByteArrayToHex(byte[] ba) => BitConverter.ToString(ba).Replace("-", "").ToLower();

    public static string Base64Encode(string plainText)
    {
        var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
        return Convert.ToBase64String(plainTextBytes);
    }

    public static string Base64Decode(string base64EncodedData)
    {
        var base64EncodedBytes = Convert.FromBase64String(base64EncodedData);
        return Encoding.UTF8.GetString(base64EncodedBytes);
    }

    public static string Base64Encode(byte[] plainText)
    {
        return Convert.ToBase64String(plainText);
    }

    public static string Base64Decode(byte[] base64EncodedData)
    {
        return Encoding.UTF8.GetString(base64EncodedData);
    }

    public static byte[] Combine(params byte[][] arrays)
    {
        byte[] rv = new byte[arrays.Sum(a => a.Length)];
        int offset = 0;
        foreach (byte[] array in arrays)
        {
            System.Buffer.BlockCopy(array, 0, rv, offset, array.Length);
            offset += array.Length;
        }
        return rv;
    }
}

EDIT:

All methods are included.

This question is not duplicate from here

derodevil
  • 811
  • 1
  • 11
  • 37
  • What errors do you get? – CodeCaster Dec 11 '21 at 06:25
  • @CodeCaster `'Key length not 128/192/256 bits.'` – derodevil Dec 11 '21 at 06:31
  • The Utf-8 encoding in C# gives a 64 bytes key. AES-256 however requires a 32 bytes key, hence the error message. To fix the issue, the key has to be shortened explicitly. In PHP this is not necessary, because here the key is cut implicitly. Alternatively, key and IV can be hex decoded in both codes (which is probably intended anyway). This results in a 32 bytes key and 16 bytes IV. Note that the recommended IV size for GCM is 12 bytes and that `AesGcm` _only_ supports this size (unlike _BouncyCaste_). – Topaco Dec 11 '21 at 15:23
  • @Topaco I've edited my question. It's because I use `Encoding.UTF8.GetBytes` instead of converting hex string to byte array. The error is now gone but the result is different from the PHP version. – derodevil Dec 11 '21 at 16:57
  • The definition of `HexToByteArray()` is missing. Did you hex decode key and IV for the comparison in the PHP code as well? Please post the ciphertexts you get for the PHP and the C# code. – Topaco Dec 11 '21 at 17:44
  • cipherText C# is `9ATz2yGEnD4DJ9Al92dUZSVDjd30P+cTIZvdTW5JyJ4=` cipherText PHP is `+u3YJpiHMy6p0mDxlquYq7utAOk7r6ppKGcnYAVD9iMPSe7fhWNNoMNto6Z6p0tM` – derodevil Dec 11 '21 at 17:45
  • If you hex decode key and IV in the C# code, then you must do the same in the PHP code (with `hex2bin()`), which is obviously not happening at the moment: `+u3...tM` is the ciphertext of the posted PHP code still performing a UTF-8 encoding for key and IV. – Topaco Dec 11 '21 at 18:00
  • I cannot reproduce `9A...J4=` with the C# code. Maybe a bug in `HexToByteArray()`, so post the implementation of this method. – Topaco Dec 11 '21 at 18:05
  • I've included all methods and update the question – derodevil Dec 11 '21 at 18:51
  • If you use your new values for key and IV and hex decode both, the PHP code produces the ciphertext `9A...J4=`, which the C# code also gives (according to your comment), see [here](https://paiza.io/projects/xvouqtUSR0w-1mqR3V3RZQ). – Topaco Dec 12 '21 at 13:23

1 Answers1

0

The first weird thing you do is this at the end of Encrypt:

// BlockCopy(Array src, int srcOffset, Array dst, int dstOffset, int count)
Buffer.BlockCopy(bcCiphertext, 0, ciphertext, 0, plaintextBytes.Length);
Buffer.BlockCopy(bcCiphertext, plaintextBytes.Length, tag, 0, tagLenth);

You're copying bytes into the ciphertext and tag (local array)!?

Another weird thing:

// new AeadParameters(KeyParameter key, int macSize, byte[] nonce)
var parameters = new AeadParameters(new KeyParameter(key), tagLenth * 8, iv);

You're writing the IV Sector into the Nonce Field?!

Charles
  • 2,721
  • 1
  • 9
  • 15