Skip to content

Commit

Permalink
fix: use base64 (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
aldy505 authored Jun 27, 2024
1 parent a417b31 commit 180ab1d
Show file tree
Hide file tree
Showing 14 changed files with 90 additions and 129 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go-version: [1.20.x, 1.21.x]
go-version: [1.21.x, 1.22.x]
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021-present Reinaldy Rafli and PHC Crypto collaborators
Copyright (c) 2024 Reinaldy Rafli and PHC Crypto collaborators

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ project. Feel free to refactor, add new feature, fix unknown bugs, and have fun!
```
MIT License
Copyright (c) 2021-present Reinaldy Rafli and PHC Crypto collaborators
Copyright (c) 2024 Reinaldy Rafli and PHC Crypto collaborators
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
30 changes: 12 additions & 18 deletions argon2/argon2.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package argon2
import (
"crypto/rand"
"crypto/subtle"
"encoding/hex"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -115,8 +114,8 @@ func Hash(plain string, config Config) (string, error) {
"t": int(config.Time),
"p": int(config.Parallelism),
},
Salt: hex.EncodeToString(salt),
Hash: hex.EncodeToString(hash),
Salt: salt,
Hash: hash,
})
return hashString, nil
}
Expand Down Expand Up @@ -144,17 +143,16 @@ func Verify(hash string, plain string) (bool, error) {
return false, ErrEmptyField
}

deserialize := format.Deserialize(hash)
deserialize, err := format.Deserialize(hash)
if err != nil {
return false, err
}

if !strings.HasPrefix(deserialize.ID, "argon2") {
return false, errors.New("hashed string is not argon instance")
}

var verifyHash []byte
decodedHash, err := hex.DecodeString(deserialize.Hash)
if err != nil {
return false, err
}
keyLen := uint32(len(decodedHash))
keyLen := uint32(len(deserialize.Hash))

time, err := strconv.ParseUint(deserialize.Params["t"].(string), 10, 32)
if err != nil {
Expand All @@ -169,18 +167,14 @@ func Verify(hash string, plain string) (bool, error) {
return false, err
}

salt, err := hex.DecodeString(deserialize.Salt)
if err != nil {
return false, err
}

var verifyHash []byte
if deserialize.ID == "argon2id" {
verifyHash = argon2.IDKey([]byte(plain), salt, uint32(time), uint32(memory), uint8(parallelism), keyLen)
verifyHash = argon2.IDKey([]byte(plain), deserialize.Salt, uint32(time), uint32(memory), uint8(parallelism), keyLen)
} else if deserialize.ID == "argon2i" {
verifyHash = argon2.Key([]byte(plain), salt, uint32(time), uint32(memory), uint8(parallelism), keyLen)
verifyHash = argon2.Key([]byte(plain), deserialize.Salt, uint32(time), uint32(memory), uint8(parallelism), keyLen)
}

if subtle.ConstantTimeCompare(verifyHash, decodedHash) == 1 {
if subtle.ConstantTimeCompare(verifyHash, deserialize.Hash) == 1 {
return true, nil
}
return false, nil
Expand Down
22 changes: 3 additions & 19 deletions argon2/argon2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,39 +106,23 @@ func TestError(t *testing.T) {
})

t.Run("should fail parsing int - 1", func(t *testing.T) {
hashString := "$argon2id$v=2$t=a,m=64,p=32$9336bb54e8f5532cc1f3050262d90b8d2c0cdca01321a8661c5e8da641798199$c9e0d744acafe5ddcb844942a75b86d5c878d3656d4ce17f5c30c3b31a117b41fd9785b1dfa47c79f6f8684acaad7055a964ba99bbc8cf225bfe405bac22d5d2"
hashString := "$argon2id$v=2$t=a,m=64,p=32$kza7VOj1UyzB8wUCYtkLjSwM3KATIahmHF6NpkF5gZk$yeDXRKyv5d3LhElCp1uG1ch402VtTOF/XDDDsxoRe0H9l4Wx36R8efb4aErKrXBVqWS6mbvIzyJb/kBbrCLV0g"
_, err := argon2.Verify(hashString, "something")
if err == nil {
t.Error("error should have been thrown:", err)
}
})

t.Run("should fail parsing int - 2", func(t *testing.T) {
hashString := "$argon2id$v=2$t=16,m=a,p=32$9336bb54e8f5532cc1f3050262d90b8d2c0cdca01321a8661c5e8da641798199$c9e0d744acafe5ddcb844942a75b86d5c878d3656d4ce17f5c30c3b31a117b41fd9785b1dfa47c79f6f8684acaad7055a964ba99bbc8cf225bfe405bac22d5d2"
hashString := "$argon2id$v=2$t=16,m=a,p=32$kza7VOj1UyzB8wUCYtkLjSwM3KATIahmHF6NpkF5gZk$yeDXRKyv5d3LhElCp1uG1ch402VtTOF/XDDDsxoRe0H9l4Wx36R8efb4aErKrXBVqWS6mbvIzyJb/kBbrCLV0g"
_, err := argon2.Verify(hashString, "something")
if err == nil {
t.Error("error should have been thrown:", err)
}
})

t.Run("should fail parsing int - 3", func(t *testing.T) {
hashString := "$argon2id$v=2$t=16,m=64,p=a$9336bb54e8f5532cc1f3050262d90b8d2c0cdca01321a8661c5e8da641798199$c9e0d744acafe5ddcb844942a75b86d5c878d3656d4ce17f5c30c3b31a117b41fd9785b1dfa47c79f6f8684acaad7055a964ba99bbc8cf225bfe405bac22d5d2"
_, err := argon2.Verify(hashString, "something")
if err == nil {
t.Error("error should have been thrown:", err)
}
})

t.Run("should fail decoding hex - salt", func(t *testing.T) {
hashString := "$argon2id$v=2$t=16,m=64,p=32$invalidSalt$c9e0d744acafe5ddcb844942a75b86d5c878d3656d4ce17f5c30c3b31a117b41fd9785b1dfa47c79f6f8684acaad7055a964ba99bbc8cf225bfe405bac22d5d2"
_, err := argon2.Verify(hashString, "something")
if err == nil {
t.Error("error should have been thrown:", err)
}
})

t.Run("should fail decoding hex - hash", func(t *testing.T) {
hashString := "$argon2id$v=2$t=16,m=64,p=32$9336bb54e8f5532cc1f3050262d90b8d2c0cdca01321a8661c5e8da641798199$invalidHash"
hashString := "$argon2id$v=2$t=16,m=64,p=a$9336bb54e8f5532cc1f3050262d90b8d2c0cdca01321a8661c5e8da64179819$yeDXRKyv5d3LhElCp1uG1ch402VtTOF/XDDDsxoRe0H9l4Wx36R8efb4aErKrXBVqWS6mbvIzyJb/kBbrCLV0g"
_, err := argon2.Verify(hashString, "something")
if err == nil {
t.Error("error should have been thrown:", err)
Expand Down
16 changes: 8 additions & 8 deletions bcrypt/bcrypt.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package bcrypt

import (
"encoding/hex"
"errors"
"strings"

Expand Down Expand Up @@ -56,7 +55,7 @@ func Hash(plain string, config Config) (string, error) {
Params: map[string]interface{}{
"r": config.Rounds,
},
Hash: hex.EncodeToString(hash),
Hash: hash,
})
return hashString, nil
}
Expand All @@ -82,15 +81,16 @@ func Verify(hash string, plain string) (bool, error) {
return false, ErrEmptyField
}

deserialize := format.Deserialize(hash)
if !strings.HasPrefix(deserialize.ID, "bcrypt") {
return false, errors.New("hashed string is not a bcrypt instance")
}
decodedHash, err := hex.DecodeString(deserialize.Hash)
deserialize, err := format.Deserialize(hash)
if err != nil {
return false, err
}
err = bcrypt.CompareHashAndPassword(decodedHash, []byte(plain))

if !strings.HasPrefix(deserialize.ID, "bcrypt") {
return false, errors.New("hashed string is not a bcrypt instance")
}

err = bcrypt.CompareHashAndPassword(deserialize.Hash, []byte(plain))
if err != nil {
return false, nil
}
Expand Down
8 changes: 0 additions & 8 deletions bcrypt/bcrypt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,6 @@ func TestError(t *testing.T) {
}
})

t.Run("should fail decoding hex - hash", func(t *testing.T) {
hashString := "$bcrypt$v=0$r=32$invalidSalt$invalidHash"
_, err := bcrypt.Verify(hashString, "something")
if err == nil {
t.Error("error should have been thrown:", err)
}
})

t.Run("should complain of empty function parameters", func(t *testing.T) {
_, err := bcrypt.Hash("", bcrypt.Config{})
if err == nil || err.Error() != "function parameters must not be empty" {
Expand Down
30 changes: 23 additions & 7 deletions format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
package format

import (
"encoding/base64"
"errors"
"fmt"
"strconv"
"strings"
)
Expand All @@ -12,10 +15,12 @@ type PHCConfig struct {
ID string
Version int
Params map[string]interface{}
Salt string
Hash string
Salt []byte
Hash []byte
}

var ErrInvalidFormat = errors.New("invalid format")

// Serialize converts PHCConfig struct into a PHC string.
// See https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
func Serialize(config PHCConfig) string {
Expand All @@ -29,11 +34,11 @@ func Serialize(config PHCConfig) string {
}

}
return "$" + config.ID + "$v=" + strconv.Itoa(config.Version) + "$" + strings.Join(params, ",") + "$" + config.Salt + "$" + config.Hash
return "$" + config.ID + "$v=" + strconv.Itoa(config.Version) + "$" + strings.Join(params, ",") + "$" + base64.RawStdEncoding.EncodeToString(config.Salt) + "$" + base64.RawStdEncoding.EncodeToString(config.Hash)
}

// Deserialize converts a PHC string into a PHCConfig struct
func Deserialize(hash string) PHCConfig {
func Deserialize(hash string) (PHCConfig, error) {
hashArray := strings.Split(hash, "$")
params := make(map[string]interface{})

Expand All @@ -46,11 +51,22 @@ func Deserialize(hash string) PHCConfig {
}

version, _ := strconv.Atoi(strings.Replace(hashArray[2], "v=", "", 1))

salt, err := base64.RawStdEncoding.DecodeString(hashArray[4])
if err != nil {
return PHCConfig{}, fmt.Errorf("%w: %v", ErrInvalidFormat, err)
}

h, err := base64.RawStdEncoding.DecodeString(hashArray[5])
if err != nil {
return PHCConfig{}, fmt.Errorf("%w: %v", ErrInvalidFormat, err)
}

return PHCConfig{
ID: hashArray[1],
Version: version,
Params: params,
Salt: hashArray[4],
Hash: hashArray[5],
}
Salt: salt,
Hash: h,
}, nil
}
16 changes: 10 additions & 6 deletions format/format_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package format_test

import (
"bytes"
"strings"
"testing"

Expand All @@ -16,19 +17,22 @@ func TestSerialize(t *testing.T) {
"Somewhere": "Far",
"Meaning": 42,
},
Salt: "SaltyText",
Hash: "HashyText",
Salt: []byte("SaltyText"),
Hash: []byte("HashyText"),
})

destructured := strings.Split(serialized, "$")
params := "Something=New,Somewhere=Far,Meaning=42"
if destructured[1] != "argon2id" || destructured[2] != "v=2" || len(destructured[3]) != len(params) || destructured[4] != "SaltyText" || destructured[5] != "HashyText" {
if destructured[1] != "argon2id" || destructured[2] != "v=2" || len(destructured[3]) != len(params) || destructured[4] != "U2FsdHlUZXh0" || destructured[5] != "SGFzaHlUZXh0" {
t.Error("Unexpected output: ", serialized)
}
}

func TestDeserialize(t *testing.T) {
deserialized := format.Deserialize("$argon2id$v=2$Something=New,Somewhere=Far,Meaning=42$SaltyText$HashyText")
deserialized, err := format.Deserialize("$argon2id$v=2$Something=New,Somewhere=Far,Meaning=42$U2FsdHlUZXh0$SGFzaHlUZXh0")
if err != nil {
t.Errorf("unexpected error: %s", err.Error())
}

if deserialized.ID != "argon2id" {
t.Error("Unexpected ID: ", deserialized.ID)
Expand All @@ -38,11 +42,11 @@ func TestDeserialize(t *testing.T) {
t.Error("Unexpected Version: ", deserialized.Version)
}

if deserialized.Salt != "SaltyText" {
if !bytes.Equal(deserialized.Salt, []byte("SaltyText")) {
t.Error("Unexpected Salt: ", deserialized.Salt)
}

if deserialized.Hash != "HashyText" {
if !bytes.Equal(deserialized.Hash, []byte("HashyText")) {
t.Error("Unexpected Hash: ", deserialized.Hash)
}

Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module github.com/aldy505/phc-crypto

go 1.21
go 1.22

require golang.org/x/crypto v0.14.0
require golang.org/x/crypto v0.24.0

require golang.org/x/sys v0.13.0 // indirect
require golang.org/x/sys v0.21.0 // indirect
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
Loading

0 comments on commit 180ab1d

Please sign in to comment.