One-time pad/Julia

Revision as of 20:41, 22 June 2019 by Wherrera (talk | contribs) (Created page with "<lang julia># One-time pad colsole based app, uses internet random.org as source with localrand() backup using Dates, HTTP const configs = Dict("OTPdir" => ".") stringtohas...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

<lang julia># One-time pad colsole based app, uses internet random.org as source with localrand() backup

using Dates, HTTP

const configs = Dict("OTPdir" => ".")

stringtohash(s) = string(hash(s), base=16)

vencode(a::Char, b) = Char((Int(a) + Int(b) - 130) % 26 + 65) vencode(atxt::String, btxt) = String(map((a, b) -> vencode(a, b), atxt, btxt)) vdecode(a::Char, b) = Char((Int(a) + 26 - Int(b)) % 26 + 65) vdecode(atxt::String, btxt) = String(map((a, b) -> vdecode(a, b), atxt, btxt))

function getrandom()

   bytes, ret, source = UInt8[], Char[], "local"
   try
       hx = HTTP.get("https://www.random.org/cgi-bin/randbyte?nbytes=1024&format=hex").body
       if length(hx) < 100
           throw("not enough bytes gotten from internet")
       end
       bytes = map(s -> parse(UInt8, s, base=16), split(strip(String(hx)), r"\s+"))
       source = "random.org"
       bytes = map((a, b) -> xor(a, b), bytes, rand(UInt8, length(bytes)))
   catch y
       @warn("internet source failure: $y")
       bytes = rand(UInt8, 1568)
   end
   for b in bytes
       ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZ***"[div(b, 9) + 1]
       if ch != '*'
           push!(ret, ch)
       end
   end
   source, ret

end

function newOTPfilename()

   dircontents = readdir(configs["OTPdir"])
   while true
       nam = "OTP" * replace(string(now()), ":" => "_") * ".1tp"
       if (i = findfirst(x -> x == nam, dircontents)) != nothing
           sleep(0.5)
       else
           return nam
       end
   end

end

function lines40by5(s)

   ret = ""
   for (i, ch) in enumerate(s)
       ret *= (i % 40 == 0) ? ch * "\n" :
           (i % 5 == 1) ? " " * ch : ch
   end
   ret

end

function createOTPfile(nletters, partnername="")

   newname, source = newOTPfilename(), "unknown"
   fp = open(newname, "w")
   needed = nletters + (nletters % 40 == 0 ? 0 : 40 - n % 40)
   data = Char[]
   while true
       source, randomdata = getrandom()
       println("Got ", length(randomdata), " letters from ", source)
       data = append!(data, randomdata)
       if length(data) >= needed
           data = data[1:needed]
           break
       end
   end
   s = lines40by5(data)
   write(fp, "# $source OTP ", stringtohash(partnername), "\n", s)
   close(fp)
   newname, needed, source

end

function readOTPfile(filename, n, skiplines=0, useused=false)

   fp = open(filename, "r+")
   seekpositions, ret = Vector{Int}(), ""
   while length(ret) < n
       if eof(fp)
           throw("Not enough letters in file")
       end
       if skiplines < 1
           push!(seekpositions, position(fp))
       end
       line = readline(fp)
       if skiplines > 0
           skiplines -= 1
           continue
       end
       if (useused || line[1] != '-') && line[1] != '#'
           ret *= replace(line, r"[^A-Z]" => "")
       else
           pop!(seekpositions)
       end
   end
   seekstart(fp)
   for pos in seekpositions
       seek(fp, pos)
       write(fp, '-')
   end
   close(fp)
   ret[1:n]

end

function getOTPfiles(dir=configs["OTPdir"])

   namedict = Dict{String, Pair{String,String}}()
   filenames = filter(nam -> occursin(r"\.1tp$", nam), readdir(dir))
   for (i, nam) in enumerate(filenames)
       fp = open(dir * "/" * nam, "r")
       lin = readline(fp)
       m = match(r"#\s+([\w\.]+)\s+OTP\s+([01-9a-fA-F]+)", lin)
       if m != nothing
           namedict[nam] = Pair(m.captures[1], m.captures[2])
       end
   end
   namedict

end

function listOTPfiles(partnername="")

   phash = stringtohash(partnername)
   files = Dict{Int, String}()
   println("Number            File name               partner code       source     count")
   for (i, p) in enumerate(getOTPfiles())
       if partnername == "" || phash == p[2][2]
           pathname = configs["OTPdir"] * "/" * p[1]
           files[i] = p[1]
           println(rpad(i, 8), rpad(p[1], 32), rpad(p[2][2], 20),
               rpad(p[2][1], 12), stat(pathname).size)
       end
   end
   files

end

function getconsoleinput(asInt=false, yn=false, default="")

   while true
       println("Enter choice ", asInt ? "number " : "", "or <enter> ", asInt ? "for 0:" : "to exit:")
       s = readline()
       if s == ""
           return asInt ? 0 : ""
       elseif !asInt
           if !yn || (s = string(uppercase(s)[1])) in ["Y", "N"]
               return s
           end
       else
           choice = tryparse(Int, s)
           if choice != nothing
               return choice
           end
       end
   end

end

function chooseOTPfiledialog()

   println("Enter partner name or <enter> for all.")
   pname = getconsoleinput()
   files = listOTPfiles(pname)
   println("Choose file number (0 to quit routine).")
   while true
       fnum = getconsoleinput(true)
       if fnum == 0
           return ""
       elseif haskey(files, fnum)
           return files[fnum]
       end
   end

end

function encryptdialog()

   if(fname = chooseOTPfiledialog()) == ""
       return
   end
   println("Skip lines marked as used?")
   useused = getconsoleinput(false, true) == "N"
   skiplines = 0
   println("Start file read at beginning?")
   if getconsoleinput(false, true) == "N"
       println("Enter number of lines to skip:")
       skiplines = getconsoleinput(true)
   end
   println("Enter lines of text to encrypt. Non-alphabet will be ignored. A blank line ends entry.")
   txt = ""
   while (lin = readline()) != ""
       txt *= lin
   end
   txt = replace(uppercase(txt), r"[^A-Z]" => "")
   otp = readOTPfile(fname, length(txt), skiplines, useused)
   println(lines40by5(vencode(txt, otp)))

end

function decryptdialog()

   if(fname = chooseOTPfiledialog()) == ""
       return
   end
   println("Skip lines marked as used?")
   useused = getconsoleinput(false, true) == "N"
   skiplines = 0
   println("Start file read at beginning?")
   if getconsoleinput(false, true) == "N"
       println("Enter number of lines to skip:")
       skiplines = getconsoleinput(true)
   end
   println("Enter lines of text to decrypt. A blank line ends entry.")
   txt = ""
   while (lin = readline()) != ""
       txt *= lin
   end
   txt = replace(txt, r"[^A-Z]" => "")
   otp = readOTPfile(fname, length(txt), skiplines, useused)
   println(vdecode(txt, otp))

end

function securedeletefile(filename, writes=100)

   ltrs = ["ABCDEFGHIJKLMNOPQRSTUVWXYZ"[i] for i in 1:26]
   pathname, len = configs["OTPdir"] * "/" * filename, stat(filename).size
   fp = open(pathname, "w")
   for _ in 1:writes
       write(fp, rand(ltrs, writes))
       seekstart(fp)
   end
   close(fp)
   try rm(pathname); catch y; @warn(y) end

end

function createfiledialog()

   println("Enter partner name or press <enter> for none:")
   nam = getconsoleinput()
   println("Enter number of letters or <enter> for 2000:")
   n = getconsoleinput(true)
   newname, nletters, source = createOTPfile(n, nam)
   println("Created file $newname in directory $(configs["OTPdir"])",
       "\nwith $nletters letters. The source was $source.\n")

end

function deletefiledialog()

   fnam = chooseOTPfiledialog()
   if fnam != ""
       println("Confim by entering file name time string as (hh_mm_ss):")
       if occursin(getconsoleinput(), fnam)
           securedeletefile(fnam)
           println("File $fnam has been overwritten and deleted.")
       else
           println("No files deleted.")
       end
   end

end

function configure()

   println("The current subdirectory is: ", configs["OTPdir"])
   println("Enter new dirpath or <enter> to leave unchanged:")
   dirname = getconsoleinput()
   if dirname != ""
       try
           readdir(dirname)
           configs["OTPdir"] = dirname
       catch
           @warn("Invalid directory pathname: $dirname")
       end
   end

end

const mainmenu = """ Welcome to One Time Pad manager.

Select menu item:

A - Add new OTP file D - Decrypt text E - Encrypt text R - Remove file (secure deletion) C - Configure subdirectory X - eXit """

function onetimepadapp()

   while true
       println(mainmenu)
       inchar = getconsoleinput()
       ltr = (inchar == "") ? "X" : string(uppercase(inchar)[1])
       if ltr == "A"
           createfiledialog()
       elseif ltr == "D"
           decryptdialog()
       elseif ltr == "E"
           encryptdialog()
       elseif ltr == "R"
           deletefiledialog()
       elseif ltr == "C"
           configure()
       elseif ltr == "X"
           break
       else
           println("Unknown choice $ltr, type $(typeof(ltr))")
       end
   end
   println("Close the console to keep past session text from being seen.")

end

onetimepadapp() </lang>