Task: One-time pad




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 
)

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)
    
    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 { 
        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: 
        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: 
            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 {
                
                fileName2 := fileName + "_cpy" 
                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: 
            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: 
            fmt.Println("The OTP (and related) files in the current directory are:\n")
            files, err := ioutil.ReadDir(".") 
            check(err)
            for _, fi := range files {
                name := fi.Name()
                if !fi.IsDir() && isOtpRelated(name) {
                    fmt.Println(name)
                }
            }
        case 4: 
            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:] 

                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: 
            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:] 

                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:] 
                    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: 
            return
        }
    }
}