/*
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.InputStreamContent;
import com.google.api.services.storage.Storage;
import com.google.api.services.storage.model.RewriteResponse;
import java.io.IOException;
import java.io.InputStream;
/**
* Demonstrates the use of GCS's CSEK features via the Java API client library
*
* This program demonstrates some quick, basic examples of using GCS's CSEK functionality.
*
* <p>When run, it begins by uploading an object named "encrypted_file.txt" to the specified bucket
* that will be protected with a provided CSEK.</p>
*
* <p>Next, it will fetch that object by providing that same CSEK to GCS.</p>
*
* <p>Finally, it will rotate that key to a new value.</p>
**/
class CustomerSuppliedEncryptionKeysSamples {
// You can (and should) generate your own CSEK Key! Try running this from the command line:
// python -c 'import base64; import os; print(base64.encodestring(os.urandom(32)))'
// Also, these encryption keys are included here for simplicity, but please remember that
// private keys should not be stored in source code.
private static final String CSEK_KEY = "4RzDI0TeWa9M/nAvYH05qbCskPaSU/CFV5HeCxk0IUA=";
// You can use openssl to quickly calculate the hash of your key. Try running this:
// openssl base64 -d <<< YOUR_KEY_FROM_ABOVE | openssl dgst -sha256 -binary | openssl base64
private static final String CSEK_KEY_HASH = "aanjNC2nwso8e2FqcWILC3/Tt1YumvIwEj34kr6PRpI=";
// Used for the key rotation example
private static final String ANOTHER_CESK_KEY = "oevtavYZC+TfGtV86kJBKTeytXAm1s2r3xIqam+QPKM=";
private static final String ANOTHER_CSEK_KEY_HASH =
"/gd0N3k3MK0SEDxnUiaswl0FFv6+5PHpo+5KD5SBCeA=";
private static final String OBJECT_NAME = "encrypted_file.txt";
/**
* Downloads a CSEK-protected object from GCS. The download may continue in the background after
* this method returns. The caller of this method is responsible for closing the input stream.
*
* @param storage A Storage object, ready for use
* @param bucketName The name of the destination bucket
* @param objectName The name of the destination object
* @param base64CseKey An AES256 key, encoded as a base64 string.
* @param base64CseKeyHash The SHA-256 hash of the above key, also encoded as a base64 string.
*
* @return An InputStream that contains the decrypted contents of the object.
*
* @throws IOException if there was some error download from GCS.
*/
public static InputStream downloadObject(
Storage storage,
String bucketName,
String objectName,
String base64CseKey,
String base64CseKeyHash)
throws Exception {
Storage.Objects.Get getObject = storage.objects().get(bucketName, objectName);
// If you're using AppEngine, turn off setDirectDownloadEnabled:
// getObject.getMediaHttpDownloader().setDirectDownloadEnabled(false);
// Now set the CSEK headers
final HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("x-goog-encryption-algorithm", "AES256");
httpHeaders.set("x-goog-encryption-key", base64CseKey);
httpHeaders.set("x-goog-encryption-key-sha256", base64CseKeyHash);
getObject.setRequestHeaders(httpHeaders);
try {
return getObject.executeMediaAsInputStream();
} catch (GoogleJsonResponseException e) {
System.out.println("Error downloading: " + e.getContent());
System.exit(1);
return null;
}
}
/**
* Uploads an object to GCS, to be stored with a customer-supplied key (CSEK). The upload may
* continue in the background after this method returns. The caller of this method is responsible
* for closing the input stream.
*
* @param storage A Storage object, ready for use
* @param bucketName The name of the destination bucket
* @param objectName The name of the destination object
* @param data An InputStream containing the contents of the object to upload
* @param base64CseKey An AES256 key, encoded as a base64 string.
* @param base64CseKeyHash The SHA-256 hash of the above key, also encoded as a base64 string.
* @throws IOException if there was some error uploading to GCS.
*/
public static void uploadObject(
Storage storage,
String bucketName,
String objectName,
InputStream data,
String base64CseKey,
String base64CseKeyHash)
throws IOException {
InputStreamContent mediaContent = new InputStreamContent("text/plain", data);
Storage.Objects.Insert insertObject =
storage.objects().insert(bucketName, null, mediaContent).setName(objectName);
// The client library's default gzip setting may cause objects to be stored with gzip encoding,
// which can be desirable in some circumstances but has some disadvantages as well, such as
// making it difficult to read only a certain range of the original object.
insertObject.getMediaHttpUploader().setDisableGZipContent(true);
// Now set the CSEK headers
final HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("x-goog-encryption-algorithm", "AES256");
httpHeaders.set("x-goog-encryption-key", base64CseKey);
httpHeaders.set("x-goog-encryption-key-sha256", base64CseKeyHash);
insertObject.setRequestHeaders(httpHeaders);
try {
insertObject.execute();
} catch (GoogleJsonResponseException e) {
System.out.println("Error uploading: " + e.getContent());
System.exit(1);
}
}
/**
* Given an existing, CSEK-protected object, changes the key used to store that object.
*
* @param storage A Storage object, ready for use
* @param bucketName The name of the destination bucket
* @param objectName The name of the destination object
* @param originalBase64Key The AES256 key currently associated with this object,
* encoded as a base64 string.
* @param originalBase64KeyHash The SHA-256 hash of the above key,
* also encoded as a base64 string.
* @param newBase64Key An AES256 key which will replace the existing key,
* encoded as a base64 string.
* @param newBase64KeyHash The SHA-256 hash of the above key, also encoded as a base64 string.
* @throws IOException if there was some error download from GCS.
*/
public static void rotateKey(
Storage storage,
String bucketName,
String objectName,
String originalBase64Key,
String originalBase64KeyHash,
String newBase64Key,
String newBase64KeyHash)
throws Exception {
Storage.Objects.Rewrite rewriteObject =
storage.objects().rewrite(bucketName, objectName, bucketName, objectName, null);
// Now set the CSEK headers
final HttpHeaders httpHeaders = new HttpHeaders();
// Specify the exiting object's current CSEK.
httpHeaders.set("x-goog-copy-source-encryption-algorithm", "AES256");
httpHeaders.set("x-goog-copy-source-encryption-key", originalBase64Key);
httpHeaders.set("x-goog-copy-source-encryption-key-sha256", originalBase64KeyHash);
// Specify the new CSEK that we would like to apply.
httpHeaders.set("x-goog-encryption-algorithm", "AES256");
httpHeaders.set("x-goog-encryption-key", newBase64Key);
httpHeaders.set("x-goog-encryption-key-sha256", newBase64KeyHash);
rewriteObject.setRequestHeaders(httpHeaders);
try {
RewriteResponse rewriteResponse = rewriteObject.execute();
// If an object is very large, you may need to continue making successive calls to
// rewrite until the operation completes.
while (!rewriteResponse.getDone()) {
System.out.println("Rewrite did not complete. Resuming...");
rewriteObject.setRewriteToken(rewriteResponse.getRewriteToken());
rewriteResponse = rewriteObject.execute();
}
} catch (GoogleJsonResponseException e) {
System.out.println("Error rotating key: " + e.getContent());
System.exit(1);
}
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.out.println("\nPlease run this with one argument: "
+ "the GCS bucket into which this program should upload an object.\n\n"
+ "You can create a bucket using gsutil like this:\n\n\t"
+ "gsutil mb gs://name-of-bucket\n\n");
System.exit(1);
}
String bucketName = args[0];
Storage storage = StorageFactory.getService();
InputStream dataToUpload = new StorageUtils.ArbitrarilyLargeInputStream(10000000);
System.out.format("Uploading object gs://%s/%s using CSEK.\n", bucketName, OBJECT_NAME);
uploadObject(storage, bucketName, OBJECT_NAME, dataToUpload, CSEK_KEY, CSEK_KEY_HASH);
System.out.format("Downloading object gs://%s/%s using CSEK.\n", bucketName, OBJECT_NAME);
InputStream objectData =
downloadObject(storage, bucketName, OBJECT_NAME, CSEK_KEY, CSEK_KEY_HASH);
StorageUtils.readStream(objectData);
System.out.println("Rotating object to use a different CSEK.");
rotateKey(storage, bucketName, OBJECT_NAME, CSEK_KEY, CSEK_KEY_HASH,
ANOTHER_CESK_KEY, ANOTHER_CSEK_KEY_HASH);
System.out.println("Done");
}
}