⚠️ Warning: This is a draft ⚠️
This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.
If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.
This is a console based app for One Time Pad file management. It uses the internet random.org as source combined with local random number generation as a backup source of random letters.
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, len))
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()