Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

256 blocksize does not work in .net core #13

Open
ByronAP opened this issue Nov 3, 2017 · 26 comments
Open

256 blocksize does not work in .net core #13

ByronAP opened this issue Nov 3, 2017 · 26 comments

Comments

@ByronAP
Copy link

ByronAP commented Nov 3, 2017

In .net core AES is used which does not support 256 blocksize which is the exact reason why RijindaelManaged doesn't work. Unless I am missing something here, if I am please correct me as I can not get it to work.

Ref: https://github.com/dotnet/corefx/issues/12064

@JasonPierce
Copy link
Contributor

What version of .NET Core are you using @ByronAP? Can you provide an example?

@ByronAP
Copy link
Author

ByronAP commented Nov 4, 2017

.net core 2.0 and any example using a 256 block size will fail because you are trying to use AES which does not support that. This is block size not key size, a 256 key should work, a 256 block size will absolutely not work unless you write the actual rounds from scratch because the .net core team has emphatically refused to support Rijndael. You can try it by making a .net core console app and trying to decrypt anything that has been encrypted using Rijndael 256. It will fail as soon as you try and set that invalid block size. To further prove this remove the net45 section in the src, it should fail on all systems since it is AES (not tested but should).

@JasonPierce
Copy link
Contributor

JasonPierce commented Nov 4, 2017

@ByronAP can you provide an example illustrating how you're using a 256 block size with Rijndael256?

Rijndael256 currently supports:

  • 128-128
  • 192-128
  • 256-128

N-256 isn't something I set out to support when I created this lib, but if a significant number of users would like to see it, I'm not opposed to investigating it's implementation. Are you certain you want to use 256 block size? It hasn't had nearly as much attention as 128 block size (which means it could have weaknesses that have yet to be discovered).

@ByronAP
Copy link
Author

ByronAP commented Nov 4, 2017

I'm sure it is not all that common in .net however some libs in php use it along with some other functions so not having it in c# creates an interoperability impediment. I personally wouldn't bother because you have to write the whole thing yourself or rip some code from bouncy castle to make it work for what is essentially an edge case.

@JasonPierce
Copy link
Contributor

Closing, since there doesn't appear to be a great interest in this feature. Will re-open if that changes.

@niemyjski
Copy link

+1

@ciacco85
Copy link

https://stackoverflow.com/questions/10168240/encrypting-decrypting-a-string-in-c-sharp

I'm using this easy and nice implementation that use 256 block size, could you have a look at it?
it's native (only system.security) and super fast to test

@Gabriel-Espinoza
Copy link

I'm using the same as @ciacco85 but doesn't work on .net core cause it's block size is 256 D:

@parabola949
Copy link

@JasonPierce
Copy link
Contributor

Reopening, since interest is spiking

@JasonPierce JasonPierce reopened this Jun 28, 2019
@tedebus
Copy link

tedebus commented Jan 10, 2020

I'm very interested, too. I have a library based created on .Net Standard. Importing it on .Net Framwork all goes right but using it on .Net Core I have problems.

@darrinjolson
Copy link

I have a similar issue, as well. I have data stored that is encrypted using an initialization vector that requires a block size of 256. I'm trying to find a way to decrypt these values in .NET Core and cannot use 256.

@JasonPierce
Copy link
Contributor

Unfortunately, as of .NET Core 3.1, RijndaelManaged still does not support block sizes greater than 128. Looking at the source code, you can see .NET Core throws an exception if you try to use 192 or 256: https://github.com/dotnet/corefx/blob/release/3.1/src/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RijndaelImplementation.cs#L37

cc: @ciacco85 , @Gabriel-Espinoza

@ByronAP
Copy link
Author

ByronAP commented Mar 3, 2020

they have made it quite clear over the years that it will never be supported since it is not an official implementation :-(

@wmmihaa
Copy link

wmmihaa commented Mar 17, 2020

+1 @JasonPierce are you still working on this?

@JasonPierce
Copy link
Contributor

JasonPierce commented Mar 18, 2020

@wmmihaa I'm keeping on eye on it. Unfortunately, as of .NET Core 3, it still does not support this. We can hope that they do add support in the new .NET 5 that is currently in Preview 1. Perhaps we'll get support for it at that time, since it is currently supported in .NET Framework 🤞

@sediinhesari
Copy link

I believe it is necessary to provide 256 blocksize encryption and decryption.
We have an old windows application that used 256 blocksize to encrypt passwords in the database.
Now we are developing a web application version.
the problem is we can not use that data in the web application which is developing with asp.net core 3

@dericferreira
Copy link

Hey,

If .NET Core 3.1 still does not support 256 initialization vector why documentation does not reflect that?

Look at this:

//RijndaelManaged
// Legal min key size = 128
// Legal max key size = 256
// Legal min block size = 128
// Legal max block size = 256

Anybody can't get the same result! Why does the documentation is not refleting the truth?

https://docs.microsoft.com/pt-br/dotnet/api/system.security.cryptography.symmetricalgorithm.legalkeysizes?view=netcore-3.1

@davidathompson
Copy link

Just discovered this, and it creates a huge block. I have to support decryption in .net core of values encrypted with .net framework using 256 bit. Arrrrgggg!!!

@wmmihaa
Copy link

wmmihaa commented Jun 2, 2020

@davidathompson ...hope this can be of help

Helper class

public static class CipherHelper
    {
        // This constant is used to determine the keysize of the encryption algorithm in bits.
        // We divide this by 8 within the code below to get the equivalent number of bytes.
        private const int Keysize = 256;

        // This constant determines the number of iterations for the password bytes generation function.
        private const int DerivationIterations = 1000;

        public static string Encrypt(string plainText, string passPhrase)
        {
            // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
            // so that the same Salt and IV values can be used when decrypting.  
            var saltStringBytes = Generate256BitsOfRandomEntropy();
            var ivStringBytes = Generate256BitsOfRandomEntropy();
            var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                var engine = new RijndaelEngine(256);
                var blockCipher = new CbcBlockCipher(engine);
                var cipher = new PaddedBufferedBlockCipher(blockCipher, new Pkcs7Padding());
                var keyParam = new KeyParameter(keyBytes);
                var keyParamWithIV = new ParametersWithIV(keyParam, ivStringBytes, 0, 32);

                cipher.Init(true, keyParamWithIV);
                var comparisonBytes = new byte[cipher.GetOutputSize(plainTextBytes.Length)];
                var length = cipher.ProcessBytes(plainTextBytes, comparisonBytes, 0);

                cipher.DoFinal(comparisonBytes, length);
                //                return Convert.ToBase64String(comparisonBytes);
                return Convert.ToBase64String(saltStringBytes.Concat(ivStringBytes).Concat(comparisonBytes).ToArray());
            }
        }

        public static string Decrypt(string cipherText, string passPhrase)
        {
            // Get the complete stream of bytes that represent:
            // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
            var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
            // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
            var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
            // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
            var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
            // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
            var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();

            using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
            {
                var keyBytes = password.GetBytes(Keysize / 8);
                var engine = new RijndaelEngine(256);
                var blockCipher = new CbcBlockCipher(engine);
                var cipher = new PaddedBufferedBlockCipher(blockCipher, new Pkcs7Padding());
                var keyParam = new KeyParameter(keyBytes);
                var keyParamWithIV = new ParametersWithIV(keyParam, ivStringBytes, 0, 32);

                cipher.Init(false, keyParamWithIV);
                var comparisonBytes = new byte[cipher.GetOutputSize(cipherTextBytes.Length)];
                var length = cipher.ProcessBytes(cipherTextBytes, comparisonBytes, 0);

                cipher.DoFinal(comparisonBytes, length);
                //return Convert.ToBase64String(saltStringBytes.Concat(ivStringBytes).Concat(comparisonBytes).ToArray());

                var nullIndex = comparisonBytes.Length - 1;
                while (comparisonBytes[nullIndex] == (byte)0)
                    nullIndex--;
                comparisonBytes = comparisonBytes.Take(nullIndex + 1).ToArray();


                var result = Encoding.UTF8.GetString(comparisonBytes, 0, comparisonBytes.Length);

                return result;
            }
        }

        private static byte[] Generate256BitsOfRandomEntropy()
        {
            var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits.
            using (var rngCsp = new RNGCryptoServiceProvider())
            {
                // Fill the array with cryptographically secure random bytes.
                rngCsp.GetBytes(randomBytes);
            }
            return randomBytes;
        }
    }

Unit test

[Test]
        public async Task CipherHelper_ShouldWork()
        {
            try
            {
                var value = "Secret to encrypt";
                var passPhrase = "your pass phrase";
                var encryptedValue = CipherHelper.Encrypt(value, passPhrase);
                var decryptedValue = CipherHelper.Decrypt(encryptedValue, passPhrase);
                
                Assert.IsTrue(value == decryptedValue);
            }
            catch (Exception ex)
            {
                Assert.Fail(ex.Message);
            }
        }

@zutroy97
Copy link

zutroy97 commented Jun 5, 2020

@wmmihaa Thank you! That is exactly what I needed. You saved me a ton of time.

@suj87
Copy link

suj87 commented Jul 30, 2020

@wmmihaa Thanks for this - seems to work flawlessly. For anyone wondering - it requires BouncyCastle.NetCore :)

@jbaumbach
Copy link

Thanks @wmmihaa and @suj87 , totally works. We were dead in the water before!

@RajeshAKumar
Copy link

RajeshAKumar commented Oct 10, 2021

We have a desktop software using WPF and written a crypto library using RijndaelManaged using .NET FX 4.0 ~2010-2011 timeframe.
Now we are moving that code base to .NET 6, and discover this issue.
We are also Microsoft Partner and this is leaving us high and dry.

If MS supported RijndaelManaged with 256 bit blocksize, then it should have a upgrade route. We can definitely use the newer and ratified AES also, but what do we do with encrypted data at rest in files. We do have to support data/ config files from our older products and can not abandon that.
Right now the only working solution is to use BouncyCastle.NetCore.
When MS has 1000s of developers, how hard is it to support 256 bit blocksize giving us a native .NET solution instead of relying on a 3rd party library.
We plan to use it for "Reading old", and generate new files using new AES.
But do not like the concept no straightforward upgrade path.

I strongly vote for this to be added to .NET 6.

@viniciusvillas
Copy link

@wmmihaa, thank you so much! Saved us a lot of time. Cheers! ❤️

@halex2005
Copy link

halex2005 commented Jun 27, 2024

@wmmihaa , thanks for code!

Just need to process corner-case of decrypting empty string.

So, instead of

                while (comparisonBytes[nullIndex] == (byte)0)
                    nullIndex--;

in Decrypt method we should check array boundaries:

                while (comparisonBytes[nullIndex] == (byte)0)
                {
                    nullIndex--;
                    if (nullIndex < 0)
                        break;
                }

Works like a charm!

And it seems like Bouncycastle.NetCore is unofficial package, so I've used official BouncyCastle.Cryptography

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests