-
Notifications
You must be signed in to change notification settings - Fork 0
/
securepassword.scm
executable file
·331 lines (280 loc) · 14.9 KB
/
securepassword.scm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
#!/usr/bin/env bash
# -*- wisp -*-
exec -a "$0" guile -L $(dirname $(realpath "$0")) -e '(securepassword)' -c '' "$@"
; !#
;; Create secure passwords, usable on US and German keyboards without problems
(define-module (securepassword)
#:export (letterblocks-nice main))
(import
(only (srfi srfi-27) random-source-make-integers
make-random-source random-source-randomize!)
(only (srfi srfi-1) first second third iota)
(srfi srfi-11 );; let-values
(srfi srfi-42)
(ice-9 optargs)
(ice-9 format)
(only (ice-9 rdelim) read-line)
(ice-9 match)
(ice-9 pretty-print))
;; newbase60 without yz_: 54 letters, 5.75 bits of entropy per letter.
(define qwertysafeletters "0123456789ABCDEFGHJKLMNPQRTUVWXabcdefghijkmnopqrstuvwx")
;; delimiters: 2.3 bits of entropy per delimiter, in the same place on main keys or the num-pad.
(define delimiters ".+-=")
(define random-source (make-random-source))
(random-source-randomize! random-source)
(define random-integer
(random-source-make-integers random-source))
(define (randomletter letters)
(string-ref letters
(random-integer
(string-length letters))))
(define (letter-index letters letter)
(string-index letters letter))
(define (block-value letterblock letters)
(let loop
((rest letterblock)
(value 0))
(if (equal? "" rest)
value
(loop
(string-drop rest 1)
(+ (* (string-length letters) value)
(letter-index letters (string-ref rest 0)))))))
(define (checkchar letters delimiters . letterblocks)
(let*
((value (block-value (apply string-append letterblocks) letters))
(modvalue (string-length delimiters))
(checksum (modulo value modvalue)))
(string-ref delimiters checksum)))
(define (flatten e)
(cond
((pair? e)
`(
(,@ flatten (car e ))
(,@ flatten (cdr e))))
((null? e)
(list))
(else
(list e))))
(define (blocks-to-passphrase blocks)
(let check
((passphrase "")
(blocks blocks))
(cond
((null? blocks)
passphrase)
((= (length blocks) 1)
(string-append passphrase (first blocks)))
(else
(check
(string-append passphrase
(first blocks)
(string
(checkchar qwertysafeletters delimiters
(first blocks )
(second blocks))))
(cdr blocks))))))
(define (single-block)
(apply string-append
(map (λ (x) (string (randomletter qwertysafeletters)))
(iota 4))))
(define (letterblocks blockcount)
(let loop
((remaining blockcount)
(blocks '()))
(if (zero? remaining)
(blocks-to-passphrase (reverse blocks))
(loop
(- remaining 1)
(cons (single-block) blocks)))))
(define (letterblock-invalid? password)
(let loop
((rest password)
(count 5))
(if (< (string-length rest) 5)
(values #f #f #f)
(let*
((check (string (string-ref rest 4)))
(block1 (string-take rest 4))
(block2
(string-take (string-drop rest 5)
(min 4 (- (string-length rest) 5))))
(calck (string (checkchar qwertysafeletters delimiters block1 block2))))
(if (not (equal? check calck))
(values check calck count)
(loop (string-drop rest 5)
(+ count 5)))))))
(define (lines-from-file filepath)
(let ((port (open-input-file filepath)))
(let reader ((lines '()))
(let ((line (read-line port)))
(if (eof-object? line)
(reverse! lines)
(reader (cons line lines)))))))
(define (split-corpus-line line)
"turn LINE into '(first-letter second-letter weight)
A LINE is formatted as cost ab, with cost a number and a and b the letters. For example:
10123151.392154863 en
0.020499130776997592 q6
"
(define (log2 number)
(/ (log number) (log 2)))
(let*
((space-index (string-index line #\space))
(weight (log2 (string->number (string-take line space-index))))
(first-letter (string-ref line (+ space-index 1)))
(second-letter (string-ref line (+ space-index 2))))
(list first-letter second-letter weight)))
(define (shift-and-scale-cost line-costs upper-limit)
"shift the COST to have cost values between zero (log-scale) and the UPPER-LIMIT."
(let*
((numbers (map (λ (x) (third x)) line-costs))
(minimum (apply min numbers))
(shifted-up (map (λ (x) (list (first x) (second x) (- (third x) minimum))) line-costs))
(shifted-numbers (map (λ (x) (third x)) shifted-up))
(maximum (apply max shifted-numbers))
(scaling-factor (/ upper-limit maximum)))
(format #t "Scaling factor: ~a\n" scaling-factor)
(map (λ(x) (list (first x) (second x) (inexact->exact (floor (* scaling-factor (third x))))))
shifted-up)))
(define (collapse-weighting letters cost)
(define (index-value char)
(string-index letters char))
(let ((collapsed (make-string (expt (string-length letters) 2) (string-ref letters 0))))
(let update-string ((cost cost))
(if (null? cost)
collapsed
(let*
((element (car cost))
(one (first element))
(two (second element))
(val (third element))
(idx (+ (index-value two) (* (index-value one) (string-length letters)))))
(string-set! collapsed idx val)
(update-string (cdr cost)))))))
(define weightletters (string-append qwertysafeletters delimiters " "))
(define scalingfactor-inverse (/ 1 2.00834859456416))
(define weight-collapsed "rmjjkkmkjjNTRRPPULMWRWMNFMNRUTJXTXWfWUQMUWdVUVFTeURJQapTiUsommkkmkkmpUTTVQQTTFKPTNPDHRPLRHaXXWXWaUJFUVTLWQXaRNPfUmVjXoqjjjjkijiiQTVVQQUKKRMTHPHNPLUPQWabXaXaQQJRXWUdRUcaPQQbkWgXomiiiiiihiiRPQWNUQJKMVQML8UPJMHQWXWaVWXLQFUaTLLBVWWRFQTjXiWnkhhihihhihNLMMPNNLTMVTHJGUNKKPKTaWTVUVQGURTMRVAQUURGPVjagTnmhhhhhihhhQQPLMTKQHNHVPTRQLPUQJUTXWVXNMMLQWQGQLKaQQHLQiQgNokhhhhhmhhgURNQHKQNFLKNJLCGGRKHKUTXUXTQRJTPTLFQQGVNAULQiRgQojhhhhihhhhMMJRJQPGGFFQGLJHPHFMJUUVWVWJLDFKTQQKKRUPGCJQhPeMnkhhhhhihhiKNLLJLQPKPNQFJCPKHLGGVVVWVXMMQHTRbDP8GUNBKLThPfMmkiijjjjjknPNHJQKJFFQHTEPJGLHGKMVVTUTUGNHELTGAFLRUP9NNQiQfKmLXUVVURNQMcbddbafVUadefdWffaXVXcogiagfeeUikqTiapjjqcaabPdNkPRURQPRQRQdeaWaWaRRXabgUNbUbaVJpLRbsXQUnbHQKnHRnVRoQNMfHbLhRTVUPQPKRLcVcgjVVeTbbaVaTbcVbRPmJLPfTGneHKJNnL6gXQfKMHbWcPiQUTPRRPQKMdXVegeWXWUbbfdWeXecWTrQHbrLLUrVKUUnQ6kcbkVQKbMcUiQQPRQLLMMRdadebabVMWccfcTghddfbdifgUfcgqNahoUdaqnfmfajaMeXgRWTPMTRQPLcabdabUTMUbVRWGaaaTUPoMNHnTNNoRRNTnDJpUTjQLGWFbKfHULMMNMPTQacWUfVacHRaXfgKcabUWHkUDRrRPcjNDdfkJFqWNiPRFaLaThTPVMLPNNMNdUXXdVdWMVbVTVQWfWUUJqNJPpPWJoMEaKnKLcQNkMRBXJVQeDJKFFFEGLRTNPRTNRRVRLNLWPRPQRQJpRMJjPTUbDKHGkTFTTJmFECaCRDaLPNQJMKJHGaVQdbUXTNVXWTfNTVXUXNoPPNmVManQJVhpTJnTPmQUEaQXMfRPQLUNKHFGgWXXeacQMVbUUaKUaWVTNoNRTpPRNpTWEJkL4TWUjdMQaPbQhJRRXXNNLMNeaWbeTWXQTcaRbTUXXVbVrTcRpTQQqKTLVoVQiUPmHNHaJbLgLQULNHHJPKeaceeaeVPaVUdaLXhgXXMpHKQnRRPkMNQQoNQbQXjRNFaKXViMTUTPLLJTHeQcheUeWPVgXWdPcbbTQLoMMUmiFkiUWMUnRQqfVjVNDXNbLgHLGGJLRFCJWJLJVHKDHAWNTHQWVWRWRVDNFN7PEUNJNGPP6ELXmEbAQ7RDaQVRNPKNJJHeVddgXXQMWcbbdLceVaaMnTQRqLTfkLKHQmGLMRPkFMPaKbRhPVTTQNMJMTeVcafbUeUVacXeHceacUToRdToMQqkUNTbnRRoeQhRcgXJaNgXUePNQLKKHXVXWaUaRHVbXeWCdeQUQQMVTaXbWiUMXmqRdEifaNLbNWMdEhPRQLLKPKPFXWcacRaRNQXVbXDVRRWXKjHJRqaVHmKKJJoHJUQPaVMDbEbUgFQNQHQUELEbUTWeVUUTTVcUaLVVQRVPoGKNrLMjpNPNKnMLcURiFQFXWWHfJXLPKNNKKDQLUPUQKMLKUWHUDNXLUFbXTQQWPJMW6MRFVTLQLQQTJQUFXGeUdbXbUVVWTQUTWMVPQGQQQdQQUWDMPFnuusksutrhru.gqbxxxwpkjiRmatVcbXVWVWXVLNQGMTMdKNQNLLDNJBLJXsmfh.gmisifgirfWrqorfiUhQfXpXXbVXTUVTVRNXVTMUJKVVTQbQKTQMLBrbomrec=oatidtjdomqoidbfMhanVdaccacWRWWUUVaTMTJUWRXVTQbVRRDwjgo-jjjxehjmsibrposjmenWkdxXjgedabaWWWkXWdaVbPVVXVXQXaPTWXtttvssuu+gqv prk=.vtrrrqTog-TccWTUUVeWUXVQPRUJMQXTLPQVTNPR9siifusmhscfhkthcsntodienMhavPWaRURRQTWRXMTWTNNFHPURMJQUQQHFrggg+inprdnioogXsrrqfffmTkbwNUWPNMRMPETMNQUTTQMPJTQRNRRQULFwjei+hphvdmrstfXvqwqfpTkQjdxHbbRMHNLHLLcTNNPQTGNRQMVHPQQPTLrr.t+svrheru-upeux.krfkiViVsBMPNNM6C6JQFQFABCFR56KJJNJ6RCNJnaUbqcWUdaaUXjecVaVnWWQULVNeQRVUTKNJRGQVWLTQHRQNLVMPLQUTPWLsgekviihpXmjorfWpospihdjUjWrabbbWVQQTRVMQNTNTQKQRVNQMRKPNQBvodjwjievbjtirsceppqghXjJiVwVabhRJRPVNWVVWaXaTURVTWWNWXTUbLupr+.r.ovhsnvujfmvxsnmesVnd=URbbRLQKeFXRaUWMQTPUTPLQPWbHUPXmqtsktqqnjouxqrbxsttorkjNkUucefbdUQUNRRKLQXRPTKLDJTFRLTDTNFsdfjtpgoqVgigrqbtnqpadbhNjepDRVRHM04L4UQRQQQU3JF6QQ54RQF94MWacafcXXXbRWaXchbbdpWeUcFPRgQhebbaaQRUWWbTTPQUQPPUQVFWRMRWLwrsv+rsqvgsruvndrvwtnpboRme+bcdVKTQPQNVXVVTVVUKNUWQWLRPTRVKtnxk.mprwfpokutfnw.rmofpWng+XdfVXQNNTPaXWXbUXXTRWVUWQWbUQVQumnj-mnwwdknmunbuuutiqkrang+JQUQJKXLBHGMLeLNQLJNKQHbJQTLJKEoptpsurmpdmt.iqXwwvhhhhgJgLtcaecTJWREBHGLLMUQUFDHUDWPKQLWRFpbfdvdiXrVWehucaihddhWXeGjcmRWVbP8fR98MRTVRNTTRMUKDLHTVLRUMucegvdeqvdbdmsdbmkfqXidhTgTpeibXTVWPbWQRNaVTTPTNPNQR4PQDGKPjjkcjjbekbhgdimafnofhdbfUhbmomjiihiggghfajgcccbbaddbNbbddePigjihghgffdgfgkbhjhgchbmQfHvjebXWUTMNQPLQLMMQLNHNXJLCMRFKKKQTPNNRPMPKQUQKQMQVWJNPTNdXgjkmkhheedffjjhihiihgihjfibiifhiakkkiijijihgkihkckmkhigceUqaqacaUUUUTQPNNWQTdTNMKNNfRULdQNQLhWbjXagXWRfifbaTbgeWVcfJJWkrqsronmmjkjuuquttttrtsurtkstrstg.wu+xvvvxrtwvvtmt.xwvxjjnrr=")
(define (weighting-from-corpusfile filename)
"this is how the weighting above was calculated"
(let*
((cost (map split-corpus-line (lines-from-file filename)))
(letters weightletters)
(shifted (shift-and-scale-cost cost (- (string-length letters) 1))))
(collapse-weighting letters
(map (λ (x) (list (first x) (second x) (string-ref letters (third x))))
shifted))))
(define (recreate-corpus-from-weighting)
"Expand the weight string back into a full corpus by using the weightletters"
(let expander
((weightleft weight-collapsed)
(letter1 weightletters)
(letter2 weightletters))
(cond
((string-null? weightleft)
#t)
((string-null? letter1)
(error "could not expand the whole corpus. Letters left: ~a" weightleft))
((string-null? letter2)
(expander weightleft (string-drop letter1 1) weightletters))
(else
(let ((cost (expt 2 (* scalingfactor-inverse (string-index weightletters (string-ref weightleft 0))))))
(format #t "~f ~a~a\n" cost (string-ref letter1 0) (string-ref letter2 0)))
(expander
(string-drop weightleft 1)
letter1
(string-drop letter2 1))))))
(define (bigram->weight bigram)
"Calculate the weight of the given bigram in a corpus"
(let*
((letter1 (string-ref bigram 0))
(letter2 (string-ref bigram 1))
(idx1 (string-index weightletters letter1))
(idx2 (string-index weightletters letter2))
;; for downcased bigrams (for phonetics) we might have to get the uppercase version
(idx1 (if idx1 idx1 (string-index weightletters (char-upcase letter1))))
(idx2 (if idx2 idx2 (string-index weightletters (char-upcase letter2))))
(len (string-length weightletters))
(costchar (string-ref weight-collapsed (+ (* idx1 len) idx2))))
(expt 2 (* scalingfactor-inverse (string-index weightletters costchar)))))
(define (word-weight word)
"calculate the probability weight of the given word to appear in a corpus given by the weight-collapsed"
(let loop
((s (string-append " " word " "))
(cost 0))
(cond
((string-null? (string-drop s 1))
cost)
((string-null? (string-drop s 2))
cost)
(else
(loop
(string-drop s 2)
(+ cost (bigram->weight (string-take s 2))))))))
(define* (string-replace-substring s substr replacement #:optional (start 0) (end (string-length s)))
"Replace every instance of substring in s by replacement."
(let ((substr-length (string-length substr)))
(if (zero? substr-length)
(error "string-replace-substring: empty substr")
(let loop
((start start)
(pieces (list (substring s 0 start))))
(let ((idx (string-contains s substr start end)))
(if idx
(loop (+ idx substr-length)
(cons* replacement
(substring s start idx)
pieces))
(string-concatenate-reverse
(cons (substring s start)
pieces))))))))
(define* (letterblocks-nice blockcount #:key (best-of 8))
"Generate BEST-OF letterblocks and return the one most likely to appear in the corpus given by weight-collapsed
best-of 8 consumes 3 bits of entropy, but creates passwords which are easier to remember. "
(define (delimiters-to-space s)
"replace all delimiters by spaces"
(let replace
((s s)
(delim delimiters))
(if (string-null? delim)
s
(replace
(string-replace-substring s (string-take delim 1) " ")
(string-drop delim 1)))))
(car
(sort
(map (λ (x) (letterblocks blockcount))
(iota best-of))
(λ (a b)
(>
(word-weight (delimiters-to-space (string-downcase a)))
(word-weight (delimiters-to-space (string-downcase b))))))))
(define (help args)
(format #t "Usage: ~a [options]
Options:
[<length> [<password-type>]] create password
--check <password> verify the checksums
--help show this message
" (first args)))
(define (main args)
(cond
((and (> (length args) 1) (equal? "--help" (second args)))
(help args))
((and (> (length args) 2) (equal? "--check" (second args)))
(let-values (((check calck count) (letterblock-invalid? (third args))))
(cond
(count
(format #t "letterblock invalid. First failed checksum: ~a should have been ~a at position ~a\n"
check calck count)
(exit 1))
(else
(format #t "valid letterblock password\n")))))
(else
(let
(
(len
(if (<= 2 (length args))
(string->number (second args))
12)))
(display (letterblocks-nice (floor (/ len 4))))
(newline)))))