From ecc131c206670023dfc6ecabff5876aaa8f7a631 Mon Sep 17 00:00:00 2001 From: Dan Sanche Date: Fri, 21 Sep 2018 13:59:56 -0700 Subject: [PATCH 01/10] added region tag for imports --- kms/api-client/asymmetric.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kms/api-client/asymmetric.py b/kms/api-client/asymmetric.py index 4d4ebcb4f3b..83278c43443 100644 --- a/kms/api-client/asymmetric.py +++ b/kms/api-client/asymmetric.py @@ -20,7 +20,7 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, padding, utils - +# [END kms_asymmetric_imports] # [START kms_get_asymmetric_public] def getAsymmetricPublicKey(client, key_path): From ca425750b84861cda2376638eae3eae167224a3c Mon Sep 17 00:00:00 2001 From: Dan Sanche Date: Tue, 25 Sep 2018 12:15:47 -0700 Subject: [PATCH 02/10] signing works with bytes --- kms/api-client/asymmetric.py | 6 +++--- kms/api-client/asymmetric_test.py | 15 +++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/kms/api-client/asymmetric.py b/kms/api-client/asymmetric.py index 83278c43443..ee0273f17e3 100644 --- a/kms/api-client/asymmetric.py +++ b/kms/api-client/asymmetric.py @@ -82,7 +82,7 @@ def signAsymmetric(message, client, key_path): """ # Note: some key algorithms will require a different hash function # For example, EC_SIGN_P384_SHA384 requires SHA384 - digest_bytes = hashlib.sha256(message.encode('ascii')).digest() + digest_bytes = hashlib.sha256(message).digest() digest64 = base64.b64encode(digest_bytes) digest_JSON = {'sha256': digest64.decode('utf-8')} @@ -106,7 +106,7 @@ def verifySignatureRSA(signature, message, client, key_path): """ public_key = getAsymmetricPublicKey(client, key_path) - digest_bytes = hashlib.sha256(message.encode('ascii')).digest() + digest_bytes = hashlib.sha256(message).digest() sig_bytes = base64.b64decode(signature) try: @@ -131,7 +131,7 @@ def verifySignatureEC(signature, message, client, key_path): """ public_key = getAsymmetricPublicKey(client, key_path) - digest_bytes = hashlib.sha256(message.encode('ascii')).digest() + digest_bytes = hashlib.sha256(message).digest() sig_bytes = base64.b64decode(signature) try: diff --git a/kms/api-client/asymmetric_test.py b/kms/api-client/asymmetric_test.py index 5f969be7db6..d50d627a4e9 100644 --- a/kms/api-client/asymmetric_test.py +++ b/kms/api-client/asymmetric_test.py @@ -89,6 +89,7 @@ class TestKMSSamples: .format(parent, keyring, ecSignId) message = 'test message 123' + message_bytes = message.encode('utf-8') client = discovery.build('cloudkms', 'v1') @@ -110,33 +111,35 @@ def test_rsa_encrypt_decrypt(self): assert plaintext == self.message def test_rsa_sign_verify(self): - sig = sample.signAsymmetric(self.message, self.client, self.rsaSign) + sig = sample.signAsymmetric(self.message_bytes, self.client, self.rsaSign) # ciphertext should be 344 characters with base64 and RSA 2048 assert len(sig) == 344, \ 'sig should be 344 chars; got {}'.format(len(sig)) assert sig[-2:] == '==', 'sig should end with ==' success = sample.verifySignatureRSA(sig, - self.message, + self.message_bytes, self.client, self.rsaSign) assert success is True, 'RSA verification failed' + changed_bytes = (self.message+".").encode('utf-8') success = sample.verifySignatureRSA(sig, - self.message+'.', + changed_bytes, self.client, self.rsaSign) assert success is False, 'verify should fail with modified message' def test_ec_sign_verify(self): - sig = sample.signAsymmetric(self.message, self.client, self.ecSign) + sig = sample.signAsymmetric(self.message_bytes, self.client, self.ecSign) assert len(sig) > 50 and len(sig) < 300, \ 'sig outside expected length range' success = sample.verifySignatureEC(sig, - self.message, + self.message_bytes, self.client, self.ecSign) assert success is True, 'EC verification failed' + changed_bytes = (self.message+".").encode('utf-8') success = sample.verifySignatureEC(sig, - self.message+'.', + changed_bytes, self.client, self.ecSign) assert success is False, 'verify should fail with modified message' From c4efd87b2c1ccf0eed30dfc332b161eeeabfd4ec Mon Sep 17 00:00:00 2001 From: Dan Sanche Date: Tue, 25 Sep 2018 13:23:26 -0700 Subject: [PATCH 03/10] encrypt/decrypt uses bytes --- kms/api-client/asymmetric.py | 10 +++++----- kms/api-client/asymmetric_test.py | 23 ++++++++++++++++------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/kms/api-client/asymmetric.py b/kms/api-client/asymmetric.py index ee0273f17e3..27998cf196a 100644 --- a/kms/api-client/asymmetric.py +++ b/kms/api-client/asymmetric.py @@ -16,6 +16,7 @@ import base64 import hashlib +# [START kms_asymmetric_imports] from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization @@ -46,15 +47,16 @@ def decryptRSA(ciphertext, client, key_path): Decrypt a given ciphertext using an 'RSA_DECRYPT_OAEP_2048_SHA256' private key stored on Cloud KMS """ + request_body={'ciphertext': base64.b64encode(ciphertext).decode()} request = client.projects() \ .locations() \ .keyRings() \ .cryptoKeys() \ .cryptoKeyVersions() \ .asymmetricDecrypt(name=key_path, - body={'ciphertext': ciphertext}) + body=request_body) response = request.execute() - plaintext = base64.b64decode(response['plaintext']).decode('utf-8') + plaintext = base64.b64decode(response['plaintext']) return plaintext # [END kms_decrypt_rsa] @@ -69,9 +71,7 @@ def encryptRSA(message, client, key_path): pad = padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None) - ciphertext = public_key.encrypt(message.encode('ascii'), pad) - ciphertext = base64.b64encode(ciphertext).decode('utf-8') - return ciphertext + return public_key.encrypt(message, pad) # [END kms_encrypt_rsa] diff --git a/kms/api-client/asymmetric_test.py b/kms/api-client/asymmetric_test.py index d50d627a4e9..f9fdb45f80f 100644 --- a/kms/api-client/asymmetric_test.py +++ b/kms/api-client/asymmetric_test.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. - +import base64 from os import environ from time import sleep @@ -100,18 +100,25 @@ def test_get_public_key(self): assert isinstance(ec_key, _EllipticCurvePublicKey), 'expected EC key' def test_rsa_encrypt_decrypt(self): - ciphertext = sample.encryptRSA(self.message, - self.client, - self.rsaDecrypt) + ciphertext_bytes = sample.encryptRSA(self.message_bytes, + self.client, + self.rsaDecrypt) + ciphertext = base64.b64encode(ciphertext_bytes).decode('utf-8') # ciphertext should be 344 characters with base64 and RSA 2048 assert len(ciphertext) == 344, \ 'ciphertext should be 344 chars; got {}'.format(len(ciphertext)) assert ciphertext[-2:] == '==', 'cipher text should end with ==' - plaintext = sample.decryptRSA(ciphertext, self.client, self.rsaDecrypt) + plaintext_bytes = sample.decryptRSA(ciphertext_bytes, + self.client, + self.rsaDecrypt) + assert plaintext_bytes == self.message_bytes + plaintext = plaintext_bytes.decode('utf-8') assert plaintext == self.message def test_rsa_sign_verify(self): - sig = sample.signAsymmetric(self.message_bytes, self.client, self.rsaSign) + sig = sample.signAsymmetric(self.message_bytes, + self.client, + self.rsaSign) # ciphertext should be 344 characters with base64 and RSA 2048 assert len(sig) == 344, \ 'sig should be 344 chars; got {}'.format(len(sig)) @@ -129,7 +136,9 @@ def test_rsa_sign_verify(self): assert success is False, 'verify should fail with modified message' def test_ec_sign_verify(self): - sig = sample.signAsymmetric(self.message_bytes, self.client, self.ecSign) + sig = sample.signAsymmetric(self.message_bytes, + self.client, + self.ecSign) assert len(sig) > 50 and len(sig) < 300, \ 'sig outside expected length range' success = sample.verifySignatureEC(sig, From 91a4324bbfc2d0b489df511eb0781f31bf7670ee Mon Sep 17 00:00:00 2001 From: Dan Sanche Date: Tue, 25 Sep 2018 14:55:34 -0700 Subject: [PATCH 04/10] fixed style issues --- kms/api-client/asymmetric.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kms/api-client/asymmetric.py b/kms/api-client/asymmetric.py index 27998cf196a..edd8e94635d 100644 --- a/kms/api-client/asymmetric.py +++ b/kms/api-client/asymmetric.py @@ -13,16 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License.rom googleapiclient import discovery +# [START kms_asymmetric_imports] import base64 import hashlib -# [START kms_asymmetric_imports] from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, padding, utils # [END kms_asymmetric_imports] + # [START kms_get_asymmetric_public] def getAsymmetricPublicKey(client, key_path): """ @@ -47,7 +48,7 @@ def decryptRSA(ciphertext, client, key_path): Decrypt a given ciphertext using an 'RSA_DECRYPT_OAEP_2048_SHA256' private key stored on Cloud KMS """ - request_body={'ciphertext': base64.b64encode(ciphertext).decode()} + request_body = {'ciphertext': base64.b64encode(ciphertext).decode()} request = client.projects() \ .locations() \ .keyRings() \ From fc79ceb031db3f00c9debe276fce714a5d0ac592 Mon Sep 17 00:00:00 2001 From: Dan Sanche Date: Tue, 25 Sep 2018 15:19:16 -0700 Subject: [PATCH 05/10] moved location of base64 encoding --- kms/api-client/asymmetric.py | 5 +++-- kms/api-client/asymmetric_test.py | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/kms/api-client/asymmetric.py b/kms/api-client/asymmetric.py index edd8e94635d..87ba956a327 100644 --- a/kms/api-client/asymmetric.py +++ b/kms/api-client/asymmetric.py @@ -48,7 +48,7 @@ def decryptRSA(ciphertext, client, key_path): Decrypt a given ciphertext using an 'RSA_DECRYPT_OAEP_2048_SHA256' private key stored on Cloud KMS """ - request_body = {'ciphertext': base64.b64encode(ciphertext).decode()} + request_body = {'ciphertext': ciphertext.decode()} request = client.projects() \ .locations() \ .keyRings() \ @@ -72,7 +72,8 @@ def encryptRSA(message, client, key_path): pad = padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None) - return public_key.encrypt(message, pad) + ciphertext = public_key.encrypt(message, pad) + return base64.b64encode(ciphertext) # [END kms_encrypt_rsa] diff --git a/kms/api-client/asymmetric_test.py b/kms/api-client/asymmetric_test.py index f9fdb45f80f..6b43b5afe63 100644 --- a/kms/api-client/asymmetric_test.py +++ b/kms/api-client/asymmetric_test.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import base64 from os import environ from time import sleep @@ -103,7 +102,7 @@ def test_rsa_encrypt_decrypt(self): ciphertext_bytes = sample.encryptRSA(self.message_bytes, self.client, self.rsaDecrypt) - ciphertext = base64.b64encode(ciphertext_bytes).decode('utf-8') + ciphertext = ciphertext_bytes.decode('utf-8') # ciphertext should be 344 characters with base64 and RSA 2048 assert len(ciphertext) == 344, \ 'ciphertext should be 344 chars; got {}'.format(len(ciphertext)) From 6e0d35e9f4659d20f6465073ffc1dc649c4ccca4 Mon Sep 17 00:00:00 2001 From: Dan Sanche Date: Tue, 25 Sep 2018 15:53:03 -0700 Subject: [PATCH 06/10] renamed message to plaintext --- kms/api-client/asymmetric.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kms/api-client/asymmetric.py b/kms/api-client/asymmetric.py index 87ba956a327..ff8da15db0f 100644 --- a/kms/api-client/asymmetric.py +++ b/kms/api-client/asymmetric.py @@ -63,7 +63,7 @@ def decryptRSA(ciphertext, client, key_path): # [START kms_encrypt_rsa] -def encryptRSA(message, client, key_path): +def encryptRSA(plaintext, client, key_path): """ Encrypt message locally using an 'RSA_DECRYPT_OAEP_2048_SHA256' public key retrieved from Cloud KMS @@ -72,7 +72,7 @@ def encryptRSA(message, client, key_path): pad = padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None) - ciphertext = public_key.encrypt(message, pad) + ciphertext = public_key.encrypt(plaintext, pad) return base64.b64encode(ciphertext) # [END kms_encrypt_rsa] From 09927c4c4038c71847c107a7045d15ca5b7e3d1e Mon Sep 17 00:00:00 2001 From: Dan Sanche Date: Tue, 25 Sep 2018 16:35:33 -0700 Subject: [PATCH 07/10] modified comments --- kms/api-client/asymmetric.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kms/api-client/asymmetric.py b/kms/api-client/asymmetric.py index ff8da15db0f..18693bca856 100644 --- a/kms/api-client/asymmetric.py +++ b/kms/api-client/asymmetric.py @@ -45,7 +45,7 @@ def getAsymmetricPublicKey(client, key_path): # [START kms_decrypt_rsa] def decryptRSA(ciphertext, client, key_path): """ - Decrypt a given ciphertext using an 'RSA_DECRYPT_OAEP_2048_SHA256' private + Decrypt the given ciphertext using an 'RSA_DECRYPT_OAEP_2048_SHA256' private key stored on Cloud KMS """ request_body = {'ciphertext': ciphertext.decode()} @@ -65,7 +65,7 @@ def decryptRSA(ciphertext, client, key_path): # [START kms_encrypt_rsa] def encryptRSA(plaintext, client, key_path): """ - Encrypt message locally using an 'RSA_DECRYPT_OAEP_2048_SHA256' public + Encrypt data locally using an 'RSA_DECRYPT_OAEP_2048_SHA256' public key retrieved from Cloud KMS """ public_key = getAsymmetricPublicKey(client, key_path) @@ -104,7 +104,7 @@ def signAsymmetric(message, client, key_path): def verifySignatureRSA(signature, message, client, key_path): """ Verify the validity of an 'RSA_SIGN_PSS_2048_SHA256' signature for the - specified plaintext message + specified message """ public_key = getAsymmetricPublicKey(client, key_path) @@ -129,7 +129,7 @@ def verifySignatureRSA(signature, message, client, key_path): def verifySignatureEC(signature, message, client, key_path): """ Verify the validity of an 'EC_SIGN_P256_SHA256' signature - for the specified plaintext message + for the specified message """ public_key = getAsymmetricPublicKey(client, key_path) From e85865b6f65b491f29c556adfb4f0c0669bbbd7c Mon Sep 17 00:00:00 2001 From: Dan Sanche Date: Thu, 27 Sep 2018 12:46:10 -0700 Subject: [PATCH 08/10] addressed PR comments --- kms/api-client/asymmetric.py | 13 ++++++------- kms/api-client/asymmetric_test.py | 8 ++++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/kms/api-client/asymmetric.py b/kms/api-client/asymmetric.py index 18693bca856..4c21cd88d57 100644 --- a/kms/api-client/asymmetric.py +++ b/kms/api-client/asymmetric.py @@ -45,10 +45,10 @@ def getAsymmetricPublicKey(client, key_path): # [START kms_decrypt_rsa] def decryptRSA(ciphertext, client, key_path): """ - Decrypt the given ciphertext using an 'RSA_DECRYPT_OAEP_2048_SHA256' private - key stored on Cloud KMS + Decrypt the input ciphertext (bytes) using an + 'RSA_DECRYPT_OAEP_2048_SHA256' private key stored on Cloud KMS """ - request_body = {'ciphertext': ciphertext.decode()} + request_body = {'ciphertext': base64.b64encode(ciphertext).decode()} request = client.projects() \ .locations() \ .keyRings() \ @@ -65,15 +65,14 @@ def decryptRSA(ciphertext, client, key_path): # [START kms_encrypt_rsa] def encryptRSA(plaintext, client, key_path): """ - Encrypt data locally using an 'RSA_DECRYPT_OAEP_2048_SHA256' public - key retrieved from Cloud KMS + Encrypt the input plaintext (bytes) locally using an + 'RSA_DECRYPT_OAEP_2048_SHA256' public key retrieved from Cloud KMS """ public_key = getAsymmetricPublicKey(client, key_path) pad = padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None) - ciphertext = public_key.encrypt(plaintext, pad) - return base64.b64encode(ciphertext) + return public_key.encrypt(plaintext, pad) # [END kms_encrypt_rsa] diff --git a/kms/api-client/asymmetric_test.py b/kms/api-client/asymmetric_test.py index 6b43b5afe63..16d913fea7b 100644 --- a/kms/api-client/asymmetric_test.py +++ b/kms/api-client/asymmetric_test.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import base64 from os import environ from time import sleep @@ -102,11 +103,10 @@ def test_rsa_encrypt_decrypt(self): ciphertext_bytes = sample.encryptRSA(self.message_bytes, self.client, self.rsaDecrypt) - ciphertext = ciphertext_bytes.decode('utf-8') + ciphertext = base64.b64encode(ciphertext_bytes).decode() # ciphertext should be 344 characters with base64 and RSA 2048 assert len(ciphertext) == 344, \ 'ciphertext should be 344 chars; got {}'.format(len(ciphertext)) - assert ciphertext[-2:] == '==', 'cipher text should end with ==' plaintext_bytes = sample.decryptRSA(ciphertext_bytes, self.client, self.rsaDecrypt) @@ -127,7 +127,7 @@ def test_rsa_sign_verify(self): self.client, self.rsaSign) assert success is True, 'RSA verification failed' - changed_bytes = (self.message+".").encode('utf-8') + changed_bytes = self.message_bytes + b'.' success = sample.verifySignatureRSA(sig, changed_bytes, self.client, @@ -145,7 +145,7 @@ def test_ec_sign_verify(self): self.client, self.ecSign) assert success is True, 'EC verification failed' - changed_bytes = (self.message+".").encode('utf-8') + changed_bytes = self.message_bytes + b'.' success = sample.verifySignatureEC(sig, changed_bytes, self.client, From 927e3cc59a11d86dba4600f785716a461ce56655 Mon Sep 17 00:00:00 2001 From: Dan Sanche Date: Fri, 28 Sep 2018 12:39:57 -0700 Subject: [PATCH 09/10] sign/verify uses bytes instead of String --- kms/api-client/asymmetric.py | 10 +++------- kms/api-client/asymmetric_test.py | 21 +++++++++------------ 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/kms/api-client/asymmetric.py b/kms/api-client/asymmetric.py index 4c21cd88d57..b5066d3279e 100644 --- a/kms/api-client/asymmetric.py +++ b/kms/api-client/asymmetric.py @@ -95,7 +95,7 @@ def signAsymmetric(message, client, key_path): .asymmetricSign(name=key_path, body={'digest': digest_JSON}) response = request.execute() - return response.get('signature', None) + return base64.b64decode(response.get('signature', None)) # [END kms_sign_asymmetric] @@ -106,13 +106,11 @@ def verifySignatureRSA(signature, message, client, key_path): specified message """ public_key = getAsymmetricPublicKey(client, key_path) - digest_bytes = hashlib.sha256(message).digest() - sig_bytes = base64.b64decode(signature) try: # Attempt verification - public_key.verify(sig_bytes, + public_key.verify(signature, digest_bytes, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32), @@ -131,13 +129,11 @@ def verifySignatureEC(signature, message, client, key_path): for the specified message """ public_key = getAsymmetricPublicKey(client, key_path) - digest_bytes = hashlib.sha256(message).digest() - sig_bytes = base64.b64decode(signature) try: # Attempt verification - public_key.verify(sig_bytes, + public_key.verify(signature, digest_bytes, ec.ECDSA(utils.Prehashed(hashes.SHA256()))) # No errors were thrown. Verification was successful diff --git a/kms/api-client/asymmetric_test.py b/kms/api-client/asymmetric_test.py index 16d913fea7b..4ce9b32aed5 100644 --- a/kms/api-client/asymmetric_test.py +++ b/kms/api-client/asymmetric_test.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import base64 from os import environ from time import sleep @@ -100,14 +99,13 @@ def test_get_public_key(self): assert isinstance(ec_key, _EllipticCurvePublicKey), 'expected EC key' def test_rsa_encrypt_decrypt(self): - ciphertext_bytes = sample.encryptRSA(self.message_bytes, - self.client, - self.rsaDecrypt) - ciphertext = base64.b64encode(ciphertext_bytes).decode() - # ciphertext should be 344 characters with base64 and RSA 2048 - assert len(ciphertext) == 344, \ - 'ciphertext should be 344 chars; got {}'.format(len(ciphertext)) - plaintext_bytes = sample.decryptRSA(ciphertext_bytes, + ciphertext = sample.encryptRSA(self.message_bytes, + self.client, + self.rsaDecrypt) + # ciphertext should be 256 characters with base64 and RSA 2048 + assert len(ciphertext) == 256, \ + 'ciphertext should be 256 chars; got {}'.format(len(ciphertext)) + plaintext_bytes = sample.decryptRSA(ciphertext, self.client, self.rsaDecrypt) assert plaintext_bytes == self.message_bytes @@ -119,9 +117,8 @@ def test_rsa_sign_verify(self): self.client, self.rsaSign) # ciphertext should be 344 characters with base64 and RSA 2048 - assert len(sig) == 344, \ - 'sig should be 344 chars; got {}'.format(len(sig)) - assert sig[-2:] == '==', 'sig should end with ==' + assert len(sig) == 256, \ + 'sig should be 256 chars; got {}'.format(len(sig)) success = sample.verifySignatureRSA(sig, self.message_bytes, self.client, From 293e6051efc504995199e24e2721cfea6770d754 Mon Sep 17 00:00:00 2001 From: Dan Sanche Date: Fri, 28 Sep 2018 16:32:16 -0700 Subject: [PATCH 10/10] decode utf-8 --- kms/api-client/asymmetric.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kms/api-client/asymmetric.py b/kms/api-client/asymmetric.py index b5066d3279e..bc313aaa849 100644 --- a/kms/api-client/asymmetric.py +++ b/kms/api-client/asymmetric.py @@ -48,7 +48,7 @@ def decryptRSA(ciphertext, client, key_path): Decrypt the input ciphertext (bytes) using an 'RSA_DECRYPT_OAEP_2048_SHA256' private key stored on Cloud KMS """ - request_body = {'ciphertext': base64.b64encode(ciphertext).decode()} + request_body = {'ciphertext': base64.b64encode(ciphertext).decode('utf-8')} request = client.projects() \ .locations() \ .keyRings() \