One-time pad/Phix

From Rosetta Code

Phix[edit]

A simple xor-based one time pad that allows any file to be encrypted and decrypted, since it seems silly to limit this to plaintext. The one time pad files are therefore binary rather than plain text, with comments and used counts in separate .1te/.1td files, rather than #/- in the .1tp itself. Uses a trivial xor rather than a Vigenere cipher, since the latter adds nothing. Ignores the "true random numbers, eg from /dev/random" part, especially since the wikipedia page cites /dev/random as potentially unsuitable for cryptographic use. Instead this just uses the builtin rand(): for better security I might suggest you replace that, possibly with several different (pseudo) random number generators.

The dump feature is only for testing, obviously, as is decrypt defaulting to the last encrypted message/file/pad. Note however that .1te/.1td handling is not: if you try to make a one time pad "bi-directional", then not only would there be some confusion and re-alignment issues should both parties send a message at the same time, but two messages encrypted with the same part of the one time pad severely weakens the security of those two messages. To prevent that, delete/do not share the .1te or .1td file, and it will not allow encryption/decryption respectively.

Note that options 4 (Encrypt plain text) and 5 (Decrypt cipher text) are also only suitable for testing: you cannot actually enter anything for option 5, it just decodes whatever was last encoded by option 4, however it would be trivial to store the output of option 4 in a file and send that, and the recipient would then decode it using option 7 (Decrypt file).

TIP: if a message gets lost, or you quit without decrypting something, then edit the recipient's .1td file manually, so that Used matches the sender's .1te file.

string default_enc = "",
default_dec = ""
 
function size_used(string name, integer now_used=0)
object txt = read_lines(name)
if sequence(txt) then
integer size, used
for i=1 to length(txt) do
if match("Size",txt[i])=1 then
{{size}} = scanf(txt[i],"Size: %d")
elsif match("Used",txt[i])=1 then
if now_used=0 then
{{used}} = scanf(txt[i],"Used: %d")
return {size,used}
else
txt[i] = sprintf("Used: %d",now_used)
if not write_lines(name,txt) then
printf(1,"error updating %s\n",{name})
end if
return 1
end if
end if
end for
end if
return {-1,-1}
end function
 
procedure list_one_time_pads(bool set_default=false)
puts(1,"\n")
sequence d = dir("*.1t?")
if d={} then
printf(1,"No one time pad files found.\n")
return
elsif set_default then
sequence bases = {},
types = {{},{},{}} -- p/e/d
for i=1 to length(d) do
string name = d[i][D_NAME]
integer dot = find('.',name)
if dot!=0 then
string base = name[1..dot-1],
ext = name[dot+1..$]
integer e = find(ext,{"1tp","1te","1td"})
if e!=0 then
integer k = find(base,bases)
if k=0 then
bases = append(bases,base)
k = length(bases)
end if
types[e] &= k
end if
end if
end for
default_enc = iff(length(types[2])=1?bases[types[2][1]]&".1te":"")
default_dec = iff(length(types[3])=1?bases[types[3][1]]&".1td":"")
else
for i=1 to length(d) do
sequence di = d[i]
string name = di[D_NAME],
info = iff(name[$]='p'?sprintf("Size: %d",di[D_SIZE])
 :sprintf("Size: %d, Used: %d",size_used(name)))
printf(1,"%s Created: %02d/%02d/%04d, %s\n",
{name,di[D_DAY],di[D_MONTH],di[D_YEAR],info})
end for
end if
end procedure
 
procedure dump_hex(sequence s)
if length(s)=0 then
printf(1,"{}\n")
else
for i=1 to length(s) do
printf(1,"%02x%c",{s[i],iff(mod(i,16)=0?'\n':' ')})
end for
end if
end procedure
 
procedure dump_file(string file, bool ashex=false)
printf(1,"The contents of %s:\n\n",{file})
if match(".1tp",file) or ashex then
integer fn = open(file,"rb")
dump_hex(get_bytes(fn, 64))
close(fn)
puts(1,iff(get_file_size(file)>64?"...\n\n","\n"))
else
puts(1,join(read_lines(file),"\n")&"\n")
end if
end procedure
 
procedure create_one_time_pad()
string filename = prompt_string("OTP file name to create (without extension):")
if find('.',filename) then
puts(1,"extension not allowed\n")
elsif filename!="" then
string pfile = filename&".1tp",
efile = filename&".1te",
dfile = filename&".1td"
if file_exists(pfile) then
string del = lower(prompt_string("File already exists - delete?:"))
if not find(del,{"y","yes"}) then return end if
if not delete_file(pfile) then
printf(1,"error deleting %s\n",{pfile})
return
end if
end if
string comment = prompt_string("comment (optional):")
integer filesize
while 1 do
string sizestr = prompt_string("file size (1MB):")
sequence res = iff(sizestr=""?{{1,"MB"}}:scanf(sizestr,"%d%s"))
if length(res)=1 then
integer k = find(upper(res[1][2]),{"","K","KB","M","MB","G","GB"})
if k!=0 then
k = {1,1024,1024*1024,1024*1204}[floor(k/2)+1]
filesize = res[1][1]*k
exit
end if
printf(1,"unknown suffix:%s - use K|M|G or KB|MB|GB\n",{res[1][2]})
end if
end while
printf(1,"Creating %s (%d bytes)\n",{pfile,filesize})
integer fn = open(pfile,"wb")
if fn=-1 then
printf(1,"error opening %s\n",{pfile})
return
end if
for i=1 to filesize do
puts(fn,rand(256)-1)
end for
close(fn)
fn = open(efile,"w")
if fn=-1 then
printf(1,"error opening %s\n",{efile})
return
end if
printf(fn,"# OTP file\n# %s\nSize: %d\nUsed: 0\n",{comment,filesize})
close(fn)
if not copy_file(efile,dfile,true) then
printf(1,"error copying %s to %s\n",{efile,dfile})
return
end if
printf(1,"\n%s and %s and %s have been created in the current directory.\n\n",
{pfile,efile,dfile})
dump_file(pfile)
dump_file(efile)
default_enc = efile
default_dec = dfile
end if
end procedure
 
procedure delete_one_time_pad()
string filename = prompt_string("OTP file name to delete (without extension):")
if find('.',filename) then
puts(1,"extension not allowed\n")
elsif filename!="" then
string efile = filename&".1te",
dfile = filename&".1td"
filename &= ".1tp"
if not file_exists(filename) then
printf(1,"file %s found\n",{filename})
return
end if
string del = lower(prompt_string("Delete file - are you sure?:"))
if not find(del,{"y","yes"}) then
printf(1,"file not deleted\n")
return
end if
if not delete_file(filename) then
printf(1,"error deleting %s\n",{filename})
return
end if
integer c = 1
c += delete_file(efile)
c += delete_file(dfile)
printf(1,"%d file%s successfully deleted.\n",{c,iff(c=1?"":"s")})
if default_enc=efile then default_enc = "" end if
if default_dec=dfile then default_dec = "" end if
end if
end procedure
 
sequence msg = {}
 
procedure crypt_text(integer de)
sequence text = iff(de='d'?msg:prompt_string("message:"))
string dflt = iff(de='d'?default_dec:default_enc),
otp = iff(length(dflt)?"OTP ("&dflt&"):":"OTP:"),
efile = prompt_string(otp)
if efile="" then efile=dflt end if
if length(efile) then
if not find('.',efile) then efile &= ".1t"&de end if
integer {size,used} = size_used(efile)
if size=-1 or used=-1 then
printf(1,"File %s cannot be opened\n",{efile})
elsif used+length(text)>size then
printf(1,"OTP exhausted (%d>%d)\n",{used+length(text),size})
else
efile[$] = 'p'
integer fn = open(efile,"rb")
if seek(fn,used)!=SEEK_OK then
printf(1,"Error in seek(%s,%d)\n",{efile,fn})
text = ""
else
msg = get_bytes(fn,length(text))
msg = sq_xor_bits(msg,text)
if de='e' then
printf(1,"Encrypted:\n")
dump_hex(msg)
printf(1,"\n\n")
else
printf(1,"Decrypted: %s\n\n",{msg})
end if
end if
close(fn)
efile[$] = de
if length(text)!=0 then
{} = size_used(efile, used+length(text))
end if
dump_file(efile)
end if
end if
end procedure
 
string last_file = ""
 
procedure crypt_file(integer de)
string file = iff(de='d' and last_file!=""?"file ("&last_file&"):":"file:"),
filename = prompt_string(file)
if de='d' and filename="" then filename=last_file end if
if filename="" then return end if
if not file_exists(filename) then
printf(1,"The file %s does not exist\n",{filename})
return
end if
atom filesize = get_file_size(filename)
integer fn = open(filename,"rb")
if fn=-1 then
printf(1,"error opening %s\n",{filename})
return
end if
sequence bytes = get_bytes(fn,filesize)
close(fn)
if length(bytes)!=filesize then ?9/0 end if -- uh?
if de='e' then
dump_file(filename, true)
end if
string outfile = prompt_string("output:")
if length(outfile)=0 then return end if
integer outfn = open(outfile,"wb")
if outfn=-1 then
printf(1,"error opening output file %s\n",{outfile})
return
end if
string dflt = iff(de='d'?default_dec:default_enc),
otp = iff(length(dflt)?"OTP ("&dflt&"):":"OTP:"),
efile = prompt_string(otp)
if efile="" then efile=dflt end if
if length(efile) then
if not find('.',efile) then efile &= ".1t"&de end if
integer {size,used} = size_used(efile)
if size=-1 or used=-1 then
printf(1,"File %s cannot be opened\n",{efile})
elsif used+filesize>size then
printf(1,"OTP exhausted (%d>%d)\n",{used+filesize,size})
else
efile[$] = 'p'
fn = open(efile,"rb")
if seek(fn,used)!=SEEK_OK then
printf(1,"Error in seek(%s,%d)\n",{efile,fn})
filesize = 0
else
msg = get_bytes(fn,filesize)
msg = sq_xor_bits(msg,bytes)
puts(outfn,msg)
end if
close(fn)
close(outfn)
efile[$] = de
if filesize!=0 then
{} = size_used(efile, used+filesize)
end if
if de='d' then
dump_file(outfile, true)
else
last_file = outfile
end if
dump_file(efile)
end if
end if
end procedure
 
list_one_time_pads(set_default:=true)
 
-- note: the trailing space after the opening """ is deliberate:
constant menu_txt = """
1. List one time pad files.
2. Create one time pad file.
3. Delete one time pad file.
4. Encrypt plain text.
5. Decrypt cipher text.
6. Encrypt file.
7. Decrypt file.
8. Quit program.
Your choice (1 to 8) : """
 
while 1 do
integer choice = prompt_number(menu_txt,{1,8})
switch choice do
case 1: list_one_time_pads()
case 2: create_one_time_pad()
case 3: delete_one_time_pad()
case 4: crypt_text('e')
case 5: crypt_text('d')
case 6: crypt_file('e')
case 7: crypt_file('d')
case 8: exit
default :puts(1,"not implemented...\n")
end switch
end while
Output:

Sample session. Similar to Kotlin, menu display/selection has been shorted to <menu N. Xxxx.>

No one time pad files found.

1. List one time pad files.
2. Create one time pad file.
3. Delete one time pad file.
4. Encrypt plain text.
5. Decrypt cipher text.
6. Encrypt file.
7. Decrypt file.
8. Quit program.
Your choice (1 to 8) : 2
OTP file name to create (without extension):007
comment (optional):Pussy Galore
file size (1MB):
Creating 007.1tp (1048576 bytes)

007.1tp and 007.1te and 007.1td have been created in the current directory.

The contents of 007.1tp:

C3 A1 90 B2 70 E6 A2 DD 27 F4 3C 21 48 D1 54 1C
D7 7E AA 09 40 CC 49 83 F4 E3 96 86 1D 48 15 F6
A3 C2 5F 47 00 BE D1 6B 4E 4A EA 50 DB F8 62 2F
D5 66 D5 0F A9 63 E6 4D 65 BB 67 70 6F 51 34 73
...

The contents of 007.1te:

# OTP file
# Pussy Galore
Size: 1048576
Used: 0

<menu 1. List one time pad files.>
007.1td  Created: 10/11/2018, Size: 1048576, Used: 0
007.1te  Created: 10/11/2018, Size: 1048576, Used: 0
007.1tp  Created: 10/11/2018, Size: 1048576

<menu 4. Encrypt plain text.>
message:This is message 1234
OTP (007.1te):
Encrypted:
97 C9 F9 C1 50 8F D1 FD 4A 91 4F 52 29 B6 31 3C
E6 4C 99 3D

The contents of 007.1te:

# OTP file
# Pussy Galore
Size: 1048576
Used: 20

<menu 5. Decrypt cipher text.>
OTP (007.1td):
Decrypted: This is message 1234

The contents of 007.1td:

# OTP file
# Pussy Galore
Size: 1048576
Used: 20

<menu 8. Quit program.>