static String createAuthorizationSignature(
            HttpServletRequest request, String uri, String credential,
            boolean queryAuth, boolean bothDateHeader) {
        // sort Amazon headers
        SortedSetMultimap<String, String> canonicalizedHeaders =
                TreeMultimap.create();
        for (String headerName : Collections.list(request.getHeaderNames())) {
            Collection<String> headerValues = Collections.list(
                    request.getHeaders(headerName));
            headerName = headerName.toLowerCase();
            if (!headerName.startsWith("x-amz-") || (bothDateHeader &&
                  headerName.equalsIgnoreCase(AwsHttpHeaders.DATE))) {
                continue;
            }
            if (headerValues.isEmpty()) {
                canonicalizedHeaders.put(headerName, "");
            }
            for (String headerValue : headerValues) {
                canonicalizedHeaders.put(headerName,
                        Strings.nullToEmpty(headerValue));
            }
        }

        // Build string to sign
        var builder = new StringBuilder()
                .append(request.getMethod())
                .append('\n')
                .append(Strings.nullToEmpty(request.getHeader(
                        HttpHeaders.CONTENT_MD5)))
                .append('\n')
                .append(Strings.nullToEmpty(request.getHeader(
                        HttpHeaders.CONTENT_TYPE)))
                .append('\n');
        String expires = request.getParameter("Expires");
        if (queryAuth) {
            // If expires is not nil, then it is query string sign
            // If expires is nil, maybe also query string sign
            // So should check other accessid param, presign to judge.
            // not the expires
            builder.append(Strings.nullToEmpty(expires));
        }  else {
            if (!bothDateHeader) {
                if (canonicalizedHeaders.containsKey(AwsHttpHeaders.DATE)) {
                    builder.append("");
                } else {
                    builder.append(request.getHeader(HttpHeaders.DATE));
                }
            }  else {
                if (!canonicalizedHeaders.containsKey(AwsHttpHeaders.DATE)) {
                    builder.append(request.getHeader(AwsHttpHeaders.DATE));
                }  else {
                    // panic
                }
            }
        }

        builder.append('\n');
        for (var entry : canonicalizedHeaders.entries()) {
            builder.append(entry.getKey()).append(':')
                    .append(entry.getValue()).append('\n');
        }
        builder.append(uri);

        char separator = '?';
        List<String> subresources = Collections.list(
                request.getParameterNames());
        Collections.sort(subresources);
        for (String subresource : subresources) {
            if (SIGNED_SUBRESOURCES.contains(subresource)) {
                builder.append(separator).append(subresource);

                String value = request.getParameter(subresource);
                if (!"".equals(value)) {
                    builder.append('=').append(value);
                }
                separator = '&';
            }
        }

        String stringToSign = builder.toString();
        logger.trace("stringToSign: {}", stringToSign);

        // Sign string
        Mac mac;
        try {
            mac = Mac.getInstance("HmacSHA1");
            mac.init(new SecretKeySpec(credential.getBytes(
                    StandardCharsets.UTF_8), "HmacSHA1"));
        } catch (InvalidKeyException | NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        return Base64.getEncoder().encodeToString(mac.doFinal(
                stringToSign.getBytes(StandardCharsets.UTF_8)));
    }
Create Amazon V2 signature.  Reference:
http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
src/main/java/org/gaul/s3proxy/AwsSignature.java