Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 4831bba

Browse filesBrowse files
added kms asymmetric samples (GoogleCloudPlatform#1638)
1 parent 603bfc0 commit 4831bba
Copy full SHA for 4831bba

File tree

Expand file treeCollapse file tree

3 files changed

+278
-0
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+278
-0
lines changed

‎kms/api-client/asymmetric.py

Copy file name to clipboard
+136Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/bin/python
2+
# Copyright 2018 Google Inc.
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.rom googleapiclient import discovery
15+
16+
import base64
17+
import hashlib
18+
19+
from cryptography.exceptions import InvalidSignature
20+
from cryptography.hazmat.backends import default_backend
21+
from cryptography.hazmat.primitives import hashes, serialization
22+
from cryptography.hazmat.primitives.asymmetric import ec, padding, utils
23+
24+
25+
# [START kms_get_asymmetric_public]
26+
def getAsymmetricPublicKey(client, key_path):
27+
"""Retrieves the public key from a saved asymmetric key pair on Cloud KMS
28+
"""
29+
request = client.projects() \
30+
.locations() \
31+
.keyRings() \
32+
.cryptoKeys() \
33+
.cryptoKeyVersions() \
34+
.getPublicKey(name=key_path)
35+
response = request.execute()
36+
key_txt = response['pem'].encode('ascii')
37+
key = serialization.load_pem_public_key(key_txt, default_backend())
38+
return key
39+
# [END kms_get_asymmetric_public]
40+
41+
42+
# [START kms_decrypt_rsa]
43+
def decryptRSA(ciphertext, client, key_path):
44+
"""Decrypt a given ciphertext using an RSA private key stored on Cloud KMS
45+
"""
46+
request = client.projects() \
47+
.locations() \
48+
.keyRings() \
49+
.cryptoKeys() \
50+
.cryptoKeyVersions() \
51+
.asymmetricDecrypt(name=key_path,
52+
body={'ciphertext': ciphertext})
53+
response = request.execute()
54+
plaintext = base64.b64decode(response['plaintext']).decode('utf-8')
55+
return plaintext
56+
# [END kms_decrypt_rsa]
57+
58+
59+
# [START kms_encrypt_rsa]
60+
def encryptRSA(message, client, key_path):
61+
"""Encrypt message locally using an RSA public key retrieved from Cloud KMS
62+
"""
63+
public_key = getAsymmetricPublicKey(client, key_path)
64+
pad = padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
65+
algorithm=hashes.SHA256(),
66+
label=None)
67+
ciphertext = public_key.encrypt(message.encode('ascii'), pad)
68+
ciphertext = base64.b64encode(ciphertext).decode('utf-8')
69+
return ciphertext
70+
# [END kms_encrypt_rsa]
71+
72+
73+
# [START kms_sign_asymmetric]
74+
def signAsymmetric(message, client, key_path):
75+
"""Create a signature for a message using a private key stored on Cloud KMS
76+
"""
77+
digest_bytes = hashlib.sha256(message.encode('ascii')).digest()
78+
digest64 = base64.b64encode(digest_bytes)
79+
80+
digest_JSON = {'sha256': digest64.decode('utf-8')}
81+
request = client.projects() \
82+
.locations() \
83+
.keyRings() \
84+
.cryptoKeys() \
85+
.cryptoKeyVersions() \
86+
.asymmetricSign(name=key_path,
87+
body={'digest': digest_JSON})
88+
response = request.execute()
89+
return response.get('signature', None)
90+
# [END kms_sign_asymmetric]
91+
92+
93+
# [START kms_verify_signature_rsa]
94+
def verifySignatureRSA(signature, message, client, key_path):
95+
"""Verify the validity of an 'RSA_SIGN_PSS_2048_SHA256' signature
96+
for the specified plaintext message
97+
"""
98+
public_key = getAsymmetricPublicKey(client, key_path)
99+
100+
digest_bytes = hashlib.sha256(message.encode('ascii')).digest()
101+
sig_bytes = base64.b64decode(signature)
102+
103+
try:
104+
# Attempt verification
105+
public_key.verify(sig_bytes,
106+
digest_bytes,
107+
padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
108+
salt_length=32),
109+
utils.Prehashed(hashes.SHA256()))
110+
# No errors were thrown. Verification was successful
111+
return True
112+
except InvalidSignature:
113+
return False
114+
# [END kms_verify_signature_rsa]
115+
116+
117+
# [START kms_verify_signature_ec]
118+
def verifySignatureEC(signature, message, client, key_path):
119+
"""Verify the validity of an 'EC_SIGN_P224_SHA256' signature
120+
for the specified plaintext message
121+
"""
122+
public_key = getAsymmetricPublicKey(client, key_path)
123+
124+
digest_bytes = hashlib.sha256(message.encode('ascii')).digest()
125+
sig_bytes = base64.b64decode(signature)
126+
127+
try:
128+
# Attempt verification
129+
public_key.verify(sig_bytes,
130+
digest_bytes,
131+
ec.ECDSA(utils.Prehashed(hashes.SHA256())))
132+
# No errors were thrown. Verification was successful
133+
return True
134+
except InvalidSignature:
135+
return False
136+
# [END kms_verify_signature_ec]

‎kms/api-client/asymmetric_test.py

Copy file name to clipboard
+141Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#!/bin/python
2+
# Copyright 2018 Google Inc.
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+
from os import environ
18+
from time import sleep
19+
20+
from cryptography.hazmat.backends.openssl.ec import _EllipticCurvePublicKey
21+
from cryptography.hazmat.backends.openssl.rsa import _RSAPublicKey
22+
from googleapiclient import discovery
23+
from googleapiclient.errors import HttpError
24+
import sample
25+
26+
27+
def create_key_helper(key_id, key_path, purpose, algorithm, t):
28+
try:
29+
t.client.projects() \
30+
.locations() \
31+
.keyRings() \
32+
.cryptoKeys() \
33+
.create(parent='{}/keyRings/{}'.format(t.parent, t.keyring),
34+
body={'purpose': purpose,
35+
'versionTemplate': {
36+
'algorithm': algorithm
37+
}
38+
},
39+
cryptoKeyId=key_id) \
40+
.execute()
41+
return True
42+
except HttpError:
43+
# key already exists
44+
return False
45+
46+
47+
def setup_module(module):
48+
"""
49+
Set up keys in project if needed
50+
"""
51+
t = TestKMSSamples()
52+
try:
53+
# create keyring
54+
t.client.projects() \
55+
.locations() \
56+
.keyRings() \
57+
.create(parent=t.parent, body={}, keyRingId=t.keyring) \
58+
.execute()
59+
except HttpError:
60+
# keyring already exists
61+
pass
62+
s1 = create_key_helper(t.rsaDecryptId, t.rsaDecrypt, 'ASYMMETRIC_DECRYPT',
63+
'RSA_DECRYPT_OAEP_2048_SHA256', t)
64+
s2 = create_key_helper(t.rsaSignId, t.rsaSign, 'ASYMMETRIC_SIGN',
65+
'RSA_SIGN_PSS_2048_SHA256', t)
66+
s3 = create_key_helper(t.ecSignId, t.ecSign, 'ASYMMETRIC_SIGN',
67+
'EC_SIGN_P224_SHA256', t)
68+
if s1 or s2 or s3:
69+
# leave time for keys to initialize
70+
sleep(20)
71+
72+
73+
class TestKMSSamples:
74+
75+
project_id = environ['GCLOUD_PROJECT']
76+
keyring = 'kms-asymmetric-samples4'
77+
parent = 'projects/{}/locations/global'.format(project_id)
78+
79+
rsaSignId = 'rsa-sign'
80+
rsaDecryptId = 'rsa-decrypt'
81+
ecSignId = 'ec-sign'
82+
83+
rsaSign = '{}/keyRings/{}/cryptoKeys/{}/cryptoKeyVersions/1' \
84+
.format(parent, keyring, rsaSignId)
85+
rsaDecrypt = '{}/keyRings/{}/cryptoKeys/{}/cryptoKeyVersions/1' \
86+
.format(parent, keyring, rsaDecryptId)
87+
ecSign = '{}/keyRings/{}/cryptoKeys/{}/cryptoKeyVersions/1' \
88+
.format(parent, keyring, ecSignId)
89+
90+
message = 'test message 123'
91+
92+
client = discovery.build('cloudkms', 'v1')
93+
94+
def test_get_public_key(self):
95+
rsa_key = sample.getAsymmetricPublicKey(self.client, self.rsaDecrypt)
96+
assert isinstance(rsa_key, _RSAPublicKey), 'expected RSA key'
97+
ec_key = sample.getAsymmetricPublicKey(self.client, self.ecSign)
98+
assert isinstance(ec_key, _EllipticCurvePublicKey), 'expected EC key'
99+
100+
def test_rsa_encrypt_decrypt(self):
101+
ciphertext = sample.encryptRSA(self.message,
102+
self.client,
103+
self.rsaDecrypt)
104+
# ciphertext should be 344 characters with base64 and RSA 2048
105+
assert len(ciphertext) == 344, \
106+
'ciphertext should be 344 chars; got {}'.format(len(ciphertext))
107+
assert ciphertext[-2:] == '==', 'cipher text should end with =='
108+
plaintext = sample.decryptRSA(ciphertext, self.client, self.rsaDecrypt)
109+
assert plaintext == self.message
110+
111+
def test_rsa_sign_verify(self):
112+
sig = sample.signAsymmetric(self.message, self.client, self.rsaSign)
113+
# ciphertext should be 344 characters with base64 and RSA 2048
114+
assert len(sig) == 344, \
115+
'sig should be 344 chars; got {}'.format(len(sig))
116+
assert sig[-2:] == '==', 'sig should end with =='
117+
success = sample.verifySignatureRSA(sig,
118+
self.message,
119+
self.client,
120+
self.rsaSign)
121+
assert success is True, 'RSA verification failed'
122+
success = sample.verifySignatureRSA(sig,
123+
self.message+'.',
124+
self.client,
125+
self.rsaSign)
126+
assert success is False, 'verify should fail with modified message'
127+
128+
def test_ec_sign_verify(self):
129+
sig = sample.signAsymmetric(self.message, self.client, self.ecSign)
130+
assert len(sig) > 50 and len(sig) < 300, \
131+
'sig outside expected length range'
132+
success = sample.verifySignatureEC(sig,
133+
self.message,
134+
self.client,
135+
self.ecSign)
136+
assert success is True, 'EC verification failed'
137+
success = sample.verifySignatureEC(sig,
138+
self.message+'.',
139+
self.client,
140+
self.ecSign)
141+
assert success is False, 'verify should fail with modified message'

‎kms/api-client/requirements.txt

Copy file name to clipboard
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
google-api-python-client==1.6.6
22
google-auth==1.4.1
33
google-auth-httplib2==0.0.3
4+
cryptography==2.3.1

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.