如何创建 SHA-2 和 AES 加密字符串以用于 .NET 中的 HTTP 请求 header?
How do you create SHA-2 and AES encrypted string to use in HTTP request header in .NET?
我正在尝试编写通过网络从外部 API 请求信息的客户端代码。
除了如何生成授权密钥的规定外,API(对我而言)足够简单。
首先是一些背景:
需要以 6 个字符串值开头:
- 代币
- 密码
- devId
- 盐
- 组织编号
- 给定密钥
现在开始加密。首先是 SHA2。
hashedString = SHA2(token + password + devId)
其次是 AES。
authKey = AES(salt + orgId + "=" + hashedString)
AES参数指定如下:
- 模式=欧洲央行
- 填充 = PKCS5Padding
- 密钥 = givenKey
我的问题是我对密码学几乎一无所知。
下面是我试图完成上述任务的代码。
// Generate Authorisation key
byte[] fieldsBytes = Encoding.ASCII.GetBytes(token + password + devId);
byte[] keyBytes = Encoding.ASCII.GetBytes(secretKey);
SHA512 shaM = new SHA512Managed();
string hashedFields = Encoding.ASCII.GetString(shaM.ComputeHash(fieldsBytes));
byte[] encryptedBytes = EncryptStringToBytes_Aes(salt + orgId + "=" + hashedfields,
keyBytes, keyBytes);
string encryptedString = Encoding.ASCII.GetString(encryptedBytes);
private byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("IV");
byte[] encrypted;
// Create an Aes object
// with the specified key and IV.
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Mode = CipherMode.ECB;
// Create an encryptor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor();
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
此代码从外部服务获得“401”。
我的第一个问题是 not 似乎是一个名为 SHA2 的 NET 方法。我能找到的最接近的是 SHA512,我不确定 SHA512 是否是 SHA2 的 .NET 实现。
其次,AES 的填充已指定为 PKCS5Padding,但我能找到的最接近的 (naming-wise) 是 PKCS7,我 不是 确定它与 PKCS5.
有多相似
还有一个 初始化向量 (IV) 的问题,AES 参数没有指定,但我看到 C# AES 示例包括。在代码中,我将其设置为与 Key 具有相同的值(我相信
API 调用 "secret key") 完全出于绝望,但我尝试在不将 IV 设置为任何值的情况下发出请求,但仍然返回 401。
我可能还应该提到我正在使用 ASCII 编码来转换 to-and-from 字节,因为我首先尝试使用 UTF8但是当实际发出 HTTP 请求时,我遇到了一个异常
说 header 值(记住我们正在生成一个将隐藏在 HTTP 请求 header 中的授权密钥)应该只用 ASCII 编码。
任何帮助我指明正确方向的人都将不胜感激,因为我对这些密码学的东西深感遗憾。
别担心,加密可能会让人觉得非常复杂。我认为你很接近。
SHA2 是哈希函数的 家族 。实际上,"SHA2" 通常表示 SHA2-256 或偶尔表示 SHA2-512。我的猜测是您的外部 API 可能使用更常见的 SHA2-256。
This answer 在 crypto.stackexchange 上解释说 PKCS#5 本质上是 PKCS#7 的一个子集。我愿意打赌,你打电话的 API 犯了那个答案中描述的同样的错误,实际上应该叫它 PKCS7Padding
。你的代码没问题!
IV 与密钥不同(或者只是 AES 的 "key")。每次加密 运行 的 IV should be random。您不应该从输入明文或输入密钥中派生它。幸运的是,AesCryptoServiceProvider.GenerateIV()
会为您生成一个。不过,将其添加到输出流中取决于您。
使用Encoding.ASCII.GetBytes()
获取明文和密钥字节对我来说很有意义。我认为这不会造成问题。
从 this excellent answer 窃取一个类似的问题(去给他们投票!),我会尝试这样的代码:
static byte[] AesEncrypt(byte[] data, byte[] key)
{
if (data == null || data.Length <= 0)
{
throw new ArgumentNullException($"{nameof(data)} cannot be empty");
}
if (key == null || key.Length != AesKeySize)
{
throw new ArgumentException($"{nameof(key)} must be length of {AesKeySize}");
}
using (var aes = new AesCryptoServiceProvider
{
Key = key,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
})
{
aes.GenerateIV();
var iv = aes.IV;
using (var encrypter = aes.CreateEncryptor(aes.Key, iv))
using (var cipherStream = new MemoryStream())
{
using (var tCryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
using (var tBinaryWriter = new BinaryWriter(tCryptoStream))
{
// prepend IV to data
cipherStream.Write(iv);
tBinaryWriter.Write(data);
tCryptoStream.FlushFinalBlock();
}
var cipherBytes = cipherStream.ToArray();
return cipherBytes;
}
}
}
除非此 API 有其他奇怪的事情发生,否则我猜可能是上面的 #3 导致您的请求失败。
我终于成功了。
问题的很大一部分是 API 文档没有指定 hashedString 必须
由 十六进制 字符组成。
它也没有说 authKey 应该是 base64 字符串。
我不知道这是否标准到不用说但知道这可能有
节省了我数小时的痛苦。我正在将 hashed/encrypted 字节转换回 ASCII
其中大部分是导致服务器发回的不可打印字符
状态为 400 BAD_REQUEST.
的 HTTP 响应
它还要求使用 SHA256 对 hashedString 进行哈希处理,但文档中没有提及。
感谢@Nate Barbettini 的回答在这方面引导我朝着正确的方向前进。
此外,与其他模式不同,AES ECB 模式似乎不需要初始化向量
像 CBC 这样的模式,所以我没有指定 IV。
对于填充,我指定了 PKCS7(再次感谢@Nate Barbettini)。
有了这些,我终于得到了代码。
string hashedFields = ComputeSha256HashHex(authToken + password + devId);
string encryptedString = AesEncryptToBase64String(saltString + orgId + "=" + hashedFields, secretKey);
private string AesEncryptToBase64String(string plainText, string key)
{
// Convert string arguments into byte arrays
byte[] keyBytes = Encoding.ASCII.GetBytes(key);
byte[] plainTextBytes = Encoding.ASCII.GetBytes(plainText);
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (key == null || key.Length <= 0)
throw new ArgumentNullException("key");
byte[] encrypted;
// Create an Aes object
// with the specified key and IV.
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = keyBytes;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Mode = CipherMode.ECB;
// Create an encryptor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor();
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return encrypted bytes as Base 64 string
return Convert.ToBase64String(encrypted);
}
private string ComputeSha256HashHex(string plainText)
{
using (SHA256 sha256Hash = SHA256.Create())
{
// ComputeHash - returns byte array
byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(plainText));
// Convert byte array to a string
return BytesToHexString(bytes);
}
}
private string BytesToHexString(byte[] bytes)
{
// Convert byte array to a string
StringBuilder builder = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
builder.Append(bytes[i].ToString("x2"));
}
return builder.ToString();
}
我正在尝试编写通过网络从外部 API 请求信息的客户端代码。
除了如何生成授权密钥的规定外,API(对我而言)足够简单。 首先是一些背景: 需要以 6 个字符串值开头:
- 代币
- 密码
- devId
- 盐
- 组织编号
- 给定密钥
现在开始加密。首先是 SHA2。
hashedString = SHA2(token + password + devId)
其次是 AES。
authKey = AES(salt + orgId + "=" + hashedString)
AES参数指定如下:
- 模式=欧洲央行
- 填充 = PKCS5Padding
- 密钥 = givenKey
我的问题是我对密码学几乎一无所知。
下面是我试图完成上述任务的代码。
// Generate Authorisation key
byte[] fieldsBytes = Encoding.ASCII.GetBytes(token + password + devId);
byte[] keyBytes = Encoding.ASCII.GetBytes(secretKey);
SHA512 shaM = new SHA512Managed();
string hashedFields = Encoding.ASCII.GetString(shaM.ComputeHash(fieldsBytes));
byte[] encryptedBytes = EncryptStringToBytes_Aes(salt + orgId + "=" + hashedfields,
keyBytes, keyBytes);
string encryptedString = Encoding.ASCII.GetString(encryptedBytes);
private byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("IV");
byte[] encrypted;
// Create an Aes object
// with the specified key and IV.
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Mode = CipherMode.ECB;
// Create an encryptor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor();
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
此代码从外部服务获得“401”。
我的第一个问题是 not 似乎是一个名为 SHA2 的 NET 方法。我能找到的最接近的是 SHA512,我不确定 SHA512 是否是 SHA2 的 .NET 实现。
其次,AES 的填充已指定为 PKCS5Padding,但我能找到的最接近的 (naming-wise) 是 PKCS7,我 不是 确定它与 PKCS5.
有多相似还有一个 初始化向量 (IV) 的问题,AES 参数没有指定,但我看到 C# AES 示例包括。在代码中,我将其设置为与 Key 具有相同的值(我相信 API 调用 "secret key") 完全出于绝望,但我尝试在不将 IV 设置为任何值的情况下发出请求,但仍然返回 401。
我可能还应该提到我正在使用 ASCII 编码来转换 to-and-from 字节,因为我首先尝试使用 UTF8但是当实际发出 HTTP 请求时,我遇到了一个异常 说 header 值(记住我们正在生成一个将隐藏在 HTTP 请求 header 中的授权密钥)应该只用 ASCII 编码。
任何帮助我指明正确方向的人都将不胜感激,因为我对这些密码学的东西深感遗憾。
别担心,加密可能会让人觉得非常复杂。我认为你很接近。
SHA2 是哈希函数的 家族 。实际上,"SHA2" 通常表示 SHA2-256 或偶尔表示 SHA2-512。我的猜测是您的外部 API 可能使用更常见的 SHA2-256。
This answer 在 crypto.stackexchange 上解释说 PKCS#5 本质上是 PKCS#7 的一个子集。我愿意打赌,你打电话的 API 犯了那个答案中描述的同样的错误,实际上应该叫它
PKCS7Padding
。你的代码没问题!IV 与密钥不同(或者只是 AES 的 "key")。每次加密 运行 的 IV should be random。您不应该从输入明文或输入密钥中派生它。幸运的是,
AesCryptoServiceProvider.GenerateIV()
会为您生成一个。不过,将其添加到输出流中取决于您。使用
Encoding.ASCII.GetBytes()
获取明文和密钥字节对我来说很有意义。我认为这不会造成问题。
从 this excellent answer 窃取一个类似的问题(去给他们投票!),我会尝试这样的代码:
static byte[] AesEncrypt(byte[] data, byte[] key)
{
if (data == null || data.Length <= 0)
{
throw new ArgumentNullException($"{nameof(data)} cannot be empty");
}
if (key == null || key.Length != AesKeySize)
{
throw new ArgumentException($"{nameof(key)} must be length of {AesKeySize}");
}
using (var aes = new AesCryptoServiceProvider
{
Key = key,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7
})
{
aes.GenerateIV();
var iv = aes.IV;
using (var encrypter = aes.CreateEncryptor(aes.Key, iv))
using (var cipherStream = new MemoryStream())
{
using (var tCryptoStream = new CryptoStream(cipherStream, encrypter, CryptoStreamMode.Write))
using (var tBinaryWriter = new BinaryWriter(tCryptoStream))
{
// prepend IV to data
cipherStream.Write(iv);
tBinaryWriter.Write(data);
tCryptoStream.FlushFinalBlock();
}
var cipherBytes = cipherStream.ToArray();
return cipherBytes;
}
}
}
除非此 API 有其他奇怪的事情发生,否则我猜可能是上面的 #3 导致您的请求失败。
我终于成功了。
问题的很大一部分是 API 文档没有指定 hashedString 必须 由 十六进制 字符组成。 它也没有说 authKey 应该是 base64 字符串。 我不知道这是否标准到不用说但知道这可能有 节省了我数小时的痛苦。我正在将 hashed/encrypted 字节转换回 ASCII 其中大部分是导致服务器发回的不可打印字符 状态为 400 BAD_REQUEST.
的 HTTP 响应它还要求使用 SHA256 对 hashedString 进行哈希处理,但文档中没有提及。 感谢@Nate Barbettini 的回答在这方面引导我朝着正确的方向前进。
此外,与其他模式不同,AES ECB 模式似乎不需要初始化向量 像 CBC 这样的模式,所以我没有指定 IV。
对于填充,我指定了 PKCS7(再次感谢@Nate Barbettini)。
有了这些,我终于得到了代码。
string hashedFields = ComputeSha256HashHex(authToken + password + devId);
string encryptedString = AesEncryptToBase64String(saltString + orgId + "=" + hashedFields, secretKey);
private string AesEncryptToBase64String(string plainText, string key)
{
// Convert string arguments into byte arrays
byte[] keyBytes = Encoding.ASCII.GetBytes(key);
byte[] plainTextBytes = Encoding.ASCII.GetBytes(plainText);
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (key == null || key.Length <= 0)
throw new ArgumentNullException("key");
byte[] encrypted;
// Create an Aes object
// with the specified key and IV.
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = keyBytes;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Mode = CipherMode.ECB;
// Create an encryptor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor();
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return encrypted bytes as Base 64 string
return Convert.ToBase64String(encrypted);
}
private string ComputeSha256HashHex(string plainText)
{
using (SHA256 sha256Hash = SHA256.Create())
{
// ComputeHash - returns byte array
byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(plainText));
// Convert byte array to a string
return BytesToHexString(bytes);
}
}
private string BytesToHexString(byte[] bytes)
{
// Convert byte array to a string
StringBuilder builder = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
builder.Append(bytes[i].ToString("x2"));
}
return builder.ToString();
}