> I'm only using URLEncoder because Strings2 does surprising things.

Makes sense, thanks for the research and explanation. Now that this logic has got bigger, perhaps pull it out as a separate method?
```
HttpRequest.Builder<?> builder = ...
String stringToSign = authSigner.createStringToSign(builder.build());
HttpRequest ret = builder.addQueryParam(HttpHeaders.EXPIRES, expiration)
          .addQueryParam("AWSAccessKeyId", identity)
         // Signature MUST be the last parameter because if it isn't, even encoded '+' values in the
         // signature will be converted to a space by a subsequent addQueryParameter.
         // See HttpRequestTest.testAddBase64AndUrlEncodedQueryParams for more details.
         .addQueryParam(TEMPORARY_SIGNATURE_PARAM, createEncodedSignature(stringToSign))
         .build();
...
private static createEncodedSignature(String stringToSign) {
   // We MUST encode the signature because addQueryParam internally _always_ decodes values
   ...
   String signature = authSigner.sign(stringToSign);
   try {
     return URLEncoder.encode(signature, Charsets.UTF_8.name());
   } catch (UnsupportedEncodingException e) {
     throw new IllegalStateException("Bad encoding on input: " + signature, e);
   }
}
```