diff --git a/okta-bcrypt-issue/go.mod b/okta-bcrypt-issue/go.mod new file mode 100644 index 0000000..157b20a --- /dev/null +++ b/okta-bcrypt-issue/go.mod @@ -0,0 +1,5 @@ +module git.hatter.ink/okta-bcrypt-issue + +go 1.24.1 + +require golang.org/x/crypto v0.40.0 // indirect diff --git a/okta-bcrypt-issue/go.sum b/okta-bcrypt-issue/go.sum new file mode 100644 index 0000000..e9e3e03 --- /dev/null +++ b/okta-bcrypt-issue/go.sum @@ -0,0 +1,2 @@ +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= diff --git a/okta-bcrypt-issue/main.go b/okta-bcrypt-issue/main.go new file mode 100644 index 0000000..45963db --- /dev/null +++ b/okta-bcrypt-issue/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + + "golang.org/x/crypto/bcrypt" +) + +// https://n0rdy.foo/posts/20250121/okta-bcrypt-lessons-for-better-apis/ +func main() { + // 18 + 55 + 1 = 74, so above 72 characters' limit of BCrypt + userId := randomString(18) + username := randomString(55) + password := "super-duper-secure-password" + + combinedString := fmt.Sprintf("%s:%s:%s", userId, username, password) + + combinedHash, err := bcrypt.GenerateFromPassword([]byte(combinedString), bcrypt.DefaultCost) + if err != nil { + panic(err) + } + + // let's try to break it + wrongPassword := "wrong-password" + wrongCombinedString := fmt.Sprintf("%s:%s:%s", userId, username, wrongPassword) + + err = bcrypt.CompareHashAndPassword(combinedHash, []byte(wrongCombinedString)) + if err != nil { + fmt.Println("Password is incorrect") + } else { + fmt.Println("Password is correct") + } +} + +func randomString(length int) string { + bytes := make([]byte, length) + _, err := rand.Read(bytes) + if err != nil { + panic(err) + } + return base64.URLEncoding.EncodeToString(bytes)[:length] +}