为什么我不能 encrypt/decrypt HMAC 密钥用于数据库存储?
Why can I not encrypt/decrypt HMAC keys for database storage?
我为每个用户生成随机 HMAC 密钥并将密钥存储在我们的数据库中。用户只有在请求时才会获得密钥,并且通常只使用我们的 API 令牌 (SWT) 作为 BASE64 编码的不透明密钥,不用担心它们的完整性。
我想在将密钥存储到我们的 SQL 服务器数据库之前对其进行加密,以防止密钥被泄露。他们加密的密钥存储在 varbinary(MAX) 列中。没有加密,一切都很好。
我使用 AES 进行加密,随机生成的 IV 存储在加密值的开头。
在我用简单字符串进行的单元测试中,一切都很好,但是,对于 HMAC 密钥,解密后的值永远不会与原始值匹配。比如,如果我生成一个 HMAC 密钥,对其进行加密,将其存储在数据库中。当我检索它、解密它并使用它们的密钥生成 HMAC 哈希时,它与原始 HMAC 哈希值不匹配。
请参阅下面的 encryption/decryption 方法。
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());
}
}
}
}
密码和盐存储在编译成代码的常量字符串文字中。我意识到这并不理想,但目前就是这样。
我认为问题有两个方面。首先,正如与 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);
}
}
}
感谢乔恩的帮助。我也很感激你不只是把它送人。如果我要学习一些东西,我不介意为此工作。这就是我得到报酬的事情。
简单和定制的东西怎么样?
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();
}
}
}
我们所做的只是使用自定义算法混淆字节。任何人都可以免费使用代码;我只是将其用作附加的安全措施。
我为每个用户生成随机 HMAC 密钥并将密钥存储在我们的数据库中。用户只有在请求时才会获得密钥,并且通常只使用我们的 API 令牌 (SWT) 作为 BASE64 编码的不透明密钥,不用担心它们的完整性。
我想在将密钥存储到我们的 SQL 服务器数据库之前对其进行加密,以防止密钥被泄露。他们加密的密钥存储在 varbinary(MAX) 列中。没有加密,一切都很好。
我使用 AES 进行加密,随机生成的 IV 存储在加密值的开头。
在我用简单字符串进行的单元测试中,一切都很好,但是,对于 HMAC 密钥,解密后的值永远不会与原始值匹配。比如,如果我生成一个 HMAC 密钥,对其进行加密,将其存储在数据库中。当我检索它、解密它并使用它们的密钥生成 HMAC 哈希时,它与原始 HMAC 哈希值不匹配。
请参阅下面的 encryption/decryption 方法。
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());
}
}
}
}
密码和盐存储在编译成代码的常量字符串文字中。我意识到这并不理想,但目前就是这样。
我认为问题有两个方面。首先,正如与 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);
}
}
}
感谢乔恩的帮助。我也很感激你不只是把它送人。如果我要学习一些东西,我不介意为此工作。这就是我得到报酬的事情。
简单和定制的东西怎么样?
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();
}
}
}
我们所做的只是使用自定义算法混淆字节。任何人都可以免费使用代码;我只是将其用作附加的安全措施。