Issue 67869881 Add decryption/encryption (#32)

* [ISSUE-67869881] add encryption/decryption

* [ISSUE-67869881] add decryption/encryption

* [ISSUE-67869881] update retrieve key  request

* [ISSUE-67869881] support orgs without encryption

* [ISSUE-67869881] update url path

* [ISSUE-67869881] update request

* [ISSUE-67869881] address comments, add test cases

* [ISSUE-67869881] add more test cases
diff --git a/accessEntity/api.go b/accessEntity/api.go
index b239409..fb49fca 100644
--- a/accessEntity/api.go
+++ b/accessEntity/api.go
@@ -246,7 +246,7 @@
 			log.Errorf("getCompanyDeveloper: %v", err)
 			return nil, newDbError(err)
 		}
-		email, err := a.DbMan.GetDevEmailByDevId(dev.DeveloperId)
+		email, err := a.DbMan.GetDevEmailByDevId(dev.DeveloperId, org)
 		if err != nil {
 			log.Errorf("getCompanyDeveloper: %v", err)
 			return nil, newDbError(err)
diff --git a/accessEntity/data.go b/accessEntity/data.go
index 3e808b0..4634f61 100644
--- a/accessEntity/data.go
+++ b/accessEntity/data.go
@@ -16,6 +16,7 @@
 import (
 	"database/sql"
 	"fmt"
+	"github.com/apid/apid-core/util"
 	"github.com/apid/apidApiMetadata/common"
 	"strings"
 )
@@ -84,7 +85,7 @@
 	return name.String, nil
 }
 
-func (d *DbManager) GetDevEmailByDevId(devId string) (string, error) {
+func (d *DbManager) GetDevEmailByDevId(devId string, org string) (string, error) {
 	query := selectDeveloperById(
 		"?",
 		"email",
@@ -94,7 +95,7 @@
 	if err != nil || !email.Valid {
 		return "", err
 	}
-	return email.String, nil
+	return email.String, err
 }
 
 func (d *DbManager) GetComNames(id string, idType string) ([]string, error) {
@@ -278,23 +279,36 @@
 
 	switch priKey {
 	case IdentifierConsumerKey:
-		return d.getAppCredentialByConsumerKey(priVal, org)
+		appCredentials, err = d.getAppCredentialByConsumerKey(priVal, org)
 	case IdentifierAppId:
-		return d.getAppCredentialByAppId(priVal, org)
+		appCredentials, err = d.getAppCredentialByAppId(priVal, org)
 	}
+
+	if err != nil {
+		return
+	}
+
+	var plaintext string
+	for i := range appCredentials {
+		if plaintext, err = d.CipherManager.TryDecryptBase64(appCredentials[i].ConsumerSecret, org); err != nil {
+			return
+		}
+		appCredentials[i].ConsumerSecret = plaintext
+	}
+
 	return
 }
 
 func (d *DbManager) GetDevelopers(org, priKey, priVal, secKey, secVal string) (developers []common.Developer, err error) {
 	switch priKey {
 	case IdentifierAppId:
-		return d.getDeveloperByAppId(priVal, org)
+		developers, err = d.getDeveloperByAppId(priVal, org)
 	case IdentifierDeveloperEmail:
-		return d.getDeveloperByEmail(priVal, org)
+		developers, err = d.getDeveloperByEmail(priVal, org)
 	case IdentifierConsumerKey:
-		return d.getDeveloperByConsumerKey(priVal, org)
+		developers, err = d.getDeveloperByConsumerKey(priVal, org)
 	case IdentifierDeveloperId:
-		return d.getDeveloperById(priVal, org)
+		developers, err = d.getDeveloperById(priVal, org)
 	}
 	return
 }
@@ -745,19 +759,10 @@
 	var prods []common.ApiProduct
 	for _, prod := range apiProducts {
 		resources := common.JsonToStringArray(prod.ApiResources)
-		if Contains(resources, resource) {
+		if util.Contains(resources, resource) {
 			prods = append(prods, prod)
 		}
 	}
 	//log.Debugf("After filter: %v", prods)
 	return prods
 }
-
-func Contains(sl []string, str string) bool {
-	for _, s := range sl {
-		if s == str {
-			return true
-		}
-	}
-	return false
-}
diff --git a/accessEntity/data_test.go b/accessEntity/data_test.go
index 9b9924d..aa3f51c 100644
--- a/accessEntity/data_test.go
+++ b/accessEntity/data_test.go
@@ -43,8 +43,9 @@
 
 			dbMan = &DbManager{
 				DbManager: common.DbManager{
-					Data:  services.Data(),
-					DbMux: sync.RWMutex{},
+					Data:          services.Data(),
+					DbMux:         sync.RWMutex{},
+					CipherManager: &DummyCipherMan{},
 				},
 			}
 			dbMan.SetDbVersion(dataTestTempDir)
@@ -529,7 +530,7 @@
 			It("GetDevEmailByDevId", func() {
 				data := "e41f04e8-9d3f-470a-8bfd-c7939945896c"
 				expected := "bar@google.com"
-				Expect(dbMan.GetDevEmailByDevId(data)).Should(Equal(expected))
+				Expect(dbMan.GetDevEmailByDevId(data, "apid-haoming")).Should(Equal(expected))
 			})
 
 			It("GetStatus", func() {
diff --git a/accessEntity/data_test.sql b/accessEntity/data_test.sql
index 8b1590a..7f01503 100644
--- a/accessEntity/data_test.sql
+++ b/accessEntity/data_test.sql
@@ -49,9 +49,9 @@
 INSERT INTO "kms_api_product" VALUES('db90a25a-15c8-42ad-96c1-63ed9682b5a9','515211e9','apigee-remote-proxy','apigee-remote-proxy','','{/**,/}','AUTO','{""}','{apigee-remote-proxy}','{prod,test}','','',NULL,'2017-09-20 23:05:09.234+00:00','haoming@apid.git','2017-09-20 23:05:09.234+00:00','haoming@apid.git','515211e9');
 INSERT INTO "kms_api_product" VALUES('fea8a6d5-8d34-477f-ac82-c397eaec06af','515211e9','testproductsdljnkpt','testproductsdljnkpt','','{/res1}','AUTO','{}','{}','{test}','','',NULL,'2017-11-02 16:00:15.608+00:00','haoming@apid.git','2017-11-02 16:00:18.125+00:00','haoming@apid.git','515211e9');
 CREATE TABLE kms_app_credential (id text,tenant_id text,consumer_secret text,app_id text,method_type text,status text,issued_at blob,expires_at blob,app_status text,scopes text,created_at blob,created_by text,updated_at blob,updated_by text,_change_selector text, primary key (id,tenant_id));
-INSERT INTO "kms_app_credential" VALUES('abcd','515211e9','secret1','408ad853-3fa0-402f-90ee-103de98d71a5','','APPROVED','2017-08-18 22:13:18.35+00:00','','','{}','2017-08-18 22:13:18.35+00:00','-NA-','2017-08-18 22:13:18.352+00:00','-NA-','515211e9');
-INSERT INTO "kms_app_credential" VALUES('dcba','515211e9','secret2','ae053aee-f12d-4591-84ef-2e6ae0d4205d','','APPROVED','2017-09-20 23:05:59.148+00:00','','','{}','2017-09-20 23:05:59.148+00:00','-NA-','2017-09-20 23:05:59.151+00:00','-NA-','515211e9');
-INSERT INTO "kms_app_credential" VALUES('wxyz','515211e9','secret3','35608afe-2715-4064-bb4d-3cbb4e82c474','','APPROVED','2017-11-02 16:00:16.512+00:00','','','{}','2017-11-02 16:00:16.512+00:00','-NA-','2017-11-02 16:00:16.514+00:00','-NA-','515211e9');
+INSERT INTO "kms_app_credential" VALUES('abcd','515211e9','encrypted:secret1','408ad853-3fa0-402f-90ee-103de98d71a5','','APPROVED','2017-08-18 22:13:18.35+00:00','','','{}','2017-08-18 22:13:18.35+00:00','-NA-','2017-08-18 22:13:18.352+00:00','-NA-','515211e9');
+INSERT INTO "kms_app_credential" VALUES('dcba','515211e9','encrypted:secret2','ae053aee-f12d-4591-84ef-2e6ae0d4205d','','APPROVED','2017-09-20 23:05:59.148+00:00','','','{}','2017-09-20 23:05:59.148+00:00','-NA-','2017-09-20 23:05:59.151+00:00','-NA-','515211e9');
+INSERT INTO "kms_app_credential" VALUES('wxyz','515211e9','encrypted:secret3','35608afe-2715-4064-bb4d-3cbb4e82c474','','APPROVED','2017-11-02 16:00:16.512+00:00','','','{}','2017-11-02 16:00:16.512+00:00','-NA-','2017-11-02 16:00:16.514+00:00','-NA-','515211e9');
 CREATE TABLE kms_app_credential_apiproduct_mapper (tenant_id text,appcred_id text,app_id text,apiprdt_id text,status text,_change_selector text, primary key (tenant_id,appcred_id,app_id,apiprdt_id));
 INSERT INTO "kms_app_credential_apiproduct_mapper" VALUES('515211e9','abcd','408ad853-3fa0-402f-90ee-103de98d71a5','b7e0970c-4677-4b05-8105-5ea59fdcf4e7','APPROVED','515211e9');
 INSERT INTO "kms_app_credential_apiproduct_mapper" VALUES('515211e9','dcba','ae053aee-f12d-4591-84ef-2e6ae0d4205d','db90a25a-15c8-42ad-96c1-63ed9682b5a9','APPROVED','515211e9');
diff --git a/accessEntity/interfaces.go b/accessEntity/interfaces.go
index ff28bf2..e12cb08 100644
--- a/accessEntity/interfaces.go
+++ b/accessEntity/interfaces.go
@@ -35,6 +35,6 @@
 	GetApiProductNames(id string, idType string) ([]string, error)
 	GetAppNames(id string, idType string) ([]string, error)
 	GetComNames(id string, idType string) ([]string, error)
-	GetDevEmailByDevId(devId string) (string, error)
+	GetDevEmailByDevId(devId string, org string) (string, error)
 	GetStatus(id, t string) (string, error)
 }
diff --git a/accessEntity/mock_test.go b/accessEntity/mock_test.go
index 5a30e27..2564f83 100644
--- a/accessEntity/mock_test.go
+++ b/accessEntity/mock_test.go
@@ -14,9 +14,30 @@
 package accessEntity
 
 import (
+	"github.com/apid/apid-core/cipher"
 	"github.com/apid/apidApiMetadata/common"
+	"strings"
 )
 
+const dummyEncryptPrefix = "encrypted:"
+
+type DummyCipherMan struct {
+}
+
+func (c *DummyCipherMan) AddOrgs(orgs []string) {
+}
+
+func (d *DummyCipherMan) TryDecryptBase64(input string, org string) (string, error) {
+	if strings.HasPrefix(input, dummyEncryptPrefix) {
+		return input[len(dummyEncryptPrefix):], nil
+	}
+	return input, nil
+}
+
+func (d *DummyCipherMan) EncryptBase64(input string, org string, mode cipher.Mode, padding cipher.Padding) (string, error) {
+	return dummyEncryptPrefix + input, nil
+}
+
 type DummyDbMan struct {
 	apiProducts       []common.ApiProduct
 	apps              []common.App
@@ -33,6 +54,10 @@
 	err               error
 }
 
+func (d *DummyDbMan) GetOrgs() (orgs []string, err error) {
+	return
+}
+
 func (d *DummyDbMan) SetDbVersion(string) {
 
 }
@@ -80,7 +105,7 @@
 	return d.comNames, d.err
 }
 
-func (d *DummyDbMan) GetDevEmailByDevId(devId string) (string, error) {
+func (d *DummyDbMan) GetDevEmailByDevId(devId string, org string) (string, error) {
 	return d.email, d.err
 }
 
diff --git a/common/cipher.go b/common/cipher.go
new file mode 100644
index 0000000..9235937
--- /dev/null
+++ b/common/cipher.go
@@ -0,0 +1,273 @@
+// Copyright 2017 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.
+package common
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"encoding/xml"
+	"fmt"
+	"github.com/apid/apid-core/cipher"
+	"io/ioutil"
+	"net/http"
+	"regexp"
+	"strings"
+	"sync"
+	"time"
+)
+
+const regEncrypted = `^\{[0-9A-Za-z]+/[0-9A-Za-z]+/[0-9A-Za-z]+\}.`
+const retrieveEncryptKeyPath = "/encryptionkey"
+const EncryptAes = "AES"
+
+const (
+	retrieveKeyRetryInterval = time.Duration(5 * time.Second)
+	retrieveKeyTimeout       = time.Duration(5 * time.Minute)
+)
+const parameterOrganization = "organization"
+const configBearerToken = "apigeesync_bearer_token"
+const headerContentType = "Content-Type"
+const (
+	typeJson = "application/json"
+	typeXml  = "text/xml"
+)
+const errorCodeNoKey = "organizations.EncryptionKeyDoesNotExist"
+
+var RegexpEncrypted = regexp.MustCompile(regEncrypted)
+
+func CreateCipherManager(client *http.Client, serverUrlBase string) *KmsCipherManager {
+	return &KmsCipherManager{
+		serverUrlBase: serverUrlBase,
+		aes:           make(map[string]*cipher.AesCipher),
+		mutex:         &sync.RWMutex{},
+		client:        client,
+		interval:      retrieveKeyRetryInterval,
+		timeout:       retrieveKeyTimeout,
+	}
+}
+
+type KmsCipherManager struct {
+	serverUrlBase string
+	// org-level AesCipher map {organization: AesCipher}
+	aes      map[string]*cipher.AesCipher
+	mutex    *sync.RWMutex
+	client   *http.Client
+	interval time.Duration
+	timeout  time.Duration
+}
+
+func (c *KmsCipherManager) AddOrgs(orgs []string) {
+	for _, org := range orgs {
+		go c.startRetrieve(org, c.interval, c.timeout)
+	}
+}
+
+func (c *KmsCipherManager) startRetrieve(org string, interval time.Duration, timeout time.Duration) {
+	timeoutChan := time.After(timeout)
+	if err := c.retrieveKey(org); err != nil {
+		log.Error(err)
+	} else {
+		return
+	}
+	ticker := time.NewTicker(interval)
+	for {
+		select {
+		case <-timeoutChan:
+			log.Error("timeout when retrieving key")
+			return
+		case <-ticker.C:
+			if err := c.retrieveKey(org); err != nil {
+				log.Error(err)
+			} else {
+				return
+			}
+		}
+	}
+}
+
+func (c *KmsCipherManager) retrieveKey(org string) error {
+	var key []byte
+	req, err := http.NewRequest(http.MethodGet, c.serverUrlBase+retrieveEncryptKeyPath, nil)
+	if err != nil {
+		return fmt.Errorf("failed to create retrieving key request for org=%s : %v", org, err)
+	}
+	pars := req.URL.Query()
+	pars[parameterOrganization] = []string{org}
+	req.URL.RawQuery = pars.Encode()
+	req.Header.Set("Authorization", "Bearer "+services.Config().GetString(configBearerToken))
+	log.Debugf("Retrieving key: %s", req.URL.String())
+	res, err := c.client.Do(req)
+	if err != nil {
+		return fmt.Errorf("failed to retrieve key for org=%s : %v", org, err)
+	}
+
+	// if 404
+	if res.StatusCode == http.StatusNotFound {
+		e, err := parseErrorResponse(res)
+		if err != nil {
+			log.Errorf("Failed to parse 404 error response for org %s: %v", org, err)
+			return err
+		}
+		// is this org has no key, stop retrying
+		if e.Code == errorCodeNoKey {
+			log.Debugf("No key is associated with org %v", org)
+			return nil
+		}
+	}
+
+	if res.StatusCode != http.StatusOK {
+		return fmt.Errorf("failed to retrieve key for org [%v] with status: %v", org, res.Status)
+	}
+
+	log.Debugf("Downloaded Encryption Key for org %s", org)
+	key64, err := ioutil.ReadAll(res.Body)
+	res.Body.Close()
+	if err != nil {
+		return fmt.Errorf("error reading encryption key: %v", err)
+	}
+	key, err = base64.StdEncoding.DecodeString(string(key64))
+	if err != nil {
+		return fmt.Errorf("error decoding encryption key: %v", err)
+	}
+	log.Debugf("Encryption Key successfully retrieved for org %s", org)
+	a, err := cipher.CreateAesCipher(key)
+	if err != nil {
+		return fmt.Errorf("CreateAesCipher error for org [%v] when CreateAesCipher: %v", org, err)
+	}
+	c.mutex.Lock()
+	c.aes[org] = a
+	c.mutex.Unlock()
+	return nil
+}
+
+// return val is nullable
+func (c *KmsCipherManager) getAesCipher(org string) *cipher.AesCipher {
+	// if exists
+	c.mutex.RLock()
+	if a := c.aes[org]; a != nil {
+		c.mutex.RUnlock()
+		return a
+	}
+	// if not exists
+	c.mutex.RUnlock()
+	if err := c.retrieveKey(org); err != nil {
+		log.Errorf("Failed to get encryption key for org=%s : %v", org, err)
+		return nil
+	}
+	c.mutex.RLock()
+	defer c.mutex.RUnlock()
+	return c.aes[org]
+}
+
+// If input is encrypted, it decodes the input with base64,
+// and then decrypt it. Otherwise, original input is returned.
+// An encrypted input should be ciphertext prepended with algorithm. An unencrypted input can have any other format.
+// An example of encrypted input is "{AES/ECB/PKCS5Padding}2jX3V3dQ5xB9C9Zl9sqyo8pmkvVP10rkEVPVhmnLHw4=".
+func (c *KmsCipherManager) TryDecryptBase64(input string, org string) (output string, err error) {
+	if !IsEncrypted(input) {
+		output = input
+		return
+	}
+
+	text, mode, padding, err := GetCiphertext(input)
+	if err != nil {
+		log.Errorf("Get ciphertext of [%v] failed: [%v], considered as unencrypted!", input, err)
+		return
+	}
+	bytes, err := base64.StdEncoding.DecodeString(text)
+	if err != nil {
+		log.Errorf("Decode base64 of [%v] failed: [%v], considered as unencrypted!", text, err)
+		return
+	}
+	aes := c.getAesCipher(org)
+	if aes == nil {
+		err = fmt.Errorf("failed to get decryption key for org: %s", org)
+		return
+	}
+	plaintext, err := aes.Decrypt(bytes, mode, padding)
+	if err != nil {
+		log.Errorf("Decrypt of [%v] failed: [%v], considered as unencrypted!", bytes, err)
+		return
+	}
+	output = string(plaintext)
+	return
+}
+
+// It encrypts the input, and then encodes the ciphertext with base64.
+// The returned string is the base64 encoding of the encrypted input, prepended with algorithm.
+// An example output is "{AES/ECB/PKCS5Padding}2jX3V3dQ5xB9C9Zl9sqyo8pmkvVP10rkEVPVhmnLHw4="
+func (c *KmsCipherManager) EncryptBase64(input string, org string, mode cipher.Mode, padding cipher.Padding) (output string, err error) {
+	aes := c.getAesCipher(org)
+	// TODO: make sure this logic is expected
+	// if failed to get key and cipher, considered this org as unencrypted
+	if aes == nil {
+		return input, nil
+	}
+	ciphertext, err := aes.Encrypt([]byte(input), mode, padding)
+	if err != nil {
+		return
+	}
+	output = fmt.Sprintf("{%s/%s/%s}%s", EncryptAes, mode, padding, base64.StdEncoding.EncodeToString(ciphertext))
+	return
+}
+
+func IsEncrypted(input string) (encrypted bool) {
+	return RegexpEncrypted.Match([]byte(input))
+}
+
+func GetCiphertext(input string) (ciphertext string, mode cipher.Mode, padding cipher.Padding, err error) {
+	list := strings.SplitN(input, "}", 2)
+	if len(list) != 2 {
+		err = fmt.Errorf("invalid input for GetCiphertext: %v", input)
+		return
+	}
+	ciphertext = list[1]
+	list = strings.Split(strings.TrimLeft(list[0], "{"), "/")
+	if len(list) != 3 {
+		err = fmt.Errorf("invalid input for GetCiphertext: %v", input)
+		return
+	}
+	// encryption algorithm
+	if list[0] != EncryptAes {
+		err = fmt.Errorf("unsupported algorithm for GetCiphertext: %v", list[0])
+		return
+	}
+	// mode
+	mode = cipher.Mode(list[1])
+	// padding
+	padding = cipher.Padding(list[2])
+	return
+}
+
+type KeyErrorResponse struct {
+	Code    string `json:"code"`
+	Message string `json:"message"`
+}
+
+func parseErrorResponse(res *http.Response) (*KeyErrorResponse, error) {
+	contentType := res.Header.Get(headerContentType)
+	defer res.Body.Close()
+	body, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		return nil, err
+	}
+	ret := &KeyErrorResponse{}
+	if contentType == typeJson {
+		return ret, json.Unmarshal(body, ret)
+	} else if contentType == typeXml {
+		return ret, xml.Unmarshal(body, ret)
+	} else {
+		return nil, fmt.Errorf("unknown error: %v", string(body))
+	}
+}
diff --git a/common/cipher_test.go b/common/cipher_test.go
new file mode 100644
index 0000000..71ff7d4
--- /dev/null
+++ b/common/cipher_test.go
@@ -0,0 +1,233 @@
+// Copyright 2017 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.
+package common
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"encoding/xml"
+	"fmt"
+	"github.com/apid/apid-core/cipher"
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	"net/http"
+	"net/http/httptest"
+	"time"
+)
+
+var _ = Describe("Cipher Test", func() {
+	var testCipherMan *KmsCipherManager
+	var testCount int
+	var testOrg string
+	plaingtext := "aUWQKgAwmaR0p2kY"
+	cipher64 := "{AES/ECB/PKCS5Padding}2jX3V3dQ5xB9C9Zl9sqyo8pmkvVP10rkEVPVhmnLHw4="
+	key := []byte{2, 122, 212, 83, 150, 164, 180, 4, 148, 242, 65, 189, 3, 188, 76, 247}
+	BeforeEach(func() {
+		testCount++
+		testOrg = fmt.Sprintf("org%d", testCount)
+	})
+
+	Context("Encryption/Decryption", func() {
+		BeforeEach(func() {
+			testCipherMan = CreateCipherManager(nil, "")
+			// set key locally
+			var err error
+			testCipherMan.aes[testOrg], err = cipher.CreateAesCipher(key)
+			Expect(err).Should(Succeed())
+		})
+
+		It("Encryption", func() {
+			Expect(testCipherMan.EncryptBase64(plaingtext, testOrg, cipher.ModeEcb, cipher.PaddingPKCS5)).
+				Should(Equal(cipher64))
+		})
+
+		It("Decryption", func() {
+			Expect(testCipherMan.TryDecryptBase64(cipher64, testOrg)).Should(Equal(plaingtext))
+		})
+
+		It("Try to decrypt unencrypted input", func() {
+			Expect(testCipherMan.TryDecryptBase64(plaingtext, testOrg)).Should(Equal(plaingtext))
+		})
+	})
+
+	Context("Retrieve new key", func() {
+		var server *httptest.Server
+		Context("Retrieve new key with lazy method", func() {
+			BeforeEach(func() {
+				// set key server
+				server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+					defer GinkgoRecover()
+					Expect(r.URL.Path).Should(Equal(retrieveEncryptKeyPath))
+					Expect(r.URL.Query().Get(parameterOrganization)).Should(Equal(testOrg))
+					Expect(w.Write([]byte(base64.StdEncoding.EncodeToString(key)))).Should(Equal(24))
+				}))
+				time.Sleep(100 * time.Millisecond)
+				testCipherMan = CreateCipherManager(&http.Client{}, server.URL)
+			})
+
+			AfterEach(func() {
+				server.Close()
+			})
+
+			It("Encryption", func() {
+				Expect(testCipherMan.EncryptBase64(plaingtext, testOrg, cipher.ModeEcb, cipher.PaddingPKCS5)).
+					Should(Equal(cipher64))
+			})
+
+			It("Decryption", func() {
+				Expect(testCipherMan.TryDecryptBase64(cipher64, testOrg)).Should(Equal(plaingtext))
+			})
+		})
+
+		Context("Retrieve new keys during initialization", func() {
+
+			AfterEach(func() {
+				server.Close()
+			})
+
+			It("Retrieve Key happy path", func() {
+				// set key server
+				server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+					defer GinkgoRecover()
+					Expect(r.URL.Path).Should(Equal(retrieveEncryptKeyPath))
+					Expect(r.URL.Query().Get(parameterOrganization)).Should(HavePrefix(testOrg))
+					Expect(w.Write([]byte(base64.StdEncoding.EncodeToString(key)))).Should(Equal(24))
+				}))
+				time.Sleep(100 * time.Millisecond)
+				testCipherMan = CreateCipherManager(&http.Client{}, server.URL)
+
+				//test 2 orgs
+				testOrg1 := testOrg + "_1"
+				testCipherMan.AddOrgs([]string{testOrg, testOrg1})
+				for {
+					time.Sleep(100 * time.Millisecond)
+					testCipherMan.mutex.RLock()
+					l := len(testCipherMan.aes)
+					testCipherMan.mutex.RUnlock()
+					if l == 2 {
+						//close server to make sure key was retrieved by "AddOrgs"
+						server.Close()
+						Expect(testCipherMan.EncryptBase64(plaingtext, testOrg, cipher.ModeEcb, cipher.PaddingPKCS5)).
+							Should(Equal(cipher64))
+						Expect(testCipherMan.EncryptBase64(plaingtext, testOrg1, cipher.ModeEcb, cipher.PaddingPKCS5)).
+							Should(Equal(cipher64))
+						return
+					}
+				}
+			}, 2)
+
+			It("Retrieve Key should retry for internal server error", func() {
+				// set key server
+				count := 0
+				server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+					defer GinkgoRecover()
+					Expect(r.URL.Path).Should(Equal(retrieveEncryptKeyPath))
+					Expect(r.URL.Query().Get(parameterOrganization)).Should(Equal(testOrg))
+					count++
+					if count == 1 {
+						w.WriteHeader(http.StatusInternalServerError)
+						return
+					}
+					if count == 2 {
+						w.WriteHeader(http.StatusNotFound)
+						return
+					}
+					Expect(w.Write([]byte(base64.StdEncoding.EncodeToString(key)))).Should(Equal(24))
+				}))
+				time.Sleep(100 * time.Millisecond)
+				testCipherMan = CreateCipherManager(&http.Client{}, server.URL)
+				testCipherMan.interval = 100 * time.Millisecond
+				//should retry in case of error
+				testCipherMan.AddOrgs([]string{testOrg})
+				for {
+					time.Sleep(100 * time.Millisecond)
+					testCipherMan.mutex.RLock()
+					aes := testCipherMan.aes[testOrg]
+					testCipherMan.mutex.RUnlock()
+					if aes != nil {
+						//close server to make sure key was retrieved by "AddOrgs"
+						server.Close()
+						Expect(testCipherMan.EncryptBase64(plaingtext, testOrg, cipher.ModeEcb, cipher.PaddingPKCS5)).
+							Should(Equal(cipher64))
+						return
+					}
+				}
+			}, 2)
+
+			It("Retrieve Key should stop retrying for JSON organizations.EncryptionKeyDoesNotExist", func() {
+				// set key server
+				server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+					defer GinkgoRecover()
+					Expect(r.URL.Path).Should(Equal(retrieveEncryptKeyPath))
+					Expect(r.URL.Query().Get(parameterOrganization)).Should(Equal(testOrg))
+
+					response := KeyErrorResponse{
+						Code:    errorCodeNoKey,
+						Message: fmt.Sprintf("Encryption key does not exist for the org [%s].", testOrg),
+					}
+					bytes, err := json.Marshal(response)
+					Expect(err).Should(Succeed())
+					w.Header().Set(headerContentType, typeJson)
+					w.WriteHeader(http.StatusNotFound)
+					Expect(w.Write(bytes)).Should(Equal(len(bytes)))
+				}))
+				time.Sleep(100 * time.Millisecond)
+				testCipherMan = CreateCipherManager(&http.Client{}, server.URL)
+				//should stop retrying after one try
+				testCipherMan.startRetrieve(testOrg, 100*time.Millisecond, 10*time.Minute)
+			}, 2)
+
+			It("Retrieve Key should stop retrying for XML organizations.EncryptionKeyDoesNotExist", func() {
+				// set key server
+				server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+					defer GinkgoRecover()
+					Expect(r.URL.Path).Should(Equal(retrieveEncryptKeyPath))
+					Expect(r.URL.Query().Get(parameterOrganization)).Should(Equal(testOrg))
+
+					response := KeyErrorResponse{
+						Code:    errorCodeNoKey,
+						Message: fmt.Sprintf("Encryption key does not exist for the org [%s].", testOrg),
+					}
+					bytes, err := xml.Marshal(response)
+					Expect(err).Should(Succeed())
+					w.Header().Set(headerContentType, typeXml)
+					w.WriteHeader(http.StatusNotFound)
+					Expect(w.Write(bytes)).Should(Equal(len(bytes)))
+				}))
+				time.Sleep(100 * time.Millisecond)
+				testCipherMan = CreateCipherManager(&http.Client{}, server.URL)
+				//should stop retrying after one try
+				testCipherMan.startRetrieve(testOrg, 100*time.Millisecond, 10*time.Minute)
+			}, 2)
+		})
+
+	})
+
+	Context("IsEncrypted", func() {
+		It("IsEncrypted", func() {
+			testData := [][]interface{}{
+				{"{AES/ECB/PKCS5Padding}foo", true},
+				{"AES/ECB/PKCS5Padding}foo", false},
+				{"{AES/ECB/PKCS5Paddingfoo", false},
+				{"{AES/ECB/}foo", false},
+				{"{AES/PKCS5Padding}foo", false},
+				{"{AES//PKCS5Padding}foo", false},
+				{"foo", false},
+			}
+			for i := range testData {
+				Expect(IsEncrypted(testData[i][0].(string))).Should(Equal(testData[i][1]))
+			}
+		})
+	})
+})
diff --git a/common/common_suite_test.go b/common/common_suite_test.go
new file mode 100644
index 0000000..724b2b5
--- /dev/null
+++ b/common/common_suite_test.go
@@ -0,0 +1,30 @@
+package common
+
+import (
+	"github.com/apid/apid-core"
+	"github.com/apid/apid-core/factory"
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	"io/ioutil"
+	"os"
+	"testing"
+)
+
+var testTempDirBase string
+
+var _ = BeforeSuite(func() {
+	apid.Initialize(factory.DefaultServicesFactory())
+	SetApidServices(apid.AllServices(), apid.Log().ForModule("apidApiMetadata"))
+	var err error
+	testTempDirBase, err = ioutil.TempDir("", "verify_apikey_")
+	Expect(err).Should(Succeed())
+})
+
+func TestCommon(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "ApiMetadata Common Suite")
+}
+
+var _ = AfterSuite(func() {
+	Expect(os.RemoveAll(testTempDirBase)).Should(Succeed())
+})
diff --git a/common/data.go b/common/data.go
index a592f97..f7c9ba6 100644
--- a/common/data.go
+++ b/common/data.go
@@ -23,10 +23,11 @@
 )
 
 type DbManager struct {
-	Data      apid.DataService
-	Db        apid.DB
-	DbMux     sync.RWMutex
-	dbVersion string
+	Data          apid.DataService
+	Db            apid.DB
+	DbMux         sync.RWMutex
+	CipherManager CipherManagerInterface
+	dbVersion     string
 }
 
 const (
@@ -96,6 +97,26 @@
 	return mapOfAttributes
 }
 
+func (dbc *DbManager) GetOrgs() (orgs []string, err error) {
+	db := dbc.GetDb()
+	rows, err := db.Query(`SELECT DISTINCT org FROM edgex_data_scope`)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	for rows.Next() {
+		var tmp sql.NullString
+		if err = rows.Scan(&tmp); err != nil {
+			return nil, err
+		}
+		if tmp.Valid {
+			orgs = append(orgs, tmp.String)
+		}
+	}
+	err = rows.Err()
+	return
+}
+
 func JsonToStringArray(fjson string) []string {
 	var array []string
 	if err := json.Unmarshal([]byte(fjson), &array); err == nil {
diff --git a/common/data_test.go b/common/data_test.go
new file mode 100644
index 0000000..7070890
--- /dev/null
+++ b/common/data_test.go
@@ -0,0 +1,102 @@
+// Copyright 2017 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.
+package common
+
+import (
+	"github.com/apid/apid-core"
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	"io/ioutil"
+	"reflect"
+	"sort"
+	"sync"
+)
+
+const fileDataTest = "data_test.sql"
+
+var _ = Describe("DataTest", func() {
+
+	Context("DB", func() {
+		var dataTestTempDir string
+		var testDbMan *DbManager
+		BeforeEach(func() {
+			var err error
+			dataTestTempDir, err = ioutil.TempDir(testTempDirBase, "sqlite3")
+			Expect(err).NotTo(HaveOccurred())
+			services.Config().Set("local_storage_path", dataTestTempDir)
+
+			testDbMan = &DbManager{
+				Data:  services.Data(),
+				DbMux: sync.RWMutex{},
+			}
+			testDbMan.SetDbVersion(dataTestTempDir)
+			Expect(testDbMan.GetDbVersion()).Should(Equal(dataTestTempDir))
+			setupTestDb(testDbMan.GetDb())
+		})
+
+		It("should get kms attributes", func() {
+			attributes := testDbMan.GetKmsAttributes("bc811169", "40753e12-a50a-429d-9121-e571eb4e43a9", "85629786-37c5-4e8c-bb45-208f3360d005", "50321842-d6ee-4e92-91b9-37234a7920c1", "test-invalid")
+			Expect(len(attributes)).Should(BeEquivalentTo(3))
+			Expect(len(attributes["40753e12-a50a-429d-9121-e571eb4e43a9"])).Should(BeEquivalentTo(1))
+			Expect(len(attributes["85629786-37c5-4e8c-bb45-208f3360d005"])).Should(BeEquivalentTo(2))
+			Expect(len(attributes["50321842-d6ee-4e92-91b9-37234a7920c1"])).Should(BeEquivalentTo(5))
+			Expect(len(attributes["test-invalid"])).Should(BeEquivalentTo(0))
+		})
+
+		It("Should get all orgs", func() {
+			orgs, err := testDbMan.GetOrgs()
+			Expect(err).Should(Succeed())
+			sort.Strings(orgs)
+			Expect(orgs).Should(Equal([]string{"apid-haoming", "apid-test"}))
+		})
+
+	})
+
+	Context("Validate common.JsonToStringArray", func() {
+
+		It("should transform simple valid json", func() {
+			array := JsonToStringArray("[\"test-1\", \"test-2\"]")
+			Expect(reflect.DeepEqual(array, []string{"test-1", "test-2"})).Should(BeTrue())
+		})
+		It("should transform simple single valid json", func() {
+			array := JsonToStringArray("[\"test-1\"]")
+			Expect(reflect.DeepEqual(array, []string{"test-1"})).Should(BeTrue())
+		})
+		It("should transform simple fake json", func() {
+			s := JsonToStringArray("{test-1,test-2}")
+			Expect(reflect.DeepEqual(s, []string{"test-1", "test-2"})).Should(BeTrue())
+		})
+		It("should transform simple single valued fake json", func() {
+			s := JsonToStringArray("{test-1}")
+			Expect(reflect.DeepEqual(s, []string{"test-1"})).Should(BeTrue())
+		})
+		It("space between fields considered as valid char", func() {
+			s := JsonToStringArray("{test-1, test-2}")
+			Expect(reflect.DeepEqual(s, []string{"test-1", " test-2"})).Should(BeTrue())
+		})
+		It("remove only last braces", func() {
+			s := JsonToStringArray("{test-1,test-2}}")
+			Expect(reflect.DeepEqual(s, []string{"test-1", "test-2}"})).Should(BeTrue())
+		})
+
+	})
+})
+
+func setupTestDb(db apid.DB) {
+	bytes, err := ioutil.ReadFile(fileDataTest)
+	Expect(err).Should(Succeed())
+	query := string(bytes)
+	_, err = db.Exec(query)
+	Expect(err).Should(Succeed())
+}
diff --git a/common/data_test.sql b/common/data_test.sql
new file mode 100644
index 0000000..1b5349b
--- /dev/null
+++ b/common/data_test.sql
@@ -0,0 +1,32 @@
+-- Copyright 2017 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.
+
+PRAGMA foreign_keys=OFF;
+BEGIN TRANSACTION;
+CREATE TABLE kms_attributes (tenant_id text,entity_id text,cust_id text,org_id text,dev_id text,comp_id text,apiprdt_id text,app_id text,appcred_id text,name text,type text,value text,_change_selector text, primary key (tenant_id,entity_id,name,type));
+INSERT INTO "kms_attributes" VALUES('bc811169','50321842-d6ee-4e92-91b9-37234a7920c1','','','','','50321842-d6ee-4e92-91b9-37234a7920c1','','','RateLimit','APIPRODUCT','RX100','bc811169');
+INSERT INTO "kms_attributes" VALUES('bc811169','85629786-37c5-4e8c-bb45-208f3360d005','','85629786-37c5-4e8c-bb45-208f3360d005','','','','','','features.isEdgexEnabled','ORGANIZATION','true','bc811169');
+INSERT INTO "kms_attributes" VALUES('bc811169','85629786-37c5-4e8c-bb45-208f3360d005','','85629786-37c5-4e8c-bb45-208f3360d005','','','','','','features.isCpsEnabled','ORGANIZATION','true','bc811169');
+INSERT INTO "kms_attributes" VALUES('bc811169','50321842-d6ee-4e92-91b9-37234a7920c1','','','','','50321842-d6ee-4e92-91b9-37234a7920c1','','','developer.quota.limit','APIPRODUCT','100','bc811169');
+INSERT INTO "kms_attributes" VALUES('bc811169','50321842-d6ee-4e92-91b9-37234a7920c1','','','','','50321842-d6ee-4e92-91b9-37234a7920c1','','','developer.quota.interval','APIPRODUCT','10','bc811169');
+INSERT INTO "kms_attributes" VALUES('bc811169','50321842-d6ee-4e92-91b9-37234a7920c1','','','','','50321842-d6ee-4e92-91b9-37234a7920c1','','','developer.quota.timeunit','APIPRODUCT','minute','bc811169');
+INSERT INTO "kms_attributes" VALUES('bc811169','50321842-d6ee-4e92-91b9-37234a7920c1','','','','','50321842-d6ee-4e92-91b9-37234a7920c1','','','Threshold','APIPRODUCT','TX100','bc811169');
+INSERT INTO "kms_attributes" VALUES('bc811169','40753e12-a50a-429d-9121-e571eb4e43a9','','','','','40753e12-a50a-429d-9121-e571eb4e43a9','','','access','APIPRODUCT','public','bc811169');
+INSERT INTO "kms_attributes" VALUES('bc811169','2d373ed6-e38f-453b-bb34-6d731d9c4815','','','','','','2d373ed6-e38f-453b-bb34-6d731d9c4815','','DisplayName','APP','demo-app','bc811169');
+CREATE TABLE kms_organization (id text,name text,display_name text,type text,tenant_id text,customer_id text,description text,created_at blob,created_by text,updated_at blob,updated_by text,_change_selector text, primary key (id,tenant_id));
+INSERT INTO "kms_organization" VALUES('e2cc4caf-40d6-4ecb-8149-ed32d04184b2','apid-haoming','apid-haoming','paid','515211e9','94cd5075-7f33-4afb-9545-a53a254277a1','','2017-08-16 22:16:06.544+00:00','foobar@google.com','2017-08-16 22:29:23.046+00:00','foobar@google.com','515211e9');
+CREATE TABLE edgex_data_scope (id text,apid_cluster_id text,scope text,org text,env text,created blob,created_by text,updated blob,updated_by text,_change_selector text,org_scope text,env_scope text, primary key (id));
+INSERT INTO "edgex_data_scope" VALUES('cc066263-6355-416d-9d59-7f3135d64953','543230f1-8c41-4bf5-94a3-f10c104ff5d4','155211e9','apid-haoming','test','2017-08-27 22:53:33.859+00:00','foobar@google.com','2017-08-27 22:53:33.859+00:00','foobar@google.com','543230f1-8c41-4bf5-94a3-f10c104ff5d4','12344caf-40d6-4ecb-8149-ed32d04184b2','1234203e-ba88-4cd5-967d-4caa88f64909');
+INSERT INTO "edgex_data_scope" VALUES('08c81eeb-57ec-43fe-8fed-cdff5494406f','123430f1-8c41-4bf5-94a3-f10c104ff5d4','165211e9','apid-test','prod','2017-08-29 02:39:34.093+00:00','foobar@google.com','2017-08-29 02:39:34.093+00:00','foobar@google.com','123430f1-8c41-4bf5-94a3-f10c104ff5d4','43214caf-40d6-4ecb-8149-ed32d04184b2','43211cae-f2a6-4663-9f36-eb17d76e6c32');
+COMMIT;
diff --git a/common/interfaces.go b/common/interfaces.go
index 59249b5..859e4bc 100644
--- a/common/interfaces.go
+++ b/common/interfaces.go
@@ -13,6 +13,8 @@
 // limitations under the License.
 package common
 
+import "github.com/apid/apid-core/cipher"
+
 type ApiManagerInterface interface {
 	InitAPI()
 }
@@ -21,4 +23,18 @@
 	SetDbVersion(string)
 	GetDbVersion() string
 	GetKmsAttributes(tenantId string, entities ...string) map[string][]Attribute
+	GetOrgs() (orgs []string, err error)
+}
+
+type CipherManagerInterface interface {
+	AddOrgs(orgs []string)
+	// If input is encrypted, it decodes the input with base64,
+	// and then decrypt it. Otherwise, original input is returned.
+	// An encrypted input should be ciphertext prepended with algorithm. An unencrypted input can have any other format.
+	// An example input is "{AES/ECB/PKCS5Padding}2jX3V3dQ5xB9C9Zl9sqyo8pmkvVP10rkEVPVhmnLHw4=".
+	TryDecryptBase64(input string, org string) (output string, err error)
+	// It encrypts the input, and then encodes the ciphertext with base64.
+	// The returned string is the base64 encoding of the encrypted input, prepended with algorithm.
+	// An example output is "{AES/ECB/PKCS5Padding}2jX3V3dQ5xB9C9Zl9sqyo8pmkvVP10rkEVPVhmnLHw4="
+	EncryptBase64(input string, org string, mode cipher.Mode, padding cipher.Padding) (output string, err error)
 }
diff --git a/glide.yaml b/glide.yaml
index abe2d4a..9e5bcb4 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -15,7 +15,7 @@
 package: github.com/apid/apidApiMetadata
 import:
 - package: github.com/apid/apid-core
-  version: master
+  version: ISSUE-67869881 
 - package: github.com/apid/apidApigeeSync
   version: master
 testImport:
diff --git a/init.go b/init.go
index 158a948..8083500 100644
--- a/init.go
+++ b/init.go
@@ -16,10 +16,20 @@
 
 import (
 	"github.com/apid/apid-core"
+	"github.com/apid/apid-core/util"
 	"github.com/apid/apidApiMetadata/accessEntity"
 	"github.com/apid/apidApiMetadata/common"
 	"github.com/apid/apidApiMetadata/verifyApiKey"
+	"net/http"
 	"sync"
+	"time"
+)
+
+const (
+	maxIdleConnsPerHost      = 1
+	httpTimeout              = 5 * time.Minute
+	configBearerToken        = "apigeesync_bearer_token"
+	configRetrieveEncKeyBase = "apimetadata_encryption_key_server_base"
 )
 
 var (
@@ -44,11 +54,30 @@
 	return common.PluginData, nil
 }
 
-func initManagers(services apid.Services) apigeeSyncHandler {
+// init http client
+func createHttpClient() *http.Client {
+	tr := util.Transport(services.Config().GetString(util.ConfigfwdProxyPortURL))
+	tr.MaxIdleConnsPerHost = maxIdleConnsPerHost
+	client := &http.Client{
+		Transport: tr,
+		Timeout:   httpTimeout,
+		CheckRedirect: func(req *http.Request, _ []*http.Request) error {
+			req.Header.Set("Authorization", "Bearer "+services.Config().GetString(configBearerToken))
+			return nil
+		},
+	}
+	return client
+}
+
+func initManagers(services apid.Services) *apigeeSyncHandler {
+
+	cipherMan := common.CreateCipherManager(createHttpClient(), services.Config().GetString(configRetrieveEncKeyBase))
+
 	verifyDbMan := &verifyApiKey.DbManager{
 		DbManager: common.DbManager{
-			Data:  services.Data(),
-			DbMux: sync.RWMutex{},
+			Data:          services.Data(),
+			DbMux:         sync.RWMutex{},
+			CipherManager: cipherMan,
 		},
 	}
 	verifyApiMan := &verifyApiKey.ApiManager{
@@ -58,8 +87,9 @@
 
 	entityDbMan := &accessEntity.DbManager{
 		DbManager: common.DbManager{
-			Data:  services.Data(),
-			DbMux: sync.RWMutex{},
+			Data:          services.Data(),
+			DbMux:         sync.RWMutex{},
+			CipherManager: cipherMan,
 		},
 	}
 
@@ -68,9 +98,10 @@
 		AccessEntityPath: accessEntity.AccessEntityPath,
 	}
 
-	syncHandler := apigeeSyncHandler{
-		dbMans:  []common.DbManagerInterface{verifyDbMan, entityDbMan},
-		apiMans: []common.ApiManagerInterface{verifyApiMan, entityApiMan},
+	syncHandler := &apigeeSyncHandler{
+		dbMans:    []common.DbManagerInterface{verifyDbMan, entityDbMan},
+		apiMans:   []common.ApiManagerInterface{verifyApiMan, entityApiMan},
+		cipherMan: cipherMan,
 	}
 	syncHandler.initListener(services)
 	return syncHandler
diff --git a/listener.go b/listener.go
index 9f6e7e2..4253cfc 100644
--- a/listener.go
+++ b/listener.go
@@ -25,8 +25,9 @@
 )
 
 type apigeeSyncHandler struct {
-	dbMans  []common.DbManagerInterface
-	apiMans []common.ApiManagerInterface
+	dbMans    []common.DbManagerInterface
+	apiMans   []common.ApiManagerInterface
+	cipherMan common.CipherManagerInterface
 }
 
 func (h *apigeeSyncHandler) initListener(services apid.Services) {
@@ -43,6 +44,12 @@
 	for _, dbMan := range h.dbMans {
 		dbMan.SetDbVersion(snapshot.SnapshotInfo)
 	}
+	// retrieve encryption keys
+	orgs, err := h.dbMans[0].GetOrgs()
+	if err != nil {
+		log.Panicf("Failed to get orgs: %v", err)
+	}
+	h.cipherMan.AddOrgs(orgs)
 	// idempotent init api for all packages
 	for _, apiMan := range h.apiMans {
 		apiMan.InitAPI()
@@ -54,7 +61,7 @@
 
 	if snapData, ok := e.(*tran.Snapshot); ok {
 		h.processSnapshot(snapData)
-	} else {
+	} else { //TODO handle changelist and retrieve key for new orgs
 		log.Debugf("Received event. No action required for apiMetadata plugin. Ignoring. %v", e)
 	}
 }
diff --git a/listener_test.go b/listener_test.go
index 3b2a77a..27756fd 100644
--- a/listener_test.go
+++ b/listener_test.go
@@ -17,6 +17,7 @@
 import (
 	"github.com/apid/apid-core"
 	"github.com/apid/apid-core/factory"
+	"github.com/apid/apidApiMetadata/common"
 	tran "github.com/apigee-labs/transicator/common"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
@@ -26,7 +27,7 @@
 
 var _ = Describe("listener", func() {
 
-	var listnerTestSyncHandler apigeeSyncHandler
+	var listenerTestSyncHandler *apigeeSyncHandler
 	var listnerTestTempDir string
 	var _ = BeforeEach(func() {
 		var err error
@@ -38,7 +39,12 @@
 		Expect(err).NotTo(HaveOccurred())
 
 		apid.InitializePlugins("")
-		listnerTestSyncHandler = initManagers(s)
+		listenerTestSyncHandler = &apigeeSyncHandler{
+			dbMans:    []common.DbManagerInterface{&DummyDbMan{}, &DummyDbMan{}},
+			apiMans:   []common.ApiManagerInterface{},
+			cipherMan: &DummyCipherMan{},
+		}
+		listenerTestSyncHandler.initListener(services)
 	})
 
 	var _ = AfterEach(func() {
@@ -52,22 +58,22 @@
 				SnapshotInfo: "test_snapshot",
 				Tables:       []tran.Table{},
 			}
-			listnerTestSyncHandler.Handle(s)
-			for _, dbMan := range listnerTestSyncHandler.dbMans {
+			listenerTestSyncHandler.Handle(s)
+			for _, dbMan := range listenerTestSyncHandler.dbMans {
 				Expect(dbMan.GetDbVersion()).Should(BeEquivalentTo(s.SnapshotInfo))
 			}
 
 		})
 
-		It("should not change version for chang event", func() {
+		It("should not change version for change event", func() {
 
-			version := listnerTestSyncHandler.dbMans[0].GetDbVersion()
+			version := listenerTestSyncHandler.dbMans[0].GetDbVersion()
 			s := &tran.Change{
 				ChangeSequence: 12321,
 				Table:          "",
 			}
-			listnerTestSyncHandler.Handle(s)
-			for _, dbMan := range listnerTestSyncHandler.dbMans {
+			listenerTestSyncHandler.Handle(s)
+			for _, dbMan := range listenerTestSyncHandler.dbMans {
 				Expect(dbMan.GetDbVersion() == version).Should(BeTrue())
 			}
 
diff --git a/mock_test.go b/mock_test.go
new file mode 100644
index 0000000..525edff
--- /dev/null
+++ b/mock_test.go
@@ -0,0 +1,52 @@
+// Copyright 2017 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.
+package apidApiMetadata
+
+import (
+	"github.com/apid/apid-core/cipher"
+	"github.com/apid/apidApiMetadata/common"
+)
+
+type DummyDbMan struct {
+	version string
+}
+
+func (d *DummyDbMan) GetOrgs() (orgs []string, err error) {
+	return
+}
+
+func (d *DummyDbMan) SetDbVersion(v string) {
+	d.version = v
+}
+func (d *DummyDbMan) GetDbVersion() string {
+	return d.version
+}
+
+func (d *DummyDbMan) GetKmsAttributes(tenantId string, entities ...string) map[string][]common.Attribute {
+	return nil
+}
+
+type DummyCipherMan struct {
+}
+
+func (c *DummyCipherMan) AddOrgs(orgs []string) {
+}
+
+func (d *DummyCipherMan) TryDecryptBase64(input string, org string) (string, error) {
+	return input, nil
+}
+
+func (d *DummyCipherMan) EncryptBase64(input string, org string, mode cipher.Mode, padding cipher.Padding) (string, error) {
+	return input, nil
+}
diff --git a/verifyApiKey/api.go b/verifyApiKey/api.go
index e06c58c..e6fcb32 100644
--- a/verifyApiKey/api.go
+++ b/verifyApiKey/api.go
@@ -16,6 +16,7 @@
 
 import (
 	"encoding/json"
+	"github.com/apid/apid-core/util"
 	"github.com/apid/apidApiMetadata/common"
 	"io"
 	"io/ioutil"
@@ -173,8 +174,8 @@
 
 	for _, apiProd := range details {
 		if len(apiProd.Resources) == 0 || validatePath(apiProd.Resources, verifyApiKeyReq.UriPath) {
-			if len(apiProd.Apiproxies) == 0 || contains(apiProd.Apiproxies, verifyApiKeyReq.ApiProxyName) {
-				if len(apiProd.Environments) == 0 || contains(apiProd.Environments, verifyApiKeyReq.EnvironmentName) {
+			if len(apiProd.Apiproxies) == 0 || util.Contains(apiProd.Apiproxies, verifyApiKeyReq.ApiProxyName) {
+				if len(apiProd.Environments) == 0 || util.Contains(apiProd.Environments, verifyApiKeyReq.EnvironmentName) {
 					bestMathcedProduct = apiProd
 					return bestMathcedProduct
 					// set rank 1 or just return
@@ -254,7 +255,7 @@
 		return &ee
 	}
 
-	if verifyApiKeyReq.ValidateAgainstApiProxiesAndEnvs && (len(apiProductDetails.Apiproxies) > 0 && !contains(apiProductDetails.Apiproxies, verifyApiKeyReq.ApiProxyName)) {
+	if verifyApiKeyReq.ValidateAgainstApiProxiesAndEnvs && (len(apiProductDetails.Apiproxies) > 0 && !util.Contains(apiProductDetails.Apiproxies, verifyApiKeyReq.ApiProxyName)) {
 		reason = "Proxy Validation Failed (" + strings.Join(apiProductDetails.Apiproxies, ", ") + " vs " + verifyApiKeyReq.ApiProxyName + ")"
 		errorCode = "oauth.v2.InvalidApiKeyForGivenResource"
 		log.Debug("Validation error occoured ", errorCode, " ", reason)
@@ -262,7 +263,7 @@
 		return &ee
 	}
 	/* Verify if the ENV matches */
-	if verifyApiKeyReq.ValidateAgainstApiProxiesAndEnvs && (len(apiProductDetails.Environments) > 0 && !contains(apiProductDetails.Environments, verifyApiKeyReq.EnvironmentName)) {
+	if verifyApiKeyReq.ValidateAgainstApiProxiesAndEnvs && (len(apiProductDetails.Environments) > 0 && !util.Contains(apiProductDetails.Environments, verifyApiKeyReq.EnvironmentName)) {
 		reason = "ENV Validation Failed (" + strings.Join(apiProductDetails.Environments, ", ") + " vs " + verifyApiKeyReq.EnvironmentName + ")"
 		errorCode = "oauth.v2.InvalidApiKeyForGivenResource"
 		log.Debug("Validation error occoured ", errorCode, " ", reason)
diff --git a/verifyApiKey/api_test.go b/verifyApiKey/api_test.go
index 9e1795c..9cce413 100644
--- a/verifyApiKey/api_test.go
+++ b/verifyApiKey/api_test.go
@@ -59,8 +59,9 @@
 
 		dbMan = &DbManager{
 			DbManager: common.DbManager{
-				Data:  serviceFactoryForTest.Data(),
-				DbMux: sync.RWMutex{},
+				Data:          serviceFactoryForTest.Data(),
+				DbMux:         sync.RWMutex{},
+				CipherManager: &DummyCipherMan{},
 			},
 		}
 		dbMan.SetDbVersion(dataTestTempDir)
diff --git a/verifyApiKey/data.go b/verifyApiKey/data.go
index 8e70faf..a5d3708 100644
--- a/verifyApiKey/data.go
+++ b/verifyApiKey/data.go
@@ -68,6 +68,13 @@
 		return errors.New("InvalidApiKey")
 	}
 
+	secret, err := dbc.CipherManager.TryDecryptBase64(dataWrapper.verifyApiKeySuccessResponse.ClientId.ClientSecret,
+		dataWrapper.verifyApiKeyRequest.OrganizationName)
+	if err != nil {
+		return err
+	}
+	dataWrapper.verifyApiKeySuccessResponse.ClientId.ClientSecret = secret
+
 	if dataWrapper.verifyApiKeySuccessResponse.App.CallbackUrl != "" {
 		dataWrapper.verifyApiKeySuccessResponse.ClientId.RedirectURIs = []string{dataWrapper.verifyApiKeySuccessResponse.App.CallbackUrl}
 	}
diff --git a/verifyApiKey/data_helper_test.go b/verifyApiKey/data_helper_test.go
index 74c19a5..57951e9 100644
--- a/verifyApiKey/data_helper_test.go
+++ b/verifyApiKey/data_helper_test.go
@@ -116,47 +116,3 @@
 	Expect(err).NotTo(HaveOccurred())
 	Expect(tx.Commit()).NotTo(HaveOccurred())
 }
-
-func setupKmsAttributesdata(db apid.DB) {
-	tx, err := db.Begin()
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(`CREATE TABLE kms_attributes (tenant_id text,entity_id text,cust_id text,org_id text,dev_id text,comp_id text,apiprdt_id text,app_id text,appcred_id text,name text,type text,value text,_change_selector text, primary key (tenant_id,entity_id,name,type));`)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(``)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(`INSERT INTO "kms_attributes" VALUES('bc811169','50321842-d6ee-4e92-91b9-37234a7920c1','','','','','50321842-d6ee-4e92-91b9-37234a7920c1','','','RateLimit','APIPRODUCT','RX100','bc811169');`)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(``)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(`INSERT INTO "kms_attributes" VALUES('bc811169','85629786-37c5-4e8c-bb45-208f3360d005','','85629786-37c5-4e8c-bb45-208f3360d005','','','','','','features.isEdgexEnabled','ORGANIZATION','true','bc811169');`)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(``)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(`INSERT INTO "kms_attributes" VALUES('bc811169','85629786-37c5-4e8c-bb45-208f3360d005','','85629786-37c5-4e8c-bb45-208f3360d005','','','','','','features.isCpsEnabled','ORGANIZATION','true','bc811169');`)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(``)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(`INSERT INTO "kms_attributes" VALUES('bc811169','50321842-d6ee-4e92-91b9-37234a7920c1','','','','','50321842-d6ee-4e92-91b9-37234a7920c1','','','developer.quota.limit','APIPRODUCT','100','bc811169');`)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(``)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(`INSERT INTO "kms_attributes" VALUES('bc811169','50321842-d6ee-4e92-91b9-37234a7920c1','','','','','50321842-d6ee-4e92-91b9-37234a7920c1','','','developer.quota.interval','APIPRODUCT','10','bc811169');`)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(``)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(`INSERT INTO "kms_attributes" VALUES('bc811169','50321842-d6ee-4e92-91b9-37234a7920c1','','','','','50321842-d6ee-4e92-91b9-37234a7920c1','','','developer.quota.timeunit','APIPRODUCT','minute','bc811169');`)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(``)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(`INSERT INTO "kms_attributes" VALUES('bc811169','50321842-d6ee-4e92-91b9-37234a7920c1','','','','','50321842-d6ee-4e92-91b9-37234a7920c1','','','Threshold','APIPRODUCT','TX100','bc811169');`)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(``)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(`INSERT INTO "kms_attributes" VALUES('bc811169','40753e12-a50a-429d-9121-e571eb4e43a9','','','','','40753e12-a50a-429d-9121-e571eb4e43a9','','','access','APIPRODUCT','public','bc811169');`)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(``)
-	Expect(err).NotTo(HaveOccurred())
-	_, err = tx.Exec(`INSERT INTO "kms_attributes" VALUES('bc811169','2d373ed6-e38f-453b-bb34-6d731d9c4815','','','','','','2d373ed6-e38f-453b-bb34-6d731d9c4815','','DisplayName','APP','demo-app','bc811169');`)
-	Expect(err).NotTo(HaveOccurred())
-	Expect(tx.Commit()).NotTo(HaveOccurred())
-}
diff --git a/verifyApiKey/data_test.go b/verifyApiKey/data_test.go
index d81914a..54d920b 100644
--- a/verifyApiKey/data_test.go
+++ b/verifyApiKey/data_test.go
@@ -40,8 +40,9 @@
 
 			dbMan = &DbManager{
 				DbManager: common.DbManager{
-					Data:  s.Data(),
-					DbMux: sync.RWMutex{},
+					Data:          s.Data(),
+					DbMux:         sync.RWMutex{},
+					CipherManager: &DummyCipherMan{},
 				},
 			}
 			dbMan.SetDbVersion(dataTestTempDir)
@@ -183,18 +184,5 @@
 			Expect(len(apiProducts)).Should(BeEquivalentTo(0))
 
 		})
-
-		It("should get kms attributes", func() {
-
-			setupKmsAttributesdata(dbMan.Db)
-			attributes := dbMan.GetKmsAttributes("bc811169", "40753e12-a50a-429d-9121-e571eb4e43a9", "85629786-37c5-4e8c-bb45-208f3360d005", "50321842-d6ee-4e92-91b9-37234a7920c1", "test-invalid")
-			Expect(len(attributes)).Should(BeEquivalentTo(3))
-			Expect(len(attributes["40753e12-a50a-429d-9121-e571eb4e43a9"])).Should(BeEquivalentTo(1))
-			Expect(len(attributes["85629786-37c5-4e8c-bb45-208f3360d005"])).Should(BeEquivalentTo(2))
-			Expect(len(attributes["50321842-d6ee-4e92-91b9-37234a7920c1"])).Should(BeEquivalentTo(5))
-			Expect(len(attributes["test-invalid"])).Should(BeEquivalentTo(0))
-
-		})
-
 	})
 })
diff --git a/verifyApiKey/mock_test.go b/verifyApiKey/mock_test.go
new file mode 100644
index 0000000..731a244
--- /dev/null
+++ b/verifyApiKey/mock_test.go
@@ -0,0 +1,30 @@
+// Copyright 2017 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.
+package verifyApiKey
+
+import "github.com/apid/apid-core/cipher"
+
+type DummyCipherMan struct {
+}
+
+func (c *DummyCipherMan) AddOrgs(orgs []string) {
+}
+
+func (d *DummyCipherMan) TryDecryptBase64(input string, org string) (string, error) {
+	return input, nil
+}
+
+func (d *DummyCipherMan) EncryptBase64(input string, org string, mode cipher.Mode, padding cipher.Padding) (string, error) {
+	return input, nil
+}
diff --git a/verifyApiKey/verifyApiKeyUtil.go b/verifyApiKey/verifyApiKeyUtil.go
index 85aa8c5..fcfaf2e 100644
--- a/verifyApiKey/verifyApiKeyUtil.go
+++ b/verifyApiKey/verifyApiKeyUtil.go
@@ -53,12 +53,3 @@
 	/* if the i/p resource is empty, no checks need to be made */
 	return len(fs) == 0
 }
-
-func contains(givenArray []string, searchString string) bool {
-	for _, element := range givenArray {
-		if element == searchString {
-			return true
-		}
-	}
-	return false
-}
diff --git a/verifyApiKey/verifyApiKeyUtil_test.go b/verifyApiKey/verifyApiKeyUtil_test.go
deleted file mode 100644
index 1c26982..0000000
--- a/verifyApiKey/verifyApiKeyUtil_test.go
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2017 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.
-
-package verifyApiKey
-
-import (
-	"github.com/apid/apidApiMetadata/common"
-	. "github.com/onsi/ginkgo"
-	. "github.com/onsi/gomega"
-	"reflect"
-)
-
-var _ = Describe("Validate Env", func() {
-
-	It("validation1", func() {
-		s := contains([]string{"foo", "bar"}, "foo")
-		Expect(s).Should(BeTrue())
-	})
-	It("validation2", func() {
-		s := contains([]string{"foo", "bar"}, "bar")
-		Expect(s).Should(BeTrue())
-	})
-	It("validation3", func() {
-		s := contains([]string{"foo", "bar"}, "xxx")
-		Expect(s).Should(BeFalse())
-	})
-	It("validation4", func() {
-		s := contains([]string{}, "xxx")
-		Expect(s).Should(BeFalse())
-	})
-})
-
-var _ = Describe("Validate common.JsonToStringArray", func() {
-
-	It("should transform simple valid json", func() {
-		array := common.JsonToStringArray("[\"test-1\", \"test-2\"]")
-		Expect(reflect.DeepEqual(array, []string{"test-1", "test-2"})).Should(BeTrue())
-	})
-	It("should transform simple single valid json", func() {
-		array := common.JsonToStringArray("[\"test-1\"]")
-		Expect(reflect.DeepEqual(array, []string{"test-1"})).Should(BeTrue())
-	})
-	It("should transform simple fake json", func() {
-		s := common.JsonToStringArray("{test-1,test-2}")
-		Expect(reflect.DeepEqual(s, []string{"test-1", "test-2"})).Should(BeTrue())
-	})
-	It("should transform simple single valued fake json", func() {
-		s := common.JsonToStringArray("{test-1}")
-		Expect(reflect.DeepEqual(s, []string{"test-1"})).Should(BeTrue())
-	})
-	It("space between fields considered as valid char", func() {
-		s := common.JsonToStringArray("{test-1, test-2}")
-		Expect(reflect.DeepEqual(s, []string{"test-1", " test-2"})).Should(BeTrue())
-	})
-	It("remove only last braces", func() {
-		s := common.JsonToStringArray("{test-1,test-2}}")
-		Expect(reflect.DeepEqual(s, []string{"test-1", "test-2}"})).Should(BeTrue())
-	})
-
-})