|
|
@@ -0,0 +1,408 @@
|
|
|
+package cn.reghao.oss.common;
|
|
|
+
|
|
|
+import javax.crypto.Mac;
|
|
|
+import javax.crypto.spec.SecretKeySpec;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.security.MessageDigest;
|
|
|
+import java.security.NoSuchAlgorithmException;
|
|
|
+import java.text.DateFormat;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Example: Signing AWS Requests with Signature Version 4 in Java.
|
|
|
+ *
|
|
|
+ * @reference: http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
|
|
|
+ * @author javaQuery
|
|
|
+ * @date 19th January, 2016
|
|
|
+ * @Github: https://github.com/javaquery/Examples
|
|
|
+ */
|
|
|
+public class AWS4Signer {
|
|
|
+ private AWS4Signer() {
|
|
|
+ }
|
|
|
+
|
|
|
+ public static class Builder {
|
|
|
+
|
|
|
+ private String accessKeyID;
|
|
|
+ private String secretAccessKey;
|
|
|
+ private String regionName;
|
|
|
+ private String serviceName;
|
|
|
+ private String xAmzDate;
|
|
|
+ private String httpMethodName;
|
|
|
+ private String canonicalURI;
|
|
|
+ private TreeMap<String, String> queryParametes;
|
|
|
+ private TreeMap<String, String> awsHeaders;
|
|
|
+ private byte[] payload;
|
|
|
+ private String sha256sum;
|
|
|
+
|
|
|
+ public Builder(String accessKeyID, String secretAccessKey) {
|
|
|
+ this.accessKeyID = accessKeyID;
|
|
|
+ this.secretAccessKey = secretAccessKey;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Builder regionName(String regionName) {
|
|
|
+ this.regionName = regionName;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Builder serviceName(String serviceName) {
|
|
|
+ this.serviceName = serviceName;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Builder xAmzDate(String xAmzDate) {
|
|
|
+ this.xAmzDate = xAmzDate;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Builder httpMethodName(String httpMethodName) {
|
|
|
+ this.httpMethodName = httpMethodName;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Builder canonicalURI(String canonicalURI) {
|
|
|
+ this.canonicalURI = canonicalURI;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Builder queryParametes(TreeMap<String, String> queryParametes) {
|
|
|
+ this.queryParametes = queryParametes;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Builder awsHeaders(TreeMap<String, String> awsHeaders) {
|
|
|
+ this.awsHeaders = awsHeaders;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Builder payload(byte[] payload) {
|
|
|
+ this.payload = payload;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Builder sha256sum(String sha256sum) {
|
|
|
+ this.sha256sum = sha256sum;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public AWS4Signer build() {
|
|
|
+ return new AWS4Signer(this);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String accessKeyID;
|
|
|
+ private String secretAccessKey;
|
|
|
+ private String regionName;
|
|
|
+ private String serviceName;
|
|
|
+ private String httpMethodName;
|
|
|
+ private String canonicalURI;
|
|
|
+ private TreeMap<String, String> queryParametes;
|
|
|
+ private TreeMap<String, String> awsHeaders;
|
|
|
+ private byte[] payload;
|
|
|
+ private String sha256sum;
|
|
|
+
|
|
|
+ /* Other variables */
|
|
|
+ private final String HMACAlgorithm = "AWS4-HMAC-SHA256";
|
|
|
+ private final String aws4Request = "aws4_request";
|
|
|
+ private String strSignedHeader;
|
|
|
+ private String xAmzDate;
|
|
|
+ private String currentDate;
|
|
|
+
|
|
|
+ private AWS4Signer(Builder builder) {
|
|
|
+ accessKeyID = builder.accessKeyID;
|
|
|
+ secretAccessKey = builder.secretAccessKey;
|
|
|
+ regionName = builder.regionName;
|
|
|
+ serviceName = builder.serviceName;
|
|
|
+ httpMethodName = builder.httpMethodName;
|
|
|
+ canonicalURI = builder.canonicalURI;
|
|
|
+ queryParametes = builder.queryParametes;
|
|
|
+ awsHeaders = builder.awsHeaders;
|
|
|
+ payload = builder.payload;
|
|
|
+ sha256sum = builder.sha256sum;
|
|
|
+
|
|
|
+ /* Get current timestamp value.(UTC) */
|
|
|
+ //xAmzDate = getTimeStamp();
|
|
|
+ xAmzDate = builder.xAmzDate;
|
|
|
+ currentDate = getDate();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Task 1: Create a Canonical Request for Signature Version 4.
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String prepareCanonicalRequest() {
|
|
|
+ StringBuilder canonicalURL = new StringBuilder("");
|
|
|
+
|
|
|
+ /* Step 1.1 Start with the HTTP request method (GET, PUT, POST, etc.), followed by a newline character. */
|
|
|
+ canonicalURL.append(httpMethodName).append("\n");
|
|
|
+
|
|
|
+ /* Step 1.2 Add the canonical URI parameter, followed by a newline character. */
|
|
|
+ canonicalURI = canonicalURI == null || canonicalURI.trim().isEmpty() ? "/" : canonicalURI;
|
|
|
+ canonicalURL.append(canonicalURI).append("\n");
|
|
|
+
|
|
|
+ /* Step 1.3 Add the canonical query string, followed by a newline character. */
|
|
|
+ StringBuilder queryString = new StringBuilder("");
|
|
|
+ if (queryParametes != null && !queryParametes.isEmpty()) {
|
|
|
+ for (Map.Entry<String, String> entrySet : queryParametes.entrySet()) {
|
|
|
+ String key = entrySet.getKey();
|
|
|
+ String value = entrySet.getValue();
|
|
|
+ queryString.append(key).append("=").append(encodeParameter(value)).append("&");
|
|
|
+ }
|
|
|
+
|
|
|
+ /* @co-author https://github.com/dotkebi @git #1 @date 16th March, 2017 */
|
|
|
+ queryString.deleteCharAt(queryString.lastIndexOf("&"));
|
|
|
+
|
|
|
+ queryString.append("\n");
|
|
|
+ } else {
|
|
|
+ queryString.append("\n");
|
|
|
+ }
|
|
|
+ canonicalURL.append(queryString);
|
|
|
+
|
|
|
+ /* Step 1.4 Add the canonical headers, followed by a newline character. */
|
|
|
+ StringBuilder signedHeaders = new StringBuilder("");
|
|
|
+ if (awsHeaders != null && !awsHeaders.isEmpty()) {
|
|
|
+ for (Map.Entry<String, String> entrySet : awsHeaders.entrySet()) {
|
|
|
+ String key = entrySet.getKey();
|
|
|
+ String value = entrySet.getValue();
|
|
|
+ signedHeaders.append(key).append(";");
|
|
|
+ canonicalURL.append(key).append(":").append(value).append("\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Note: Each individual header is followed by a newline character, meaning the complete list ends with a newline character. */
|
|
|
+ canonicalURL.append("\n");
|
|
|
+ } else {
|
|
|
+ canonicalURL.append("\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Step 1.5 Add the signed headers, followed by a newline character. */
|
|
|
+ strSignedHeader = signedHeaders.substring(0, signedHeaders.length() - 1); // Remove last ";"
|
|
|
+ canonicalURL.append(strSignedHeader).append("\n");
|
|
|
+
|
|
|
+ /* Step 1.6 Use a hash (digest) function like SHA256 to create a hashed value from the payload in the body of the HTTP or HTTPS. */
|
|
|
+ canonicalURL.append(sha256sum);
|
|
|
+ /*payload = payload == null ? "UNSIGNED-PAYLOAD".getBytes(StandardCharsets.UTF_8) : payload;
|
|
|
+ try {
|
|
|
+ String sha256Hex = DigestUtil.sha256sum(payload);
|
|
|
+ canonicalURL.append(sha256Hex);
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }*/
|
|
|
+
|
|
|
+ return canonicalURL.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Task 2: Create a String to Sign for Signature Version 4.
|
|
|
+ *
|
|
|
+ * @param canonicalURL
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String createStringToSign(String canonicalURL) {
|
|
|
+ String stringToSign = "";
|
|
|
+
|
|
|
+ /* Step 2.1 Start with the algorithm designation, followed by a newline character. */
|
|
|
+ stringToSign = HMACAlgorithm + "\n";
|
|
|
+
|
|
|
+ /* Step 2.2 Append the request date value, followed by a newline character. */
|
|
|
+ stringToSign += xAmzDate + "\n";
|
|
|
+
|
|
|
+ /* Step 2.3 Append the credential scope value, followed by a newline character. */
|
|
|
+ stringToSign += currentDate + "/" + regionName + "/" + serviceName + "/" + aws4Request + "\n";
|
|
|
+
|
|
|
+ /* Step 2.4 Append the hash of the canonical request that you created in Task 1: Create a Canonical Request for Signature Version 4. */
|
|
|
+ stringToSign += generateHex(canonicalURL);
|
|
|
+ return stringToSign;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Task 3: Calculate the AWS Signature Version 4.
|
|
|
+ *
|
|
|
+ * @param stringToSign
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String calculateSignature(String stringToSign) {
|
|
|
+ try {
|
|
|
+ /* Step 3.1 Derive your signing key */
|
|
|
+ byte[] signatureKey = getSignatureKey(secretAccessKey, currentDate, regionName, serviceName);
|
|
|
+
|
|
|
+ /* Step 3.2 Calculate the signature. */
|
|
|
+ byte[] signature = HmacSHA256(signatureKey, stringToSign);
|
|
|
+
|
|
|
+ /* Step 3.2.1 Encode signature (byte[]) to Hex */
|
|
|
+ String strHexSignature = bytesToHex(signature);
|
|
|
+ return strHexSignature;
|
|
|
+ } catch (Exception ex) {
|
|
|
+ ex.printStackTrace();
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Task 4: Add the Signing Information to the Request. We'll return Map of
|
|
|
+ * all headers put this headers in your request.
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public Map<String, String> getHeaders() {
|
|
|
+ awsHeaders.put("x-amz-date", xAmzDate);
|
|
|
+
|
|
|
+ /* Execute Task 1: Create a Canonical Request for Signature Version 4. */
|
|
|
+ String canonicalURL = prepareCanonicalRequest();
|
|
|
+
|
|
|
+ /* Execute Task 2: Create a String to Sign for Signature Version 4. */
|
|
|
+ String stringToSign = createStringToSign(canonicalURL);
|
|
|
+
|
|
|
+ /* Execute Task 3: Calculate the AWS Signature Version 4. */
|
|
|
+ String signature = calculateSignature(stringToSign);
|
|
|
+ if (signature != null) {
|
|
|
+ Map<String, String> header = new HashMap<>(0);
|
|
|
+ header.put("x-amz-date", xAmzDate);
|
|
|
+ header.put("Authorization", buildAuthorizationString(signature));
|
|
|
+ return header;
|
|
|
+ } else {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getSignature() {
|
|
|
+ /* Execute Task 1: Create a Canonical Request for Signature Version 4. */
|
|
|
+ String canonicalURL = prepareCanonicalRequest();
|
|
|
+
|
|
|
+ /* Execute Task 2: Create a String to Sign for Signature Version 4. */
|
|
|
+ String stringToSign = createStringToSign(canonicalURL);
|
|
|
+
|
|
|
+ /* Execute Task 3: Calculate the AWS Signature Version 4. */
|
|
|
+ String signature = calculateSignature(stringToSign);
|
|
|
+ return signature;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Build string for Authorization header.
|
|
|
+ *
|
|
|
+ * @param strSignature
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String buildAuthorizationString(String strSignature) {
|
|
|
+ return HMACAlgorithm + " "
|
|
|
+ + "Credential=" + accessKeyID + "/" + getDate() + "/" + regionName + "/" + serviceName + "/" + aws4Request + ","
|
|
|
+ + "SignedHeaders=" + strSignedHeader + ","
|
|
|
+ + "Signature=" + strSignature;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Generate Hex code of String.
|
|
|
+ *
|
|
|
+ * @param data
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String generateHex(String data) {
|
|
|
+ MessageDigest messageDigest;
|
|
|
+ try {
|
|
|
+ messageDigest = MessageDigest.getInstance("SHA-256");
|
|
|
+ messageDigest.update(data.getBytes(StandardCharsets.UTF_8));
|
|
|
+ byte[] digest = messageDigest.digest();
|
|
|
+ return String.format("%064x", new java.math.BigInteger(1, digest));
|
|
|
+ } catch (NoSuchAlgorithmException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Apply HmacSHA256 on data using given key.
|
|
|
+ *
|
|
|
+ * @param data
|
|
|
+ * @param key
|
|
|
+ * @return
|
|
|
+ * @throws Exception
|
|
|
+ * @reference:
|
|
|
+ * http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-java
|
|
|
+ */
|
|
|
+ private byte[] HmacSHA256(byte[] key, String data) throws Exception {
|
|
|
+ String algorithm = "HmacSHA256";
|
|
|
+ Mac mac = Mac.getInstance(algorithm);
|
|
|
+ mac.init(new SecretKeySpec(key, algorithm));
|
|
|
+ return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Generate AWS signature key.
|
|
|
+ *
|
|
|
+ * @param key
|
|
|
+ * @param date
|
|
|
+ * @param regionName
|
|
|
+ * @param serviceName
|
|
|
+ * @return
|
|
|
+ * @throws Exception
|
|
|
+ * @reference
|
|
|
+ * http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-java
|
|
|
+ */
|
|
|
+ private byte[] getSignatureKey(String key, String date, String regionName, String serviceName) throws Exception {
|
|
|
+ byte[] kSecret = ("AWS4" + key).getBytes(StandardCharsets.UTF_8);
|
|
|
+ byte[] kDate = HmacSHA256(kSecret, date);
|
|
|
+ byte[] kRegion = HmacSHA256(kDate, regionName);
|
|
|
+ byte[] kService = HmacSHA256(kRegion, serviceName);
|
|
|
+ byte[] kSigning = HmacSHA256(kService, aws4Request);
|
|
|
+ return kSigning;
|
|
|
+ }
|
|
|
+
|
|
|
+ final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Convert byte array to Hex
|
|
|
+ *
|
|
|
+ * @param bytes
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String bytesToHex(byte[] bytes) {
|
|
|
+ char[] hexChars = new char[bytes.length * 2];
|
|
|
+ for (int j = 0; j < bytes.length; j++) {
|
|
|
+ int v = bytes[j] & 0xFF;
|
|
|
+ hexChars[j * 2] = hexArray[v >>> 4];
|
|
|
+ hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
|
|
+ }
|
|
|
+ return new String(hexChars).toLowerCase();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get timestamp. yyyyMMdd'T'HHmmss'Z'
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String getTimeStamp() {
|
|
|
+ DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
|
|
|
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));//server timezone
|
|
|
+ return dateFormat.format(new Date());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get date. yyyyMMdd
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String getDate() {
|
|
|
+ DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
|
|
|
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));//server timezone
|
|
|
+ return dateFormat.format(new Date());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Using {@link URLEncoder#encode(String, String) } instead of
|
|
|
+ * {@link URLEncoder#encode(String) }
|
|
|
+ *
|
|
|
+ * @co-author https://github.com/dotkebi
|
|
|
+ * @date 16th March, 2017
|
|
|
+ * @git #1
|
|
|
+ * @param param
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String encodeParameter(String param){
|
|
|
+ try {
|
|
|
+ return URLEncoder.encode(param, StandardCharsets.UTF_8);
|
|
|
+ } catch (Exception e) {
|
|
|
+ return URLEncoder.encode(param, StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|