创建 RSA-SHA1 签名
Create RSA-SHA1 signature
是否可以使用存储在 Azure Key Vault 中的 X509 证书创建 RSA-SHA1 签名? [不可否认证书]
不幸的是,我无法将哈希算法更改为 SHA256 或更安全的算法,我确实需要将证书作为密钥存储在 Azure Key Vault 中。
到目前为止我已经试过了
await kvClient.SignAsync(keyVaultUrl, "RSNULL", digest); // digest = 20byte SHA1
await kvClient.SignAsync(keyVaultUrl, "RSNULL", ans1Digest); // asn1Digest = 35byte SHA1 wrapped in ANS1 structure
签名长度似乎是正确的(256 字节),但验证失败(在正确实现签名验证实现的节点上)。
我也试过像这样手动实现签名算法(使用 keyVault.EncryptAsync
):
- 数据>>计算SHA1摘要
- 将 SHA1 摘要包装在 ASN.1 结构中(连同 SHA1 对象标识符)
- 填充 ANS.1 结构,使摘要匹配证书的密钥长度
- 使用RSA1_5>>签名
加密填充的ANS1.structure
我一定是做错了什么。不确定是否需要所有步骤。
如果 Microsoft 的任何人阅读此文。即使 SHA1 签名被认为不安全,您能否实施它?漂亮,请在上面加糖:-)
我的盲目 "professional" 意见是您没有正确构造 PKCS#1 DigestInfo 结构。以下控制台应用程序对 SHA1 和 SHA256 算法都适用(没有测试其他算法):
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.KeyVault;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
namespace AzureKeyVaultTestApp1
{
static class Program
{
static HashAlgorithmName _hashAlg = HashAlgorithmName.SHA1;
static string _clientId = "00000000-0000-0000-0000-000000000000";
static string _clientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
static string _certId = "https://XXXXXXXX.vault.azure.net/certificates/TestCert1/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
static string _keyId = "https://XXXXXXXX.vault.azure.net/keys/TestCert1/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
static async Task<string> AuthenticationCallback(string authority, string resource, string scope)
{
var context = new AuthenticationContext(authority);
var result = await context.AcquireTokenAsync(resource, new ClientCredential(_clientId, _clientSecret));
return result.AccessToken;
}
static async Task Main(string[] args)
{
KeyVaultClient client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(AuthenticationCallback));
// Compute digest of data
byte[] dataToSign = Encoding.ASCII.GetBytes("Hello world!");
byte[] hash = HashAlgorithm.Create(_hashAlg.Name).ComputeHash(dataToSign);
// Construct DER encoded PKCS#1 DigestInfo structure defined in RFC 8017
byte[] pkcs1DigestInfo = CreatePkcs1DigestInfo(hash, _hashAlg);
// Sign digest with private key
var keyOperationResult = await client.SignAsync(_keyId, "RSNULL", pkcs1DigestInfo).ConfigureAwait(false);
byte[] signature = keyOperationResult.Result;
// Get public key from certificate
var certBundle = await client.GetCertificateAsync(_certId).ConfigureAwait(false);
X509Certificate2 cert = new X509Certificate2(certBundle.Cer);
RSA rsaPubKey = cert.GetRSAPublicKey();
// Verify digest signature with public key
if (!rsaPubKey.VerifyHash(hash, signature, _hashAlg, RSASignaturePadding.Pkcs1))
throw new Exception("Invalid signature");
}
private static byte[] CreatePkcs1DigestInfo(byte[] hash, HashAlgorithmName hashAlgorithm)
{
if (hash == null || hash.Length == 0)
throw new ArgumentNullException(nameof(hash));
byte[] pkcs1DigestInfo = null;
if (hashAlgorithm == HashAlgorithmName.MD5)
{
if (hash.Length != 16)
throw new ArgumentException("Invalid lenght of hash value");
pkcs1DigestInfo = new byte[] { 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
}
else if (hashAlgorithm == HashAlgorithmName.SHA1)
{
if (hash.Length != 20)
throw new ArgumentException("Invalid lenght of hash value");
pkcs1DigestInfo = new byte[] { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
}
else if (hashAlgorithm == HashAlgorithmName.SHA256)
{
if (hash.Length != 32)
throw new ArgumentException("Invalid lenght of hash value");
pkcs1DigestInfo = new byte[] { 0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
}
else if (hashAlgorithm == HashAlgorithmName.SHA384)
{
if (hash.Length != 48)
throw new ArgumentException("Invalid lenght of hash value");
pkcs1DigestInfo = new byte[] { 0x30, 0x41, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
}
else if (hashAlgorithm == HashAlgorithmName.SHA512)
{
if (hash.Length != 64)
throw new ArgumentException("Invalid lenght of hash value");
pkcs1DigestInfo = new byte[] { 0x30, 0x51, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
}
return pkcs1DigestInfo;
}
}
}
2020 年 11 月更新:
这里有一个 link 示例,描述了如何使用最新的 Azure SDK 客户端库执行此操作:
How to encrypt and decrypt a single block of plain text with an RSA key
请注意,它还描述了使用 DefaultAzureCredential 进行身份验证,这比以前的回调模式使用起来更简单。
还有一个迁移指南,比较了如何使用旧客户端 here。
是否可以使用存储在 Azure Key Vault 中的 X509 证书创建 RSA-SHA1 签名? [不可否认证书]
不幸的是,我无法将哈希算法更改为 SHA256 或更安全的算法,我确实需要将证书作为密钥存储在 Azure Key Vault 中。
到目前为止我已经试过了
await kvClient.SignAsync(keyVaultUrl, "RSNULL", digest); // digest = 20byte SHA1
await kvClient.SignAsync(keyVaultUrl, "RSNULL", ans1Digest); // asn1Digest = 35byte SHA1 wrapped in ANS1 structure
签名长度似乎是正确的(256 字节),但验证失败(在正确实现签名验证实现的节点上)。
我也试过像这样手动实现签名算法(使用 keyVault.EncryptAsync
):
- 数据>>计算SHA1摘要
- 将 SHA1 摘要包装在 ASN.1 结构中(连同 SHA1 对象标识符)
- 填充 ANS.1 结构,使摘要匹配证书的密钥长度
- 使用RSA1_5>>签名 加密填充的ANS1.structure
我一定是做错了什么。不确定是否需要所有步骤。
如果 Microsoft 的任何人阅读此文。即使 SHA1 签名被认为不安全,您能否实施它?漂亮,请在上面加糖:-)
我的盲目 "professional" 意见是您没有正确构造 PKCS#1 DigestInfo 结构。以下控制台应用程序对 SHA1 和 SHA256 算法都适用(没有测试其他算法):
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.KeyVault;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
namespace AzureKeyVaultTestApp1
{
static class Program
{
static HashAlgorithmName _hashAlg = HashAlgorithmName.SHA1;
static string _clientId = "00000000-0000-0000-0000-000000000000";
static string _clientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
static string _certId = "https://XXXXXXXX.vault.azure.net/certificates/TestCert1/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
static string _keyId = "https://XXXXXXXX.vault.azure.net/keys/TestCert1/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
static async Task<string> AuthenticationCallback(string authority, string resource, string scope)
{
var context = new AuthenticationContext(authority);
var result = await context.AcquireTokenAsync(resource, new ClientCredential(_clientId, _clientSecret));
return result.AccessToken;
}
static async Task Main(string[] args)
{
KeyVaultClient client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(AuthenticationCallback));
// Compute digest of data
byte[] dataToSign = Encoding.ASCII.GetBytes("Hello world!");
byte[] hash = HashAlgorithm.Create(_hashAlg.Name).ComputeHash(dataToSign);
// Construct DER encoded PKCS#1 DigestInfo structure defined in RFC 8017
byte[] pkcs1DigestInfo = CreatePkcs1DigestInfo(hash, _hashAlg);
// Sign digest with private key
var keyOperationResult = await client.SignAsync(_keyId, "RSNULL", pkcs1DigestInfo).ConfigureAwait(false);
byte[] signature = keyOperationResult.Result;
// Get public key from certificate
var certBundle = await client.GetCertificateAsync(_certId).ConfigureAwait(false);
X509Certificate2 cert = new X509Certificate2(certBundle.Cer);
RSA rsaPubKey = cert.GetRSAPublicKey();
// Verify digest signature with public key
if (!rsaPubKey.VerifyHash(hash, signature, _hashAlg, RSASignaturePadding.Pkcs1))
throw new Exception("Invalid signature");
}
private static byte[] CreatePkcs1DigestInfo(byte[] hash, HashAlgorithmName hashAlgorithm)
{
if (hash == null || hash.Length == 0)
throw new ArgumentNullException(nameof(hash));
byte[] pkcs1DigestInfo = null;
if (hashAlgorithm == HashAlgorithmName.MD5)
{
if (hash.Length != 16)
throw new ArgumentException("Invalid lenght of hash value");
pkcs1DigestInfo = new byte[] { 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
}
else if (hashAlgorithm == HashAlgorithmName.SHA1)
{
if (hash.Length != 20)
throw new ArgumentException("Invalid lenght of hash value");
pkcs1DigestInfo = new byte[] { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
}
else if (hashAlgorithm == HashAlgorithmName.SHA256)
{
if (hash.Length != 32)
throw new ArgumentException("Invalid lenght of hash value");
pkcs1DigestInfo = new byte[] { 0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
}
else if (hashAlgorithm == HashAlgorithmName.SHA384)
{
if (hash.Length != 48)
throw new ArgumentException("Invalid lenght of hash value");
pkcs1DigestInfo = new byte[] { 0x30, 0x41, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
}
else if (hashAlgorithm == HashAlgorithmName.SHA512)
{
if (hash.Length != 64)
throw new ArgumentException("Invalid lenght of hash value");
pkcs1DigestInfo = new byte[] { 0x30, 0x51, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
}
return pkcs1DigestInfo;
}
}
}
2020 年 11 月更新:
这里有一个 link 示例,描述了如何使用最新的 Azure SDK 客户端库执行此操作:
How to encrypt and decrypt a single block of plain text with an RSA key
请注意,它还描述了使用 DefaultAzureCredential 进行身份验证,这比以前的回调模式使用起来更简单。
还有一个迁移指南,比较了如何使用旧客户端 here。