Skip to content

Commit

Permalink
update minimum, default, and maximum password lengths
Browse files Browse the repository at this point in the history
The new values are based on estimates I made using the BC script at
_src/sec_pw_bits.bc_. The source tree was getting crowded, so source
files were moved to a subdirectory.
  • Loading branch information
guijan committed Dec 29, 2024
1 parent 03ce3a0 commit 5ea5c39
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 31 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ jobs:
- name: test
run: meson test -C build
- name: installer
run: ./dictpw_installer.sh -o 'build\setup-dictpw-${{matrix.sys}}.exe'
run: src/dictpw_installer.sh -o 'build\setup-dictpw-${{matrix.sys}}.exe'
- uses: actions/[email protected]
if: always()
with:
Expand Down Expand Up @@ -254,8 +254,8 @@ jobs:
run: meson test -C build
- name: inst
run: |
$out = "build\setup-dictpw-vs-${{matrix.cc}}-${{matrix.arch}}.exe"
makensis -DOUTFILE="$out" dictpw.nsi
$out = "..\build\setup-dictpw-vs-${{matrix.cc}}-${{matrix.arch}}.exe"
makensis -DOUTFILE="$out" src/dictpw.nsi
ls build/
- uses: actions/[email protected]
if: always()
Expand Down
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
Copyright (c) 2021-2022 Guilherme Janczak <[email protected]>
Copyright (c) 2021-2022, 2024 Guilherme Janczak <[email protected]>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
Expand All @@ -15,25 +15,27 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-->

# Dictpw - generate password from dictionary
Dictpw randomly picks 5 words off a 7776-word dictionary and prints them with a
Dictpw randomly picks 4 words off a 7776-word dictionary and prints them with a
dot between each word. This is the
[Diceware](https://en.wikipedia.org/wiki/Diceware) method of password generation
from the command line.

Which of the following 2 passwords is easier to memorize?
```
computer.stuffy.dexterity.carve.wife
computer.stuffy.dexterity.carve
J#2%Q*PDfNI
```

## Analysis
There are __7776^5__ or __28430288029929701376__ possible passwords using
dictpw's default word count.
There are __(23+23+10+10)^10__ or __1568336880910795776__ possible passwords in
a random 10 character password composed of uppercase characters, lowercase
characters, digits, and the symbols on top of each digit on the keyboard, or 18
times less possible passwords. Dictpw sits between a 10 and 11 character long
password of such a scheme by default.
A password scheme's security can be measured by the number of distinct passwords
it can generate. To keep these incredibly large numbers intelligible, they're
given as exponents of 2, or bits. dictpw's default password length can generate
__7776^4__ distinct passwords, or __52 bits__ of security. Based on very
conservative estimates made using [my calculator](src/sec_pw_bits.bc), a 100-day
attempt to crack such a password with 500000 USD budget for hardware (not
counting electricity and labor) would have a 25% chance of succeeding in 2024.

The reasoning for these numbers is included in the calculator's source code.

## Build instructions
Dictpw depends on [Meson](https://mesonbuild.com/), a C compiler, and a
Expand Down
10 changes: 5 additions & 5 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ foreach func, header : funcs
endforeach

gen_dict = executable('gen_dict',
'gen_dict.c',
'src/gen_dict.c',
native: true)
gen_dict_h = custom_target('gen_dict',
output: 'gen_dict.h',
Expand All @@ -59,8 +59,8 @@ gen_dict_h = custom_target('gen_dict',
command: gen_dict)

dictpw = executable('dictpw',
'dict.c',
'dictpw.c',
'src/dict.c',
'src/dictpw.c',
caps_c,
gen_dict_h,
install: true,
Expand All @@ -69,7 +69,7 @@ dictpw = executable('dictpw',
win_subsystem: 'console')
subdir('test')

install_man('dictpw.1')
install_man('src/dictpw.1')

if host_machine.system() == 'windows' or host_machine.system() == 'cygwin'
# Build a .txt version of the manual too. For producing the installer.
Expand All @@ -78,7 +78,7 @@ if host_machine.system() == 'windows' or host_machine.system() == 'cygwin'
man_groff = custom_target('man_groff',
command: [find_program('groff'), '-mdoc', '-Tascii',
'-Z'],
input: [files('dictpw.1')],
input: [files('src/dictpw.1')],
feed: true,
output: ['dictpw.groff'],
capture: true)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
8 changes: 5 additions & 3 deletions dictpw.c → src/dictpw.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
#include "dict.h"
#include <caps.h>

/* Values based on conservative calculations in sec_pw_bits.bc */
enum {
MINWORD = 4,
MAXWORD = 10,
MINWORD = 3,
DEFWORD = 4,
MAXWORD = 8,
};

static int nflag = 5; /* How many words make up a password. */
static int nflag = DEFWORD; /* How many words make up a password. */
static int hflag = 0; /* Has the help flag been used? */

static void usage(void);
Expand Down
14 changes: 7 additions & 7 deletions dictpw.nsi → src/dictpw.nsi
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,22 @@
!include "FileFunc.nsh"

!ifndef EXEFILE
!define EXEFILE "build\dictpw.exe"
!define EXEFILE "..\build\dictpw.exe"
!endif

!ifndef DOCFILE
!define DOCFILE "build\dictpw.txt"
!define DOCFILE "..\build\dictpw.txt"
!endif

!ifndef OUTFILE
!define OUTFILE "build\setup-dictpw.exe"
!define OUTFILE "..\build\setup-dictpw.exe"
!endif

!define LIBOBSD_LICENSE "build\subprojects\libobsd\LICENSE_libobsd.txt"
!define LIBOBSD_LICENSE "..\build\subprojects\libobsd\LICENSE_libobsd.txt"

!define MUI_DIRECTORYPAGE_VARIABLE "$INSTDIR"
!include "MUI2.nsh"
!insertmacro MUI_PAGE_LICENSE "LICENSE.md"
!insertmacro MUI_PAGE_LICENSE "..\LICENSE.md"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_DIRECTORY
Expand All @@ -59,11 +59,11 @@ Section
!ifdef MSYS
File /oname=bin\msys-2.0.dll "${MSYS}"
!endif
File "build\LICENSE.txt"
File "..\build\LICENSE.txt"
!if /FileExists "${LIBOBSD_LICENSE}"
File "${LIBOBSD_LICENSE}"
!endif
File "build\README.txt"
File "..\build\README.txt"
File "${DOCFILE}"
WriteUninstaller "$INSTDIR\uninstall.exe"

Expand Down
6 changes: 3 additions & 3 deletions dictpw_installer.sh → src/dictpw_installer.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/sh -evx

# Copyright (c) 2022 Guilherme Janczak <[email protected]>
# Copyright (c) 2022, 2024 Guilherme Janczak <[email protected]>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
Expand Down Expand Up @@ -32,7 +32,7 @@ fi
MSYS='-DNULL'
# Distribute msys-2.0.dll for the MSYS build.
if [ "$MSYSTEM" = "MSYS" ]; then
msysdll='build\msys-2.0.dll'
msysdll='..\build\msys-2.0.dll'
cp /usr/bin/msys-2.0.dll "$msysdll"
MSYS="-DMSYS=$msysdll"
fi
Expand All @@ -47,4 +47,4 @@ fi

# The odd quoting is because the whole command line needs to be passed as a
# single argument to the subshell.
$subsh "makensis '$MSYS' '$OUTFILE' dictpw.nsi"
$subsh "makensis '$MSYS' '$OUTFILE' src/dictpw.nsi"
File renamed without changes.
102 changes: 102 additions & 0 deletions src/sec_pw_bits.bc
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/usr/bin/bc -l

# Copyright (c) 2024 Guilherme Janczak <[email protected]>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

# sec_pw_bits: calculate bits of security needed for secure passwords
#
# Meaning of parameters:
# d = The deadline, in seconds, for the bruteforce attempt.
# s = Acceptable chance of success of the bruteforce attempt in %.
# r = number of hashes per second of the best hashing machine
# b = how much money is available to spend on hashing machines.
# c = the price of a single hashing machine.
define sec_pw_bits(d, s, r, b, c){
auto o, t
o = scale
scale = 8 # Arbitrary, for performance.
t = l(d * 100/s * r * b/c) / l(2)
scale = 1
t = t/1
scale = o
return t
}

define days(n){
return (60*60*24*n)
}

define terahash(n){
return (n*10^9)
}

# Reasonable defaults:
d = days(96) # https://en.wikipedia.org/wiki/DESCHALL_Project took 96 days.
s = 25 # Arbitrary.
# Budget of https://en.wikipedia.org/wiki/EFF_DES_cracker was 250000 USD in
# 1998. Value is scaled to 2024 according to inflation using Google.
b = 479036

# Reasonable values for plain sha256 hashing:
# Antminer S19 XP Hyd bitcoin hash rate, i.e. sha256 rate. Google says it's the
# fastest bitcoin miner today.
r = terahash(255)
c = 5000 # Cost of Antminer according to Google.

"This program estimates how many bits of security a password needs to be safe."
# A newline.
"
"
"It differentiates between services and devices. Services are run by others,"
"
"
"and assumed to be incompetent-they use a plain sha256 to store passwords."
"
"
"Devices are systems you own, and assumed to be competent-they use some kind"
"
"
"of password expansion algorithm to store passwords."
"

"
"(d)eadline in seconds = "; d
"(s)uccess chance % = "; s
"hardware hashes pe(r) second = "; r
"hardware (b)udget = "; b
"hardware (c)ost = "; c
"services: sec_pw_bits(d, s, r, b, c) = "; sec_pw_bits(d, s, r, b, c)
"
"

# Reasonable values for key expansion algorithm.
# /usr/src/lib/libc/crypt.c on OpenBSD 7.5 says the system uses bcrypt and
# benchmarks rounds looking for a value between 6 and 16 rounds that takes
# around 0.1s. My system uses 10 rounds.
# https://gist.github.com/epixoip/63c2ad11baf7bbd57544 says 133KH/s for 8x Titan
# X and bcrypt 5 rounds. Each additional round doubles work, so we're looking at
# 4KH/s at 10 rounds. Unfortunately, that result is from 2015, and it's 2024,
# but we can eyeball progress with Moore's Law, so scale the result based on the
# year.
# 133064 * 2^((year-2015)/2 - (rounds-5))
r = 133064 * 2^((2024-2015) - (10-5))/2
c = 999*8 # GTX Titan X MSRP at launch.
"hardware hashes pe(r) second = "; r
"hardware (c)ost = "; c
"devices : sec_pw_bits(d, s, r, b, c) = "; sec_pw_bits(d, s, r, b, c)
"
"
"You may change d, s, r, b, and c around and call sec_pw_bits() again."
"
"

0 comments on commit 5ea5c39

Please sign in to comment.