Java转C#:MD5签名代码迁移、签名无效问题排查及排序优化
Looks like you're already halfway there—you figured out that sorting parameters by key was the missing piece, but you want to clean up the code by moving that sorting logic directly into your GenerateSign method instead of handling it manually every time. Let's break down how to align your C# code perfectly with the original Java implementation, and fix any subtle mismatches that might have caused the signature failure.
First, Match the Java Logic Step-by-Step
The original Java code does three critical things your initial C# code missed or handled differently:
- Sorts parameters by key (ordinal string order) before concatenation
- Does NOT URL-encode parameter values (your initial C# code was doing this, which would definitely break the signature)
- Concatenates all entries without extra null/empty filtering (unless your business requires it, the Java code doesn't include this)
Revised C# Code With Built-In Sorting
Here's the updated code that integrates key sorting directly into GenerateSign, and aligns 1:1 with the Java implementation:
using System.Text; using System.Security.Cryptography; using System.Collections.Generic; using System.Linq; // ... private static string GenerateSign(Dictionary<string, object> query, string apiSecret) { var sb = new StringBuilder(); // 1. Sort dictionary entries by key using ordinal order (matches Java's comparingByKey()) var sortedParams = query.OrderBy(kvp => kvp.Key, StringComparer.Ordinal); // 2. Concatenate key=value pairs separated by & (no URL encoding!) foreach (var kvp in sortedParams) { // Optional: Uncomment if you need to skip null/empty values (match your initial logic) // if (string.IsNullOrWhiteSpace(kvp.Value?.ToString())) // continue; if (sb.Length > 0) sb.Append('&'); sb.Append(kvp.Key) .Append('=') .Append(kvp.Value?.ToString() ?? string.Empty); } // 3. Append the required api_secret parameter if (sb.Length > 0) sb.Append('&'); sb.Append("api_secret=").Append(apiSecret); return sb.ToString(); } private static string Sign(string source) { using var md5 = MD5.Create(); var sourceBytes = Encoding.UTF8.GetBytes(source); var hash = md5.ComputeHash(sourceBytes); // Convert bytes to lowercase hex string, mirroring Java's exact logic var output = new StringBuilder(hash.Length * 2); foreach (byte b in hash) { int current = b & 0xff; if (current < 16) output.Append('0'); output.Append(current.ToString("x")); // "x" ensures lowercase hex } return output.ToString(); }
Key Fixes & Explanations
- Built-in sorting: The
OrderBy(kvp => kvp.Key, StringComparer.Ordinal)uses the same ordinal, culture-insensitive sorting as Java'sMap.Entry.comparingByKey(), so you no longer need to sort the dictionary manually before calling the method. - Removed URL encoding: The original Java code doesn't encode parameter values, so removing
HttpUtility.UrlEncodeensures the input string to MD5 matches exactly what the Java code uses. - Exact hex conversion: While your original
Signmethod worked, the revised byte-to-hex logic mirrors Java's implementation perfectly, eliminating any potential edge cases (though both approaches produce the same result here). - Optional null handling: If you need to skip empty/null values like your initial code did, uncomment the filtering check. Just make sure this matches the Java code's behavior—if Java includes empty values, remove the check entirely.
How to Use It
Now you can call the methods directly without pre-sorting the dictionary:
var @params = new Dictionary<string, object> { { "api_key", _apiKey }, { "req_time", now }, { "op", "sub.personal" } }; var signature = Sign(GenerateSign(@params, _apiSecret));
This should produce an identical signature to the Java code every time.
内容的提问来源于stack exchange,提问作者nop

