为什么我不能加密/解密HMAC密钥以进行数据库存储?

时间:2016-05-10 15:52:48

标签: c# .net cryptography aes hmac

我为每个用户生成随机HMAC密钥,并将密钥存储在我们的数据库中。用户只有在获得密钥时才会获取密钥,并且通常只使用我们的API令牌(SWT)作为BASE64编码的不透明密钥,并且不用担心它们的完整性。

我希望在将密钥存储到SQL Server数据库之前加密密钥,以防止密钥被泄露。它们加密的密钥存储在varbinary(MAX)列中。没有加密,一切都很好。

我使用AES进行加密,并使用随机生成的IV存储在加密值的开头。

在我使用简单字符串的单元测试中,一切都很好,但是,使用HMAC密钥,解密后的值永远不会与原始字符匹配。如同,如果我生成HMAC密钥,加密它,将其存储在数据库中。当我检索它,解密它,并使用它们键生成HMAC哈希时,它与原始HMAC哈希值不匹配。

请参阅下面的加密/解密方法。

public static byte[] Encrypt(byte[] value)
    {
        using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
        {
            Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(_password, Encoding.ASCII.GetBytes(_salt));

            aes.Key = key.GetBytes(aes.KeySize / 8);
            aes.GenerateIV();
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;

            using (var crypt = aes.CreateEncryptor(aes.Key, aes.IV))
            using (MemoryStream ms = new MemoryStream())
            {
                using (CryptoStream cs = new CryptoStream(ms, crypt, CryptoStreamMode.Write))
                {
                    cs.Write(aes.IV, 0, aes.IV.Length);

                    using (BinaryWriter bw = new BinaryWriter(cs))
                    {                            
                        bw.Write(value);
                        cs.FlushFinalBlock();
                    }

                    return ms.ToArray();
                }
            }
        }
    }

public static byte[] Decrypt(byte[] value)
    {
        using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
        {
            Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(_password, Encoding.ASCII.GetBytes(_salt));

            aes.Key = key.GetBytes(aes.KeySize / 8);
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;

            using (MemoryStream ms = new MemoryStream(value))
            {
                byte[] iv = new byte[aes.IV.Length];

                ms.Read(iv, 0, aes.IV.Length);
                aes.IV = iv;

                using (var crypt = aes.CreateDecryptor(aes.Key, aes.IV))
                using (CryptoStream cs = new CryptoStream(ms, crypt, CryptoStreamMode.Read))
                {
                    using (StreamReader sr = new StreamReader(cs))
                        return Encoding.ASCII.GetBytes(sr.ReadToEnd());
                }
            }
        }
    }

密码和salt存储在编译成代码的const字符串文字中。我意识到这不是理想的,但现在就是这样。

2 个答案:

答案 0 :(得分:0)

我认为这个问题有两个问题。第一个,正如Jon Skeet所讨论的那样,IV被加密,因此用于解密该值时不同。我通过直接写入MemoryStream而不是CryptoStream来纠正以下代码。

public static byte[] Encrypt(byte[] value)
    {
        using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
        {
            Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(_password, Encoding.ASCII.GetBytes(_salt));

            aes.Key = key.GetBytes(aes.KeySize / 8);
            aes.GenerateIV();
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;

            using (var crypt = aes.CreateEncryptor(aes.Key, aes.IV))
            using (MemoryStream ms = new MemoryStream())
            using (CryptoStream cs = new CryptoStream(ms, crypt, CryptoStreamMode.Write))
            using (BinaryWriter bw = new BinaryWriter(cs))
            {
                ms.Write(aes.IV, 0, aes.IV.Length);
                bw.Write(value);
                cs.FlushFinalBlock();

                return ms.ToArray();
            }
        }
    }

下一部分我并不完全确定,但我确实认为Jon对于读取字符串并返回字节数组存在一些问题也是正确的。我通过使用类似于他在此处找到的代码更正了这一点:jonskeet.uk/csharp/readbinary.html将流直接读入字节数组。请参阅以下代码,其中ReadStream()是我基于Jon编写的方法。

public static byte[] Decrypt(byte[] value)
    {
        using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
        {
            Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(_password, Encoding.ASCII.GetBytes(_salt));

            aes.Key = key.GetBytes(aes.KeySize / 8);
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;

            using (MemoryStream ms = new MemoryStream(value))
            {
                byte[] iv = new byte[aes.IV.Length];

                ms.Read(iv, 0, aes.IV.Length);
                aes.IV = iv;

                using (var crypt = aes.CreateDecryptor(aes.Key, aes.IV))
                using (CryptoStream cs = new CryptoStream(ms, crypt, CryptoStreamMode.Read))
                   return ReadStream(cs, 0, ms.Length);
            }
        }
    }

感谢Jon的帮助。我也感谢你,不仅仅是放弃它。如果我要学习一些东西,我不介意为它工作。这是我得到的报酬。

答案 1 :(得分:-1)

简单和定制的东西怎么样?

using System.Security.Cryptography;
using System.Text;

namespace GrimoireTactics.Framework.Security
{
    public enum ObfuscatorType
    {
        Encrypt,
        Decrypt
    }
    public class Obfuscator
    {
        private string _seed;
        private byte[] _hashedSeedBytes;
        private readonly SHA256Managed _hashingAlgorithm;
        public string Seed
        {
            get
            {
                return _seed;
            }
            set
            {
                this._seed = value;
                SeedHash = GenerateHash(value);
                this._hashedSeedBytes = GetBytes(SeedHash);
            }
        }

        public byte[] SeedBytes
        {
            get
            {
                return _hashedSeedBytes;
            }
        }

        public string SeedHash { get; private set; }

        public Obfuscator(string seed)
        {
            this._hashingAlgorithm = new SHA256Managed();
            this.Seed = seed;
        }


        public byte[] Encrypt(byte[] data)
        {
            return Transform(data, ObfuscatorType.Encrypt);
        }

        public byte[] Encrypt(string data)
        {
            return Transform(GetBytes(data), ObfuscatorType.Encrypt);
        }

        public byte[] Decrypt(byte[] data)
        {
            return Transform(data, ObfuscatorType.Decrypt);
        }

        public byte[] Transform(byte[] bytes, ObfuscatorType type)
        {
            int passwordShiftIndex = 0;
            byte[] data = bytes;
            byte offset = 0;
            switch (type)
            {
                case ObfuscatorType.Encrypt:
                    for (int i = 0; i < data.Length; i++)
                    {
                        byte currentByte = _hashedSeedBytes[passwordShiftIndex];
                        offset += (byte)(1 + currentByte); // Incrementing Offset
                        data[i] = (byte)(data[i] + currentByte + offset);
                        passwordShiftIndex = (passwordShiftIndex + 1) % _hashedSeedBytes.Length;
                    }
                    break;
                case ObfuscatorType.Decrypt:
                    for (int i = 0; i < data.Length; i++)
                    {
                        byte currentByte = _hashedSeedBytes[passwordShiftIndex];
                        offset += (byte)(1 + currentByte); // Incrementing Offset
                        data[i] = (byte)(data[i] - currentByte - offset);
                        passwordShiftIndex = (passwordShiftIndex + 1) % _hashedSeedBytes.Length;
                    }
                    break;
            }
            return data;
        }

        public byte[] GetBytes(string data)
        {
            return Encoding.UTF8.GetBytes(data);
        }

        public byte[] GetBytes(string data, Encoding encoding)
        {
            return encoding.GetBytes(data);
        }

        public string GetString(byte[] data)
        {
            return Encoding.UTF8.GetString(data);
        }

        public string GetString(byte[] data, Encoding encoding)
        {
            return encoding.GetString(data);
        }

        public string GenerateHash(string text)
        {
            byte[] bytes = Encoding.UTF8.GetBytes(text);
            byte[] hash = _hashingAlgorithm.ComputeHash(bytes);
            string hashString = string.Empty;
            for (int index = 0; index < hash.Length; index++)
            {
                byte x = hash[index];
                hashString += $"{x:x2}";
            }
            return hashString;
        }
    }
}

用法示例:

using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using GrimoireDevelopmentKit.DevelopmentKit.UserInterface;
using GrimoireTactics.Framework.OpenGL.Modeling;
using GrimoireTactics.Framework.OpenGL.Texturing;
using GrimoireTactics.Framework.Security;

namespace GrimoireDevelopmentKit.DevelopmentKit
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            //Application.EnableVisualStyles();
            //Application.SetCompatibleTextRenderingDefault(false);
            //Application.Run(new DevelopmentKitEditor());

            Obfuscator obs = new Obfuscator("My Arbitary Seed");
            byte[] obufsicatedData = obs.Encrypt("Some Top Secret Data");
            byte[] unobufsicatedData = obs.Decrypt(obufsicatedData);
            Console.WriteLine(obs.GetString(unobufsicatedData));
            Console.Read();
        }
    }
}

我们所做的就是使用自定义算法对字节进行模糊处理。代码可供任何人免费使用;我只是将它作为一种额外的安全措施。