Skip to content

Commit 5b8ba30

Browse files
committed
Merge pull request #137 from GoogleCloudPlatform/csk
Add a new example for using Customer-Supplied Encryption Keys.
2 parents 54efe62 + 57b949b commit 5b8ba30

File tree

6 files changed

+424
-60
lines changed

6 files changed

+424
-60
lines changed

storage/json-api/README.md

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Google Cloud Storage (GCS) and the Google Java API Client library
2+
3+
Google Cloud Storage Service features a REST-based API that allows developers to store and access arbitrarily-large objects. These sample Java applications demonstrate how to access the Google Cloud Storage JSON API using the Google Java API Client Libraries. For more information, read the [Google Cloud Storage JSON API Overview][1].
4+
5+
## Quickstart
6+
7+
1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/), including the [gcloud tool](https://cloud.google.com/sdk/gcloud/).
8+
9+
1. Setup the gcloud tool.
10+
11+
```
12+
gcloud init
13+
```
14+
15+
1. Clone this repo.
16+
17+
```
18+
git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git
19+
```
20+
21+
1. Install [Maven](http://maven.apache.org/).
22+
23+
1. Build this project from this directory:
24+
25+
```
26+
mvn package
27+
```
28+
29+
1. Run one of the sample apps by specifying its class name and a bucket name:
30+
31+
```
32+
mvn exec:java -Dexec.mainClass=StorageSample \
33+
-Dexec.args="ABucketName"
34+
```
35+
36+
Note that if it's been a while, you may need to login with gcloud.
37+
38+
```
39+
gcloud auth login
40+
```
41+
42+
## Products
43+
- [Google Cloud Storage][2]
44+
45+
## Language
46+
- [Java][3]
47+
48+
## Dependencies
49+
- [Google APIs Client Library for Java][4]
50+
51+
[1]: https://cloud.google.com/storage/docs/json_api
52+
[2]: https://cloud.google.com/storage
53+
[3]: https://java.com
54+
[4]: http://code.google.com/p/google-api-java-client/
55+

storage/json-api/pom.xml

+21-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
1-
<?xml version="1.0" encoding="UTF-8"?>
1+
<!--
2+
Copyright 2016 Google Inc. All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
216
<project>
317
<parent>
418
<artifactId>doc-samples</artifactId>
@@ -19,36 +33,23 @@
1933
</properties>
2034

2135
<build>
22-
<plugins>
23-
<plugin>
24-
<groupId>org.codehaus.mojo</groupId>
25-
<artifactId>exec-maven-plugin</artifactId>
26-
<version>1.1</version>
27-
<executions>
28-
<execution>
29-
<goals>
30-
<goal>java</goal>
31-
</goals>
32-
</execution>
33-
</executions>
34-
<configuration>
35-
<mainClass>StorageSample</mainClass>
36-
</configuration>
37-
</plugin>
38-
</plugins>
3936
<finalName>${project.artifactId}-${project.version}</finalName>
4037
</build>
4138
<dependencies>
4239
<dependency>
4340
<groupId>com.google.apis</groupId>
4441
<artifactId>google-api-services-storage</artifactId>
45-
<version>v1-rev18-1.19.0</version>
42+
<version>v1-rev65-1.21.0</version>
43+
</dependency>
44+
<dependency>
45+
<groupId>com.google.oauth-client</groupId>
46+
<artifactId>google-oauth-client-jetty</artifactId>
47+
<version>1.21.0</version>
4648
</dependency>
4749
<!-- Test Dependencies -->
4850
<dependency>
4951
<groupId>junit</groupId>
5052
<artifactId>junit</artifactId>
51-
<version>4.10</version>
5253
<scope>test</scope>
5354
</dependency>
5455
<dependency>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/*
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
18+
import com.google.api.client.http.HttpHeaders;
19+
import com.google.api.client.http.InputStreamContent;
20+
import com.google.api.services.storage.Storage;
21+
import com.google.api.services.storage.model.RewriteResponse;
22+
23+
import java.io.IOException;
24+
import java.io.InputStream;
25+
26+
/**
27+
* Demonstrates the use of GCS's CSEK features via the Java API client library
28+
*
29+
* This program demonstrates some quick, basic examples of using GCS's CSEK functionality.
30+
*
31+
* <p>When run, it begins by uploading an object named "encrypted_file.txt" to the specified bucket
32+
* that will be protected with a provided CSEK.</p>
33+
*
34+
* <p>Next, it will fetch that object by providing that same CSEK to GCS.</p>
35+
*
36+
* <p>Finally, it will rotate that key to a new value.</p>
37+
**/
38+
class CustomerSuppliedEncryptionKeysSamples {
39+
40+
// You can (and should) generate your own CSEK Key! Try running this from the command line:
41+
// python -c 'import base64; import os; print(base64.encodestring(os.urandom(32)))'
42+
// Also, these encryption keys are included here for simplicity, but please remember that
43+
// private keys should not be stored in source code.
44+
private static final String CSEK_KEY = "4RzDI0TeWa9M/nAvYH05qbCskPaSU/CFV5HeCxk0IUA=";
45+
46+
// You can use openssl to quickly calculate the hash of your key. Try running this:
47+
// openssl base64 -d <<< YOUR_KEY_FROM_ABOVE | openssl dgst -sha256 -binary | openssl base64
48+
private static final String CSEK_KEY_HASH = "aanjNC2nwso8e2FqcWILC3/Tt1YumvIwEj34kr6PRpI=";
49+
50+
// Used for the key rotation example
51+
private static final String ANOTHER_CESK_KEY = "oevtavYZC+TfGtV86kJBKTeytXAm1s2r3xIqam+QPKM=";
52+
private static final String ANOTHER_CSEK_KEY_HASH =
53+
"/gd0N3k3MK0SEDxnUiaswl0FFv6+5PHpo+5KD5SBCeA=";
54+
55+
private static final String OBJECT_NAME = "encrypted_file.txt";
56+
57+
/**
58+
* Downloads a CSEK-protected object from GCS. The download may continue in the background after
59+
* this method returns. The caller of this method is responsible for closing the input stream.
60+
*
61+
* @param storage A Storage object, ready for use
62+
* @param bucketName The name of the destination bucket
63+
* @param objectName The name of the destination object
64+
* @param base64CseKey An AES256 key, encoded as a base64 string.
65+
* @param base64CseKeyHash The SHA-256 hash of the above key, also encoded as a base64 string.
66+
*
67+
* @return An InputStream that contains the decrypted contents of the object.
68+
*
69+
* @throws IOException if there was some error download from GCS.
70+
*/
71+
public static InputStream downloadObject(
72+
Storage storage,
73+
String bucketName,
74+
String objectName,
75+
String base64CseKey,
76+
String base64CseKeyHash)
77+
throws Exception {
78+
Storage.Objects.Get getObject = storage.objects().get(bucketName, objectName);
79+
80+
// If you're using AppEngine, turn off setDirectDownloadEnabled:
81+
// getObject.getMediaHttpDownloader().setDirectDownloadEnabled(false);
82+
83+
// Now set the CSEK headers
84+
final HttpHeaders httpHeaders = new HttpHeaders();
85+
httpHeaders.set("x-goog-encryption-algorithm", "AES256");
86+
httpHeaders.set("x-goog-encryption-key", base64CseKey);
87+
httpHeaders.set("x-goog-encryption-key-sha256", base64CseKeyHash);
88+
89+
getObject.setRequestHeaders(httpHeaders);
90+
91+
try {
92+
return getObject.executeMediaAsInputStream();
93+
} catch (GoogleJsonResponseException e) {
94+
System.out.println("Error downloading: " + e.getContent());
95+
System.exit(1);
96+
return null;
97+
}
98+
}
99+
100+
/**
101+
* Uploads an object to GCS, to be stored with a customer-supplied key (CSEK). The upload may
102+
* continue in the background after this method returns. The caller of this method is responsible
103+
* for closing the input stream.
104+
*
105+
* @param storage A Storage object, ready for use
106+
* @param bucketName The name of the destination bucket
107+
* @param objectName The name of the destination object
108+
* @param data An InputStream containing the contents of the object to upload
109+
* @param base64CseKey An AES256 key, encoded as a base64 string.
110+
* @param base64CseKeyHash The SHA-256 hash of the above key, also encoded as a base64 string.
111+
* @throws IOException if there was some error uploading to GCS.
112+
*/
113+
public static void uploadObject(
114+
Storage storage,
115+
String bucketName,
116+
String objectName,
117+
InputStream data,
118+
String base64CseKey,
119+
String base64CseKeyHash)
120+
throws IOException {
121+
InputStreamContent mediaContent = new InputStreamContent("text/plain", data);
122+
Storage.Objects.Insert insertObject =
123+
storage.objects().insert(bucketName, null, mediaContent).setName(objectName);
124+
// The client library's default gzip setting may cause objects to be stored with gzip encoding,
125+
// which can be desirable in some circumstances but has some disadvantages as well, such as
126+
// making it difficult to read only a certain range of the original object.
127+
insertObject.getMediaHttpUploader().setDisableGZipContent(true);
128+
129+
// Now set the CSEK headers
130+
final HttpHeaders httpHeaders = new HttpHeaders();
131+
httpHeaders.set("x-goog-encryption-algorithm", "AES256");
132+
httpHeaders.set("x-goog-encryption-key", base64CseKey);
133+
httpHeaders.set("x-goog-encryption-key-sha256", base64CseKeyHash);
134+
135+
insertObject.setRequestHeaders(httpHeaders);
136+
137+
try {
138+
insertObject.execute();
139+
} catch (GoogleJsonResponseException e) {
140+
System.out.println("Error uploading: " + e.getContent());
141+
System.exit(1);
142+
}
143+
}
144+
145+
/**
146+
* Given an existing, CSEK-protected object, changes the key used to store that object.
147+
*
148+
* @param storage A Storage object, ready for use
149+
* @param bucketName The name of the destination bucket
150+
* @param objectName The name of the destination object
151+
* @param originalBase64Key The AES256 key currently associated with this object,
152+
* encoded as a base64 string.
153+
* @param originalBase64KeyHash The SHA-256 hash of the above key,
154+
* also encoded as a base64 string.
155+
* @param newBase64Key An AES256 key which will replace the existing key,
156+
* encoded as a base64 string.
157+
* @param newBase64KeyHash The SHA-256 hash of the above key, also encoded as a base64 string.
158+
* @throws IOException if there was some error download from GCS.
159+
*/
160+
public static void rotateKey(
161+
Storage storage,
162+
String bucketName,
163+
String objectName,
164+
String originalBase64Key,
165+
String originalBase64KeyHash,
166+
String newBase64Key,
167+
String newBase64KeyHash)
168+
throws Exception {
169+
Storage.Objects.Rewrite rewriteObject =
170+
storage.objects().rewrite(bucketName, objectName, bucketName, objectName, null);
171+
172+
// Now set the CSEK headers
173+
final HttpHeaders httpHeaders = new HttpHeaders();
174+
175+
// Specify the exiting object's current CSEK.
176+
httpHeaders.set("x-goog-copy-source-encryption-algorithm", "AES256");
177+
httpHeaders.set("x-goog-copy-source-encryption-key", originalBase64Key);
178+
httpHeaders.set("x-goog-copy-source-encryption-key-sha256", originalBase64KeyHash);
179+
180+
// Specify the new CSEK that we would like to apply.
181+
httpHeaders.set("x-goog-encryption-algorithm", "AES256");
182+
httpHeaders.set("x-goog-encryption-key", newBase64Key);
183+
httpHeaders.set("x-goog-encryption-key-sha256", newBase64KeyHash);
184+
185+
rewriteObject.setRequestHeaders(httpHeaders);
186+
187+
try {
188+
RewriteResponse rewriteResponse = rewriteObject.execute();
189+
190+
// If an object is very large, you may need to continue making successive calls to
191+
// rewrite until the operation completes.
192+
while (!rewriteResponse.getDone()) {
193+
System.out.println("Rewrite did not complete. Resuming...");
194+
rewriteObject.setRewriteToken(rewriteResponse.getRewriteToken());
195+
rewriteResponse = rewriteObject.execute();
196+
}
197+
} catch (GoogleJsonResponseException e) {
198+
System.out.println("Error rotating key: " + e.getContent());
199+
System.exit(1);
200+
}
201+
}
202+
203+
public static void main(String[] args) throws Exception {
204+
if (args.length != 1) {
205+
System.out.println("\nPlease run this with one argument: "
206+
+ "the GCS bucket into which this program should upload an object.\n\n"
207+
+ "You can create a bucket using gsutil like this:\n\n\t"
208+
+ "gsutil mb gs://name-of-bucket\n\n");
209+
System.exit(1);
210+
}
211+
String bucketName = args[0];
212+
213+
Storage storage = StorageFactory.getService();
214+
InputStream dataToUpload = new StorageUtils.ArbitrarilyLargeInputStream(10000000);
215+
216+
System.out.format("Uploading object gs://%s/%s using CSEK.\n", bucketName, OBJECT_NAME);
217+
uploadObject(storage, bucketName, OBJECT_NAME, dataToUpload, CSEK_KEY, CSEK_KEY_HASH);
218+
219+
System.out.format("Downloading object gs://%s/%s using CSEK.\n", bucketName, OBJECT_NAME);
220+
InputStream objectData =
221+
downloadObject(storage, bucketName, OBJECT_NAME, CSEK_KEY, CSEK_KEY_HASH);
222+
StorageUtils.readStream(objectData);
223+
224+
System.out.println("Rotating object to use a different CSEK.");
225+
rotateKey(storage, bucketName, OBJECT_NAME, CSEK_KEY, CSEK_KEY_HASH,
226+
ANOTHER_CESK_KEY, ANOTHER_CSEK_KEY_HASH);
227+
228+
System.out.println("Done");
229+
}
230+
231+
}

0 commit comments

Comments
 (0)