概要
http://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-signed-request-examples.html
で紹介されているpython実装をJavaに書き換えました
環境
- Java 1.8.0_25
ソースコード
Githubで公開しているので最新版はそちらを御覧ください
package com.kakakikikeke.sample.v4;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import com.kakakikikeke.sample.utils.Utils;
public class Signature4Creator {
static byte[] getHmacSHA256(String data, byte[] key) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return mac.doFinal(data.getBytes("UTF-8"));
}
static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) throws Exception {
byte[] kSecret = ("AWS4" + key).getBytes("UTF-8");
byte[] kDate = getHmacSHA256(dateStamp, kSecret);
byte[] kRegion = getHmacSHA256(regionName, kDate);
byte[] kService = getHmacSHA256(serviceName, kRegion);
byte[] kSigning = getHmacSHA256("aws4_request", kService);
return kSigning;
}
static String getHmacSHA256Digest(String data, byte[] key) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
byte[] digest = mac.doFinal(data.getBytes("UTF-8"));
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < digest.length; i++) {
String tmp = Integer.toHexString(digest[i] & 0xff);
if (tmp.length() == 1) {
buffer.append("0").append(tmp);
} else {
buffer.append(tmp);
}
}
return buffer.toString();
}
static String getDigest(String str) throws UnsupportedEncodingException, NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(str.getBytes("UTF-8"));
byte[] digest = md.digest();
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < digest.length; i++) {
String tmp = Integer.toHexString(digest[i] & 0xff);
if (tmp.length() == 1) {
buffer.append("0").append(tmp);
} else {
buffer.append(tmp);
}
}
return buffer.toString();
}
static void execUrl(String url, Map<String, String> headers) throws HttpException, IOException {
// HttpMethodBase hmb = new GetMethod(url);
// for POST Method
HttpMethodBase hmb = new PostMethod(url);
for (Entry<String, String> entry : headers.entrySet()) {
hmb.setRequestHeader(entry.getKey(), entry.getValue());
}
System.out.println("======url======");
System.out.println(url);
System.out.println("======headers======");
for (Header header : hmb.getRequestHeaders()) {
System.out.println(header.getName());
System.out.println(header.getValue());
}
HttpClient hc = new HttpClient();
hc.getHostConfiguration().setProxy("sample.proxy.com", 8080);
int code = hc.executeMethod(hmb);
String body = hmb.getResponseBodyAsString();
System.out.println(code);
System.out.println(body);
}
public static void main(String[] args) throws Exception {
// String method = "GET";
// for POST Method
String method = "POST";
String service = "ec2";
String host = "ec2.amazonaws.com";
String region = "us-east-1";
String endpoint = "https://ec2.amazonaws.com";
String requestParameters = "Action=DescribeRegions&Version=2013-10-15";
String accessKey = "Please input your aws accessKey";
String secretKey = "Please input your aws secretKey";
String amzdate = Utils.getTimestamp("yyyyMMdd'T'HHmmss'Z'", "UTC");
String datestamp = Utils.getTimestamp("yyyyMMdd", "UTC");
// Take 1
String canonicalURI = "/";
String canonicalQueryString = requestParameters;
String canonicalHeaders = "host:" + host + "\n" + "x-amz-date:" + amzdate + "\n";
String signedHeaders = "host;x-amz-date";
String payloadHash = getDigest("");
String canonicalRequest = method + "\n" + canonicalURI + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + payloadHash;
System.out.println("======canonicalRequest======");
System.out.println(canonicalRequest);
// Take 2
String algorithm = "AWS4-HMAC-SHA256";
String credentialScope = datestamp + "/" + region + "/" + service + "/" + "aws4_request";
String stringToSign = algorithm + "\n" + amzdate + "\n" + credentialScope + "\n" + getDigest(canonicalRequest);
System.out.println("======stringToSign======");
System.out.println(stringToSign);
// Take 3
byte[] signingKey = getSignatureKey(secretKey, datestamp, region, service);
String signature = getHmacSHA256Digest(stringToSign, signingKey);
System.out.println("======signature======");
System.out.println(signature);
// Take 4
String authorizationHeader = algorithm + " " + "Credential=" + accessKey + "/" + credentialScope + ", " + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
System.out.println("======authorizationHeader======");
System.out.println(authorizationHeader);
// Send Request
String requestURL = endpoint + "?" + canonicalQueryString;
System.out.println("======requestURL======");
System.out.println(requestURL);
Map<String, String> headers = new HashMap<String, String>();
headers.put("host", host);
headers.put("x-amz-date", amzdate);
headers.put("Authorization", authorizationHeader);
execUrl(requestURL, headers);
}
}
mainで宣言している文字列部分を必要な情報に書き換えれば別のAPIも呼び出すことができると思います
サンプルではEC2のDescribeRegionsをコールしています
AccessKeyとSecretKeyを自分のキーに書き換えてください
プロキシを通すのが前提になっているソースなので不要であればexecUrl
メソッドのプロキシの部分をコメントアウトするか削除してください
結構いろいろなライブラリを使っているので必要に合わせてjarをインポートしてください
Mavenであればpom.xmlを公開しているので、同じものを書けばOKです
書き換えたらこのクラス自体を実行すればOKです
最後に
Signature Version 4はややこしすぎて困ります
Java SDKを使えばいいじゃんという話ですが、AWS互換APIとかを作っている方とかは実装がどうなっているのか知りたくなると思います
SDKの実装見ればいいじゃんという話にもなりますが、SDKのソースを見るのが結構たいへんで見てもどこを持ってくればいいのかよくわからず、結局いろいろドキュメントとか読みながら頑張ってJavaで書いてみました
0 件のコメント:
コメントを投稿