[ISSUE-67869881] add cipher
diff --git a/.travis.yml b/.travis.yml index 5080b8b..64f6935 100644 --- a/.travis.yml +++ b/.travis.yml
@@ -14,4 +14,4 @@ script: - diff -u <(echo -n) <(gofmt -d $(git ls-files | grep '.go$' | grep -v vendor)) - go vet $(glide novendor) - - go test $(glide novendor) + - go test -covermode=atomic $(glide novendor)
diff --git a/cipher/cipher.go b/cipher/cipher.go new file mode 100644 index 0000000..06d8242 --- /dev/null +++ b/cipher/cipher.go
@@ -0,0 +1,186 @@ +// 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 cipher + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" + "sync" +) + +type Mode string +type Padding string + +const ( + ModeEcb = Mode("ECB") +) + +const ( + PaddingPKCS7 = Padding("PKCS7Padding") + PaddingPKCS5 = Padding("PKCS5Padding") +) + +var ( + supportedModes = map[Mode]bool{ + ModeEcb: true, + } + supportedPadding = map[Padding]bool{ + PaddingPKCS7: true, + PaddingPKCS5: true, + } +) + +func (p Padding) padding(input []byte) (output []byte) { + switch p { + case PaddingPKCS5: + fallthrough + case PaddingPKCS7: + //PKCS7Padding + numPad := 16 - (len(input) % 16) + output = make([]byte, len(input)+numPad) + for i := copy(output, []byte(input)); i < len(output); i++ { + output[i] = byte(numPad) + } + } + return +} + +func (p Padding) strip(input []byte) (output []byte) { + switch p { + case PaddingPKCS5: + fallthrough + case PaddingPKCS7: + //remove PKCS7Padding + numPad := int(input[len(input)-1]) + output = input[:(len(input) - numPad)] + } + return +} + +// Create a new AesCipher object with the specified encryption mode and padding algorithm. +func CreateAesCipher(key []byte) (*AesCipher, error) { + a := &AesCipher{ + mutex: &sync.RWMutex{}, + } + if err := a.SetKey(key); err != nil { + return nil, err + } + return a, nil +} + +// An object to perform AES encryption/decryption. +type AesCipher struct { + key []byte + block cipher.Block + mutex *sync.RWMutex +} + +// Set/Change the AES key, accepted key's bit-size is 128/192/256. +func (a *AesCipher) SetKey(key []byte) (err error) { + a.mutex.Lock() + defer a.mutex.Unlock() + a.key = key + a.block, err = aes.NewCipher(key) + return err +} + +// Encrypt the plaintext. Padding is performed before encryption, so the +// plaintext input can have any length. +func (a *AesCipher) Encrypt(plaintext []byte, mode Mode, padding Padding) (ciphertext []byte, err error) { + // check mode + if !supportedModes[mode] { + return nil, &ErrModeUnsupported{ + mode: mode, + } + } + // check padding + if !supportedPadding[padding] { + return nil, &ErrPaddingUnsupported{ + padding: padding, + } + } + // padding + text := padding.padding(plaintext) + ciphertext = text + + // encrypt + a.mutex.RLock() + block := a.block + a.mutex.RUnlock() + + switch mode { + case ModeEcb: + size := block.BlockSize() + for len(text) > 0 { + block.Encrypt(text, text) + text = text[size:] + } + } + return +} + +// Decrypt the ciphertext. Padding is removed after decryption, so the +// plaintext output can have different length from input ciphertext. +func (a *AesCipher) Decrypt(ciphertext []byte, mode Mode, padding Padding) (plaintext []byte, err error) { + // check mode + if !supportedModes[mode] { + return nil, &ErrModeUnsupported{ + mode: mode, + } + } + // check padding + if !supportedPadding[padding] { + return nil, &ErrPaddingUnsupported{ + padding: padding, + } + } + + // encrypt + a.mutex.RLock() + block := a.block + a.mutex.RUnlock() + + switch mode { + case ModeEcb: + plaintext = make([]byte, len(ciphertext)) + buffer := plaintext + size := block.BlockSize() + for len(ciphertext) > 0 { + block.Decrypt(buffer, ciphertext) + ciphertext = ciphertext[size:] + buffer = buffer[size:] + } + } + + // strip padding + plaintext = padding.strip(plaintext) + return +} + +type ErrModeUnsupported struct { + mode Mode +} + +func (e *ErrModeUnsupported) Error() string { + return fmt.Sprintf("mode unsupported: %v", e.mode) +} + +type ErrPaddingUnsupported struct { + padding Padding +} + +func (e *ErrPaddingUnsupported) Error() string { + return fmt.Sprintf("padding unsupported: %v", e.padding) +}
diff --git a/cipher/cipher_test.go b/cipher/cipher_test.go new file mode 100644 index 0000000..0bdde75 --- /dev/null +++ b/cipher/cipher_test.go
@@ -0,0 +1,118 @@ +// 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 cipher_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/apid/apid-core/cipher" + "testing" +) + +func TestEvents(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Cipher Suite") +} + +var _ = Describe("APID Cipher", func() { + + Context("AES", func() { + + Context("AES/ECB/PKCS7Padding Encrypt/Decrypt", func() { + type testData struct { + key []byte + plaintext []byte + ciphertext []byte + } + + data := []testData{ + { + // 128-bit + []byte{2, 122, 212, 83, 150, 164, 180, 4, 148, 242, 65, 189, 3, 188, 76, 247}, + []byte("aUWQKgAwmaR0p2kY"), + // 32-byte after padding + []byte{218, 53, 247, 87, 119, 80, 231, 16, 125, 11, 214, 101, 246, 202, 178, 163, 202, 102, + 146, 245, 79, 215, 74, 228, 17, 83, 213, 134, 105, 203, 31, 14}, + }, + { + // 192-bit + []byte{2, 122, 212, 83, 150, 164, 180, 4, 148, 242, 65, 189, 3, 188, 76, 247, + 2, 122, 212, 83, 150, 164, 180, 4}, + []byte("a"), + // 16-byte after padding + []byte{225, 2, 177, 65, 152, 88, 116, 43, 71, 215, 84, 240, 221, 175, 11, 131}, + }, + { + // 256-bit + []byte{2, 122, 212, 83, 150, 164, 180, 4, 148, 242, 65, 189, 3, 188, 76, 247, + 2, 122, 212, 83, 150, 164, 180, 4, 148, 242, 65, 189, 3, 188, 76, 247}, + []byte(""), + // 16-byte after padding + []byte{88, 192, 164, 235, 153, 89, 14, 134, 224, 122, 31, 36, 238, 117, 121, 117}, + }, + } + It("Encrypt", func() { + for i := 0; i < len(data); i++ { + c, err := cipher.CreateAesCipher(data[i].key) + Expect(err).Should(Succeed()) + Expect(c.Encrypt(data[i].plaintext, cipher.ModeEcb, cipher.PaddingPKCS5)).Should(Equal(data[i].ciphertext)) + Expect(c.Encrypt(data[i].plaintext, cipher.ModeEcb, cipher.PaddingPKCS7)).Should(Equal(data[i].ciphertext)) + } + }) + + It("Decrypt", func() { + for i := 0; i < len(data); i++ { + c, err := cipher.CreateAesCipher(data[i].key) + Expect(err).Should(Succeed()) + Expect(c.Encrypt(data[i].plaintext, cipher.ModeEcb, cipher.PaddingPKCS5)).Should(Equal(data[i].ciphertext)) + Expect(c.Encrypt(data[i].plaintext, cipher.ModeEcb, cipher.PaddingPKCS7)).Should(Equal(data[i].ciphertext)) + } + }) + }) + + It("SetKey", func() { + key := make([]byte, 16) + plaintext := []byte("aUWQKgAwmaR0p2kY") + ciphertext := []byte{218, 53, 247, 87, 119, 80, 231, 16, 125, 11, 214, 101, 246, 202, 178, 163, 202, 102, 146, 245, 79, 215, 74, 228, 17, 83, 213, 134, 105, 203, 31, 14} + c, err := cipher.CreateAesCipher(key) + Expect(err).Should(Succeed()) + key = []byte{2, 122, 212, 83, 150, 164, 180, 4, 148, 242, 65, 189, 3, 188, 76, 247} + Expect(c.SetKey(key)).Should(Succeed()) + Expect(c.Encrypt(plaintext, cipher.ModeEcb, cipher.PaddingPKCS5)).Should(Equal(ciphertext)) + Expect(c.Decrypt(ciphertext, cipher.ModeEcb, cipher.PaddingPKCS7)).Should(Equal(plaintext)) + }) + + It("Invalid Parameters", func() { + _, err := cipher.CreateAesCipher(make([]byte, 15)) + Expect(err).ToNot(Succeed()) + _, err = cipher.CreateAesCipher(nil) + Expect(err).ToNot(Succeed()) + key := make([]byte, 16) + c, err := cipher.CreateAesCipher(key) + Expect(err).Should(Succeed()) + _, err = c.Encrypt([]byte{1, 2, 3}, cipher.Mode("unsupported"), cipher.PaddingPKCS7) + Expect(err).ToNot(Succeed()) + _, err = c.Encrypt([]byte{1, 2, 3}, cipher.ModeEcb, cipher.Padding("unsupported")) + Expect(err).ToNot(Succeed()) + _, err = c.Decrypt([]byte{88, 192, 164, 235, 153, 89, 14, 134, 224, 122, 31, 36, 238, 117, 121, 117}, + cipher.Mode("unsupported"), cipher.PaddingPKCS7) + Expect(err).ToNot(Succeed()) + _, err = c.Decrypt([]byte{88, 192, 164, 235, 153, 89, 14, 134, 224, 122, 31, 36, 238, 117, 121, 117}, + cipher.ModeEcb, cipher.Padding("unsupported")) + Expect(err).ToNot(Succeed()) + }) + }) +})