diff --git a/crypto/aes.go b/crypto/aes.go new file mode 100644 index 00000000..43a4c9ac --- /dev/null +++ b/crypto/aes.go @@ -0,0 +1,58 @@ +package crypto + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "fmt" + "io" +) + +// Base64AESEncode AES encode +func Base64AESEncode(key []byte, text string) (string, error) { + + plaintext := []byte(text) + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + // The IV needs to be unique, but not secure. Therefore it's common to + // include it at the beginning of the ciphertext. + ciphertext := make([]byte, aes.BlockSize+len(plaintext)) + iv := ciphertext[:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return "", err + } + + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) + + // convert to base64 + return base64.URLEncoding.EncodeToString(ciphertext), nil +} + +// Base64AESDecode AES decode +func Base64AESDecode(key []byte, cryptoText string) (string, error) { + ciphertext, _ := base64.URLEncoding.DecodeString(cryptoText) + + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + // The IV needs to be unique, but not secure. Therefore it's common to + // include it at the beginning of the ciphertext. + if len(ciphertext) < aes.BlockSize { + return "", fmt.Errorf("ciphertext too short") + } + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + + stream := cipher.NewCFBDecrypter(block, iv) + + // XORKeyStream can work in-place if the two arguments are the same. + stream.XORKeyStream(ciphertext, ciphertext) + return fmt.Sprintf("%s", ciphertext), nil +} diff --git a/crypto/aes_test.go b/crypto/aes_test.go new file mode 100644 index 00000000..d68c8861 --- /dev/null +++ b/crypto/aes_test.go @@ -0,0 +1,37 @@ +package crypto + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" +) + +func TestBase64AES(t *testing.T) { + + originalText := "encrypt this golang" + key := []byte("example key 1234") + + // encrypt value to base64 + cryptoText, err := Base64AESEncode(key, originalText) + if err != nil { + t.Fatal(err) + } + + // encrypt base64 crypto to original value + text, err := Base64AESDecode(key, cryptoText) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "encrypt this golang", text) +} + +func TestBase64AESProcess(t *testing.T) { + + args := []interface{}{"example key 1234", "encrypt this golang"} + cryptoText := gou.NewProcess("yao.crypto.AESBase64Encode", args...).Run() + + args = []interface{}{"example key 1234", cryptoText} + text := gou.NewProcess("yao.crypto.AESBase64Decode", args...).Run() + assert.Equal(t, "encrypt this golang", text) +} diff --git a/crypto/process.go b/crypto/process.go index dba48dd9..594dcf93 100644 --- a/crypto/process.go +++ b/crypto/process.go @@ -8,7 +8,8 @@ import ( func init() { gou.RegisterProcessHandler("yao.crypto.hash", ProcessHash) // deprecated → crypto.Hash gou.RegisterProcessHandler("yao.crypto.hmac", ProcessHmac) // deprecated → crypto.Hash - + gou.RegisterProcessHandler("yao.crypto.AESBase64Encode", processBase64AESEncode) + gou.RegisterProcessHandler("yao.crypto.AESBase64Decode", processBase64AESDecode) gou.AliasProcess("yao.crypto.hash", "crypto.Hash") gou.AliasProcess("yao.crypto.hmac", "crypto.Hmac") } @@ -60,3 +61,25 @@ func ProcessHmac(process *gou.Process) interface{} { } return res } + +func processBase64AESEncode(process *gou.Process) interface{} { + process.ValidateArgNums(2) + key := process.ArgsString(0) + value := process.ArgsString(1) + res, err := Base64AESEncode([]byte(key), value) + if err != nil { + exception.New("error: %s value: %s", 400, err, value).Throw() + } + return res +} + +func processBase64AESDecode(process *gou.Process) interface{} { + process.ValidateArgNums(2) + key := process.ArgsString(0) + value := process.ArgsString(1) + res, err := Base64AESDecode([]byte(key), value) + if err != nil { + exception.New("error: %s value: %s", 400, err, value).Throw() + } + return res +}