Leveraging ASP.NET machineKey For Encrypting My Ow

2019-03-08 02:09发布

问题:

I have some data I want to encrypt in an ASP.NET MVC application to prevent users from tampering with it. I can use the Cryptography classes to do the actual encryption/decryption, no problem there. The main problem is figuring out where to store the encryption key and managing changes to it.

Since ASP.NET already maintains a machineKey for various things (ViewData encryption, etc), I was wondering if there were any ASP.NET functions which let me encrypt/decrypt my own data using the machineKey? This way I would not have to devise my own key management system.

回答1:

With .NET Framwork 4.5 you should use the new API:

public class StringProtector
{

    private const string Purpose = "Authentication Token";

    public string Protect(string unprotectedText)
    {
        var unprotectedBytes = Encoding.UTF8.GetBytes(unprotectedText);
        var protectedBytes = MachineKey.Protect(unprotectedBytes, Purpose);
        var protectedText = Convert.ToBase64String(protectedBytes);
        return protectedText;
    }

    public string Unprotect(string protectedText)
    {
        var protectedBytes = Convert.FromBase64String(protectedText);
        var unprotectedBytes = MachineKey.Unprotect(protectedBytes, Purpose);
        var unprotectedText = Encoding.UTF8.GetString(unprotectedBytes);
        return unprotectedText;
    }

}

Ideally the "Purpose" should be a known one time valid value to prevent forging.



回答2:

The new MachineKey class in ASP.NET 4.0 does exactly what you want.

For example:

public static class StringEncryptor {
    public static string Encrypt(string plaintextValue) {
        var plaintextBytes = Encoding.UTF8.GetBytes(plaintextValue);
        return MachineKey.Encode(plaintextBytes, MachineKeyProtection.All);
    }

    public static string Decrypt(string encryptedValue) {
        try {
            var decryptedBytes = MachineKey.Decode(encryptedValue, MachineKeyProtection.All);
            return Encoding.UTF8.GetString(decryptedBytes);
        }
        catch {
            return null;
        }
    }
}

UPDATE: As mentioned here, be careful how you use this or you could allow someone to forge a forms authentication token.



回答3:

I guess not directly. I can't remember where I got this from, probably a combination of Reflector and some blogs.

public abstract class MyAwesomeClass
{
    private static byte[] cryptKey;

    private static MachineKeySection machineKeyConfig =
        (MachineKeySection)ConfigurationManager
            .GetSection("system.web/machineKey");

    // ... snip ...

    static MyAwesomeClass()
    {
        string configKey;
        byte[] key;

        configKey = machineKeyConfig.DecryptionKey;
        if (configKey.Contains("AutoGenerate"))
        {
            throw new ConfigurationErrorsException(
                Resources.MyAwesomeClass_ExplicitAlgorithmRequired);
        }

        key = HexStringToByteArray(configKey);

        cryptKey = key;
    }

    // ... snip ...

    protected static byte[] Encrypt(byte[] inputBuffer)
    {
        SymmetricAlgorithm algorithm;
        byte[] outputBuffer;

        if (inputBuffer == null)
        {
            throw new ArgumentNullException("inputBuffer");
        }

        algorithm = GetCryptAlgorithm();

        using (var ms = new MemoryStream())
        {
            algorithm.GenerateIV();
            ms.Write(algorithm.IV, 0, algorithm.IV.Length);

            using (var cs = new CryptoStream(
                 ms, 
                 algorithm.CreateEncryptor(), 
                 CryptoStreamMode.Write))
            {
                cs.Write(inputBuffer, 0, inputBuffer.Length);
                cs.FlushFinalBlock();
            }

            outputBuffer = ms.ToArray();
        }

        return outputBuffer;
    }

    protected static byte[] Decrypt(string input)
    {
        SymmetricAlgorithm algorithm;
        byte[] inputBuffer, inputVectorBuffer, outputBuffer;

        if (input == null)
        {
            throw new ArgumentNullException("input");
        }

        algorithm = GetCryptAlgorithm();
        outputBuffer = null;

        try
        {
            inputBuffer = Convert.FromBase64String(input);

            inputVectorBuffer = new byte[algorithm.IV.Length];
            Array.Copy(
                 inputBuffer, 
                 inputVectorBuffer,
                 inputVectorBuffer.Length);
            algorithm.IV = inputVectorBuffer;

            using (var ms = new MemoryStream())
            {
                using (var cs = new CryptoStream(
                    ms, 
                    algorithm.CreateDecryptor(), 
                    CryptoStreamMode.Write))
                {
                    cs.Write(
                        inputBuffer,
                        inputVectorBuffer.Length, 
                        inputBuffer.Length - inputVectorBuffer.Length);
                    cs.FlushFinalBlock();
                }

                outputBuffer = ms.ToArray();
            }
        }
        catch (FormatException e)
        {
            throw new CryptographicException(
                "The string could not be decoded.", e);
        }

        return outputBuffer;
    }

    // ... snip ...

    private static SymmetricAlgorithm GetCryptAlgorithm()
    {
        SymmetricAlgorithm algorithm;
        string algorithmName;

        algorithmName = machineKeyConfig.Decryption;
        if (algorithmName == "Auto")
        {
            throw new ConfigurationErrorsException(
                Resources.MyAwesomeClass_ExplicitAlgorithmRequired);
        }

        switch (algorithmName)
        {
            case "AES":
                algorithm = new RijndaelManaged();
                break;
            case "3DES":
                algorithm = new TripleDESCryptoServiceProvider();
                break;
            case "DES":
                algorithm = new DESCryptoServiceProvider();
                break;
            default:
                throw new ConfigurationErrorsException(
                    string.Format(
                        CultureInfo.InvariantCulture,
                        Resources.MyAwesomeClass_UnrecognizedAlgorithmName,
                        algorithmName));
        }

        algorithm.Key = cryptKey;

        return algorithm;
    }

    private static byte[] HexStringToByteArray(string str)
    {
        byte[] buffer;

        if (str == null)
        {
            throw new ArgumentNullException("str");
        }

        if (str.Length % 2 == 1)
        {
            str = '0' + str;
        }

        buffer = new byte[str.Length / 2];

        for (int i = 0; i < buffer.Length; ++i)
        {
            buffer[i] = byte.Parse(
                str.Substring(i * 2, 2),
                NumberStyles.HexNumber,
                CultureInfo.InvariantCulture);
        }

        return buffer;
    }
}

Caveat emptor!



回答4:

If you're working with 3.5 or earlier you can avoid a lot of code and just do this:

public static string Encrypt(string cookieValue)
{
    return FormsAuthentication.Encrypt(new FormsAuthenticationTicket(1,
                                                                     string.Empty,
                                                                     DateTime.Now,
                                                                     DateTime.Now.AddMinutes(20160),
                                                                     true,
                                                                     cookieValue));
}

public static string Decrypt(string encryptedTicket)
{
    return FormsAuthentication.Decrypt(encryptedTicket).UserData;
}

One of my colleagues talked me into it and I think it's fairly reasonable to do this for custom cookies, if not for general encryption needs.



回答5:

You might be able to reuse the MembershipProvider.EncryptPassword method, which in turn uses some (unfortunately internal) encryption methods of the MachineKeySection class.