Skip to content

Commit

Permalink
feat(perf): optimization via profiling:
Browse files Browse the repository at this point in the history
Signed-off-by: Thibault Normand <me@zenithar.org>
  • Loading branch information
Zenithar committed Jan 15, 2023
1 parent 56e3f2b commit 6734a81
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 205 deletions.
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ More examples - [here](example_test.go)
goos: darwin
goarch: arm64
pkg: zntr.io/paseto/v3
Benchmark_Paseto_Encrypt-10 74997 14869 ns/op 8800 B/op 70 allocs/op
Benchmark_Paseto_Decrypt-10 83835 14272 ns/op 7912 B/op 66 allocs/op
Benchmark_Paseto_Sign-10 7545 156930 ns/op 9812 B/op 99 allocs/op
Benchmark_Paseto_Verify-10 1986 615306 ns/op 3578 B/op 59 allocs/op
Benchmark_Paseto_Encrypt-10 74648 14735 ns/op 8288 B/op 60 allocs/op
Benchmark_Paseto_Decrypt-10 84492 14211 ns/op 7762 B/op 58 allocs/op
Benchmark_Paseto_Sign-10 7495 157311 ns/op 9076 B/op 87 allocs/op
Benchmark_Paseto_Verify-10 1976 604464 ns/op 3433 B/op 51 allocs/op
PASS
ok zntr.io/paseto/v3 5.400s
ok zntr.io/paseto/v3 5.335s
```

### V4
Expand All @@ -71,12 +71,12 @@ ok zntr.io/paseto/v3 5.400s
goos: darwin
goarch: arm64
pkg: zntr.io/paseto/v4
Benchmark_Paseto_Encrypt-10 426136 2811 ns/op 2752 B/op 23 allocs/op
Benchmark_Paseto_Decrypt-10 517602 2244 ns/op 1920 B/op 19 allocs/op
Benchmark_Paseto_Sign-10 48186 24887 ns/op 1480 B/op 15 allocs/op
Benchmark_Paseto_Verify-10 22714 52885 ns/op 552 B/op 10 allocs/op
Benchmark_Paseto_Encrypt-10 461188 2567 ns/op 2272 B/op 13 allocs/op
Benchmark_Paseto_Decrypt-10 570516 2086 ns/op 1776 B/op 11 allocs/op
Benchmark_Paseto_Sign-10 48141 24877 ns/op 912 B/op 5 allocs/op
Benchmark_Paseto_Verify-10 22591 52607 ns/op 416 B/op 3 allocs/op
PASS
ok zntr.io/paseto/v4 6.851s
ok zntr.io/paseto/v4 6.588s
```

## License
Expand Down
39 changes: 9 additions & 30 deletions internal/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,11 @@
package common

import (
"bytes"
"crypto/subtle"
"encoding/binary"
)

// https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Common.md#authentication-padding
func PreAuthenticationEncoding(pieces ...[]byte) ([]byte, error) {
output := &bytes.Buffer{}

func PreAuthenticationEncoding(pieces ...[]byte) []byte {
// Precompute length to allocate the buffer
// PieceCount (8B) || ( PieceLen (8B) || Piece (*B) )*
bufLen := 8
Expand All @@ -35,40 +31,23 @@ func PreAuthenticationEncoding(pieces ...[]byte) ([]byte, error) {
}

// Pre-allocate the buffer
output.Grow(bufLen)
output := make([]byte, bufLen)

// Encode piece count
count := len(pieces)
if err := binary.Write(output, binary.LittleEndian, uint64(count)); err != nil {
return nil, err
}
binary.LittleEndian.PutUint64(output, uint64(len(pieces)))

offset := 8
// For each element
for i := range pieces {
// Encode size
if err := binary.Write(output, binary.LittleEndian, uint64(len(pieces[i]))); err != nil {
return nil, err
}
binary.LittleEndian.PutUint64(output[offset:], uint64(len(pieces[i])))
offset += 8

// Encode data
if _, err := output.Write(pieces[i]); err != nil {
return nil, err
}
copy(output[offset:], pieces[i])
offset += len(pieces[i])
}

// No error
return output.Bytes(), nil
}

// SecureCompare use constant time function to compare the two given array.
func SecureCompare(given, actual []byte) bool {
if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 {
return subtle.ConstantTimeCompare(given, actual) == 1
}
// Securely compare actual to itself to keep constant time, but always return false
if subtle.ConstantTimeCompare(actual, actual) == 1 {
return false
}

return false
return output
}
69 changes: 5 additions & 64 deletions internal/common/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,16 @@ func TestPreAuthenticationEncoding(t *testing.T) {
pieces [][]byte
}
tests := []struct {
name string
args args
want []byte
wantErr bool
name string
args args
want []byte
}{
{
name: "empty",
args: args{
pieces: nil,
},
wantErr: false,
want: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
want: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
},
{
name: "one",
Expand All @@ -47,7 +45,6 @@ func TestPreAuthenticationEncoding(t *testing.T) {
[]byte("test"),
},
},
wantErr: false,
want: []byte{
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Count
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Length
Expand All @@ -57,66 +54,10 @@ func TestPreAuthenticationEncoding(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := PreAuthenticationEncoding(tt.args.pieces...)
if (err != nil) != tt.wantErr {
t.Errorf("PreAuthenticationEncoding() error = %v, wantErr %v", err, tt.wantErr)
return
}
got := PreAuthenticationEncoding(tt.args.pieces...)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("PreAuthenticationEncoding() = %v, want %v", got, tt.want)
}
})
}
}

func TestSecureCompare(t *testing.T) {
type args struct {
given []byte
actual []byte
}
tests := []struct {
name string
args args
want bool
}{
{
name: "not equal, same size",
args: args{
given: []byte{0x01},
actual: []byte{0x02},
},
want: false,
},
{
name: "not equal, different size",
args: args{
given: []byte{0x01, 0x02},
actual: []byte{0x02},
},
want: false,
},
{
name: "equal, different size",
args: args{
given: []byte{0x00},
actual: []byte{},
},
want: false,
},
{
name: "equal, same size",
args: args{
given: []byte{0x01},
actual: []byte{0x01},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := SecureCompare(tt.args.given, tt.args.actual); got != tt.want {
t.Errorf("SecureCompare() = %v, want %v", got, tt.want)
}
})
}
}
5 changes: 1 addition & 4 deletions v3/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,7 @@ func kdf(key *LocalKey, n []byte) (ek, n2, ak []byte, err error) {

func mac(ak, h, n, c, f, i []byte) ([]byte, error) {
// Compute pre-authentication message
preAuth, err := common.PreAuthenticationEncoding([]byte(h), n, c, f, i)
if err != nil {
return nil, fmt.Errorf("unable to compute pre-authentication content: %w", err)
}
preAuth := common.PreAuthenticationEncoding([]byte(h), n, c, f, i)

// Compute MAC
mac := hmac.New(sha512.New384, ak)
Expand Down
37 changes: 19 additions & 18 deletions v3/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"io"

"zntr.io/paseto/internal/common"
)

// GenerateLocalKey generates a key for local encryption.
Expand Down Expand Up @@ -101,22 +100,24 @@ func Encrypt(r io.Reader, key *LocalKey, m, f, i []byte) ([]byte, error) {
body = append(body, t...)

// Encode body as RawURLBase64
encodedBody := make([]byte, base64.RawURLEncoding.EncodedLen(len(body)))
base64.RawURLEncoding.Encode(encodedBody, body)
tokenLen := base64.RawURLEncoding.EncodedLen(len(body))
footerLen := base64.RawURLEncoding.EncodedLen(len(f)) + 1
if len(f) > 0 {
tokenLen += base64.RawURLEncoding.EncodedLen(len(f)) + 1
}

final := make([]byte, tokenLen)
base64.RawURLEncoding.Encode(final, body)

// Assemble final token
final := append([]byte(LocalPrefix), encodedBody...)
if len(f) > 0 {
final[tokenLen-footerLen] = '.'
// Encode footer as RawURLBase64
encodedFooter := make([]byte, base64.RawURLEncoding.EncodedLen(len(f)))
base64.RawURLEncoding.Encode(encodedFooter, []byte(f))

// Assemble body and footer
final = append(final, append([]byte("."), encodedFooter...)...)
base64.RawURLEncoding.Encode(final[tokenLen-footerLen+1:], []byte(f))
}

// No error
return final, nil
return append([]byte(LocalPrefix), final...), nil
}

// PASETO v3 symmetric decryption primitive
Expand Down Expand Up @@ -144,24 +145,24 @@ func Decrypt(key *LocalKey, input, f, i []byte) ([]byte, error) {
// Check footer usage
if len(f) > 0 {
// Split the footer and the body
parts := bytes.SplitN(input, []byte("."), 2)
if len(parts) != 2 {
footerIdx := bytes.Index(input, []byte("."))
if footerIdx == 0 {
return nil, errors.New("paseto: invalid token, footer is missing but expected")
}

// Decode footer
footer := make([]byte, base64.RawURLEncoding.DecodedLen(len(parts[1])))
if _, err := base64.RawURLEncoding.Decode(footer, parts[1]); err != nil {
footer := make([]byte, base64.RawURLEncoding.DecodedLen(len(input[footerIdx+1:])))
if _, err := base64.RawURLEncoding.Decode(footer, input[footerIdx+1:]); err != nil {
return nil, fmt.Errorf("paseto: invalid token, footer has invalid encoding: %w", err)
}

// Compare footer
if !common.SecureCompare(f, footer) {
if subtle.ConstantTimeCompare(f, footer) == 0 {
return nil, errors.New("paseto: invalid token, footer mismatch")
}

// Continue without footer
input = parts[0]
input = input[:footerIdx]
}

// Decode token
Expand All @@ -188,7 +189,7 @@ func Decrypt(key *LocalKey, input, f, i []byte) ([]byte, error) {
}

// Time-constant compare MAC
if !common.SecureCompare(t, t2) {
if subtle.ConstantTimeCompare(t, t2) == 0 {
return nil, errors.New("paseto: invalid pre-authentication header")
}

Expand Down
Loading

0 comments on commit 6734a81

Please sign in to comment.