Search code examples
c#encryptiongrailsbouncycastlejasypt

Decrypt string with BC


I have a web application written using Grails 6.1.2. I have to encrypt a couple of fields in the database so I decide to use the grails-jasypt based on the famous jasypt library. It works like a charm.

Now I have to write a small C# application and I need to access the DB to read some of those encrypted fields. Of course I know my password and algorithm used by the web app, from the properties I have:

jasypt {
    algorithm = "PBEWITHSHA256AND256BITAES-CBC-BC"
    providerName = "BC"
    password = "my-strong-pwd"
    keyObtentionIterations = 1000 }

Unfortunately the plugin in Grails hides all the code that has been used to encrypt the string I just add in the domain this mapping to be sure to encrypt and decrypt what I need:

static mapping = { dbPassword type: GormEncryptedStringType } 

I wrote this static function in C# to decrypt the string that I got from the DB:

 public static string Decrypt(string toDecrypt)
    {
        byte[] decoded = Convert.FromBase64String(toDecrypt);
        if (decoded == null || decoded.Length <= 16)
        {
            throw new ArgumentException("Bad input data", "toDecrypt");
        }

        byte[] salt = new byte[16];
        byte[] wrappedKey = new byte[decoded.Length - 16];
        Array.Copy(decoded, 0, salt, 0, 16);
        Array.Copy(decoded, 16, wrappedKey, 0, decoded.Length - 16);

        int iterationCount = 1000;
        string algName = "PBEWITHSHA256AND256BITAES-CBC-BC";

        PbeParametersGenerator generator = new Pkcs5S2ParametersGenerator(new Org.BouncyCastle.Crypto.Digests.Sha256Digest());
        generator.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(key.ToCharArray()), salt, iterationCount);

        ICipherParameters parameters = generator.GenerateDerivedParameters("AES", 256);

        IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CBC/NoPadding");
        cipher.Init(false, new ParametersWithIV(parameters, new byte[16])); // Assuming 16-byte IV

        byte[] keyText = cipher.DoFinal(wrappedKey);
        return Convert.ToBase64String(keyText);
    }

This works but the string is not been decrypted correctly, I don't know much about BC, I probably have to change the GetCipher string but I don't know with what. I try to add the padding with AES/CBC/PKCS5Padding but I got "pad block corrupted"


Solution

  • The decryption with PBEWITHSHA256AND256BITAES-CBC-BC is implemented incorrectly, the following implementation works:

    using Org.BouncyCastle.Asn1;
    using Org.BouncyCastle.Crypto;
    using Org.BouncyCastle.Security;
    
    ...
    
    // input data
    string algorithm = "PBEWITHSHA256AND256BITAES-CBC-BC";
    int iterations = 1000;
    string password = "dfglhkgfhljktypoio34537567--,dfd435";
    string encryptedDataB64 = "Xw4VoIGs7FtD71gVwrDlCweSK7S/Z8ikg39rTxdiOcg=";
    
    // separate salt and ciphertext
    byte[] encryptedData = Convert.FromBase64String(encryptedDataB64);
    byte[] salt = encryptedData[0..16];
    byte[] ciphertext = encryptedData[16..];
    
    // decrypt with PBEWITHSHA256AND256BITAES-CBC-BC
    IBufferedCipher cipher = CipherUtilities.GetCipher(algorithm);
    Asn1Encodable algParams = PbeUtilities.GenerateAlgorithmParameters(algorithm, salt, iterations);
    ICipherParameters cipherParams = PbeUtilities.GenerateCipherParameters(algorithm, password.ToCharArray(), algParams);
    cipher.Init(false, cipherParams);
    byte[] decrypted = cipher.DoFinal(ciphertext);
    
    // UTF-8 decode and output
    Console.WriteLine(Encoding.UTF8.GetString(decrypted)); // Test1234$$
    

    Edit:
    The above implementation uses the identifier PBEWITHSHA256AND256BITAES-CBC-BC and does not require any further detailed knowledge of the algorithm.

    An alternative (more low-level) way, in which the algorithm (AES/CBC/PKCS7Padding), the key derivation (PKCS#12) and parameters such as key size and IV size must be explicitly specified, is:

    using Org.BouncyCastle.Crypto.Digests;
    using Org.BouncyCastle.Crypto.Generators;
    ...
    // apply PKCS#12 key derivation and decrypt with AES/CBC/PKCS7Padding
    IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CBC/PKCS7Padding");
    PbeParametersGenerator generator = new Pkcs12ParametersGenerator(new Sha256Digest());
    generator.Init(PbeParametersGenerator.Pkcs12PasswordToBytes(password.ToCharArray()), salt, iterations);
    ICipherParameters cipherParams = generator.GenerateDerivedParameters("AES", 256, 128);
    cipher.Init(false, cipherParams); 
    byte[] decrypted = cipher.DoFinal(ciphertext);
    

    A comparison of the second approach with your code shows that in the first place you used the wrong key derivation and the IV size was not specified.