0

I’m encrypting sensitive data using the C# AES class, Aes.Create() method before saving to an SQLite DB. My base round trip is:

base round trip

*Saving/retrieving each Byte[ ] without converting threw “The specified key is not a valid size for this algorithm.” when decrypting.

The process leverages the MSDN example and is working well:

decrypted samples

I’m concerned, however, that cipher, key & IV values must all be stored/retrieved together and am attempting to disguise the values stored in the DB. I’ve tried three procedures to disguise/revert the relevant string values. Unfortunately adding a disguise/revert procedure throws the following error when decrypting:

System.Security.Cryptography.CryptographicException
HResult=0x80131430 Message=Padding is invalid and cannot be removed. Source=System.Core

I've tried:

  • switching from Aes.Create to AesManaged; result: no change
  • setting Padding = PaddingMode.None; result: threw "'System.Security.Cryptography.CryptographicException' occurred in mscorlib.dll 'Length of the data to encrypt is invalid.'"
  • setting Padding = PaddingMode.Zeros; result: ran with no error but round trips returned incorrect & unreadable values (see image):

unusable round trip values

A number of posts (of the many, most of the issues are base process round trip failures) in this forum regarding this error state that the Padding error can be thrown for a variety of reasons only one of which is actually a Padding error. One post ( Padding is invalid and cannot be removed?, 4th reply sorted by Trending) states:

If the same key and initialization vector are used for encoding and decoding, this issue does not come from data decoding but from data encoding.

Since my keys & IVs are the same this looked hopeful. However, as the above image implies if I'm interpreting it correctly, changing encoding doesn't seem to help.

Since my base processs by itself works I suspect my case fall into the "not really a Padding error" category.

The disguise/revert procedures

First procedure

  1. Remove the “=” character(s) at the end of each string since it’s a blatant indicator of “something important”
  2. Split cipher, key & IV strings based on a random number into two halves
  3. Swap the two halves’ positions and insert unique random text of random length between the halves
  4. Create a random length “reversion code” capturing randomized split indices and resultant string lengths

Second procedure: same as the first but leave “=” character(s) intact.

Third procedure

  1. Split the random text of three different random length strings
  2. “Wrap” them around the cipher, key & IV strings
  3. Create the “reversion code”

For all three procedures: upon retrieval from the DB reverse the process using the “reversion code”. In the third procedure the original strings should, theoretically, be “untouched”.

In all three cases output indicates Original and Reverted are identical (single run testing output sample):

reverted

The disguise/revert code

Code for the third procedure since it probably has the greatest probability of working. The Encrypt & Decrypt methods are included in case they're relevant:

private static string _cipherText = "", _keyText = "", _vectorText = "";

// Prior to saving: third procedure
public static void SaveSensitivePrep(string input, bool parent)
{
    input = "secretID";
    Console.WriteLine($"\nOriginal:   {input}");
    
    string startString = default, extractor = default;
    int disguiseLen = 0, startStringLen = 0, doneStringLen = 0;
    for (int i = 0; i < 3; i++)
    {
        using (Aes toEncrypt = Aes.Create())
        {
            byte[] cypherBytes = Encrypt(input, toEncrypt.Key, toEncrypt.IV);
            string cipherText = Convert.ToBase64String(cypherBytes);
            string keyText = Convert.ToBase64String(toEncrypt.Key);
            string vectorText = Convert.ToBase64String(toEncrypt.IV);
            
            if (i == 0) _cipherText = startString = cipherText;
            else if (i == 1) _keyText = startString = keyText;
            else if (i == 2) _vectorText = startString = vectorText;

            int disguiseSelector = new Random().Next(101);
            string disguiseTxt = default;
            List<Disguises> disguise_DB = DBconnect.Disguises_Load();
            for (int c = 0; c < disguise_DB.Count; c++)
            {
                if (c == disguiseSelector) { disguiseTxt = disguise_DB[c].Linkages; break; }
            }
            disguiseLen = disguiseTxt.Length;
            int disguiseSplitIndex = new Random().Next(10, 22);
            string startTxt = disguiseTxt.Substring(0, disguiseSplitIndex);
            string endTxt = disguiseTxt.Substring(disguiseSplitIndex);

            string doneString = $"{startTxt}{startString}{endTxt}";
            doneStringLen = doneString.Length;
            extractor = $"{disguiseSplitIndex}{disguiseLen}{startStringLen}" +
                        $"{disguiseSelector + new Random().Next(5000000)}";

            if (i == 0) DBconnect.Encrypted_Update_CT(Host, extractor, doneString);
            else if (i == 1) DBconnect.Encrypted_Update_CK(Host, extractor, doneString);
            else if (i == 2) DBconnect.Encrypted_Update_CV(Host, extractor, doneString);
        }
    }
}

// After retrieval: third procedure
public static string LoadSensitive(string input, bool parent)
{
    string cipherText = "", keyText = "", vectorText = "";
    for (int i = 0; i < 3; i++)
    {
        string extractor = "";
        int disguiseLeft, disguiseRight;
        List<EncryptedClass> host = DBconnect.Encrypted_Load(Host);
        string doneString = "";
        if (i == 0) { extractor = host[0].RCC; doneString = host[0].cipherText; }
        else if (i == 1) { extractor = host[0].RCK; doneString = host[0].keyText; }
        else if (i == 2) { extractor = host[0].RCV; doneString = host[0].vectorText; }

        string disguiseSplitter = extractor.Substring(0, 2);
        string disguiseLen_t = extractor.Substring(2, 2);
        string startStringLen_t = extractor.Substring(4, 2);

        int.TryParse(disguiseSplitter, out int indx);
        int.TryParse(disguiseLen_t, out int disguiseLen);
        int.TryParse(startStringLen_t, out int startStringLen);

        disguiseRight = disguiseLen - indx;
        disguiseLeft = disguiseLen - disguiseRight;
        string e_extrctd = doneString.Substring(disguiseLeft, startStringLen);

        if (i == 0) cipherTxt = revertedString;
        else if (i == 1) keyTxt = revertedString;
        else vectorTxt = revertedString;
    }
    byte[] cypher = Convert.FromBase64String(cipherText);
    byte[] key = Convert.FromBase64String(keyText);
    byte[] vector = Convert.FromBase64String(vectorText);

    return Decrypt(cypher, key, vector);
}

static byte[] Encrypt(string input, byte[] key, byte[] iv)
{
    // NULL/Length > 0 checks.

    byte[] encrypted;
    using (Aes input_e = Aes.Create())
    {
        input_e.Key = key;
        input_e.IV = iv;

        ICryptoTransform ict = input_e.CreateEncryptor(input_e.Key, input_e.IV);

        using (MemoryStream msInput = new MemoryStream())
        {
            using (CryptoStream csInput = new CryptoStream(msInput, ict, CryptoStreamMode.Write))
            {
                using (StreamWriter swEncrypt = new StreamWriter(csInput))
                {
                    swEncrypt.Write(input);
                }
                encrypted = msInput.ToArray();
            }
        }
    }
    return encrypted;
}

static string Decrypt(byte[] cipherText, byte[] keyText, byte[] vectorText)
{
    // NULL/Length > 0 checks.

    string roundtrip = null;
    using (Aes output_e = Aes.Create())
    {
        output_e.Key = keyText;
        output_e.IV = vectorText;

        ICryptoTransform ict = output_e.CreateDecryptor(output_e.Key, output_e.IV);

        using (MemoryStream msOutput = new MemoryStream(cipherText))
        {
            using (CryptoStream csOutput = new CryptoStream(msOutput, ict, CryptoStreamMode.Read))
            {
                using (StreamReader srOutput = new StreamReader(csOutput))
                {
                    roundtrip = srOutput.ReadToEnd();
                }
            }
        }
    }
    return roundtrip;
}

Any guidance on how/why the disguise/revert is getting deformed will be appreciated.

EDIT: scope clarification: the security functionality in this instance is for a Winforms/SQLite app which has an FTP component (WinSCP). The app's high level objective is a desktop tool streamlining TLS/SSL Certificate request/issuance via Let's Encrypt. The primary security objective is protecting FTP session credentials should an unauthorized & semi-knowledgeable person gain access to the DB. The secondary security objective is to keep it as simple as possible.

Art Hansen
  • 57
  • 8
  • 1
    You are moving entirely in the wrong direction, using obfuscation to achieve security. The key must be kept separate from the ciphertext. Now yes, keeping the key secret is hard. But you can achieve this by e.g. putting it into a restricted configuration file. Banks and what not keep the key in a HSM. You can use asymmetric cryptography to only require the secret / private key during decryption. The IV however can remain public. For CBC mode you need an unpredictable - i.e. public but random - IV. usually it is just prefixed to the ciphertext. – Maarten Bodewes Jan 23 '23 at 13:12
  • Currently you are not understanding cryptography and you try to resolve that by programming your way out of it. Instead, try and follow a course or read a book, preferably one that goes into key management. – Maarten Bodewes Jan 23 '23 at 13:16
  • @Maarten Bodewes you're absolutely correct; my understanding of cryptography is very low but I'm not trying to correct that with this project. Certainly cryptography is mission-critical to a broad variety of institutions but probably not in my current case (see scope clarification above). I'm intrigued by a "restricted configuration file". What is that ("googling" the phrase doesn't return any useful hits) and how do I create/use one? – Art Hansen Jan 23 '23 at 17:18
  • 1
    @ArtHansen: I think Maarten just means a separate file that has OS file permissions set to prevent other users from stealing it. – Ben Voigt Jan 23 '23 at 17:43
  • Yep, that. It could also just store the secret or private key. You could also protect the key using a password, but it has the disadvantage that you actually need to provide it during startup and possibly during restarts of the system, which is not good for uptime. – Maarten Bodewes Jan 23 '23 at 22:46
  • So on Windows (for example) create a file containing the Cryptographic key, save it somewhere in the user's ArrData directory and password protect that file? – Art Hansen Jan 24 '23 at 11:38

0 Answers0