RosettaCodeData/Task/One-time-pad/Go/one-time-pad.go
2023-07-01 13:44:08 -04:00

344 lines
11 KiB
Go

package main
import (
"bufio"
"crypto/rand"
"fmt"
"io/ioutil"
"log"
"math/big"
"os"
"strconv"
"strings"
"unicode"
)
const (
charsPerLine = 48
chunkSize = 6
cols = 8
demo = true // would normally be set to false
)
type fileType int
const (
otp fileType = iota
enc
dec
)
var scnr = bufio.NewScanner(os.Stdin)
func check(err error) {
if err != nil {
log.Fatal(err)
}
}
func toAlpha(s string) string {
var filtered []rune
for _, r := range s {
if unicode.IsUpper(r) {
filtered = append(filtered, r)
}
}
return string(filtered)
}
func isOtpRelated(s string) bool {
return strings.HasSuffix(s, ".1tp") || strings.HasSuffix(s, "1tp_cpy") ||
strings.HasSuffix(s, ".1tp_enc") || strings.HasSuffix(s, "1tp_dec")
}
func makePad(nLines int) string {
nChars := nLines * charsPerLine
bytes := make([]byte, nChars)
/* generate random upper case letters */
max := big.NewInt(26)
for i := 0; i < nChars; i++ {
n, err := rand.Int(rand.Reader, max)
check(err)
bytes[i] = byte(65 + n.Uint64())
}
return inChunks(string(bytes), nLines, otp)
}
func vigenere(text, key string, encrypt bool) string {
bytes := make([]byte, len(text))
var ci byte
for i, c := range text {
if encrypt {
ci = (byte(c) + key[i] - 130) % 26
} else {
ci = (byte(c) + 26 - key[i]) % 26
}
bytes[i] = ci + 65
}
temp := len(bytes) % charsPerLine
if temp > 0 { // pad with random characters so each line is a full one
max := big.NewInt(26)
for i := temp; i < charsPerLine; i++ {
n, err := rand.Int(rand.Reader, max)
check(err)
bytes = append(bytes, byte(65+n.Uint64()))
}
}
ft := enc
if !encrypt {
ft = dec
}
return inChunks(string(bytes), len(bytes)/charsPerLine, ft)
}
func inChunks(s string, nLines int, ft fileType) string {
nChunks := len(s) / chunkSize
remainder := len(s) % chunkSize
chunks := make([]string, nChunks)
for i := 0; i < nChunks; i++ {
chunks[i] = s[i*chunkSize : (i+1)*chunkSize]
}
if remainder > 0 {
chunks = append(chunks, s[nChunks*chunkSize:])
}
var sb strings.Builder
for i := 0; i < nLines; i++ {
j := i * cols
sb.WriteString(" " + strings.Join(chunks[j:j+cols], " ") + "\n")
}
ss := " file\n" + sb.String()
switch ft {
case otp:
return "# OTP" + ss
case enc:
return "# Encrypted" + ss
default: // case dec:
return "# Decrypted" + ss
}
}
func menu() int {
fmt.Println(`
1. Create one time pad file.
2. Delete one time pad file.
3. List one time pad files.
4. Encrypt plain text.
5. Decrypt cipher text.
6. Quit program.
`)
choice := 0
for choice < 1 || choice > 6 {
fmt.Print("Your choice (1 to 6) : ")
scnr.Scan()
choice, _ = strconv.Atoi(scnr.Text())
check(scnr.Err())
}
return choice
}
func main() {
for {
choice := menu()
fmt.Println()
switch choice {
case 1: // Create OTP
fmt.Println("Note that encrypted lines always contain 48 characters.\n")
fmt.Print("OTP file name to create (without extension) : ")
scnr.Scan()
fileName := scnr.Text() + ".1tp"
nLines := 0
for nLines < 1 || nLines > 1000 {
fmt.Print("Number of lines in OTP (max 1000) : ")
scnr.Scan()
nLines, _ = strconv.Atoi(scnr.Text())
}
check(scnr.Err())
key := makePad(nLines)
file, err := os.Create(fileName)
check(err)
_, err = file.WriteString(key)
check(err)
file.Close()
fmt.Printf("\n'%s' has been created in the current directory.\n", fileName)
if demo {
// a copy of the OTP file would normally be on a different machine
fileName2 := fileName + "_cpy" // copy for decryption
file, err := os.Create(fileName2)
check(err)
_, err = file.WriteString(key)
check(err)
file.Close()
fmt.Printf("'%s' has been created in the current directory.\n", fileName2)
fmt.Println("\nThe contents of these files are :\n")
fmt.Println(key)
}
case 2: // Delete OTP
fmt.Println("Note that this will also delete ALL associated files.\n")
fmt.Print("OTP file name to delete (without extension) : ")
scnr.Scan()
toDelete1 := scnr.Text() + ".1tp"
check(scnr.Err())
toDelete2 := toDelete1 + "_cpy"
toDelete3 := toDelete1 + "_enc"
toDelete4 := toDelete1 + "_dec"
allToDelete := []string{toDelete1, toDelete2, toDelete3, toDelete4}
deleted := 0
fmt.Println()
for _, name := range allToDelete {
if _, err := os.Stat(name); !os.IsNotExist(err) {
err = os.Remove(name)
check(err)
deleted++
fmt.Printf("'%s' has been deleted from the current directory.\n", name)
}
}
if deleted == 0 {
fmt.Println("There are no files to delete.")
}
case 3: // List OTPs
fmt.Println("The OTP (and related) files in the current directory are:\n")
files, err := ioutil.ReadDir(".") // already sorted by file name
check(err)
for _, fi := range files {
name := fi.Name()
if !fi.IsDir() && isOtpRelated(name) {
fmt.Println(name)
}
}
case 4: // Encrypt
fmt.Print("OTP file name to use (without extension) : ")
scnr.Scan()
keyFile := scnr.Text() + ".1tp"
if _, err := os.Stat(keyFile); !os.IsNotExist(err) {
file, err := os.Open(keyFile)
check(err)
bytes, err := ioutil.ReadAll(file)
check(err)
file.Close()
lines := strings.Split(string(bytes), "\n")
le := len(lines)
first := le
for i := 0; i < le; i++ {
if strings.HasPrefix(lines[i], " ") {
first = i
break
}
}
if first == le {
fmt.Println("\nThat file has no unused lines.")
continue
}
lines2 := lines[first:] // get rid of comments and used lines
fmt.Println("Text to encrypt :-\n")
scnr.Scan()
text := toAlpha(strings.ToUpper(scnr.Text()))
check(scnr.Err())
tl := len(text)
nLines := tl / charsPerLine
if tl%charsPerLine > 0 {
nLines++
}
if len(lines2) >= nLines {
key := toAlpha(strings.Join(lines2[0:nLines], ""))
encrypted := vigenere(text, key, true)
encFile := keyFile + "_enc"
file2, err := os.Create(encFile)
check(err)
_, err = file2.WriteString(encrypted)
check(err)
file2.Close()
fmt.Printf("\n'%s' has been created in the current directory.\n", encFile)
for i := first; i < first+nLines; i++ {
lines[i] = "-" + lines[i][1:]
}
file3, err := os.Create(keyFile)
check(err)
_, err = file3.WriteString(strings.Join(lines, "\n"))
check(err)
file3.Close()
if demo {
fmt.Println("\nThe contents of the encrypted file are :\n")
fmt.Println(encrypted)
}
} else {
fmt.Println("Not enough lines left in that file to do encryption.")
}
} else {
fmt.Println("\nThat file does not exist.")
}
case 5: // Decrypt
fmt.Print("OTP file name to use (without extension) : ")
scnr.Scan()
keyFile := scnr.Text() + ".1tp_cpy"
check(scnr.Err())
if _, err := os.Stat(keyFile); !os.IsNotExist(err) {
file, err := os.Open(keyFile)
check(err)
bytes, err := ioutil.ReadAll(file)
check(err)
file.Close()
keyLines := strings.Split(string(bytes), "\n")
le := len(keyLines)
first := le
for i := 0; i < le; i++ {
if strings.HasPrefix(keyLines[i], " ") {
first = i
break
}
}
if first == le {
fmt.Println("\nThat file has no unused lines.")
continue
}
keyLines2 := keyLines[first:] // get rid of comments and used lines
encFile := keyFile[0:len(keyFile)-3] + "enc"
if _, err := os.Stat(encFile); !os.IsNotExist(err) {
file2, err := os.Open(encFile)
check(err)
bytes, err := ioutil.ReadAll(file2)
check(err)
file2.Close()
encLines := strings.Split(string(bytes), "\n")[1:] // exclude comment line
nLines := len(encLines)
if len(keyLines2) >= nLines {
encrypted := toAlpha(strings.Join(encLines, ""))
key := toAlpha(strings.Join(keyLines2[0:nLines], ""))
decrypted := vigenere(encrypted, key, false)
decFile := keyFile[0:len(keyFile)-3] + "dec"
file3, err := os.Create(decFile)
check(err)
_, err = file3.WriteString(decrypted)
check(err)
file3.Close()
fmt.Printf("\n'%s' has been created in the current directory.\n", decFile)
for i := first; i < first+nLines; i++ {
keyLines[i] = "-" + keyLines[i][1:]
}
file4, err := os.Create(keyFile)
check(err)
_, err = file4.WriteString(strings.Join(keyLines, "\n"))
check(err)
file4.Close()
if demo {
fmt.Println("\nThe contents of the decrypted file are :\n")
fmt.Println(decrypted)
}
}
} else {
fmt.Println("Not enough lines left in that file to do decryption.")
}
} else {
fmt.Println("\nThat file does not exist.")
}
case 6: // Quit program
return
}
}
}