如何使用私钥进行身份验证 - Walmart Affiliate
How to authenticate using private key - Walmart Affiliate
我正在尝试使用 Walmart Affiliate API,它使用 public/private 令牌进行身份验证。我无法弄清楚我在步骤 provided.
中遗漏了什么
我目前有一个 DelegatingHandler
来添加所需的 Headers 值。我正在使用 BouncyCastle 来帮助进行私人令牌签名,这就是我目前所拥有的。
public static string Generate(string version, string consumerId, string timestamp)
{
// Canonicalize the headers, following after the java code in the docs.
string[] canonicalStrings = Canonicalize(version, consumerId, timestamp);
// Read the file with the password protected private key
StreamReader stream= new StreamReader(@"..\key");
PasswordFinder finder = new PasswordFinder("1234");
// Actually get the private key
PemReader pemReader= new PemReader(stream, finder);
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
RSAParameters rsa = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)keyPair.Private);
// Create the RSA Provider and import the private key
RSACryptoServiceProvider provider = new RSACryptoServiceProvider(2048);
provider.ImportParameters(rsa);
// Sign the canonicalized data
byte[] signedData = provider.SignData(Encoding.UTF8.GetBytes(canonicalStrings[1]), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
// Convert the bytes to a base-64 string.
return Convert.ToBase64String(signedData);
}
private static string[] Canonicalize(string version, string consumerId, string timestamp)
{
// Follow after the java code, which just orders the keys/values.
StringBuilder keyBuilder = new StringBuilder();
StringBuilder valueBuilder = new StringBuilder();
SortedDictionary<string, string> dictionary = new SortedDictionary<string, string>() { { Constants.HEADER_COMSUMER_ID, consumerId }, { Constants.HEADER_TIMESTAMP, timestamp }, { Constants.HEADER_KEY_VERSION, version } };
foreach (string key in dictionary.Keys)
{
keyBuilder.Append($"{key.Trim()};");
valueBuilder.AppendLine($"{dictionary[key].Trim()}");
}
return new string[] {keyBuilder.ToString(), valueBuilder.ToString()};
}
这是通过我的 DelegatingHandler
调用的:
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string version = _walmartConfig.CurrentValue.Version; // Get Version from config
string consumerId = _walmartConfig.CurrentValue.StageConsumerId; // Get ConsumerID from config
string timestamp = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
string signature = Generator.Generate(version, consumerId, timestamp); // Generate signature
request.Headers.Add(Constants.HEADER_KEY_VERSION, version);
request.Headers.Add(Constants.HEADER_COMSUMER_ID, consumerId);
request.Headers.Add(Constants.HEADER_TIMESTAMP, timestamp);
request.Headers.Add(Constants.HEADER_SIGNATURE, signature);
return base.SendAsync(request, cancellationToken);
}
它是通过 docs 中提到的示例调用启动的:
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://developer.api.walmart.com/api-proxy/service/affil/product/v2/taxonomy"))
{
HttpResponseMessage response = await _client.SendAsync(request); // This returns HTTP 401.
return response.Content.ToString();
}
我的私钥是按照 Windows 中提到的 here 的步骤生成的,但我使用 PuTTy 菜单项导出了私钥:转换 -> 导出 OpenSSH 密钥
私钥文件类似于:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,F014B20CAD95382A
0CE3...
-----END RSA PRIVATE KEY-----
我认为我正确地遵循了指南,但我仍然从他们的 API 那里收到 HTTP 401。谁能找出我做错了什么?
我最终主要通过 unix 终端使用 OpenSSL 密钥创建解决了这个问题,但如果它对其他人有帮助,这就是最终产品。
用法:
string signature = Signer.SignData(Signer.Canonicalize(version, consumerId, timestamp)[1], _keyManager.Key);
_keyManager.Key
是使用BouncyCastle读取密码保护的私钥找到的
StreamReader sr = File.OpenText("c:\key.pem");
PemReader pr = new PemReader(sr, new PasswordFinder("123"));
RsaPrivateCrtKeyParameters keyPair = pr.ReadObject() as RsaPrivateCrtKeyParameters;
return DotNetUtilities.ToRSAParameters(keyPair);
终于 Signer.SignData
实施。
public static string SignData(string message, RSAParameters privateKey)
{
byte[] signedBytes;
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
byte[] originalBytes = Encoding.UTF8.GetBytes(message);
try
{
rsa.ImportParameters(privateKey);
signedBytes = rsa.SignData(originalBytes, CryptoConfig.MapNameToOID("SHA256"));
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return null;
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
return Convert.ToBase64String(signedBytes);
}
为了确认它是否有效,我使用了 public 键来验证。 public 密钥的获取与私有密钥类似。
public static bool Verify(string originalData, string base64SignedData, RSAParameters publicKey)
{
bool success = false;
byte[] signedBytes = Convert.FromBase64String(base64SignedData);
byte[] bytesToVerify = Encoding.UTF8.GetBytes(originalData);
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
try
{
rsa.ImportParameters(publicKey);
SHA256 sha256 = new SHA256Managed();
byte[] hashedData = sha256.ComputeHash(signedBytes);
success = rsa.VerifyData(bytesToVerify, CryptoConfig.MapNameToOID("SHA256"), signedBytes);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return false;
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
return success;
}
将它们放在一起作为测试:
public void SignTest()
{
// Arrange
string version = "1";
string consumerId = "8644d500-eyue-47gh-9b2b-54d5a4b9d45t";
string timestamp = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
StreamReader sr = File.OpenText(@"C:\privateKey.pem");
PemReader pr = new PemReader(sr, new PasswordFinder("123"));
RsaPrivateCrtKeyParameters keyPair = (RsaPrivateCrtKeyParameters)pr.ReadObject();
RSAParameters rsaPrivateParameters = DotNetUtilities.ToRSAParameters(keyPair);
StreamReader sr2 = File.OpenText(@"C:\publicKey.pem");
PemReader pr2 = new PemReader(sr2, new PasswordFinder("123"));
var keyPair2 = pr2.ReadObject();
RSAParameters rsaPublicParameters = DotNetUtilities.ToRSAParameters((RsaKeyParameters)keyPair2);
string[] canonicalForm = Signer.Canonicalize(version, consumerId, timestamp);
// Act
string signedData = Signer.SignData(canonicalForm[1], rsaPrivateParameters);
bool validated = Signer.Verify(canonicalForm[1], signedData, rsaPublicParameters);
// Assert
Assert.True(validated);
}
我正在尝试使用 Walmart Affiliate API,它使用 public/private 令牌进行身份验证。我无法弄清楚我在步骤 provided.
中遗漏了什么我目前有一个 DelegatingHandler
来添加所需的 Headers 值。我正在使用 BouncyCastle 来帮助进行私人令牌签名,这就是我目前所拥有的。
public static string Generate(string version, string consumerId, string timestamp)
{
// Canonicalize the headers, following after the java code in the docs.
string[] canonicalStrings = Canonicalize(version, consumerId, timestamp);
// Read the file with the password protected private key
StreamReader stream= new StreamReader(@"..\key");
PasswordFinder finder = new PasswordFinder("1234");
// Actually get the private key
PemReader pemReader= new PemReader(stream, finder);
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
RSAParameters rsa = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)keyPair.Private);
// Create the RSA Provider and import the private key
RSACryptoServiceProvider provider = new RSACryptoServiceProvider(2048);
provider.ImportParameters(rsa);
// Sign the canonicalized data
byte[] signedData = provider.SignData(Encoding.UTF8.GetBytes(canonicalStrings[1]), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
// Convert the bytes to a base-64 string.
return Convert.ToBase64String(signedData);
}
private static string[] Canonicalize(string version, string consumerId, string timestamp)
{
// Follow after the java code, which just orders the keys/values.
StringBuilder keyBuilder = new StringBuilder();
StringBuilder valueBuilder = new StringBuilder();
SortedDictionary<string, string> dictionary = new SortedDictionary<string, string>() { { Constants.HEADER_COMSUMER_ID, consumerId }, { Constants.HEADER_TIMESTAMP, timestamp }, { Constants.HEADER_KEY_VERSION, version } };
foreach (string key in dictionary.Keys)
{
keyBuilder.Append($"{key.Trim()};");
valueBuilder.AppendLine($"{dictionary[key].Trim()}");
}
return new string[] {keyBuilder.ToString(), valueBuilder.ToString()};
}
这是通过我的 DelegatingHandler
调用的:
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string version = _walmartConfig.CurrentValue.Version; // Get Version from config
string consumerId = _walmartConfig.CurrentValue.StageConsumerId; // Get ConsumerID from config
string timestamp = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
string signature = Generator.Generate(version, consumerId, timestamp); // Generate signature
request.Headers.Add(Constants.HEADER_KEY_VERSION, version);
request.Headers.Add(Constants.HEADER_COMSUMER_ID, consumerId);
request.Headers.Add(Constants.HEADER_TIMESTAMP, timestamp);
request.Headers.Add(Constants.HEADER_SIGNATURE, signature);
return base.SendAsync(request, cancellationToken);
}
它是通过 docs 中提到的示例调用启动的:
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://developer.api.walmart.com/api-proxy/service/affil/product/v2/taxonomy"))
{
HttpResponseMessage response = await _client.SendAsync(request); // This returns HTTP 401.
return response.Content.ToString();
}
我的私钥是按照 Windows 中提到的 here 的步骤生成的,但我使用 PuTTy 菜单项导出了私钥:转换 -> 导出 OpenSSH 密钥
私钥文件类似于:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,F014B20CAD95382A
0CE3...
-----END RSA PRIVATE KEY-----
我认为我正确地遵循了指南,但我仍然从他们的 API 那里收到 HTTP 401。谁能找出我做错了什么?
我最终主要通过 unix 终端使用 OpenSSL 密钥创建解决了这个问题,但如果它对其他人有帮助,这就是最终产品。
用法:
string signature = Signer.SignData(Signer.Canonicalize(version, consumerId, timestamp)[1], _keyManager.Key);
_keyManager.Key
是使用BouncyCastle读取密码保护的私钥找到的
StreamReader sr = File.OpenText("c:\key.pem");
PemReader pr = new PemReader(sr, new PasswordFinder("123"));
RsaPrivateCrtKeyParameters keyPair = pr.ReadObject() as RsaPrivateCrtKeyParameters;
return DotNetUtilities.ToRSAParameters(keyPair);
终于 Signer.SignData
实施。
public static string SignData(string message, RSAParameters privateKey)
{
byte[] signedBytes;
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
byte[] originalBytes = Encoding.UTF8.GetBytes(message);
try
{
rsa.ImportParameters(privateKey);
signedBytes = rsa.SignData(originalBytes, CryptoConfig.MapNameToOID("SHA256"));
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return null;
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
return Convert.ToBase64String(signedBytes);
}
为了确认它是否有效,我使用了 public 键来验证。 public 密钥的获取与私有密钥类似。
public static bool Verify(string originalData, string base64SignedData, RSAParameters publicKey)
{
bool success = false;
byte[] signedBytes = Convert.FromBase64String(base64SignedData);
byte[] bytesToVerify = Encoding.UTF8.GetBytes(originalData);
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
try
{
rsa.ImportParameters(publicKey);
SHA256 sha256 = new SHA256Managed();
byte[] hashedData = sha256.ComputeHash(signedBytes);
success = rsa.VerifyData(bytesToVerify, CryptoConfig.MapNameToOID("SHA256"), signedBytes);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return false;
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
return success;
}
将它们放在一起作为测试:
public void SignTest()
{
// Arrange
string version = "1";
string consumerId = "8644d500-eyue-47gh-9b2b-54d5a4b9d45t";
string timestamp = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
StreamReader sr = File.OpenText(@"C:\privateKey.pem");
PemReader pr = new PemReader(sr, new PasswordFinder("123"));
RsaPrivateCrtKeyParameters keyPair = (RsaPrivateCrtKeyParameters)pr.ReadObject();
RSAParameters rsaPrivateParameters = DotNetUtilities.ToRSAParameters(keyPair);
StreamReader sr2 = File.OpenText(@"C:\publicKey.pem");
PemReader pr2 = new PemReader(sr2, new PasswordFinder("123"));
var keyPair2 = pr2.ReadObject();
RSAParameters rsaPublicParameters = DotNetUtilities.ToRSAParameters((RsaKeyParameters)keyPair2);
string[] canonicalForm = Signer.Canonicalize(version, consumerId, timestamp);
// Act
string signedData = Signer.SignData(canonicalForm[1], rsaPrivateParameters);
bool validated = Signer.Verify(canonicalForm[1], signedData, rsaPublicParameters);
// Assert
Assert.True(validated);
}