Signature V4 sorting query string not implemented correctly
com.amazonaws.auth.AbstractAWSSigner.getCanonicalizedQueryString(Map<String, List<String>> parameters)
There is a comment in the method.
"Signing protocol expects the param values also to be sorted after url encoding in addition to sorted parameter names."
The implementation also follows the comment. But this doesn’t follow the Signature V4 specification. Sorting should be done before URL encoding, as in the comment in this method header.
"The canonicalized query string is formed by first sorting all the query string parameters, then URI encoding both the key and value and then joining them, in order, separating key value pairs with an '&'."
Hi @verandar, both comments are correct, one is about param values and the other about param names.
Are you having issues with signature V4? If so, can you provide a code example and stacktrace?
The comment in the method is wrong. There is no actual issue, but there are potential issues. For example, the following test fails.
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.junit.Test;
import com.amazonaws.util.SdkHttpUtils;
public class AWS4SignerTest {
@Test
public void test() {
String param = "param";
String value1 = "-";
String value2 = "{";
// value1 is less than value2
assertTrue(value1.compareTo(value2) < 0);
Map<String, List<String>> parameters = new HashMap<String, List<String>>();
parameters.put(param, Arrays.asList(value1, value2));
String encodedParam = SdkHttpUtils.urlEncode(param, false);
String expected = String.format("%s=%s&%s=%s",
encodedParam, SdkHttpUtils.urlEncode(value1, false),
encodedParam, SdkHttpUtils.urlEncode(value2, false));
// fail
assertEquals(expected, getCanonicalizedQueryString(parameters));
}
// Copied from com.amazonaws.auth.AbstractAWSSigner
/**
* Examines the specified query string parameters and returns a
* canonicalized form.
* <p>
* The canonicalized query string is formed by first sorting all the query
* string parameters, then URI encoding both the key and value and then
* joining them, in order, separating key value pairs with an '&'.
*
* @param parameters
* The query string parameters to be canonicalized.
*
* @return A canonicalized form for the specified query string parameters.
*/
protected String getCanonicalizedQueryString(Map<String, List<String>> parameters) {
final SortedMap<String, List<String>> sorted = new TreeMap<String, List<String>>();
/**
* Signing protocol expects the param values also to be sorted after url
* encoding in addition to sorted parameter names.
*/
for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
final String encodedParamName = SdkHttpUtils.urlEncode(
entry.getKey(), false);
final List<String> paramValues = entry.getValue();
final List<String> encodedValues = new ArrayList<String>(
paramValues.size());
for (String value : paramValues) {
encodedValues.add(SdkHttpUtils.urlEncode(value, false));
}
Collections.sort(encodedValues);
sorted.put(encodedParamName, encodedValues);
}
final StringBuilder result = new StringBuilder();
for(Map.Entry<String, List<String>> entry : sorted.entrySet()) {
for(String value : entry.getValue()) {
if (result.length() > 0) {
result.append("&");
}
result.append(entry.getKey())
.append("=")
.append(value);
}
}
return result.toString();
}
}
The point is that both parameter names and values should be sorted before URL encoding, not after URL encoding.
Implementation: It's wrong. Sorting both parameter names and values after URL encoding.
Method header comment: It's correct if "query string parameters" means that both parameter names and values
Method body comment: It's wrong.
I see what you are saying now @verandar, thank you for raising the issue.
The team will discuss internally and I will post here when I get an update.
Just wanted to comment to confirm we're still aware this is a potential issue. If anyone's being bitten by it, let us know via +1s on the original question and that'll help get it prioritized.
@verandar
We won't change this behavior before the upcoming v1 Maintenance Mode, so I'll close this issue.
I don't know if the documentation changed after this was reported, but the IAM User Guide mentions that the sorting occurs after the encoding:
CanonicalQueryString – The URI-encoded query string parameters. You URI-encode each name and values individually. You must also sort the parameters in the canonical query string alphabetically by key name. The sorting occurs after encoding.
Reference:
- Announcing end-of-support for AWS SDK for Java v1.x effective December 31, 2025 - blog post
This issue is now closed.
Comments on closed issues are hard for our team to see. If you need more assistance, please open a new issue that references this one.