Skip to content

Commit

Permalink
- added code documentation
Browse files Browse the repository at this point in the history
- improved entropy of distribution
- added a few basic tests
  • Loading branch information
Aizen committed Oct 4, 2024
1 parent 377eed8 commit 5c16201
Show file tree
Hide file tree
Showing 6 changed files with 19,410 additions and 27,056 deletions.
17 changes: 11 additions & 6 deletions cmd/genpw/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ type Passphrase struct {

func main() {
cert := flag.Bool("c", false, "run entropy certification algorithm (for developers)")
words := flag.Uint64("w", 4, "number of words per password")
passwords := flag.Uint64("p", 4, "number of passwords to generate")
depth := flag.Uint64("cd", 1, "certification depth, larger number -> more accurate results (for developers)")
words := flag.Uint64("w", 3, "number of words per password")
passwords := flag.Uint64("p", 6, "number of passwords to generate")
flag.Parse()
if *cert {
certify()
certify(*depth)
}

pws := []Passphrase{}
Expand All @@ -45,17 +46,19 @@ func main() {
}
}

func certify() {
func certify(udepth uint64) {
cnt := make(map[string]int)
iN := 0
Q := 128
nominal_H := 0.0
nominal_H2 := 0.0
cnt_nom_H := 0.0
for {
Q += Q / 14
for range Q {
w, nh := cp.GenMixWord()
nominal_H += nh
nominal_H2 += nh * nh
cnt_nom_H++
cnt[w]++
iN++
Expand All @@ -76,9 +79,11 @@ func certify() {
fmt.Print("|")
}
nomH := nominal_H / cnt_nom_H
nomH2 := nominal_H2 / cnt_nom_H
stddev := math.Sqrt(max(nomH2-nomH*nomH, 0.0001))
gap := nomH - H
fmt.Printf("%v | E[H]-E=%.2f E[H] = %.2f ", strings.Repeat(" ", 60-CH), gap, nomH)
if gap < 0.01 {
fmt.Printf("%v | E[H]-E=%.2f E[H] = %.2f ∂E[H] = %.2f ", strings.Repeat(" ", 60-CH), gap, nomH, stddev)
if gap < stddev/(1+float64(udepth)) {
fmt.Print("\n")
fmt.Println(H)
break
Expand Down
34 changes: 33 additions & 1 deletion cryptipass.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Package cryptipass v1.3.0 provides functionality for generating secure passphrases
// composed of random words, with a focus on cryptographic security and entropy calculation.
package cryptipass

import (
Expand All @@ -9,17 +11,30 @@ import (

var rng *rand.Rand

// init initializes the cryptographically secure random number generator (RNG).
// It reads 32 bytes of entropy from crypto/rand and uses them to seed a ChaCha8-based RNG.
// If the required number of bytes is not read, it logs a fatal error.
func init() {
seed := [32]byte{}
n, err := cr.Reader.Read(seed[:])
if err != nil || n != 32 {
log.Fatal(n, err, seed)
}
rng = rand.New(rand.NewChaCha8(seed))
// Faster but non-cryptographic
// Faster but non-cryptographic alternative:
// rng = rand.New(rand.NewPCG(rng.Uint64(), rng.Uint64()))
}

// NewPassphrase generates a passphrase consisting of the specified number of random words.
// Each word is chosen by the GenMixWord function, and the total entropy of the passphrase
// is calculated and returned along with the passphrase.
//
// - Parameters:
// words (uint64): The number of words to include in the passphrase.
//
// - Return values:
// string: The generated passphrase, with words joined by periods.
// float64: The total entropy (in bits) of the passphrase, indicating its strength.
func NewPassphrase(words uint64) (string, float64) {
wordvec := []string{}
total_entropy := 0.0
Expand All @@ -31,12 +46,29 @@ func NewPassphrase(words uint64) (string, float64) {
return strings.Join(wordvec, "."), total_entropy
}

// GenMixWord generates a single word of random length and returns it along with its entropy.
// The word length is determined by the PickLength function, and the word itself is generated
// by the GenWord function.
//
// - Return values:
// string: The generated word of random length.
// float64: The entropy contributed by both the word length and the word itself.
func GenMixWord() (string, float64) {
l, entropy_l := PickLength()
w, entropy_w := GenWord(l)
return w, entropy_l + entropy_w
}

// GenWord generates a word of exactly n characters and returns it along with the entropy
// associated with the process. The characters are selected by calling PickNext iteratively
// until the word reaches the desired length.
//
// - Parameters:
// n (int): The number of characters to include in the generated word.
//
// - Return values:
// string: The generated word consisting of n characters.
// float64: The total entropy contributed by the character selection process.
func GenWord(n int) (string, float64) {
s := ""
total_entropy := 0.0
Expand Down
73 changes: 72 additions & 1 deletion cryptipass_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cryptipass_test

import (
"strings"
"testing"

"github.com/francescoalemanno/cryptipass"
Expand All @@ -11,7 +12,77 @@ func TestBasic(t *testing.T) {
if len(pw) < 15 {
t.Fatalf(`Wrong length "%s"`, pw)
}
if H < 60 {
if H < 40 {
// this event is so unlikely (p = 9.86588*10^-10) it must not happen.
t.Fatalf(`Wrong entropy "%s"`, pw)
}
}

// TestNewPassphrase tests the NewPassphrase function for generating a passphrase and validating its length.
func TestNewPassphrase(t *testing.T) {
words := uint64(5)
passphrase, entropy := cryptipass.NewPassphrase(words)
wordList := strings.Split(passphrase, ".")

if len(wordList) != int(words) {
t.Errorf("Expected %d words, got %d", words, len(wordList))
}

if entropy <= 0 {
t.Errorf("Expected entropy to be greater than 0, got %f", entropy)
}
}

// TestGenMixWord tests that GenMixWord generates a word and returns a positive entropy value.
func TestGenMixWord(t *testing.T) {
word, entropy := cryptipass.GenMixWord()

if len(word) == 0 {
t.Error("Expected a word, got an empty string")
}

if entropy < 0 {
t.Errorf("Expected entropy to be greater than 0, got %f", entropy)
}
}

// TestGenWord tests that GenWord generates a word of a specific length and returns a positive entropy value.
func TestGenWord(t *testing.T) {
length := 6
word, entropy := cryptipass.GenWord(length)

if len(word) != length {
t.Errorf("Expected word length %d, got %d", length, len(word))
}

if entropy < 0 {
t.Errorf("Expected entropy to be greater than 0, got %f", entropy)
}
}

// TestPickNext tests that PickNext generates a valid character appended to the seed and returns entropy.
func TestPickNext(t *testing.T) {
seed := "te"
updatedSeed, entropy := cryptipass.PickNext(seed)

if len(updatedSeed) <= len(seed) {
t.Errorf("Expected updated seed to be longer, got %s", updatedSeed)
}

if entropy < 0 {
t.Errorf("Expected entropy to be >= 0, got %f", entropy)
}
}

// TestPickLength tests that PickLength generates a valid word length and returns entropy.
func TestPickLength(t *testing.T) {
length, entropy := cryptipass.PickLength()

if length < 3 || length > 9 {
t.Errorf("Expected length to be between 3 and 9, got %d", length)
}

if entropy < 0 {
t.Errorf("Expected entropy to be greater than 0, got %f", entropy)
}
}
Loading

0 comments on commit 5c16201

Please sign in to comment.