RosettaCodeData/Task/One-time-pad/Kotlin/one-time-pad.kts
2024-10-16 18:07:41 -07:00

242 lines
9.5 KiB
Kotlin

// version 1.2.31
import java.io.File
import java.security.SecureRandom
const val CHARS_PER_LINE = 48
const val CHUNK_SIZE = 6
const val COLS = 8
const val DEMO = true // would normally be set to false
enum class FileType { OTP, ENC, DEC }
fun Char.isAlpha() = this in 'A'..'Z'
fun String.toAlpha() = this.filter { it.isAlpha() }
fun String.isOtpRelated() = endsWith(".1tp") || endsWith(".1tp_cpy") ||
endsWith(".1tp_enc") || endsWith(".1tp_dec")
fun makePad(nLines: Int): String {
val nChars = nLines * CHARS_PER_LINE
val sr = SecureRandom()
val sb = StringBuilder(nChars)
/* generate random upper case letters */
for (i in 0 until nChars) sb.append((sr.nextInt(26) + 65).toChar())
return sb.toString().inChunks(nLines, FileType.OTP)
}
fun vigenere(text: String, key: String, encrypt: Boolean = true): String {
val sb = StringBuilder(text.length)
for ((i, c) in text.withIndex()) {
val ci = if (encrypt)
(c.toInt() + key[i].toInt() - 130) % 26
else
(c.toInt() - key[i].toInt() + 26) % 26
sb.append((ci + 65).toChar())
}
val temp = sb.length % CHARS_PER_LINE
if (temp > 0) { // pad with random characters so each line is a full one
val sr = SecureRandom()
for (i in temp until CHARS_PER_LINE) sb.append((sr.nextInt(26) + 65).toChar())
}
val ft = if (encrypt) FileType.ENC else FileType.DEC
return sb.toString().inChunks(sb.length / CHARS_PER_LINE, ft)
}
fun String.inChunks(nLines: Int, ft: FileType): String {
val chunks = this.chunked(CHUNK_SIZE)
val sb = StringBuilder(this.length + nLines * (COLS + 1))
for (i in 0 until nLines) {
val j = i * COLS
sb.append(" ${chunks.subList(j, j + COLS).joinToString(" ")}\n")
}
val s = " file\n" + sb.toString()
return when (ft) {
FileType.OTP -> "# OTP" + s
FileType.ENC -> "# Encrypted" + s
FileType.DEC -> "# Decrypted" + s
}
}
fun menu(): Int {
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.
|
""".trimMargin())
var choice: Int?
do {
print("Your choice (1 to 6) : ")
choice = readLine()!!.toIntOrNull()
}
while (choice == null || choice !in 1..6)
return choice
}
fun main(args: Array<String>) {
mainLoop@ while (true) {
val choice = menu()
println()
when (choice) {
1 -> { // Create OTP
println("Note that encrypted lines always contain 48 characters.\n")
print("OTP file name to create (without extension) : ")
val fileName = readLine()!! + ".1tp"
var nLines: Int?
do {
print("Number of lines in OTP (max 1000) : ")
nLines = readLine()!!.toIntOrNull()
}
while (nLines == null || nLines !in 1..1000)
val key = makePad(nLines)
File(fileName).writeText(key)
println("\n'$fileName' has been created in the current directory.")
if (DEMO) {
// a copy of the OTP file would normally be on a different machine
val fileName2 = fileName + "_cpy" // copy for decryption
File(fileName2).writeText(key)
println("'$fileName2' has been created in the current directory.")
println("\nThe contents of these files are :\n")
println(key)
}
}
2 -> { // Delete OTP
println("Note that this will also delete ALL associated files.\n")
print("OTP file name to delete (without extension) : ")
val toDelete1 = readLine()!! + ".1tp"
val toDelete2 = toDelete1 + "_cpy"
val toDelete3 = toDelete1 + "_enc"
val toDelete4 = toDelete1 + "_dec"
val allToDelete = listOf(toDelete1, toDelete2, toDelete3, toDelete4)
var deleted = 0
println()
for (name in allToDelete) {
val f = File(name)
if (f.exists()) {
f.delete()
deleted++
println("'$name' has been deleted from the current directory.")
}
}
if (deleted == 0) println("There are no files to delete.")
}
3 -> { // List OTPs
println("The OTP (and related) files in the current directory are:\n")
val otpFiles = File(".").listFiles().filter {
it.isFile() && it.name.isOtpRelated()
}.map { it.name }.toMutableList()
otpFiles.sort()
println(otpFiles.joinToString("\n"))
}
4 -> { // Encrypt
print("OTP file name to use (without extension) : ")
val keyFile = readLine()!! + ".1tp"
val kf = File(keyFile)
if (kf.exists()) {
val lines = File(keyFile).readLines().toMutableList()
var first = lines.size
for (i in 0 until lines.size) {
if (lines[i].startsWith(" ")) {
first = i
break
}
}
if (first == lines.size) {
println("\nThat file has no unused lines.")
continue@mainLoop
}
val lines2 = lines.drop(first) // get rid of comments and used lines
println("Text to encrypt :-\n")
val text = readLine()!!.toUpperCase().toAlpha()
val len = text.length
var nLines = len / CHARS_PER_LINE
if (len % CHARS_PER_LINE > 0) nLines++
if (lines2.size >= nLines) {
val key = lines2.take(nLines).joinToString("").toAlpha()
val encrypted = vigenere(text, key)
val encFile = keyFile + "_enc"
File(encFile).writeText(encrypted)
println("\n'$encFile' has been created in the current directory.")
for (i in first until first + nLines) {
lines[i] = "-" + lines[i].drop(1)
}
File(keyFile).writeText(lines.joinToString("\n"))
if (DEMO) {
println("\nThe contents of the encrypted file are :\n")
println(encrypted)
}
}
else println("Not enough lines left in that file to do encryption")
}
else println("\nThat file does not exist.")
}
5 -> { // Decrypt
print("OTP file name to use (without extension) : ")
val keyFile = readLine()!! + ".1tp_cpy"
val kf = File(keyFile)
if (kf.exists()) {
val keyLines = File(keyFile).readLines().toMutableList()
var first = keyLines.size
for (i in 0 until keyLines.size) {
if (keyLines[i].startsWith(" ")) {
first = i
break
}
}
if (first == keyLines.size) {
println("\nThat file has no unused lines.")
continue@mainLoop
}
val keyLines2 = keyLines.drop(first) // get rid of comments and used lines
val encFile = keyFile.dropLast(3) + "enc"
val ef = File(encFile)
if (ef.exists()) {
val encLines = File(encFile).readLines().drop(1) // exclude comment line
val nLines = encLines.size
if (keyLines2.size >= nLines) {
val encrypted = encLines.joinToString("").toAlpha()
val key = keyLines2.take(nLines).joinToString("").toAlpha()
val decrypted = vigenere(encrypted, key, false)
val decFile = keyFile.dropLast(3) + "dec"
File(decFile).writeText(decrypted)
println("\n'$decFile' has been created in the current directory.")
for (i in first until first + nLines) {
keyLines[i] = "-" + keyLines[i].drop(1)
}
File(keyFile).writeText(keyLines.joinToString("\n"))
if (DEMO) {
println("\nThe contents of the decrypted file are :\n")
println(decrypted)
}
}
else println("Not enough lines left in that file to do decryption")
}
else println("\n'$encFile' is missing.")
}
else println("\nThat file does not exist.")
}
else -> return // Quit
}
}
}