Skip to content

Commit

Permalink
Merge pull request #2 from GGP1/add_secret_from_string
Browse files Browse the repository at this point in the history
Add function to get a `Secret` from a string
  • Loading branch information
GGP1 authored Jul 18, 2024
2 parents f4b14c2 + 02b384d commit 6a1460e
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 5 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/benchmarks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Benchmarks

on:
push:
branches:
- master
pull_request:

jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.22

- name: Run benchmarks
run: go test -benchmem -bench .
25 changes: 25 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Tests

on:
push:
branches:
- master
pull_request:

jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.22

- name: Run tests
run: go test ./... -race
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 Gastón Palomeque
Copyright (c) 2024 Gastón Palomeque

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 All @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
45 changes: 44 additions & 1 deletion atoll.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// cryptographically secure numbers and offer a high level of randomness.
package atoll

import "math"
import (
"math"
"strings"
)

// 1 trillion is the number of guesses per second Edward Snowden said we should be prepared for.
const guessesPerSecond = 1000000000000
Expand Down Expand Up @@ -31,3 +34,43 @@ func Keyspace(secret Secret) float64 {
func SecondsToCrack(secret Secret) float64 {
return Keyspace(secret) / guessesPerSecond
}

// SecretFromString returns a secret with the same parameters that were used for the provided string.
//
// Given the complexity to determine if the secret is a passphrase when separators are a custom set
// of characters, it will always be of type Password.
func SecretFromString(str string) Secret {
if len(str) == 0 {
return &Password{}
}

allLevels := []Level{Lower, Upper, Digit, Space, Special}
levels := make([]Level, 0, len(allLevels))
for _, lvl := range allLevels {
if strings.ContainsAny(string(lvl), str) {
levels = append(levels, lvl)
}
}

// If the password was composed of characters that are not part of the pre-defined levels,
// use all of them by default
if len(levels) == 0 {
levels = allLevels
}

characters := make(map[rune]struct{}, len(str))
repeat := false
for _, r := range str {
if _, ok := characters[r]; ok {
repeat = true
break
}
characters[r] = struct{}{}
}

return &Password{
Repeat: repeat,
Levels: levels,
Length: uint64(len(str)),
}
}
85 changes: 85 additions & 0 deletions atoll_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,88 @@ func TestSecondsToCrack(t *testing.T) {
})
}
}

func TestSecretFromString(t *testing.T) {
cases := []struct {
expected Secret
str string
}{
{
str: "abc",
expected: &Password{
Length: 3,
Levels: []Level{Lower},
Repeat: false,
},
},
{
str: "aBc",
expected: &Password{
Length: 3,
Levels: []Level{Lower, Upper},
Repeat: false,
},
},
{
str: "aB1",
expected: &Password{
Length: 3,
Levels: []Level{Lower, Upper, Digit},
Repeat: false,
},
},
{
str: "aB1 _",
expected: &Password{
Length: 5,
Levels: []Level{Lower, Upper, Digit, Space, Special},
Repeat: false,
},
},
{
str: "aBa",
expected: &Password{
Length: 3,
Levels: []Level{Lower, Upper},
Repeat: true,
},
},
{
str: "KTPBv0y~e\\wj,kA]>!jgdG\"q",
expected: &Password{
Length: 24,
Levels: []Level{Lower, Upper, Digit, Special},
Repeat: true,
},
},
{
str: "世界",
expected: &Password{
Length: 6, // String contains multi-byte characters
Levels: []Level{Lower, Upper, Digit, Space, Special},
Repeat: false,
},
},
{
str: "परीक्षा",
expected: &Password{
Length: 21, // String contains multi-byte characters
Levels: []Level{Lower, Upper, Digit, Space, Special},
Repeat: false,
},
},
{
str: "",
expected: &Password{},
},
}

for _, tc := range cases {
t.Run(tc.str, func(t *testing.T) {
got := SecretFromString(tc.str)
if got.Entropy() != tc.expected.Entropy() {
t.Errorf("Expected %f, got %f", tc.expected.Entropy(), got.Entropy())
}
})
}
}
27 changes: 26 additions & 1 deletion benchmark_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package atoll

import "testing"
import (
mrand "math/rand/v2"
"testing"
)

var password = &Password{
Length: 15,
Expand All @@ -26,6 +29,28 @@ func BenchmarkNewPassword(b *testing.B) {
}
}

func BenchmarkSecretFromString(b *testing.B) {
b.StopTimer()

// Preload strings
strs := make([]string, b.N)
chars := Lower + Upper + Digit + Space + Special

for i := 0; i < b.N; i++ {
buf := make([]byte, 24)
for j := 0; j < 24; j++ {
buf[j] = chars[mrand.IntN(len(chars))]
}
strs[i] = string(buf)
}

b.StartTimer()

for i := 0; i < b.N; i++ {
_ = SecretFromString(strs[i])
}
}

var passphrase = &Passphrase{
Length: 6,
Separator: "-",
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/GGP1/atoll

go 1.20
go 1.22

0 comments on commit 6a1460e

Please sign in to comment.