Available in Classic and VPC
Review the support information for the Object Storage API, which is compatible with the Amazon S3 API, and verify the common API calls and authentication methods. Requests generated using the S3 API on NAVER Cloud Platform Object Storage must be authenticated using AWS's authorization header implementation. Object Storage supports Signature Version 2 and Signature Version 4 authentication methods, and this guide is based on Version 4.
Supported API
The Object Storage API enables control over various features necessary for storage management and usage. See the Object Storage API guide for the list of supported operations.
Call API
The Object Storage API call steps are as follows:
1. Create authentication key.
2. Check calling domain
3. Authentication
4. Call
1. Create authentication key.
The method for creating an authentication key to use the Object Storage API is identical to the authentication key creation method for the Ncloud API. See 1. Create authentication key of Ncoud API.
2. Check calling domain
While it supports both HTTP and HTTPS protocols, we recommend using HTTPS for data protection. The region-specific calling domain information is as follows:
| Region | Region name | Calling domain |
|---|---|---|
| Korea | kr-standard | https://kr.object.ncloudstorage.com |
| US West (New) | us-standard | https://us.object.ncloudstorage.com |
| Singapore (New) | sg-standard | https://sg.object.ncloudstorage.com |
| Japan (New) | jp-standard | https://jp.object.ncpstorage.com |
| Germany (New) | de-standard | https://de.object.ncloudstorage.com |
3. Authentication
The following describes how to create an authorization header.
1) Create canonical request
2) Create string to sign
3) Create signing key
4) Create signature
5) Create authorization header
1) Create canonical request
The structure of the canonical request and descriptions for each item are as follows:
<HTTPMethod>\n
<CanonicalURI>\n
<CanonicalQueryString>\n
<CanonicalHeaders>\n
<SignedHeaders>\n
<HashedPayload>
-
HTTPMethod
Declare the HTTP method to use. Example: PUT -
CanonicalURI
Define the resource to access in a standardized (URI-encoded) format. Example: /path/object
- Object key names can't encode English letters, numbers, or special characters "-", "_", "~", ".", and "/".
- Space characters must be encoded as "%20".
-
CanonicalQueryString
If request parameters exist, define them in a standardized format (URI-encoded) and list them in alphabetical order.UriEncode("marker")+"="+UriEncode("someMarker")+"&"+ UriEncode("max-keys")+"="+UriEncode("20") + "&" + UriEncode("prefix")+"="+UriEncode("somePrefix") -
CanonicalHeaders
Define the header names and values included in the request in a standardized manner. Convert all header names to lowercase and remove leading and trailing whitespace from header values. Insert a newline character\nat the end of each header entry. At this point, the headers must be sorted alphabetically by header name.
Thehostheader and headers starting withx-amz-are mandatory. Including all headers is recommended for more accurate data consistency verification.
All requests using Signature Version 4 authentication must include thex-amz-content-sha256header. You must provide the hash value for the data (payload), with two options available.- Signed payload: Enter the hexadecimal value of the SHA256 hash of the payload. If there is no payload, enter the hash value of an empty string.
- Unsigned payload: To not sign the payload, enter the string
UNSIGNED-PAYLOADas the value of thex-amz-content-sha256header.
Lowercase(<HeaderName1>)+":"+Trim(<value>)+"\n" Lowercase(<HeaderName2>)+":"+Trim(<value>)+"\n" -
SignedHeaders
List the header names included in the request. Convert all header names to lowercase and sort them alphabetically. Enter a semicolon (";") as the separator between each header name.host;x-amz-content-sha256;x-amz-date -
HashedPayload
Enter the hexadecimal value of the SHA256 hash of the payload. (Same as the value of thex-amz-content-sha256header) If there is no payload, enter the hash value of an empty string. To not sign the data (payload), enter theUNSIGNED-PAYLOADstring.Hex(SHA256Hash(<payload>) or Hex(SHA256Hash("")) or UNSIGNED-PAYLOAD
2) Create string to sign
The structure of the string to sign and descriptions for each item are as follows:
AWS4-HMAC-SHA256\n
<Timestamp>\n
<Scope>\n
Hex(SHA256Hash(<CanonicalRequest>))
-
Timestamp
It must be displayed according to the ISO 8601 standard specification based on UTC (Coordinated Universal Time). Example: 20161128T152924Z -
Scope
The scope string is structured as follows:<Date>/<Region>/s3/aws4_request- Date: Enter in
YYYYMMDDformat. - Region: Enter the Region name. The Region name can be found in 2. Check calling domain. While the Region name is a required input, NAVER Cloud Platform Object Storage uses the domain to distinguish Regions, so it does not verify the consistency of the Region name.
- Date: Enter in
-
CanonicalRequest
Enter the hexadecimal value of the SHA256 hash of the generated canonical request.
3) Create signing key
The structure of the signing key and descriptions for each item are as follows:
kSecret = <SecretKey>
kDate = HMAC-SHA256("AWS4" + kSecret, <Date>)
kRegion = HMAC-SHA256(kDate, <Region>)
kService = HMAC-SHA256(kRegion, "s3")
kSigning = HMAC-SHA256(kService, "aws4_request")
-
SecretKey
Enter the secret key of the API authentication key. It can be found in My Account > Manage account and security > Manage security > Manage access > API authentication key in the NAVER Cloud Platform console. -
Date
Enter inYYYYMMDDformat. -
Region
Enter the Region name. The Region name can be found in 2. Check calling domain. While the Region name is a required input, NAVER Cloud Platform Object Storage uses the domain to distinguish Regions, so it does not verify the consistency of the Region name.
4) Create signature
The structure of the signature and descriptions for each item are as follows:
Hex(HMAC-SHA256(<SigningKey>, <StringToSign>))
-
Signing Key
Calculate the HMAC-SHA256 hash of the signing key and display it as a hexadecimal value. -
StringToSign
Calculate the HMAC-SHA256 hash of the string to sign and display it as a hexadecimal value.
5) Create authorization header
The structure of the authorization header and descriptions for each item are as follows:
Authorization: AWS4-HMAC-SHA256 Credential=<AccessKeyID>/<Scope>, SignedHeaders=<SignedHeaders>, Signature=<Signature>
-
AccessKeyID
Enter the access key ID of the API authentication key. It can be found in My Account > Manage account and security > Manage security > Manage access > API authentication key in the NAVER Cloud Platform console. -
Scope
The scope string is structured as follows:<Date>/<Region>/s3/aws4_request- Date: Enter in
YYYYMMDDformat. - Region: Enter the Region name. The Region name can be found in 2. Check calling domain. While the Region name is a required input, NAVER Cloud Platform Object Storage uses the domain to distinguish Regions, so it does not verify the consistency of the Region name.
- Date: Enter in
-
SignedHeaders
List the header names included in the request. Convert all header names to lowercase and sort them alphabetically. Enter a semicolon (";") as the separator between each header name.host;x-amz-content-sha256;x-amz-date -
Signature
Enter the signature generated in 4) Create signature.
The following describes examples of generating language-specific authorization headers.
public class ObjectStorageSample {
private static byte[] sign(String stringData, byte[] key) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
byte[] data = stringData.getBytes(CHARSET_NAME);
Mac e = Mac.getInstance(HMAC_ALGORITHM);
e.init(new SecretKeySpec(key, HMAC_ALGORITHM));
return e.doFinal(data);
}
private static String hash(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest e = MessageDigest.getInstance(HASH_ALGORITHM);
e.update(text.getBytes(CHARSET_NAME));
return Hex.encodeHexString(e.digest());
}
private static String getStandardizedQueryParameters(String queryString) throws UnsupportedEncodingException {
TreeMap<String, String> sortedQueryParameters = new TreeMap<>();
// sort by key name
if (queryString != null && !queryString.isEmpty()) {
String[] queryStringTokens = queryString.split("&");
for (String field : queryStringTokens) {
String[] fieldTokens = field.split("=");
if (fieldTokens.length > 0) {
if (fieldTokens.length > 1) {
sortedQueryParameters.put(fieldTokens[0], fieldTokens[1]);
} else {
sortedQueryParameters.put(fieldTokens[0], "");
}
}
}
}
StringBuilder standardizedQueryParametersBuilder = new StringBuilder();
int count = 0;
for (String key : sortedQueryParameters.keySet()) {
if (count > 0) {
standardizedQueryParametersBuilder.append("&");
}
standardizedQueryParametersBuilder.append(key).append("=");
if (sortedQueryParameters.get(key) != null && !sortedQueryParameters.get(key).isEmpty()) {
standardizedQueryParametersBuilder.append(URLEncoder.encode(sortedQueryParameters.get(key), CHARSET_NAME));
}
count++;
}
return standardizedQueryParametersBuilder.toString();
}
private static TreeMap<String, String> getSortedHeaders(Header[] headers) {
TreeMap<String, String> sortedHeaders = new TreeMap<>();
// sort by header name
for (Header header : headers) {
String headerName = header.getName().toLowerCase();
sortedHeaders.put(headerName, header.getValue());
}
return sortedHeaders;
}
private static String getSignedHeaders(TreeMap<String, String> sortedHeaders) {
StringBuilder signedHeadersBuilder = new StringBuilder();
for (String headerName : sortedHeaders.keySet()) {
signedHeadersBuilder.append(headerName.toLowerCase()).append(";");
}
String s = signedHeadersBuilder.toString();
if (s.endsWith(";")) {
s = s.substring(0, s.length() - 1);
}
return s;
}
private static String getStandardizedHeaders(TreeMap<String, String> sortedHeaders) {
StringBuilder standardizedHeadersBuilder = new StringBuilder();
for (String headerName : sortedHeaders.keySet()) {
standardizedHeadersBuilder.append(headerName.toLowerCase()).append(":").append(sortedHeaders.get(headerName)).append("\n");
}
return standardizedHeadersBuilder.toString();
}
private static String getCanonicalRequest(HttpUriRequest request, String standardizedQueryParameters, String standardizedHeaders, String signedHeaders) {
StringBuilder canonicalRequestBuilder = new StringBuilder().append(request.getMethod()).append("\n")
.append(request.getURI().getPath()).append("\n")
.append(standardizedQueryParameters).append("\n")
.append(standardizedHeaders).append("\n")
.append(signedHeaders).append("\n")
.append(UNSIGNED_PAYLOAD);
return canonicalRequestBuilder.toString();
}
private static String getScope(String datestamp, String regionName) {
StringBuilder scopeBuilder = new StringBuilder().append(datestamp).append("/")
.append(regionName).append("/")
.append(SERVICE_NAME).append("/")
.append(REQUEST_TYPE);
return scopeBuilder.toString();
}
private static String getStringToSign(String timestamp, String scope, String canonicalRequest) throws NoSuchAlgorithmException, UnsupportedEncodingException {
StringBuilder stringToSignBuilder = new StringBuilder(AWS_ALGORITHM)
.append("\n")
.append(timestamp).append("\n")
.append(scope).append("\n")
.append(hash(canonicalRequest));
return stringToSignBuilder.toString();
}
private static String getSignature(String secretKey, String datestamp, String regionName, String stringToSign) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
byte[] kSecret = ("AWS4" + secretKey).getBytes(CHARSET_NAME);
byte[] kDate = sign(datestamp, kSecret);
byte[] kRegion = sign(regionName, kDate);
byte[] kService = sign(SERVICE_NAME, kRegion);
byte[] signingKey = sign(REQUEST_TYPE, kService);
return Hex.encodeHexString(sign(stringToSign, signingKey));
}
private static String getAuthorization(String accessKey, String scope, String signedHeaders, String signature) {
String signingCredentials = accessKey + "/" + scope;
String credential = "Credential=" + signingCredentials;
String signerHeaders = "SignedHeaders=" + signedHeaders;
String signatureHeader = "Signature=" + signature;
StringBuilder authHeaderBuilder = new StringBuilder().append(AWS_ALGORITHM).append(" ")
.append(credential).append(", ")
.append(signerHeaders).append(", ")
.append(signatureHeader);
return authHeaderBuilder.toString();
}
private static void authorization(HttpUriRequest request, String regionName, String accessKey, String secretKey) throws Exception {
Date now = new Date();
DATE_FORMATTER.setTimeZone(TimeZone.getTimeZone("UTC"));
TIME_FORMATTER.setTimeZone(TimeZone.getTimeZone("UTC"));
String datestamp = DATE_FORMATTER.format(now);
String timestamp = TIME_FORMATTER.format(now);
request.addHeader("X-Amz-Date", timestamp);
request.addHeader("X-Amz-Content-Sha256", UNSIGNED_PAYLOAD);
String standardizedQueryParameters = getStandardizedQueryParameters(request.getURI().getQuery());
TreeMap<String, String> sortedHeaders = getSortedHeaders(request.getAllHeaders());
String signedHeaders = getSignedHeaders(sortedHeaders);
String standardizedHeaders = getStandardizedHeaders(sortedHeaders);
String canonicalRequest = getCanonicalRequest(request, standardizedQueryParameters, standardizedHeaders, signedHeaders);
System.out.println("> canonicalRequest :");
System.out.println(canonicalRequest);
String scope = getScope(datestamp, regionName);
String stringToSign = getStringToSign(timestamp, scope, canonicalRequest);
System.out.println("> stringToSign :");
System.out.println(stringToSign);
String signature = getSignature(secretKey, datestamp, regionName, stringToSign);
String authorization = getAuthorization(accessKey, scope, signedHeaders, signature);
request.addHeader("Authorization", authorization);
}
private static void putObject(String bucketName, String objectName, String localFilePath) throws Exception {
HttpClient httpClient = HttpClientBuilder.create().build();
HttpPut request = new HttpPut(ENDPOINT + "/" + bucketName + "/" + objectName);
request.addHeader("Host", request.getURI().getHost());
request.setEntity(new FileEntity(new File(localFilePath)));
authorization(request, REGION_NAME, ACCESS_KEY, SECRET_KEY);
HttpResponse response = httpClient.execute(request);
System.out.println("Response : " + response.getStatusLine());
}
private static void getObject(String bucketName, String objectName, String localFilePath) throws Exception {
HttpClient httpClient = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(ENDPOINT + "/" + bucketName + "/" + objectName);
request.addHeader("Host", request.getURI().getHost());
authorization(request, REGION_NAME, ACCESS_KEY, SECRET_KEY);
HttpResponse response = httpClient.execute(request);
System.out.println("Response : " + response.getStatusLine());
InputStream is = response.getEntity().getContent();
File targetFile = new File(localFilePath);
OutputStream os = new FileOutputStream(targetFile);
byte[] buffer = new byte[8 * 1024];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
is.close();
os.close();
}
private static void listObjects(String bucketName, String queryString) throws Exception {
HttpClient httpClient = HttpClientBuilder.create().build();
URI uri = new URI(ENDPOINT + "/" + bucketName + "?" + queryString);
HttpGet request = new HttpGet(uri);
request.addHeader("Host", request.getURI().getHost());
authorization(request, REGION_NAME, ACCESS_KEY, SECRET_KEY);
HttpResponse response = httpClient.execute(request);
System.out.println("> Response : " + response.getStatusLine());
int i;
InputStream is = response.getEntity().getContent();
StringBuffer buffer = new StringBuffer();
byte[] b = new byte[4096];
while ((i = is.read(b)) != -1) {
buffer.append(new String(b, 0, i));
}
System.out.println(buffer.toString());
}
private static final String CHARSET_NAME = "UTF-8";
private static final String HMAC_ALGORITHM = "HmacSHA256";
private static final String HASH_ALGORITHM = "SHA-256";
private static final String AWS_ALGORITHM = "AWS4-HMAC-SHA256";
private static final String SERVICE_NAME = "s3";
private static final String REQUEST_TYPE = "aws4_request";
private static final String UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyyMMdd");
private static final SimpleDateFormat TIME_FORMATTER = new SimpleDateFormat("yyyyMMdd\'T\'HHmmss\'Z\'");
private static final String REGION_NAME = "kr-standard";
private static final String ENDPOINT = "https://kr.object.ncloudstorage.com";
private static final String ACCESS_KEY = "ACCESS_KEY_ID";
private static final String SECRET_KEY = "SECRET_KEY";
public static void main(String[] args) throws Exception {
String bucketName = "sample-bucket";
String objectName = "sample-object.txt";
String sourceFilePath = "/tmp/source.txt";
String targetFilePath = "/tmp/target.txt";
putObject(bucketName, objectName, sourceFilePath);
getObject(bucketName, objectName, targetFilePath);
String queryString = "max-keys=10&delimiter=/";
listObjects(bucketName, queryString);
}
}
import hashlib
import hmac
import datetime
import requests
import urllib
def get_hash(key, msg):
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
def create_signed_headers(headers):
signed_headers = []
for k in sorted(headers):
signed_headers.append('%s;' % k)
return ''.join(signed_headers)[:-1]
def create_standardized_headers(headers):
signed_headers = []
for k in sorted(headers):
signed_headers.append('%s:%s\n' % (k, headers[k]))
return ''.join(signed_headers)
def create_standardized_query_parameters(request_parameters):
standardized_query_parameters = []
if request_parameters:
for k in sorted(request_parameters):
standardized_query_parameters.append('%s=%s' % (k, urllib.quote(request_parameters[k], safe='')))
return '&'.join(standardized_query_parameters)
else:
return ''
class ObjectStorageSample:
def __init__(self):
self.region = 'kr-standard'
self.endpoint = 'https://kr.object.ncloudstorage.com'
self.host = 'kr.object.ncloudstorage.com'
self.access_key = 'ACCESS_KEY_ID'
self.secret_key = 'SECRET_KEY'
self.payload_hash = 'UNSIGNED-PAYLOAD'
self.hashing_algorithm = 'AWS4-HMAC-SHA256'
self.service_name = 's3'
self.request_type = 'aws4_request'
self.time_format = '%Y%m%dT%H%M%SZ'
self.date_format = '%Y%m%d'
def _create_credential_scope(self, date_stamp):
return date_stamp + '/' + self.region + '/' + self.service_name + '/' + self.request_type
def _create_canonical_request(self, http_method, request_path, request_parameters, headers):
standardized_query_parameters = create_standardized_query_parameters(request_parameters)
standardized_headers = create_standardized_headers(headers)
signed_headers = create_signed_headers(headers)
canonical_request = (http_method + '\n' +
request_path + '\n' +
standardized_query_parameters + '\n' +
standardized_headers + '\n' +
signed_headers + '\n' +
self.payload_hash)
print('canonical_request:\n%s\n' % canonical_request)
return canonical_request
def _create_string_to_sign(self, time_stamp, credential_scope, canonical_request):
string_to_sign = (self.hashing_algorithm + '\n' +
time_stamp + '\n' +
credential_scope + '\n' +
hashlib.sha256(canonical_request.encode('utf-8')).hexdigest())
print('string_to_sign:\n%s\n' % string_to_sign)
return string_to_sign
def _create_signature_key(self, date_stamp):
key_date = get_hash(('AWS4' + self.secret_key).encode('utf-8'), date_stamp)
key_string = get_hash(key_date, self.region)
key_service = get_hash(key_string, self.service_name)
key_signing = get_hash(key_service, self.request_type)
return key_signing
def _create_authorization_header(self, headers, signature_key, string_to_sign, credential_scope):
signed_headers = create_signed_headers(headers)
signature = hmac.new(signature_key, string_to_sign.encode('utf-8'), hashlib.sha256).hexdigest()
return (self.hashing_algorithm + ' ' +
'Credential=' + self.access_key + '/' + credential_scope + ', ' +
'SignedHeaders=' + signed_headers + ', ' +
'Signature=' + signature)
def _sign(self, http_method, request_path, headers, time, request_parameters=None):
time_stamp = time.strftime(self.time_format)
date_stamp = time.strftime(self.date_format)
credential_scope = self._create_credential_scope(date_stamp)
canonical_request = self._create_canonical_request(http_method, request_path, request_parameters, headers)
string_to_sign = self._create_string_to_sign(time_stamp, credential_scope, canonical_request)
signature_key = self._create_signature_key(date_stamp)
headers['authorization'] = self._create_authorization_header(headers, signature_key, string_to_sign, credential_scope)
def put_object(self, bucket_name, object_name, source_file_path, request_parameters=None):
http_method = 'PUT'
with open(source_file_path) as f:
time = datetime.datetime.utcnow()
time_stamp = time.strftime(self.time_format)
headers = {'x-amz-date': time_stamp,
'x-amz-content-sha256': self.payload_hash,
'host': self.host}
request_path = '/%s/%s' % (bucket_name, object_name)
self._sign(http_method, request_path, headers, time, request_parameters)
request_url = self.endpoint + request_path
r = requests.put(request_url, headers=headers, params=request_parameters, data=f.read())
print('Response code: %d' % r.status_code)
def get_object(self, bucket_name, object_name, target_file_path, request_parameters=None):
http_method = 'GET'
time = datetime.datetime.utcnow()
time_stamp = time.strftime(self.time_format)
headers = {'x-amz-date': time_stamp,
'x-amz-content-sha256': self.payload_hash,
'host': self.host}
request_path = '/%s/%s' % (bucket_name, object_name)
self._sign(http_method, request_path, headers, time, request_parameters)
request_url = self.endpoint + request_path
r = requests.get(request_url, headers=headers, params=request_parameters, stream=True)
print('Response code: %d' % r.status_code)
if r.status_code == 200:
with open(target_file_path, 'wb') as f:
f.write(r.content)
def list_objects(self, bucket_name, request_parameters=None):
http_method = 'GET'
time = datetime.datetime.utcnow()
time_stamp = time.strftime(self.time_format)
headers = {'x-amz-date': time_stamp,
'x-amz-content-sha256': self.payload_hash,
'host': self.host}
request_path = '/%s' % bucket_name
self._sign(http_method, request_path, headers, time, request_parameters)
request_url = self.endpoint + request_path
r = requests.get(request_url, headers=headers, params=request_parameters)
print('Response code: %d' % r.status_code)
print('Response content:\n%s' % r.content)
if __name__ == '__main__':
sample = ObjectStorageSample()
sample.put_object('sample-bucket', 'sample-object.txt', '/tmp/source.txt')
sample.get_object('sample-bucket', 'sample-object.txt', '/tmp/target.txt')
sample.list_objects('sample-bucket', request_parameters={'max-keys': '10', 'delimiter': '/'})
4. Call
Call the API based on the content in steps 1 through 3. The result of the API call can be categorized as either success or failure. If the response is successful, check the returned result. If the response fails, an error code is returned. Check the returned error code and retry the call.
Succeeded
For information on handling responses to API calls, see the Object Storage API guide.
Failure
Error codes returned upon call failure include common error codes and operation-specific error codes. For error codes, see the Object Storage API guide.