0

I'm using openssl to encrypt/decrypt strings in PHP:

function str_encryptaesgcm($plaintext, $password, $encoding = null) {
    $aes = array("key" => substr(hash("sha256", $password, true), 0, 32), "cipher" => "aes-256-gcm", "iv" => openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-gcm")));
    $encryptedstring = openssl_encrypt($plaintext, $aes["cipher"], $aes["key"], OPENSSL_RAW_DATA, $aes["iv"], $aes["tag"], "", 16);
    return $encoding == "hex" ? bin2hex($aes["iv"].$encryptedstring.$aes["tag"]) : ($encoding == "base64" ? base64_encode($aes["iv"].$encryptedstring.$aes["tag"]) : $aes["iv"].$encryptedstring.$aes["tag"]);
}

function str_decryptaesgcm($encryptedstring, $password, $encoding = null) {
    $encryptedstring = $encoding == "hex" ? hex2bin($encryptedstring) : ($encoding == "base64" ? base64_decode($encryptedstring) : $encryptedstring);
    $aes = array("key" => substr(hash("sha256", $password, true), 0, 32), "cipher" => "aes-256-gcm", "ivlength" => openssl_cipher_iv_length("aes-256-gcm"), "iv" => substr($encryptedstring, 0, openssl_cipher_iv_length("aes-256-gcm")), "tag" => substr($encryptedstring, -16));
    return openssl_decrypt(substr($encryptedstring, $aes["ivlength"], -16), $aes["cipher"], $aes["key"], OPENSSL_RAW_DATA, $aes["iv"], $aes["tag"]);
}

and everything works correctly, in fact I get:

$text = "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...";
$pass = "A random password to encrypt";
$enc = str_encryptaesgcm($text, $pass, "base64"); // OUTPUT: TrbntVEj8GEGeLE6ZYJnDIXnqSese5biWn604NePb2r6jsFhuzJsNHnN2GCizrGfhP4W39tahrGj0tORxvUbDpGT76WHr/v2wmnHHHiDGyjeKlWLu9/gfeualYvhsNF/N9inSpqxE2lQ+/vwpUJKYJw3bfo7DoGPDNk=
$dec = str_decryptaesgcm($enc, $pass, "base64"); // OUTPUT: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...

Unfortunately though, I need to decrypt the string from C#, therefore I'm using BouncyCastle to do this, and this is the class that I'm using:

using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace PoGORaidEngine.Crypto
{
    internal static class AESGCM
    {
        private const int KEY_BIT_SIZE = 256;
        private const int MAC_BIT_SIZE = 128;
        private const int NONCE_BIT_SIZE = 96; // 12 bytes (openssl)

        internal static string DecryptString(string EncryptedString, string Password)
        {
            if (string.IsNullOrEmpty(EncryptedString))
                return string.Empty;

            byte[] Key = Encoding.UTF8.GetBytes(SHA256String(Password).Substring(0, 32));
            byte[] EncryptedData = Convert.FromBase64String(EncryptedString);

            if (Key == null || Key.Length != KEY_BIT_SIZE / 8)
                throw new ArgumentException(string.Format("Key needs to be {0} bit.", KEY_BIT_SIZE), "Key");

            using (MemoryStream MStream = new MemoryStream(EncryptedData))
            using (BinaryReader Binary = new BinaryReader(MStream))
            {
                byte[] IV = Binary.ReadBytes(NONCE_BIT_SIZE / 8);
                GcmBlockCipher Cipher = new GcmBlockCipher(new AesEngine());
                Cipher.Init(false, new AeadParameters(new KeyParameter(Key), MAC_BIT_SIZE, IV));

                byte[] CipherText = Binary.ReadBytes(EncryptedData.Length - IV.Length);
                byte[] PlainText = new byte[Cipher.GetOutputSize(CipherText.Length)];

                int Length = Cipher.ProcessBytes(CipherText, 0, CipherText.Length, PlainText, 0);
                Cipher.DoFinal(PlainText, Length);

                return Encoding.UTF8.GetString(PlainText);
            }
        }

        private static string SHA256String(string Password)
        {
            using (SHA256 Hash = SHA256.Create())
            {
                byte[] PasswordBytes = Hash.ComputeHash(Encoding.UTF8.GetBytes(Password));
                StringBuilder SB = new StringBuilder();

                for (int i = 0; i < PasswordBytes.Length; i++)
                    SB.Append(PasswordBytes[i].ToString("X2"));

                return SB.ToString();
            }
        }
    }
}

but when I call the method to decrypt the following exception is thrown:

Org.BouncyCastle.Crypto.InvalidCipherTextException: mac check in GCM failed

I wasted several hours trying to figure out the problem but without success, I also tried to search here, on Stackoverflow, but nothing I found answers my question, not even this answer. Is there anyone who has tested and tried to decrypt from PHP (openssl) to C# with BouncyCastle using AES256-GCM? Thanks in advance for any help.

UPDATE

I've tried to update the PHP method to encrypt in order to see if data is ok:

function str_encryptaesgcm($plaintext, $password, $encoding = null) {
    $aes = array("key" => substr(hash("sha256", $password, true), 0, 32), "cipher" => "aes-256-gcm", "iv" => openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-gcm")));
    $encryptedstring = openssl_encrypt($plaintext, $aes["cipher"], $aes["key"], OPENSSL_RAW_DATA, $aes["iv"], $aes["tag"], "", 16);

    switch ($encoding) {
        case "base64":
            return array("encrypteddata" => base64_encode($aes["iv"].$encryptedstring.$aes["tag"]), "iv" => base64_encode($aes["iv"]), "encryptedstring" => base64_encode($encryptedstring), "tag" => base64_encode($aes["tag"]));
        case "hex":
            return array("encrypteddata" => bin2hex($aes["iv"].$encryptedstring.$aes["tag"]), "iv" => bin2hex($aes["iv"]), "encryptedstring" => bin2hex($encryptedstring), "tag" => bin2hex($aes["tag"]));
        default:
            return array("encrypteddata" => $aes["iv"].$encryptedstring.$aes["tag"], "iv" => $aes["iv"], "encryptedstring" => $encryptedstring, "tag" => $aes["tag"]);
    }
}

So I get:

{"encrypteddata":"w2eUXD41sCgTBvN7PtKlhzHB0lodPohnOh8V1lWsATvRujwsV18DDftGqJLqpsWxatYUX7C0jxjLcPQUoazIfiVdRmAsbGAKuvXYSsNjQ6ahGY4AxowAp0p\/IGDuYWbCrof6GZHUyoxv9Ry8NP1yxNItnBlUhGS8ua0=","iv":"w2eUXD41sCgTBvN7","encryptedstring":"PtKlhzHB0lodPohnOh8V1lWsATvRujwsV18DDftGqJLqpsWxatYUX7C0jxjLcPQUoazIfiVdRmAsbGAKuvXYSsNjQ6ahGY4AxowAp0p\/IGDuYWbCrof6GZHUyoxv9Q==","tag":"HLw0\/XLE0i2cGVSEZLy5rQ=="}

This makes me understand that on the PHP side everything is ok, also because when I perform the decryption from PHP everything works correctly. I tried to update the class on the C# code as proposed by Micheal Fehr, however I get a new exception: Org.BouncyCastle.Crypto.InvalidCipherTextException: data too short:

using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace PoGORaidEngine.Crypto
{
    internal static class AESGCM
    {
        private const int MAC_BIT_SIZE = 128;
        private const int NONCE_BIT_SIZE = 96;

        internal static string DecryptString(string EncryptedString, string Password)
        {
            if (string.IsNullOrEmpty(EncryptedString))
                return string.Empty;

            byte[] EncryptedData = Convert.FromBase64String(EncryptedString);
            byte[] Key = DerivateKey(Password);
            byte[] IV;
            byte[] CipherText;
            byte[] Tag;

            using (MemoryStream MStream = new MemoryStream(EncryptedData))
            using (BinaryReader Binary = new BinaryReader(MStream))
            {
                IV = Binary.ReadBytes(NONCE_BIT_SIZE / 8);
                CipherText = Binary.ReadBytes(EncryptedData.Length - IV.Length - (MAC_BIT_SIZE / 8));
                Tag = Binary.ReadBytes(MAC_BIT_SIZE / 8);
            }

            byte[] AAED = new byte[0];
            byte[] DecryptedData = new byte[CipherText.Length];

            GcmBlockCipher Cipher = new GcmBlockCipher(new AesEngine());
            Cipher.Init(false, new AeadParameters(new KeyParameter(Key), MAC_BIT_SIZE, IV, AAED));
            int Length = Cipher.ProcessBytes(CipherText, 0, CipherText.Length, DecryptedData, 0);
            Cipher.DoFinal(DecryptedData, Length);

            return Encoding.UTF8.GetString(DecryptedData);
        }

        private static byte[] DerivateKey(string Password)
        {
            using (SHA256 Hash = SHA256.Create())
                return Hash.ComputeHash(Encoding.UTF8.GetBytes(Password));
        }
    }
}

As a counter test I tried to get in base64 IV, clean encrypted string and tag and the data match perfectly, as in PHP. I'm sure the solution is very close. The problem arises on: Cipher.DoFinal(DecryptedData, Length); (new byte[CipherText.Length]).

--- SOLUTION ---

Note: I've replaced the simple SHA256 derivation key with PBKDF2-SHA512 (with 20K iterations) to improve the security.

PHP functions:

function str_encryptaesgcm($plaintext, $password, $encoding = null) {
    $keysalt = openssl_random_pseudo_bytes(16);
    $key = hash_pbkdf2("sha512", $password, $keysalt, 20000, 32, true);
    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-gcm"));
    $tag = "";
    $encryptedstring = openssl_encrypt($plaintext, "aes-256-gcm", $key, OPENSSL_RAW_DATA, $iv, $tag, "", 16);
    return $encoding == "hex" ? bin2hex($keysalt.$iv.$encryptedstring.$tag) : ($encoding == "base64" ? base64_encode($keysalt.$iv.$encryptedstring.$tag) : $keysalt.$iv.$encryptedstring.$tag);
}

function str_decryptaesgcm($encryptedstring, $password, $encoding = null) {
    $encryptedstring = $encoding == "hex" ? hex2bin($encryptedstring) : ($encoding == "base64" ? base64_decode($encryptedstring) : $encryptedstring);
    $keysalt = substr($encryptedstring, 0, 16);
    $key = hash_pbkdf2("sha512", $password, $keysalt, 20000, 32, true);
    $ivlength = openssl_cipher_iv_length("aes-256-gcm");
    $iv = substr($encryptedstring, 16, $ivlength);
    $tag = substr($encryptedstring, -16);
    return openssl_decrypt(substr($encryptedstring, 16 + $ivlength, -16), "aes-256-gcm", $key, OPENSSL_RAW_DATA, $iv, $tag);
}

C# Class (using AesGcm available in .NET Core 3 or above)

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace PRBMono.Crypto
{
    internal static class AES
    {
        private static readonly int NONCE_BITS_SIZE = AesGcm.NonceByteSizes.MaxSize;
        private static readonly int SALTKEY_BITS_SIZE = AesGcm.TagByteSizes.MaxSize;
        private static readonly int MAC_BITS_SIZE = AesGcm.TagByteSizes.MaxSize;
        private static readonly int KEY_ITERATIONS = 20000;

        internal static string EncryptString(string String, bool Base64Encode = true)
        {
            if (string.IsNullOrEmpty(String))
                return null;

            byte[] SaltKey = CryptoMethods.RandomBytes(SALTKEY_BITS_SIZE);
            byte[] Key = CryptoMethods.PBKDF2DerivateKey(Server.Config.Server_AESKey, HashAlgorithmName.SHA512, SaltKey, KEY_ITERATIONS, 32);
            
            using AesGcm Aes = new(Key);
            byte[] Data = Encoding.UTF8.GetBytes(String);
            byte[] CipherData = new byte[Data.Length];
            byte[] IV = CryptoMethods.RandomBytes(NONCE_BITS_SIZE);
            byte[] Tag = new byte[MAC_BITS_SIZE];
            Aes.Encrypt(IV, Data, CipherData, Tag);

            using MemoryStream MStream = new();
            using (BinaryWriter Binary = new(MStream))
            {
                Binary.Write(SaltKey);
                Binary.Write(IV);
                Binary.Write(CipherData);
                Binary.Write(Tag);
            }

            return Base64Encode ? Convert.ToBase64String(MStream.ToArray()) : CryptoMethods.ByteArrayToHex(MStream.ToArray()).ToLower();
        }

        internal static string DecryptString(string EncryptedString, bool Base64Encode = true)
        {
            if (string.IsNullOrEmpty(EncryptedString))
                return string.Empty;

            byte[] EncryptedData = Base64Encode ? Convert.FromBase64String(EncryptedString) : CryptoMethods.HexToByteArray(EncryptedString);
            byte[] SaltKey, Key, IV, CipherData, Tag;

            using (MemoryStream MStream = new(EncryptedData))
            using (BinaryReader Binary = new(MStream))
            {
                SaltKey = Binary.ReadBytes(SALTKEY_BITS_SIZE);
                Key = CryptoMethods.PBKDF2DerivateKey(Server.Config.Server_AESKey, HashAlgorithmName.SHA512, SaltKey, KEY_ITERATIONS, 32);
                IV = Binary.ReadBytes(NONCE_BITS_SIZE);
                CipherData = Binary.ReadBytes(EncryptedData.Length - SaltKey.Length - IV.Length - MAC_BITS_SIZE);
                Tag = Binary.ReadBytes(MAC_BITS_SIZE);
            }

            using AesGcm Aes = new(Key);
            byte[] DecryptedData = new byte[CipherData.Length];
            Aes.Decrypt(IV, CipherData, Tag, DecryptedData);

            return Encoding.UTF8.GetString(DecryptedData);
        }
    }
}

C# Class (With BouncyCastle):

using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace PoGORaidEngine.Crypto
{
    internal static class AESGCM
    {
        private const int MAC_BIT_SIZE = 128;
        private const int SALTKEY_BIT_SIZE = 128;
        private const int NONCE_BIT_SIZE = 96;

        internal static string DecryptString(string EncryptedString, string Password)
        {
            if (string.IsNullOrEmpty(EncryptedString))
                return string.Empty;

            byte[] EncryptedData = Convert.FromBase64String(EncryptedString);
            byte[] SaltKey;
            byte[] Key;
            byte[] IV;
            byte[] CipherText;
            byte[] Tag;

            using (MemoryStream MStream = new MemoryStream(EncryptedData))
            using (BinaryReader Binary = new BinaryReader(MStream))
            {
                SaltKey = Binary.ReadBytes(SALTKEY_BIT_SIZE / 8);
                Key = PBKDF2DerivateKey(Password, HashAlgorithmName.SHA512, SaltKey, 20000, 32);
                IV = Binary.ReadBytes(NONCE_BIT_SIZE / 8);
                CipherText = Binary.ReadBytes(EncryptedData.Length - SaltKey.Length - IV.Length - (MAC_BIT_SIZE / 8));
                Tag = Binary.ReadBytes(MAC_BIT_SIZE / 8);
            }

            byte[] DecryptedData = new byte[CipherText.Length];
            byte[] CipherTextTag = new byte[CipherText.Length + Tag.Length];
            Buffer.BlockCopy(CipherText, 0, CipherTextTag, 0, CipherText.Length);
            Buffer.BlockCopy(Tag, 0, CipherTextTag, CipherText.Length, Tag.Length);

            GcmBlockCipher Cipher = new GcmBlockCipher(new AesEngine());
            Cipher.Init(false, new AeadParameters(new KeyParameter(Key), MAC_BIT_SIZE, IV));
            int Length = Cipher.ProcessBytes(CipherTextTag, 0, CipherTextTag.Length, DecryptedData, 0);
            Cipher.DoFinal(DecryptedData, Length);

            return Encoding.UTF8.GetString(DecryptedData);
        }

        private static byte[] PBKDF2DerivateKey(string Password, HashAlgorithmName Algorithm, byte[] Salt, int Iterations, int Length)
        {
            using (Rfc2898DeriveBytes DeriveBytes = new Rfc2898DeriveBytes(Password, Salt, Iterations, Algorithm))
                return DeriveBytes.GetBytes(Length);
        }
    }
}
Marco Concas
  • 1,665
  • 20
  • 25
  • You don't need an external lib for PBKDF2, see https://github.com/java-crypto/cross_platform_crypto/blob/main/Pbkdf2/Pbkdf2.cs – Michael Fehr Jan 28 '21 at 16:31
  • I am aware that it is possible to avoid using external libraries, I for one avoid if possible but the class Rfc2898DeriveBytes does not support SHA512. This is why I'm using AspNet's NuGet. – Marco Concas Jan 28 '21 at 17:20
  • 1
    You checked my link ? The example is running **PBKDF2** with SHA-1, SHA-256 and **SHA-512** without an external library. THis is a link to an online compiler that runs the example: https://repl.it/@javacrypto/CpcCsharpPbkdf2#main.cs – Michael Fehr Jan 28 '21 at 17:57
  • @MichaelFehr fixed. Thank you so much. – Marco Concas Jan 28 '21 at 20:09

2 Answers2

1

I'm not for sure that your "key derivation" on C# is working as on PHP, so I used my own one. As well, I used an own decryption function that runs without Bouncy Castle, and it could be a good basis for your work with Bouncy Castle.

Kindly note that a key derivation using SHA-hashes is UNSECURE and you should use something like PBKDF2 for this task.

I'm using the sample output

TrbntVEj8GEGeLE6ZYJnDIXnqSese5biWn604NePb2r6jsFhuzJsNHnN2GCizrGfhP4W39tahrGj0tORxvUbDpGT76WHr/v2wmnHHHiDGyjeKlWLu9/gfeualYvhsNF/N9inSpqxE2lQ+/vwpUJKYJw3bfo7DoGPDNk=

as input for the decryption function in C# - this is the result:

AES GCM 256 String decryption
* * * Decryption * * *
ciphertext (Base64): TrbntVEj8GEGeLE6ZYJnDIXnqSese5biWn604NePb2r6jsFhuzJsNHnN2GCizrGfhP4W39tahrGj0tORxvUbDpGT76WHr/v2wmnHHHiDGyjeKlWLu9/gfeualYvhsNF/N9inSpqxE2lQ+/vwpUJKYJw3bfo7DoGPDNk=
plaintext: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...

Kindly note that my code has no exception handling and is for educational purpose only, the code is running with .net 5 in an online compiler (https://dotnetfiddle.net/WvUkXf):

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

public class Program {
    public static void Main() {
        Console.WriteLine("AES GCM 256 String decryption");

        // decryption
        Console.WriteLine("\n* * * Decryption * * *");

        string password = "A random password to encrypt";
        //generate hash of password # # # this is UNSECURE # # #
        SHA256 mySHA256 = SHA256.Create();
        byte[] Key = mySHA256.ComputeHash(Encoding.UTF8.GetBytes(password));
        // ciphertext taken from encryption function in PHP
        string soCiphertextBase64 = "TrbntVEj8GEGeLE6ZYJnDIXnqSese5biWn604NePb2r6jsFhuzJsNHnN2GCizrGfhP4W39tahrGj0tORxvUbDpGT76WHr/v2wmnHHHiDGyjeKlWLu9/gfeualYvhsNF/N9inSpqxE2lQ+/vwpUJKYJw3bfo7DoGPDNk=";
        Console.WriteLine("ciphertext (Base64): " + soCiphertextBase64);
        string soDecryptedtext = soAesGcmDecryptFromBase64(Key, soCiphertextBase64);
        Console.WriteLine("plaintext: " + soDecryptedtext);
    }

    static string soAesGcmDecryptFromBase64(byte[] key, string data) {
        const int MAC_BIT_SIZE = 128;
        const int NONCE_BIT_SIZE = 96; // 12 bytes (openssl)
        byte[] EncryptedData = Convert.FromBase64String(data);
        byte[] IV;
        byte[] CipherText;
        byte[] Tag;
        using (MemoryStream MStream = new MemoryStream(EncryptedData))
        using (BinaryReader Binary = new BinaryReader(MStream)) {
            IV = Binary.ReadBytes(NONCE_BIT_SIZE / 8);
            CipherText = Binary.ReadBytes(EncryptedData.Length - IV.Length - (MAC_BIT_SIZE / 8));
            Tag = Binary.ReadBytes((MAC_BIT_SIZE / 8));
        }
        string decryptedtext;
        byte[] associatedData = new byte[0];
        byte[] decryptedData = new byte[CipherText.Length];
        using(var cipher = new AesGcm(key)) {
            cipher.Decrypt(IV, CipherText, Tag, decryptedData, associatedData);
            decryptedtext = Encoding.UTF8.GetString(decryptedData, 0, decryptedData.Length);
            return decryptedtext;
        }
    }
}

Edit: Did you notice that the GCM-Tag is never used in your updated decryption function?

Bouncy Castle's GCM function works similar to the Java-pendant and needs a ciphertext that has the tag appended to the ciphertext.

In the end you need to do some byte array copy operations:

byte[] CipherTextTag = new byte[CipherText.Length + Tag.Length];
System.Buffer.BlockCopy(CipherText, 0, CipherTextTag, 0, CipherText.Length);
System.Buffer.BlockCopy(Tag, 0, CipherTextTag, CipherText.Length, Tag.Length);
int Length = Cipher.ProcessBytes(CipherTextTag, 0, CipherTextTag.Length, DecryptedData, 0);

Using this "complete ciphertext" you get it to run with the original PHP code:

AES GCM 256 String decryption
* * * Decryption * * *
ciphertext (Base64): aV+gDmSBbi9PjOT9FD8LcuISbEQ5F3q0X8qzf3MKiDzxo12WQVirsnltbApLMMG9JScVfTXx7PJw7EVFoKz8JLMYLMu/JsRGcfvihSK+d/yeRTBEuJHL74Hv2Zr7b4CoMJhEUmYF3KT2Onlj4lI5ChOjmgXvpSev/xc=
plaintext: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...

The complete code:

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;

public class Program {
    public static void Main() {
        Console.WriteLine("AES GCM 256 String decryption");

        // decryption
        Console.WriteLine("\n* * * Decryption * * *");
        
        string password = "A random password to encrypt";
        //generate hash of password # # # this is UNSECURE # # #

        string soCiphertextBase64 = "aV+gDmSBbi9PjOT9FD8LcuISbEQ5F3q0X8qzf3MKiDzxo12WQVirsnltbApLMMG9JScVfTXx7PJw7EVFoKz8JLMYLMu/JsRGcfvihSK+d/yeRTBEuJHL74Hv2Zr7b4CoMJhEUmYF3KT2Onlj4lI5ChOjmgXvpSev/xc=";
        Console.WriteLine("ciphertext (Base64): " + soCiphertextBase64);
        string soDecryptedtextAsk = DecryptString(soCiphertextBase64, password);
        Console.WriteLine("plaintext: " + soDecryptedtextAsk);
    }
    
    static string DecryptString(string EncryptedString, string Password)
        {
            const int MAC_BIT_SIZE = 128;
            const int NONCE_BIT_SIZE = 96;
            if (string.IsNullOrEmpty(EncryptedString))
                return string.Empty;
            byte[] EncryptedData = Convert.FromBase64String(EncryptedString);
            byte[] Key = DerivateKey(Password);
            byte[] IV;
            byte[] CipherText;
            byte[] Tag;
            using (MemoryStream MStream = new MemoryStream(EncryptedData))
            using (BinaryReader Binary = new BinaryReader(MStream))
            {
                IV = Binary.ReadBytes(NONCE_BIT_SIZE / 8);
                CipherText = Binary.ReadBytes(EncryptedData.Length - IV.Length - (MAC_BIT_SIZE / 8));
                Tag = Binary.ReadBytes(MAC_BIT_SIZE / 8);
            }
            byte[] AAED = new byte[0];
            byte[] DecryptedData = new byte[CipherText.Length];
            GcmBlockCipher Cipher = new GcmBlockCipher(new AesEngine());
            Cipher.Init(false, new AeadParameters(new KeyParameter(Key), MAC_BIT_SIZE, IV, AAED));
            // combine ciphertext + tag
            byte[] CipherTextTag = new byte[CipherText.Length + Tag.Length];
            System.Buffer.BlockCopy(CipherText, 0, CipherTextTag, 0, CipherText.Length);
            System.Buffer.BlockCopy(Tag, 0, CipherTextTag, CipherText.Length, Tag.Length);
            int Length = Cipher.ProcessBytes(CipherTextTag, 0, CipherTextTag.Length, DecryptedData, 0);
            //int Length = Cipher.ProcessBytes(CipherText, 0, CipherText.Length, DecryptedData, 0);
            Cipher.DoFinal(DecryptedData, Length);
            return Encoding.UTF8.GetString(DecryptedData);
        }

        private static byte[] DerivateKey(string Password)
        {
            using (SHA256 Hash = SHA256.Create())
                return Hash.ComputeHash(Encoding.UTF8.GetBytes(Password));
        }
}
Michael Fehr
  • 5,827
  • 2
  • 19
  • 40
  • Hi, first thanks for your answer. I've edited my class like this: https://pastebin.com/raw/bWPjhHwr but now a new exception is thrown: Org.BouncyCastle.Crypto.InvalidCipherTextException: data too short – Marco Concas Jan 28 '21 at 12:17
  • It's always a good idea to debug the data, and you could compare the "BC" data easily with the solution I've posted to find out which data is too short :-) Btw: kindly edit your post and **add** the new code from your pastebin-source as 3rd party sources may go away, thanks. – Michael Fehr Jan 28 '21 at 12:27
  • Updated. I still don't know where is the problem. – Marco Concas Jan 28 '21 at 12:44
  • @Marco Concas: kindly see my edited answer :-) – Michael Fehr Jan 28 '21 at 12:59
  • Here's the missing part, yeah! Just the tag! The problem has been solved. I will edit the key derivation with a more secure algorithm as proposed by you and I'll update the answer. I thank you for the time you have dedicated to me and for this community. – Marco Concas Jan 28 '21 at 13:08
  • I've edited the question with the final solution and the pbkdf2 key derivation. – Marco Concas Jan 28 '21 at 15:46
0

Marco - I'm curious - Are these the only two languages you are using (PHP & C#)? we have written some cross language / libraries for GCM (128 & 256) using Java, C#, Go, Python, Ruby, C, with Openssl, bouncycastle, and others and have not run into any problems like you have but we are handling key management a little differently. We have not added PHP yet but have seen some differences in some of the PHP interfaces with OpenSSL compared to C.

Gary
  • 71
  • 2
  • I did not understand the question. – Marco Concas Jan 28 '21 at 17:20
  • Sorry - probably more of a statement to try and help point away from the encryption libraries and at the PBKDF. In our project we are using 8 different languages and several different crypto libraries (OpenSSL, Bouncy Castle, and others) with GCM 128 & 256 and they all interoperate perfectly. Encryption key comes from an HSM so no PBKDF needed. We are looking at adding PHP support but have seen some differences in with the OpenSSL APIs available compared to the other languages, especially with GCM. – Gary Jan 29 '21 at 07:55
  • Have you taken a step back and tried simple key / iv (no sha / kdf) just to make sure everything else is working? – Gary Jan 29 '21 at 07:56
  • I have already solved the problem, everything works perfectly and safely. See the beginning of my post with the solution. – Marco Concas Jan 29 '21 at 16:26