#!/usr/bin/env sh
# -*- wisp -*-
exec guile -L $(dirname $(dirname $(realpath "$0"))) --language=wisp -e '(@@ (examples securepassword) main)' -s "$0" "$@"
; !#

;; Create secure passwords, usable on US and German keyboards without problems

;; As of 2011, a single device can do 2,800,000,000 guesses per second.
;; Today this should be 10 billion guesses per second.
;; According to a recovery company which sells crackers at 1.5k$, as of
;; 2016 a zip-file can be attacked with 100,000 guesses per second.

;; Codinghorror quotes[1] codohale[2] on the cost of buying 5 billion
;; cracked md5 hashes per second in 2010 for just 3$ per hour. This
;; should be around 20 billion guesses per second today.

;; [1]: https://blog.codinghorror.com/speed-hashing/
;; [2]: http://codahale.com/how-to-safely-store-a-password/ 

;; A password with 8 letters and 2 delimiters (length 8, entropy 50)
;; would on average withstand the strong attack with a single device
;; for 2.5 days. It would take around one day to crack with 20 billion
;; guesses per second, so you could buy a cracked md5-secured 8 letter
;; + 2 delimiter password for 72$ (assuming that it was salted,
;; otherwise you can buy all these md5’ed passwords for around 144$).

;; The 8 letter and 2 delimiter password would withstand the weak
;; attack until 2032 (when it would be cracked in one year), assuming
;; doubling of processing power every two years. Cracking it in one
;; day would be possible in 2049.

;; A password with 12 letters and 3 delimiters (length 12, entropy 75)
;; should withstand the strong attack until 2069 (then it would be
;; cracked in one year), assuming doubling of processing power every
;; two years, the weak until 2099.

;; For every factor of 1000 (i.e. 1024 computers), the time to get a
;; solution is reduced by 20 years.  Using every existing cell phone,
;; the 12 letter key would be cracked by the method with 100,000
;; guesses per second in 2039 (within one year). Facebook could do
;; that with Javascript, so you might want to use a longer password if
;; your data has to be secure for longer than 22 years.

;; Using Landauer’s principle[3], we can estimate the minimum energy
;; needed to to check a password solution with a computer at room
;; temperature, assuming that reversible entropy computing isn’t
;; realized and quantum computers have to stick to Landauer’s limit: A
;; single bit-flip requires approximately 3 Zeptojoule[4] at room
;; temperature, so we can flip 333 e18 bits per second with one Watt
;; of Energy. Processing any information requires at least one
;; bit-flip. Reducing the temperature to 1.e-7K (reachable with
;; evaporative cooling) would theoretically allow increasing the bit
;; flips per Joule to 1e30. That gives a plausible maximum of password
;; checks per expended energy. Assuming that someone would dedicate a
;; large nuclear powerplant with 1 Gigawatt of output to cracking your
;; password, a 160 bit password would withstand the attack for about
;; 23 years.

;; [3]: https://en.wikipedia.org/wiki/Landauer's_principle
;; [4]: http://advances.sciencemag.org/content/2/3/e1501492 "DOI: 10.1126/sciadv.1501492"

;; With the password scheme described here, a password with 28 letters
;; and 6 delimiters (172 bits of entropy) should be secure for almost
;; 100,000 years in the Landauer limit at 1.e-7K, with the energy of a
;; large nuclear power plant devoted to cracking it.

;; With 24 letters and 5 delimiters it would only last about a month,
;; though. Mind exponentials and the linear limit of the human
;; lifespan :)

;; However using the total energy of the sun (about 0.5e21 W), a 28
;; letter, 6 delimiter password would survive for just about 5
;; seconds. To reach 50 years of password survival against an attacker
;; harnessing the energy of the sun (a type II civilization on the
;; Kardashev scale[5] devoting its whole civilization to cracking your
;; password), you’d need 200 bits of entropy. A 36 letter, 8 delimiter
;; password (221 bits of entropy) would last about 100 billion
;; years. With that it would very likely outlast that civilization
;; (and its star).

;; [5]: https://en.wikipedia.org/wiki/Kardashev_scale

;; An example of a 28 letter, 6 delimiter password would be:
;; GV7r!dcbm!venf,nGoH-MDjX,vBT8.1vWF
;; Don’t use this one, though :)

;; If you ever wanted to anger a type II civilization, encrypt their
;; vital information with a 36 letter, 8 delimiter password like this:
;; CB6d,D7fX-5sLV!mgCp,kTvG-He6n-7Fg9.REX3-r9F5
;; keep in mind, though, that they might have other means to get it
;; than brute force. And when they come for you, they will all be
;; *really angry* :)


define-module : examples securepassword
              . #:export : password yearstillcrackable

import
    only (srfi srfi-27) random-source-make-integers
      . make-random-source random-source-randomize!
    only (srfi srfi-1) second third iota
    srfi srfi-42
    ice-9 optargs


define : years-to-crack-landau-limit-evaporative-cooling-nuclear-powerplant entropy
       . "Estimate of the years needed to crack the password in the landauer limit"
       let*
        : seconds/day : * 60 60 24
          days/year 365.25
          tempK 1e-7
          room-temp-fraction {tempK / 300}
          guesses/Joule {1 / {3e-21 * room-temp-fraction}}
          Gigawatt 1e9
          guesses/second : * Gigawatt guesses/Joule
          seconds/day : * 60 60 24
          days/year 365.25
          guesses/year : * guesses/second seconds/day days/year
        / (expt 2 entropy) guesses/year 2


define* : yearstillcrackable entropy #:key (guesses/second 100000) (number-of-devices 1)
       . "Estimate of the years it will take until the password is crackable"
       let 
        : seconds/day : * 60 60 24
          days/year 365.25
        ` 
             in-one-second
               , * 2
                  / 
                    log : / (expt 2 entropy) (* guesses/second number-of-devices)
                    log 2
             in-one-day
               , * 2
                  / 
                    log : / (expt 2 entropy) (* seconds/day guesses/second number-of-devices)
                    log 2
             in-one-year 
               , * 2
                  / 
                    log : / (expt 2 entropy) (* days/year seconds/day guesses/second number-of-devices)
                    log 2


;; newbase60 without yz_: 55 letters, 5.78 bits of entropy per letter.
define qwertysafeletters "0123456789ABCDEFGHJKLMNPQRSTUVWXabcdefghijkmnopqrstuvwx"
;; delimiters: 2 bits of entropy per delimiter.
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 : flatten e
    cond 
       : pair? e
         ` 
           ,@ flatten : car e 
           ,@ flatten : cdr e
       : null? e
         list
       else 
         list e


define : password/srfi-42 length
       list->string
         append-ec (: i (iota length 1))
           cons : randomletter qwertysafeletters
             if : and (not (= i length)) : zero? : modulo i 4
                cons : randomletter delimiters
                  list
                list


define : password/map length
       list->string
         flatten
           map
             lambda : i
               let
                 : letter : randomletter qwertysafeletters
                 if : and (not (= i length)) : zero? : modulo i 4
                    list letter 
                      randomletter delimiters
                    list letter
             iota length 1


define : password length
       let fill
         : letters '()
           remaining length
         if : zero? remaining
            reverse-list->string letters
            fill
              cons : randomletter qwertysafeletters
                if : and (not (= length remaining)) : zero? : modulo remaining 4
                   cons : randomletter delimiters
                        . letters
                   . letters
              - remaining 1


define : main args
      let
       :
         len
           if : <= 2 : length args
              string->number : second args
              . 8
       let 
         : idx (if (> 3 (length args)) 1 (string->number (third args)))
         cond
           : = idx 1
             display : password len
           : = idx 2
             display : password/map len
           : = idx 3
             display : password/srfi-42 len
         newline