diff --git a/config.json b/config.json index f316f56..fb18424 100644 --- a/config.json +++ b/config.json @@ -744,6 +744,14 @@ "practices": [], "prerequisites": [], "difficulty": 1 + }, + { + "slug": "simple-cipher", + "name": "Simple Cipher", + "uuid": "75f36774-7406-4234-9369-0923d1e5cc95", + "practices": [], + "prerequisites": [], + "difficulty": 3 } ] }, diff --git a/exercises/practice/simple-cipher/.docs/instructions.md b/exercises/practice/simple-cipher/.docs/instructions.md new file mode 100644 index 0000000..475af61 --- /dev/null +++ b/exercises/practice/simple-cipher/.docs/instructions.md @@ -0,0 +1,66 @@ +# Instructions + +Implement a simple shift cipher like Caesar and a more secure substitution cipher. + +## Step 1 + +"If he had anything confidential to say, he wrote it in cipher, that is, by so changing the order of the letters of the alphabet, that not a word could be made out. +If anyone wishes to decipher these, and get at their meaning, he must substitute the fourth letter of the alphabet, namely D, for A, and so with the others." +—Suetonius, Life of Julius Caesar + +Ciphers are very straight-forward algorithms that allow us to render text less readable while still allowing easy deciphering. +They are vulnerable to many forms of cryptanalysis, but Caesar was lucky that his enemies were not cryptanalysts. + +The Caesar Cipher was used for some messages from Julius Caesar that were sent afield. +Now Caesar knew that the cipher wasn't very good, but he had one ally in that respect: almost nobody could read well. +So even being a couple letters off was sufficient so that people couldn't recognize the few words that they did know. + +Your task is to create a simple shift cipher like the Caesar Cipher. +This image is a great example of the Caesar Cipher: + +![Caesar Cipher][img-caesar-cipher] + +For example: + +Giving "iamapandabear" as input to the encode function returns the cipher "ldpdsdqgdehdu". +Obscure enough to keep our message secret in transit. + +When "ldpdsdqgdehdu" is put into the decode function it would return the original "iamapandabear" letting your friend read your original message. + +## Step 2 + +Shift ciphers quickly cease to be useful when the opposition commander figures them out. +So instead, let's try using a substitution cipher. +Try amending the code to allow us to specify a key and use that for the shift distance. + +Here's an example: + +Given the key "aaaaaaaaaaaaaaaaaa", encoding the string "iamapandabear" +would return the original "iamapandabear". + +Given the key "ddddddddddddddddd", encoding our string "iamapandabear" +would return the obscured "ldpdsdqgdehdu" + +In the example above, we've set a = 0 for the key value. +So when the plaintext is added to the key, we end up with the same message coming out. +So "aaaa" is not an ideal key. +But if we set the key to "dddd", we would get the same thing as the Caesar Cipher. + +## Step 3 + +The weakest link in any cipher is the human being. +Let's make your substitution cipher a little more fault tolerant by providing a source of randomness and ensuring that the key contains only lowercase letters. + +If someone doesn't submit a key at all, generate a truly random key of at least 100 lowercase characters in length. + +## Extensions + +Shift ciphers work by making the text slightly odd, but are vulnerable to frequency analysis. +Substitution ciphers help that, but are still very vulnerable when the key is short or if spaces are preserved. +Later on you'll see one solution to this problem in the exercise "crypto-square". + +If you want to go farther in this field, the questions begin to be about how we can exchange keys in a secure way. +Take a look at [Diffie-Hellman on Wikipedia][dh] for one of the first implementations of this scheme. + +[img-caesar-cipher]: https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Caesar_cipher_left_shift_of_3.svg/320px-Caesar_cipher_left_shift_of_3.svg.png +[dh]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange diff --git a/exercises/practice/simple-cipher/.meta/config.json b/exercises/practice/simple-cipher/.meta/config.json new file mode 100644 index 0000000..66e762b --- /dev/null +++ b/exercises/practice/simple-cipher/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "erikschierboom" + ], + "files": { + "solution": [ + "simple-cipher.R" + ], + "test": [ + "test_simple-cipher.R" + ], + "example": [ + ".meta/example.R" + ] + }, + "blurb": "Implement a simple shift cipher like Caesar and a more secure substitution cipher.", + "source": "Substitution Cipher at Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Substitution_cipher" +} diff --git a/exercises/practice/simple-cipher/.meta/example.R b/exercises/practice/simple-cipher/.meta/example.R new file mode 100644 index 0000000..2ff9e2a --- /dev/null +++ b/exercises/practice/simple-cipher/.meta/example.R @@ -0,0 +1,26 @@ +generate_key <- function () { + sample(letters, 100, replace = TRUE) |> paste0(collapse = "") +} + +encode <- function(plaintext, key) { + shift(plaintext, key, `+`) +} + +decode <- function(ciphertext, key) { + shift(ciphertext, key, `-`) +} + +shift <- function(text, key, op) { + text_idxs <- letter_idxs(text) + key_idxs <- letter_idxs(key) |> rep_len(length.out = nchar(text)) + shifted_idxs <- op(text_idxs, key_idxs) %% 26 + 1 + letters[shifted_idxs] |> paste0(collapse = "") +} + +letter_idxs <- function(text) { + text |> strsplit("") |> unlist() |> sapply(FUN = letter_idx) +} + +letter_idx <- function(letter) { + utf8ToInt(letter) - utf8ToInt("a") +} diff --git a/exercises/practice/simple-cipher/.meta/tests.toml b/exercises/practice/simple-cipher/.meta/tests.toml new file mode 100644 index 0000000..77e6571 --- /dev/null +++ b/exercises/practice/simple-cipher/.meta/tests.toml @@ -0,0 +1,46 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[b8bdfbe1-bea3-41bb-a999-b41403f2b15d] +description = "Random key cipher -> Can encode" + +[3dff7f36-75db-46b4-ab70-644b3f38b81c] +description = "Random key cipher -> Can decode" + +[8143c684-6df6-46ba-bd1f-dea8fcb5d265] +description = "Random key cipher -> Is reversible. I.e., if you apply decode in a encoded result, you must see the same plaintext encode parameter as a result of the decode method" + +[defc0050-e87d-4840-85e4-51a1ab9dd6aa] +description = "Random key cipher -> Key is made only of lowercase letters" + +[565e5158-5b3b-41dd-b99d-33b9f413c39f] +description = "Substitution cipher -> Can encode" + +[d44e4f6a-b8af-4e90-9d08-fd407e31e67b] +description = "Substitution cipher -> Can decode" + +[70a16473-7339-43df-902d-93408c69e9d1] +description = "Substitution cipher -> Is reversible. I.e., if you apply decode in a encoded result, you must see the same plaintext encode parameter as a result of the decode method" + +[69a1458b-92a6-433a-a02d-7beac3ea91f9] +description = "Substitution cipher -> Can double shift encode" + +[21d207c1-98de-40aa-994f-86197ae230fb] +description = "Substitution cipher -> Can wrap on encode" + +[a3d7a4d7-24a9-4de6-bdc4-a6614ced0cb3] +description = "Substitution cipher -> Can wrap on decode" + +[e31c9b8c-8eb6-45c9-a4b5-8344a36b9641] +description = "Substitution cipher -> Can encode messages longer than the key" + +[93cfaae0-17da-4627-9a04-d6d1e1be52e3] +description = "Substitution cipher -> Can decode messages longer than the key" diff --git a/exercises/practice/simple-cipher/simple-cipher.R b/exercises/practice/simple-cipher/simple-cipher.R new file mode 100644 index 0000000..43bfda2 --- /dev/null +++ b/exercises/practice/simple-cipher/simple-cipher.R @@ -0,0 +1,11 @@ +generate_key <- function () { + +} + +encode <- function(plaintext, key) { + +} + +decode <- function(ciphertext, key) { + +} diff --git a/exercises/practice/simple-cipher/test_simple-cipher.R b/exercises/practice/simple-cipher/test_simple-cipher.R new file mode 100644 index 0000000..cebc88b --- /dev/null +++ b/exercises/practice/simple-cipher/test_simple-cipher.R @@ -0,0 +1,62 @@ +source("./simple-cipher.R") +library(testthat) + +test_that("Random key cipher - Key is made only of lowercase letters", { + key <- generate_key() + expect_equal(all(grepl("^[a-z]+$", key)), TRUE) +}) + +test_that("Random key cipher - Can encode", { + key <- generate_key() + expect_equal(encode("aaaaaaaaaa", key), substr(key, 1, 10)) +}) + +test_that("Random key cipher - Can decode", { + key <- generate_key() + expect_equal(decode(substr(key, 1, 10), key), "aaaaaaaaaa") +}) + +test_that("Random key cipher - Is reversible. I.e., if you apply decode in a encoded result, you must see the same plaintext encode parameter as a result of the decode method", { # nolint + key <- generate_key() + expect_equal(decode(encode("abcdefghij", key), key), "abcdefghij") +}) + +test_that("Substitution cipher - Can encode", { + key <- "abcdefghij" + expect_equal(encode("aaaaaaaaaa", key), "abcdefghij") +}) + +test_that("Substitution cipher - Can decode", { + key <- "abcdefghij" + expect_equal(decode("abcdefghij", key), "aaaaaaaaaa") +}) + +test_that("Substitution cipher - Is reversible. I.e., if you apply decode in a encoded result, you must see the same plaintext encode parameter as a result of the decode method", { # nolint + key <- "abcdefghij" + expect_equal(decode(encode("abcdefghij", key), key), "abcdefghij") +}) + +test_that("Substitution cipher - Can double shift encode", { + key <- "iamapandabear" + expect_equal(encode("iamapandabear", key), "qayaeaagaciai") +}) + +test_that("Substitution cipher - Can wrap on encode", { + key <- "abcdefghij" + expect_equal(encode("zzzzzzzzzz", key), "zabcdefghi") +}) + +test_that("Substitution cipher - Can wrap on decode", { + key <- "abcdefghij" + expect_equal(decode("zabcdefghi", key), "zzzzzzzzzz") +}) + +test_that("Substitution cipher - Can encode messages longer than the key", { + key <- "abc" + expect_equal(encode("iamapandabear", key), "iboaqcnecbfcr") +}) + +test_that("Substitution cipher - Can decode messages longer than the key", { + key <- "abc" + expect_equal(decode("iboaqcnecbfcr", key), "iamapandabear") +})