mirror of https://github.com/gofiber/fiber.git
v3: Enforce key length for EncryptCookie middleware default functions (#3056)
* Support for key length, Add benchmarks for EncryptCookie middleware * Format tests * Add tests for panics and key check in Encryptor and Decryptor functions * Add tests for base64 decoding errors * Update docs/middleware/encryptcookie.md Co-authored-by: Jason McNeil <sixcolors@mac.com> * Update middleware/encryptcookie/utils.go Co-authored-by: Jason McNeil <sixcolors@mac.com> * Add suggestions from code review --------- Co-authored-by: Jason McNeil <sixcolors@mac.com>pull/3062/head
parent
55138fa506
commit
d17eb99377
|
@ -44,9 +44,6 @@ jobs:
|
|||
slug: gofiber/fiber
|
||||
|
||||
repeated:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [stable]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fetch Repository
|
||||
|
@ -55,7 +52,7 @@ jobs:
|
|||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
go-version: stable
|
||||
|
||||
- name: Test
|
||||
run: go run gotest.tools/gotestsum@latest -f testname -- ./... -race -count=15 -shuffle=on
|
||||
|
|
|
@ -16,8 +16,10 @@ This middleware encrypts cookie values and not the cookie names.
|
|||
// Intitializes the middleware
|
||||
func New(config ...Config) fiber.Handler
|
||||
|
||||
// Returns a random 32 character long string
|
||||
func GenerateKey() string
|
||||
// GenerateKey returns a random string of 16, 24, or 32 bytes.
|
||||
// The length of the key determines the AES encryption algorithm used:
|
||||
// 16 bytes for AES-128, 24 bytes for AES-192, and 32 bytes for AES-256-GCM.
|
||||
func GenerateKey(length int) string
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
@ -55,9 +57,8 @@ app.Post("/", func(c fiber.Ctx) error {
|
|||
```
|
||||
|
||||
:::note
|
||||
`Key` must be a 32 character string. It's used to encrypt the values, so make sure it is random and keep it secret.
|
||||
You can run `openssl rand -base64 32` or call `encryptcookie.GenerateKey()` to create a random key for you.
|
||||
Make sure not to set `Key` to `encryptcookie.GenerateKey()` because that will create a new key every run.
|
||||
The `Key` parameter requires an encoded string of 16, 24, or 32 bytes for encryption, corresponding to AES-128, AES-192, and AES-256-GCM standards, respectively. Ensure the key is randomly generated and securely stored.
|
||||
To generate a 32 char key, use `openssl rand -base64 32` or `encryptcookie.GenerateKey(32)`. Avoid dynamically generating a new `Key` with `encryptcookie.GenerateKey(32)` at each application startup to prevent rendering previously encrypted data inaccessible.
|
||||
:::
|
||||
|
||||
## Config
|
||||
|
@ -83,6 +84,7 @@ var ConfigDefault = Config{
|
|||
```
|
||||
|
||||
## Usage With Other Middlewares That Reads Or Modify Cookies
|
||||
|
||||
Place the `encryptcookie` middleware before any other middleware that reads or modifies cookies. For example, if you are using the CSRF middleware, ensure that the `encryptcookie` middleware is placed before it. Failure to do so may prevent the CSRF middleware from reading the encrypted cookie.
|
||||
|
||||
You may also choose to exclude certain cookies from encryption. For instance, if you are using the `CSRF` middleware with a frontend framework like Angular, and the framework reads the token from a cookie, you should exclude that cookie from encryption. This can be achieved by adding the cookie name to the Except array in the configuration:
|
||||
|
@ -99,3 +101,22 @@ app.Use(csrf.New(csrf.Config{
|
|||
CookieHTTPOnly: false,
|
||||
}))
|
||||
```
|
||||
|
||||
## Encryption Algorithms
|
||||
|
||||
The default Encryptor and Decryptor functions use `AES-256-GCM` for encryption and decryption. If you need to use `AES-128` or `AES-192` instead, you can do so by changing the length of the key when calling `encryptcookie.GenerateKey(length)` or by providing a key of one of the following lengths:
|
||||
- AES-128 requires a 16-byte key.
|
||||
- AES-192 requires a 24-byte key.
|
||||
- AES-256 requires a 32-byte key.
|
||||
|
||||
For example, to generate a key for AES-128:
|
||||
|
||||
```go
|
||||
key := encryptcookie.GenerateKey(16)
|
||||
```
|
||||
|
||||
And for AES-192:
|
||||
|
||||
```go
|
||||
key := encryptcookie.GenerateKey(24)
|
||||
```
|
|
@ -275,6 +275,10 @@ We've updated several fields from a single string (containing comma-separated va
|
|||
|
||||
We've added support for `zstd` compression on top of `gzip`, `deflate`, and `brotli`.
|
||||
|
||||
### EncryptCookie
|
||||
|
||||
Added support for specifying Key length when using `encryptcookie.GenerateKey(length)`. This allows the user to generate keys compatible with `AES-128`, `AES-192`, and `AES-256` (Default).
|
||||
|
||||
### Session
|
||||
|
||||
:::caution
|
||||
|
|
|
@ -18,18 +18,19 @@ type Config struct {
|
|||
|
||||
// Base64 encoded unique key to encode & decode cookies.
|
||||
//
|
||||
// Required. Key length should be 32 characters.
|
||||
// You may use `encryptcookie.GenerateKey()` to generate a new key.
|
||||
// Required. Key length should be 16, 24, or 32 bytes when decoded
|
||||
// if using the default EncryptCookie and DecryptCookie functions.
|
||||
// You may use `encryptcookie.GenerateKey(length)` to generate a new key.
|
||||
Key string
|
||||
|
||||
// Custom function to encrypt cookies.
|
||||
//
|
||||
// Optional. Default: EncryptCookie
|
||||
// Optional. Default: EncryptCookie (using AES-GCM)
|
||||
Encryptor func(decryptedString, key string) (string, error)
|
||||
|
||||
// Custom function to decrypt cookies.
|
||||
//
|
||||
// Optional. Default: DecryptCookie
|
||||
// Optional. Default: DecryptCookie (using AES-GCM)
|
||||
Decryptor func(encryptedString, key string) (string, error)
|
||||
}
|
||||
|
||||
|
@ -52,7 +53,6 @@ func configDefault(config ...Config) Config {
|
|||
cfg = config[0]
|
||||
|
||||
// Set default values
|
||||
|
||||
if cfg.Next == nil {
|
||||
cfg.Next = ConfigDefault.Next
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package encryptcookie
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
|
@ -10,10 +12,92 @@ import (
|
|||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
var testKey = GenerateKey()
|
||||
func Test_Middleware_Panics(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Empty Key", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := fiber.New()
|
||||
require.Panics(t, func() {
|
||||
app.Use(New(Config{
|
||||
Key: "",
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Invalid Key", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Panics(t, func() {
|
||||
GenerateKey(11)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Middleware_InvalidKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
length int
|
||||
}{
|
||||
{11},
|
||||
{25},
|
||||
{60},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(strconv.Itoa(tt.length)+"_length_encrypt", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
key := make([]byte, tt.length)
|
||||
_, err := rand.Read(key)
|
||||
require.NoError(t, err)
|
||||
keyString := base64.StdEncoding.EncodeToString(key)
|
||||
|
||||
_, err = EncryptCookie("SomeThing", keyString)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run(strconv.Itoa(tt.length)+"_length_decrypt", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
key := make([]byte, tt.length)
|
||||
_, err := rand.Read(key)
|
||||
require.NoError(t, err)
|
||||
keyString := base64.StdEncoding.EncodeToString(key)
|
||||
|
||||
_, err = DecryptCookie("SomeThing", keyString)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Middleware_InvalidBase64(t *testing.T) {
|
||||
t.Parallel()
|
||||
invalidBase64 := "invalid-base64-string-!@#"
|
||||
|
||||
t.Run("encryptor", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := EncryptCookie("SomeText", invalidBase64)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "failed to base64-decode key")
|
||||
})
|
||||
|
||||
t.Run("decryptor_key", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := DecryptCookie("SomeText", invalidBase64)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "failed to base64-decode key")
|
||||
})
|
||||
|
||||
t.Run("decryptor_value", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := DecryptCookie(invalidBase64, GenerateKey(32))
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "failed to base64-decode value")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Middleware_Encrypt_Cookie(t *testing.T) {
|
||||
t.Parallel()
|
||||
testKey := GenerateKey(32)
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
|
@ -75,6 +159,7 @@ func Test_Middleware_Encrypt_Cookie(t *testing.T) {
|
|||
|
||||
func Test_Encrypt_Cookie_Next(t *testing.T) {
|
||||
t.Parallel()
|
||||
testKey := GenerateKey(32)
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
|
@ -99,6 +184,7 @@ func Test_Encrypt_Cookie_Next(t *testing.T) {
|
|||
|
||||
func Test_Encrypt_Cookie_Except(t *testing.T) {
|
||||
t.Parallel()
|
||||
testKey := GenerateKey(32)
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
|
@ -143,6 +229,7 @@ func Test_Encrypt_Cookie_Except(t *testing.T) {
|
|||
|
||||
func Test_Encrypt_Cookie_Custom_Encryptor(t *testing.T) {
|
||||
t.Parallel()
|
||||
testKey := GenerateKey(32)
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
|
@ -189,3 +276,409 @@ func Test_Encrypt_Cookie_Custom_Encryptor(t *testing.T) {
|
|||
require.Equal(t, 200, ctx.Response.StatusCode())
|
||||
require.Equal(t, "value=SomeThing", string(ctx.Response.Body()))
|
||||
}
|
||||
|
||||
func Test_GenerateKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
length int
|
||||
}{
|
||||
{16},
|
||||
{24},
|
||||
{32},
|
||||
}
|
||||
|
||||
decodeBase64 := func(t *testing.T, s string) []byte {
|
||||
t.Helper()
|
||||
data, err := base64.StdEncoding.DecodeString(s)
|
||||
require.NoError(t, err)
|
||||
return data
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(strconv.Itoa(tt.length)+"_length", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
key := GenerateKey(tt.length)
|
||||
decodedKey := decodeBase64(t, key)
|
||||
require.Len(t, decodedKey, tt.length)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Invalid Length", func(t *testing.T) {
|
||||
require.Panics(t, func() { GenerateKey(10) })
|
||||
require.Panics(t, func() { GenerateKey(20) })
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_Middleware_Encrypt_Cookie(b *testing.B) {
|
||||
testKey := GenerateKey(32)
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Key: testKey,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("value=" + c.Cookies("test"))
|
||||
})
|
||||
app.Post("/", func(c fiber.Ctx) error {
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "test",
|
||||
Value: "SomeThing",
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
||||
h := app.Handler()
|
||||
|
||||
b.Run("Empty Cookie", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||
h(ctx)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Invalid Cookie", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||
ctx.Request.Header.SetCookie("test", "Invalid")
|
||||
h(ctx)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Valid Cookie", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.Header.SetMethod(fiber.MethodPost)
|
||||
h(ctx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_Encrypt_Cookie_Next(b *testing.B) {
|
||||
testKey := GenerateKey(32)
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Key: testKey,
|
||||
Next: func(_ fiber.Ctx) bool {
|
||||
return true
|
||||
},
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "test",
|
||||
Value: "SomeThing",
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
||||
h := app.Handler()
|
||||
|
||||
b.Run("Encrypt Cookie Next", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||
ctx.Request.SetRequestURI("/")
|
||||
h(ctx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_Encrypt_Cookie_Except(b *testing.B) {
|
||||
testKey := GenerateKey(32)
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Key: testKey,
|
||||
Except: []string{
|
||||
"test1",
|
||||
},
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "test1",
|
||||
Value: "SomeThing",
|
||||
})
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "test2",
|
||||
Value: "SomeThing",
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
h := app.Handler()
|
||||
|
||||
b.Run("Encrypt Cookie Except", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||
h(ctx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_Encrypt_Cookie_Custom_Encryptor(b *testing.B) {
|
||||
testKey := GenerateKey(32)
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Key: testKey,
|
||||
Encryptor: func(decryptedString, _ string) (string, error) {
|
||||
return base64.StdEncoding.EncodeToString([]byte(decryptedString)), nil
|
||||
},
|
||||
Decryptor: func(encryptedString, _ string) (string, error) {
|
||||
decodedBytes, err := base64.StdEncoding.DecodeString(encryptedString)
|
||||
return string(decodedBytes), err
|
||||
},
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("value=" + c.Cookies("test"))
|
||||
})
|
||||
app.Post("/", func(c fiber.Ctx) error {
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "test",
|
||||
Value: "SomeThing",
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
h := app.Handler()
|
||||
|
||||
b.Run("Custom Encryptor Post", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.Header.SetMethod(fiber.MethodPost)
|
||||
h(ctx)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Custom Encryptor Get", func(b *testing.B) {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.Header.SetMethod(fiber.MethodPost)
|
||||
h(ctx)
|
||||
encryptedCookie := fasthttp.Cookie{}
|
||||
encryptedCookie.SetKey("test")
|
||||
require.True(b, ctx.Response.Header.Cookie(&encryptedCookie), "Get cookie value")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||
ctx.Request.Header.SetCookie("test", string(encryptedCookie.Value()))
|
||||
h(ctx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_Middleware_Encrypt_Cookie_Parallel(b *testing.B) {
|
||||
testKey := GenerateKey(32)
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Key: testKey,
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("value=" + c.Cookies("test"))
|
||||
})
|
||||
app.Post("/", func(c fiber.Ctx) error {
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "test",
|
||||
Value: "SomeThing",
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
||||
h := app.Handler()
|
||||
|
||||
b.Run("Empty Cookie Parallel", func(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||
h(ctx)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("Invalid Cookie Parallel", func(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||
ctx.Request.Header.SetCookie("test", "Invalid")
|
||||
h(ctx)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("Valid Cookie Parallel", func(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.Header.SetMethod(fiber.MethodPost)
|
||||
h(ctx)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_Encrypt_Cookie_Next_Parallel(b *testing.B) {
|
||||
testKey := GenerateKey(32)
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Key: testKey,
|
||||
Next: func(_ fiber.Ctx) bool {
|
||||
return true
|
||||
},
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "test",
|
||||
Value: "SomeThing",
|
||||
})
|
||||
return nil
|
||||
})
|
||||
|
||||
h := app.Handler()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||
ctx.Request.SetRequestURI("/")
|
||||
h(ctx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_Encrypt_Cookie_Except_Parallel(b *testing.B) {
|
||||
testKey := GenerateKey(32)
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Key: testKey,
|
||||
Except: []string{
|
||||
"test1",
|
||||
},
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "test1",
|
||||
Value: "SomeThing",
|
||||
})
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "test2",
|
||||
Value: "SomeThing",
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
h := app.Handler()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||
h(ctx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_Encrypt_Cookie_Custom_Encryptor_Parallel(b *testing.B) {
|
||||
testKey := GenerateKey(32)
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(New(Config{
|
||||
Key: testKey,
|
||||
Encryptor: func(decryptedString, _ string) (string, error) {
|
||||
return base64.StdEncoding.EncodeToString([]byte(decryptedString)), nil
|
||||
},
|
||||
Decryptor: func(encryptedString, _ string) (string, error) {
|
||||
decodedBytes, err := base64.StdEncoding.DecodeString(encryptedString)
|
||||
return string(decodedBytes), err
|
||||
},
|
||||
}))
|
||||
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
return c.SendString("value=" + c.Cookies("test"))
|
||||
})
|
||||
app.Post("/", func(c fiber.Ctx) error {
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "test",
|
||||
Value: "SomeThing",
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
h := app.Handler()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.Header.SetMethod(fiber.MethodPost)
|
||||
h(ctx)
|
||||
encryptedCookie := fasthttp.Cookie{}
|
||||
encryptedCookie.SetKey("test")
|
||||
require.True(b, ctx.Response.Header.Cookie(&encryptedCookie), "Get cookie value")
|
||||
|
||||
b.ResetTimer()
|
||||
for pb.Next() {
|
||||
ctx := &fasthttp.RequestCtx{}
|
||||
ctx.Request.Header.SetMethod(fiber.MethodGet)
|
||||
ctx.Request.Header.SetCookie("test", string(encryptedCookie.Value()))
|
||||
h(ctx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_GenerateKey(b *testing.B) {
|
||||
tests := []struct {
|
||||
length int
|
||||
}{
|
||||
{16},
|
||||
{24},
|
||||
{32},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(strconv.Itoa(tt.length), func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
GenerateKey(tt.length)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_GenerateKey_Parallel(b *testing.B) {
|
||||
tests := []struct {
|
||||
length int
|
||||
}{
|
||||
{16},
|
||||
{24},
|
||||
{32},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(strconv.Itoa(tt.length), func(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
GenerateKey(tt.length)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
"io"
|
||||
)
|
||||
|
||||
var ErrInvalidKeyLength = errors.New("encryption key must be 16, 24, or 32 bytes")
|
||||
|
||||
// EncryptCookie Encrypts a cookie value with specific encryption key
|
||||
func EncryptCookie(value, key string) (string, error) {
|
||||
keyDecoded, err := base64.StdEncoding.DecodeString(key)
|
||||
|
@ -17,6 +19,11 @@ func EncryptCookie(value, key string) (string, error) {
|
|||
return "", fmt.Errorf("failed to base64-decode key: %w", err)
|
||||
}
|
||||
|
||||
keyLen := len(keyDecoded)
|
||||
if keyLen != 16 && keyLen != 24 && keyLen != 32 {
|
||||
return "", ErrInvalidKeyLength
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(keyDecoded)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create AES cipher: %w", err)
|
||||
|
@ -29,11 +36,10 @@ func EncryptCookie(value, key string) (string, error) {
|
|||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return "", fmt.Errorf("failed to read: %w", err)
|
||||
return "", fmt.Errorf("failed to read nonce: %w", err)
|
||||
}
|
||||
|
||||
ciphertext := gcm.Seal(nonce, nonce, []byte(value), nil)
|
||||
|
||||
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||
}
|
||||
|
||||
|
@ -43,6 +49,12 @@ func DecryptCookie(value, key string) (string, error) {
|
|||
if err != nil {
|
||||
return "", fmt.Errorf("failed to base64-decode key: %w", err)
|
||||
}
|
||||
|
||||
keyLen := len(keyDecoded)
|
||||
if keyLen != 16 && keyLen != 24 && keyLen != 32 {
|
||||
return "", ErrInvalidKeyLength
|
||||
}
|
||||
|
||||
enc, err := base64.StdEncoding.DecodeString(value)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to base64-decode value: %w", err)
|
||||
|
@ -65,7 +77,6 @@ func DecryptCookie(value, key string) (string, error) {
|
|||
}
|
||||
|
||||
nonce, ciphertext := enc[:nonceSize], enc[nonceSize:]
|
||||
|
||||
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decrypt ciphertext: %w", err)
|
||||
|
@ -74,16 +85,21 @@ func DecryptCookie(value, key string) (string, error) {
|
|||
return string(plaintext), nil
|
||||
}
|
||||
|
||||
// GenerateKey Generates an encryption key
|
||||
func GenerateKey() string {
|
||||
const keyLen = 32
|
||||
ret := make([]byte, keyLen)
|
||||
// GenerateKey returns a random string of 16, 24, or 32 bytes.
|
||||
// The length of the key determines the AES encryption algorithm used:
|
||||
// 16 bytes for AES-128, 24 bytes for AES-192, and 32 bytes for AES-256-GCM.
|
||||
func GenerateKey(length int) string {
|
||||
if length != 16 && length != 24 && length != 32 {
|
||||
panic(ErrInvalidKeyLength)
|
||||
}
|
||||
|
||||
if _, err := rand.Read(ret); err != nil {
|
||||
key := make([]byte, length)
|
||||
|
||||
if _, err := rand.Read(key); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(ret)
|
||||
return base64.StdEncoding.EncodeToString(key)
|
||||
}
|
||||
|
||||
// Check given cookie key is disabled for encryption or not
|
||||
|
|
Loading…
Reference in New Issue