ataravati
ataravati

Reputation: 9145

Encryption with MachineKey is not persistent

I use the MachineKey.Protect() method to encrypt the id passed as a query string in my asp.net MVC application.

Here's the code I use to encrypt/decrypt:

public static string Encrypt(this string expression)
{
    if (string.IsNullOrEmpty(expression))
        return string.Empty;

    byte[] stream = Encoding.Unicode.GetBytes(expression);
    byte[] encodedValue = MachineKey.Protect(stream);            
    return HttpServerUtility.UrlTokenEncode(encodedValue);            
}

public static string Decrypt(this string expression)
{
    if (string.IsNullOrEmpty(expression))
        return string.Empty;

    byte[] stream = HttpServerUtility.UrlTokenDecode(expression);
    byte[] decodedValue = MachineKey.Unprotect(stream);
    return Encoding.Unicode.GetString(decodedValue);
}

And, here is the MachineKey element in my web.config file:

<system.web>
    .
    .
    .
    <machineKey validationKey="xxx" decryptionKey="xxx" validation="SHA1" decryption="AES" />
</system.web>

The problem is the encrypted id is not persistent. Every time I call the method, I get a new encrypted expression. How do I make it persistent?

Upvotes: 4

Views: 5718

Answers (2)

sga101
sga101

Reputation: 1904

Summary:

If you want to get the same result every time, you need to use a different method to protect your data. MachineKey.Protect uses a different IV for each run resulting in a different result every time.

Detail

Microsoft makes the source code for a lot of the dot net framework freely viewable on the internet.

Starting from the top: MachineKey

The protect method uses the AspNetCryptoServiceProvider

If you follow the code through AspNetCryptoServiceProvider.GetCryptoService into NetFXCryptoService, you will find this:

public byte[] Protect(byte[] clearData) {
        // The entire operation is wrapped in a 'checked' block because any overflows should be treated as failures.
        checked {

            // These SymmetricAlgorithm instances are single-use; we wrap it in a 'using' block.
            using (SymmetricAlgorithm encryptionAlgorithm = _cryptoAlgorithmFactory.GetEncryptionAlgorithm()) {
                // Initialize the algorithm with the specified key and an appropriate IV
                encryptionAlgorithm.Key = _encryptionKey.GetKeyMaterial();

                if (_predictableIV) {
                    // The caller wanted the output to be predictable (e.g. for caching), so we'll create an
                    // appropriate IV directly from the input buffer. The IV length is equal to the block size.
                    encryptionAlgorithm.IV = CryptoUtil.CreatePredictableIV(clearData, encryptionAlgorithm.BlockSize);
                }
                else {
                    // If the caller didn't ask for a predictable IV, just let the algorithm itself choose one.
                    encryptionAlgorithm.GenerateIV();
                }
                byte[] iv = encryptionAlgorithm.IV;

                using (MemoryStream memStream = new MemoryStream()) {
                    memStream.Write(iv, 0, iv.Length);

                    // At this point:
                    // memStream := IV

                    // Write the encrypted payload to the memory stream.
                    using (ICryptoTransform encryptor = encryptionAlgorithm.CreateEncryptor()) {
                        using (CryptoStream cryptoStream = new CryptoStream(memStream, encryptor, CryptoStreamMode.Write)) {
                            cryptoStream.Write(clearData, 0, clearData.Length);
                            cryptoStream.FlushFinalBlock();

                            // At this point:
                            // memStream := IV || Enc(Kenc, IV, clearData)

                            // These KeyedHashAlgorithm instances are single-use; we wrap it in a 'using' block.
                            using (KeyedHashAlgorithm signingAlgorithm = _cryptoAlgorithmFactory.GetValidationAlgorithm()) {
                                // Initialize the algorithm with the specified key
                                signingAlgorithm.Key = _validationKey.GetKeyMaterial();

                                // Compute the signature
                                byte[] signature = signingAlgorithm.ComputeHash(memStream.GetBuffer(), 0, (int)memStream.Length);

                                // At this point:
                                // memStream := IV || Enc(Kenc, IV, clearData)
                                // signature := Sign(Kval, IV || Enc(Kenc, IV, clearData))

                                // Append the signature to the encrypted payload
                                memStream.Write(signature, 0, signature.Length);

                                // At this point:
                                // memStream := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData))

                                // Algorithm complete
                                byte[] protectedData = memStream.ToArray();
                                return protectedData;
                            }
                        }
                    }
                }
            }
        }
    }

The class was initialised with the default options, so _predictableIV is false.

Therefore, it uses a new IV every time, which means the result will be different everytime, even with the same input.

The IV is included in the result so the Unprotect method can reverse the encryption.

Upvotes: 9

Igor
Igor

Reputation: 308

Try adding a purpose argument to the Machine.Key.Proctect method! Like so.

public static class Key
{       
    public static string EncryptWithAPurpose(this string expression, string[] purpose)
    {
        if (string.IsNullOrEmpty(expression))
            return string.Empty;

        byte[] stream = Encoding.Unicode.GetBytes(expression);
        byte[] encodedValue = MachineKey.Protect(stream, purpose);
        return HttpServerUtility.UrlTokenEncode(encodedValue);
    }

    public static string DecryptWithAPurpose(this string expression, string[] purpose)
    {
        if (string.IsNullOrEmpty(expression))
            return string.Empty;

        byte[] stream = HttpServerUtility.UrlTokenDecode(expression);

        byte[] decodedValue = MachineKey.Unprotect(stream,purpose);
        return Encoding.Unicode.GetString(decodedValue);
    }

}

For testing:

  1. Add a Link -> @Html.ActionLink("Get new key", "GetKey", new { id = Guid.NewGuid()})
  2. Add the controller method
  3. Add the ViewModel
  4. Add the View.

{

public ActionResult GetKey(Guid id)  
{
            var vm = new vmGetKey();

            vm.Guid = id;

            //create a purpose
            var purpose = new string[] { "Test", "WithAPurpose" };

            //encrypt  key1
            vm.key1 = Key.EncryptWithAPurpose(id.ToString(),  purpose);

            //encrypt Key2
            vm.key2 = Key.EncryptWithAPurpose(id.ToString(), purpose);

            //decrypt key1
            vm.key1_DecryptResult = Key.DecryptWithAPurpose(vm.key1, purpose);

            //decrypt key2
            vm.key2_DecryptResult = Key.DecryptWithAPurpose(vm.key2, purpose);

            return View(vm);
}


public class vmGetKey
{
    public Guid Guid { get; set; }
    public string key1 { get; set; }
    public string key2 { get; set; }
    public string key1_DecryptResult { get; set; }
    public string key2_DecryptResult { get; set; }        
}        




    }

enter image description here

Upvotes: 0

Related Questions