lib/kms/gcpcrypt/encryption.go (78 lines of code) (raw):

// Copyright 2019 Google LLC // // 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. // Package gcpcrypt contains a client of GCP Cloud KMS symmetric encryption. // GCP Cloud KMS symmetric encryption service is an encryption/decryption service // where keys are stored securely on GCP. package gcpcrypt import ( "context" "fmt" "cloud.google.com/go/kms/apiv1" /* copybara-comment */ "google.golang.org/grpc/codes" /* copybara-comment */ "google.golang.org/grpc/status" /* copybara-comment */ rpb "google.golang.org/genproto/googleapis/cloud/kms/v1" /* copybara-comment: resources_go_proto */ kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" /* copybara-comment: service_go_proto */ ) // Client of GCP CloudKMS symmetric encryption service. // Sensitive data should be encrypted in database. // With GCP CloudKMS, we don't need to worry about how to store the key safely. // This Client wraps CloudKMS client in common interface, developer can easier // support other KMS. type Client struct { cryptoKeyID string client *kms.KeyManagementClient } // New returns Client. func New(ctx context.Context, projectID, keyRingLocation, keyRingName, keyName string, client *kms.KeyManagementClient) (*Client, error) { // Try create key ring. createRingReq := &kmspb.CreateKeyRingRequest{ Parent: locationName(projectID, keyRingLocation), KeyRingId: keyRingName, } if _, err := client.CreateKeyRing(ctx, createRingReq); err != nil && status.Code(err) != codes.AlreadyExists { return nil, fmt.Errorf("client.CreateKeyRing(ctx, %q) failed: %v", ringName(projectID, keyRingLocation, keyRingName), err) } // Try create key. createKeyReq := &kmspb.CreateCryptoKeyRequest{ Parent: ringName(projectID, keyRingLocation, keyRingName), CryptoKeyId: keyName, CryptoKey: &rpb.CryptoKey{ Purpose: rpb.CryptoKey_ENCRYPT_DECRYPT, VersionTemplate: &rpb.CryptoKeyVersionTemplate{ Algorithm: rpb.CryptoKeyVersion_GOOGLE_SYMMETRIC_ENCRYPTION, }, }, } if _, err := client.CreateCryptoKey(ctx, createKeyReq); err != nil && status.Code(err) != codes.AlreadyExists { return nil, fmt.Errorf("client.CreateCryptoKey(ctx, %q) failed: %v", cryptoKeyName(projectID, keyRingLocation, keyRingName, keyName), err) } // Get key information. key, err := client.GetCryptoKey(ctx, &kmspb.GetCryptoKeyRequest{Name: cryptoKeyName(projectID, keyRingLocation, keyRingName, keyName)}) if err != nil { return nil, fmt.Errorf("client.GetCryptoKey(ctx, %q) failed: %v", cryptoKeyName(projectID, keyRingLocation, keyRingName, keyName), err) } // Ensure key is use for symmetric encryption and use correct algorithm. if key.Purpose != rpb.CryptoKey_ENCRYPT_DECRYPT || key.VersionTemplate.Algorithm != rpb.CryptoKeyVersion_GOOGLE_SYMMETRIC_ENCRYPTION { return nil, fmt.Errorf("key %q has incorrect purpose %q or algorithm %q", cryptoKeyName(projectID, keyRingLocation, keyRingName, keyName), key.Purpose.String(), key.VersionTemplate.Algorithm.String()) } return &Client{cryptoKeyID: cryptoKeyName(projectID, keyRingLocation, keyRingName, keyName), client: client}, nil } // Encrypt data with Cloud KMS. func (s *Client) Encrypt(ctx context.Context, data []byte, additionalAuthData string) ([]byte, error) { req := &kmspb.EncryptRequest{ Name: s.cryptoKeyID, Plaintext: data, AdditionalAuthenticatedData: []byte(additionalAuthData), } resp, err := s.client.Encrypt(ctx, req) if err != nil { return nil, fmt.Errorf("kms.Encrypt(%+v) failed: %v", req.Name, err) } return resp.Ciphertext, nil } // Decrypt data with Cloud KMS. func (s *Client) Decrypt(ctx context.Context, encrypted []byte, additionalAuthData string) ([]byte, error) { req := &kmspb.DecryptRequest{ Name: s.cryptoKeyID, Ciphertext: encrypted, AdditionalAuthenticatedData: []byte(additionalAuthData), } resp, err := s.client.Decrypt(ctx, req) if err != nil { return nil, fmt.Errorf("kms.Decrypt(%+v) failed: %v", req.Name, err) } return resp.Plaintext, nil } func locationName(proj, loc string) string { return "projects/" + proj + "/locations/" + loc } func ringName(proj, loc, ring string) string { return locationName(proj, loc) + "/keyRings/" + ring } func cryptoKeyName(proj, loc, ring, key string) string { return ringName(proj, loc, ring) + "/cryptoKeys/" + key }