344 lines
11 KiB
Go
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
|
|
}
|
|
}
|
|
}
|