The Problem
When working with API integrations, signature generation and verification become repetitive tasks. Initially, implementing signatures on a per-endpoint basis leads to sctatered, hard-to-maintain code. A better approach involves creating a reusable utility that handles signature generation consistently across different endpoints.
Design Approach
A fluent interface enables dynamic composition of signature steps. Each operation—such as sorting parameters, encoding values, or applying hashing algorithms—chains together through method calls that modify an internal state.
Usage Examples
var signSource = new Dictionary<string, string>
{
{ "param1", "1" },
{ "param3", "3+" },
{ "param2", "2" }
};
var builder = new HttpSignatureBuilder();
builder.SetSource(signSource);
// Configure sorting and formatting rules
builder.SetSource(signSource, options =>
{
options.SortByKey = true;
options.EncodeValues = false;
options.IncludeKeys = true;
options.KeyValueDelimiter = "=";
options.ItemDelimiter = "&";
options.Encoding = Encoding.UTF8;
});
// With this configuration, the signature base becomes: param1=1¶m2=2¶m3=3
// Add prefix and suffix
builder.SetSource(signSource).AddPrefix("TestPrefix").AddSuffix("TestSuffix");
// Apply transformations
builder.SetSource(signSource).ApplyBase64();
builder.SetSource(signSource).ApplyMD5(lowerCase: true);
builder.SetSource(signSource).ApplySHA1(lowerCase: true);
// Retrieve the result
string signature = builder.SetSource(signSource).Build();
// Chain multiple transformations
string signature = builder.SetSource(signSource).ApplyBase64().ApplyMD5().ApplySHA1();
Implementation
SignatureEntry
Represents an individual parameter in the signature construction.
namespace SignatureLibrary.HttpSign
{
internal class SignatureEntry
{
public string Key { get; }
public string Value { get; }
public SignatureEntry(string key, string value)
{
Key = key;
Value = value;
}
}
}
SignatureOptions
Defines how the signature base string gets constructed and transformed.
using System.Text;
namespace SignatureLibrary.HttpSign
{
public class SignatureOptions
{
public bool SortByKey { get; set; } = false;
public bool EncodeValues { get; set; } = false;
public bool IncludeKeys { get; set; } = true;
public string KeyValueDelimiter { get; set; } = "=";
public string ItemDelimiter { get; set; } = "&";
public Encoding Encoding { get; set; } = Encoding.UTF8;
}
}
HttpSignatureBuilder
The core component that orchestrates signature creation through method chaining.
using System;
using System.Collections.Generic;
using System.Linq;
namespace SignatureLibrary.HttpSign
{
public class HttpSignatureBuilder
{
private readonly SignatureOptions _options = new SignatureOptions();
private string _signatureBuffer;
public HttpSignatureBuilder SetSource(Dictionary<string, string> source, Action<SignatureOptions> configure = null)
{
configure?.Invoke(_options);
if (source == null || source.Count == 0)
{
throw new ArgumentException("Source data cannot be null or empty", nameof(source));
}
var entries = new List<SignatureEntry>(source.Count);
foreach (var kvp in source)
{
var value = kvp.Value;
if (_options.EncodeValues)
{
value = System.Web.HttpUtility.UrlEncode(value, _options.Encoding);
}
entries.Add(new SignatureEntry(kvp.Key, value));
}
if (_options.SortByKey)
{
entries = entries.OrderBy(e => e.Key).ToList();
}
if (_options.IncludeKeys)
{
_signatureBuffer = string.Join(
_options.ItemDelimiter,
entries.Select(e => $"{e.Key}{_options.KeyValueDelimiter}{e.Value}"));
}
else
{
_signatureBuffer = string.Join(_options.ItemDelimiter, entries.Select(e => e.Value));
}
return this;
}
public HttpSignatureBuilder AddPrefix(string value)
{
_signatureBuffer = $"{value}{_signatureBuffer}";
return this;
}
public HttpSignatureBuilder AddSuffix(string value)
{
_signatureBuffer = $"{_signatureBuffer}{value}";
return this;
}
public HttpSignatureBuilder ApplyUrlEncode()
{
_signatureBuffer = System.Web.HttpUtility.UrlEncode(_signatureBuffer, _options.Encoding);
return this;
}
public HttpSignatureBuilder ApplyBase64()
{
_signatureBuffer = Base64Encoder.Encode(_signatureBuffer);
return this;
}
public HttpSignatureBuilder ApplyMD5(bool lowerCase = true)
{
_signatureBuffer = lowerCase
? MD5Encoder.ToLowerHex(_signatureBuffer)
: MD5Encoder.ToUpperHex(_signatureBuffer);
return this;
}
public HttpSignatureBuilder ApplySHA1(bool lowerCase = true)
{
_signatureBuffer = lowerCase
? SHA1Encoder.ToLowerHex(_signatureBuffer)
: SHA1Encoder.ToUpperHex(_signatureBuffer);
return this;
}
public string Build()
{
return _signatureBuffer;
}
}
}