public URL getSignature(String base, Map<String, String> queryParams) {
    // Add the RTM specific query params to the map for signing
    Map<String, String> modifiedParams = new HashMap<>();
    modifiedParams.putAll(queryParams);
    modifiedParams.put("api_key", appCredentials.getKey());
    modifiedParams.put("auth_token", authToken);

    List<String> orderedKeys = modifiedParams.keySet().stream().collect(Collectors.toList());
    Collections.sort(orderedKeys);

    List<String> queryParamStrings = new ArrayList<>();
    StringBuilder resultBuilder = new StringBuilder();

    resultBuilder.append(appCredentials.getSecret());
    for (String key : orderedKeys) {
      // trim all keys and values from whitespace - We don't want to escape all whitespace values,
      // because the RTM endpoint will generate the signature with the unescaped whitespace and
      // compare that to the signature generated.
      String k = key.trim();
      String v = modifiedParams.get(key).trim();

      resultBuilder.append(k).append(v);
      queryParamStrings.add(k + "=" + v);
    }

    try {
      MessageDigest md = MessageDigest.getInstance("MD5");
      byte[] thedigest = md.digest(resultBuilder.toString().getBytes(StandardCharsets.UTF_8));
      String signature = BaseEncoding.base16().encode(thedigest).toLowerCase();
      return new URL(base + "?" + String.join("&", queryParamStrings) + "&api_sig=" + signature);
    } catch (NoSuchAlgorithmException e) {
      throw new IllegalStateException("Couldn't find MD5 hash", e);
    } catch (MalformedURLException e) {
      throw new IllegalArgumentException("Couldn't parse authUrl", e);
    }
  }
Generates signatures hash based on the algorithm described:
https://www.rememberthemilk.com/services/api/authentication.rtm
extensions/data-transfer/portability-data-transfer-rememberthemilk/src/main/java/org/datatransferproject/transfer/rememberthemilk/tasks/RememberTheMilkSignatureGenerator.java