[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())
+ })
+ })
+})