如何创建 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 个字符串值开头:

现在开始加密。首先是 SHA2。

hashedString = SHA2(token + password + devId)

其次是 AES。

authKey = AES(salt + orgId + "=" + hashedString)

AES参数指定如下:

我的问题是我对密码学几乎一无所知。

下面是我试图完成上述任务的代码。

// 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 编码。

任何帮助我指明正确方向的人都将不胜感激,因为我对这些密码学的东西深感遗憾。

别担心,加密可能会让人觉得非常复杂。我认为你很接近。

  1. SHA2 是哈希函数的 家族 。实际上,"SHA2" 通常表示 SHA2-256 或偶尔表示 SHA2-512。我的猜测是您的外部 API 可能使用更常见的 SHA2-256。

  2. This answer 在 crypto.stackexchange 上解释说 PKCS#5 本质上是 PKCS#7 的一个子集。我愿意打赌,你打电话的 API 犯了那个答案中描述的同样的错误,实际上应该叫它 PKCS7Padding。你的代码没问题!

  3. IV 与密钥不同(或者只是 AES 的 "key")。每次加密 运行 的 IV should be random。您不应该从输入明文或输入密钥中派生它。幸运的是,AesCryptoServiceProvider.GenerateIV() 会为您生成一个。不过,将其添加到输出流中取决于您

  4. 使用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();
        }