diff --git a/config.json b/config.json index 65eae6fa..2ea97784 100644 --- a/config.json +++ b/config.json @@ -605,6 +605,22 @@ "lists", "list-methods" ] + }, + { + "slug": "simple-cipher", + "name": "Simple Cipher", + "uuid": "83b2fe48-3b61-4c08-b711-51fe5b654638", + "practices": [], + "prerequisites": [], + "difficulty" : 5, + "topics" : [ + "strings", + "string-methods", + "loops", + "numbers", + "lists", + "randomness" + ] } ] }, diff --git a/dev/src/BaselineOfExercism/BaselineOfExercism.class.st b/dev/src/BaselineOfExercism/BaselineOfExercism.class.st index d9f33ec7..caa17ac4 100644 --- a/dev/src/BaselineOfExercism/BaselineOfExercism.class.st +++ b/dev/src/BaselineOfExercism/BaselineOfExercism.class.st @@ -18,7 +18,7 @@ BaselineOfExercism class >> exerciseGoldenTestPackageNames [ BaselineOfExercism class >> exercisePackageNames [ "Answer the list of exercise package names (as we don't yet have proper projects)" - ^ #('Exercise@Acronym' 'Exercise@Allergies' 'Exercise@Anagram' 'Exercise@ArmstrongNumbers' 'Exercise@AtbashCipher' 'Exercise@Binary' 'Exercise@BinarySearchTree' 'Exercise@Bowling' 'Exercise@CircularBuffer' 'Exercise@Clock' 'Exercise@CollatzConjecture' 'Exercise@Darts' 'Exercise@Diamond' 'Exercise@Die' 'Exercise@Etl' 'Exercise@FlattenArray' 'Exercise@Forth' 'Exercise@GradeSchool' 'Exercise@Grains' 'Exercise@Hamming' 'Exercise@HelloWorld' 'Exercise@HighScores' 'Exercise@IsbnVerifier' 'Exercise@Isogram' 'Exercise@Leap' 'Exercise@Luhn' 'Exercise@MatchingBrackets' 'Exercise@Matrix' 'Exercise@Minesweeper' 'Exercise@Pangram' 'Exercise@Proverb' 'Exercise@Raindrops' 'Exercise@ResistorColorDuo' 'Exercise@ReverseString' 'Exercise@RobotSimulator' 'Exercise@RomanNumerals' 'Exercise@SecretHandshake' 'Exercise@Sieve' 'Exercise@SpaceAge' 'Exercise@SumOfMultiples' 'Exercise@Tournament' 'Exercise@TwelveDays' 'Exercise@TwoFer' 'Exercise@Welcome' 'Exercise@WordCount') + ^ #('Exercise@Acronym' 'Exercise@Allergies' 'Exercise@Anagram' 'Exercise@ArmstrongNumbers' 'Exercise@AtbashCipher' 'Exercise@Binary' 'Exercise@BinarySearchTree' 'Exercise@Bowling' 'Exercise@CircularBuffer' 'Exercise@Clock' 'Exercise@CollatzConjecture' 'Exercise@Darts' 'Exercise@Diamond' 'Exercise@Die' 'Exercise@Etl' 'Exercise@FlattenArray' 'Exercise@Forth' 'Exercise@GradeSchool' 'Exercise@Grains' 'Exercise@Hamming' 'Exercise@HelloWorld' 'Exercise@HighScores' 'Exercise@IsbnVerifier' 'Exercise@Isogram' 'Exercise@Leap' 'Exercise@Luhn' 'Exercise@MatchingBrackets' 'Exercise@Matrix' 'Exercise@Minesweeper' 'Exercise@Pangram' 'Exercise@Proverb' 'Exercise@Raindrops' 'Exercise@ResistorColorDuo' 'Exercise@ReverseString' 'Exercise@RobotSimulator' 'Exercise@RomanNumerals' 'Exercise@SecretHandshake' 'Exercise@Sieve' 'Exercise@SimpleCipher' 'Exercise@SpaceAge' 'Exercise@SumOfMultiples' 'Exercise@Tournament' 'Exercise@TwelveDays' 'Exercise@TwoFer' 'Exercise@Welcome' 'Exercise@WordCount') ] { #category : #baselines } diff --git a/dev/src/Exercise@SimpleCipher/SimpleCipher.class.st b/dev/src/Exercise@SimpleCipher/SimpleCipher.class.st new file mode 100644 index 00000000..9c528393 --- /dev/null +++ b/dev/src/Exercise@SimpleCipher/SimpleCipher.class.st @@ -0,0 +1,88 @@ +" +I represent Simple Cipher, I do simple encoding/decoding based on either supplied or own generated key. I support operations encode, decode with input string parameter. Optionally, key can be passed, based on which encoding/decoding should happen. +" +Class { + #name : #SimpleCipher, + #superclass : #Object, + #instVars : [ + 'key', + 'alphabet' + ], + #category : #'Exercise@SimpleCipher' +} + +{ #category : #accessing } +SimpleCipher >> alphabet [ + + "lowercase alphabet, it is cached since we do not want to create copies for every loop iteration" + ^ alphabet ifNil: [ alphabet := Character alphabet ] +] + +{ #category : #exercism } +SimpleCipher >> decode: aString [ + + ^ aString withIndexCollect: [: char :idx| + self alphabet at: (self decodedIndexOf: char atKeyPosition: idx ) + ] +] + +{ #category : #exercism } +SimpleCipher >> decodedIndexOf: aChar atKeyPosition: keyPos [ + + |idx| + idx := (self alphabet indexOf: aChar) - (self keyDistanceAt: keyPos). + idx < 1 ifTrue: [ ^ self alphabet size + idx ]. + ^ idx +] + +{ #category : #exercism } +SimpleCipher >> encode: aString [ + + ^ aString withIndexCollect: [: char :idx| + self alphabet at: (self encodedIndexOf: char atKeyPosition: idx ) + ] +] + +{ #category : #exercism } +SimpleCipher >> encodedIndexOf: aChar atKeyPosition: keyPos [ + + |idx| + idx := (self alphabet indexOf: aChar) + (self keyDistanceAt: keyPos). + idx > self alphabet size ifTrue: [ ^ idx \\ self alphabet size ]. + ^ idx +] + +{ #category : #private } +SimpleCipher >> generateRandomKey [ + + | aKey | + aKey := String new: 100. + 1 to: 100 do: [:idx | aKey at: idx put: self alphabet atRandom ]. + ^ aKey +] + +{ #category : #accessing } +SimpleCipher >> key [ + + ^ key ifNil: [ key := self generateRandomKey ] +] + +{ #category : #accessing } +SimpleCipher >> key: lowerCaseString [ + + key := lowerCaseString +] + +{ #category : #exercism } +SimpleCipher >> keyDistanceAt: keyPos [ + |aPos| + "use modulo to wrap index to size of key" + aPos := keyPos \\ self key size. + + "when current index of encoded/decoded letter is equal to size of key" + aPos isZero ifTrue: [ aPos := self key size ]. + + "returns for how many letters target letter should be shifted" + ^ (self alphabet indexOf: (self key at: aPos)) - 1 + +] diff --git a/dev/src/Exercise@SimpleCipher/SimpleCipherTest.class.st b/dev/src/Exercise@SimpleCipher/SimpleCipherTest.class.st new file mode 100644 index 00000000..f39ceeb5 --- /dev/null +++ b/dev/src/Exercise@SimpleCipher/SimpleCipherTest.class.st @@ -0,0 +1,244 @@ +" +# SimpleCipher + +# Description + +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 + +## Hint + +Try using arithmetic operations like modulo to wrap indexes, when exceeded. Character, String classes are your friends too. +" +Class { + #name : #SimpleCipherTest, + #superclass : #ExercismTest, + #instVars : [ + 'simpleCipherCalculator' + ], + #category : #'Exercise@SimpleCipher' +} + +{ #category : #config } +SimpleCipherTest class >> exercise [ + + ^(ExercismExercise for: self) + isCore: false; + isAutoApproved: true; + difficulty: 4; + topics: #('strings' 'string-methods' 'loops' 'numbers' 'lists' 'randomness'); + yourself +] + +{ #category : #config } +SimpleCipherTest class >> uuid [ + "Answer a unique id for this exercise" + ^'83b2fe48-3b61-4c08-b711-51fe5b654638' +] + +{ #category : #config } +SimpleCipherTest class >> version [ + "Generated from specification: 14 September 2023" + ^'Not specified' +] + +{ #category : #running } +SimpleCipherTest >> setUp [ + super setUp. + simpleCipherCalculator := SimpleCipher new +] + +{ #category : #tests } +SimpleCipherTest >> test01_RandomKeyCipherCanEncode [ + "Tip: Remember to review the class [Comment] tab" + + + + | result | + + result := simpleCipherCalculator encode: 'aaaaaaaaaa'. + "originally: 'cipher.key.substring(0, plaintext.length)'" + self assert: result equals: (simpleCipherCalculator key copyFrom: 1 to: 10). +] + +{ #category : #tests } +SimpleCipherTest >> test02_RandomKeyCipherCanDecode [ + + + + | result | + + "originally: 'cipher.key.substring(0, expected.length)'" + result := simpleCipherCalculator decode: (simpleCipherCalculator key copyFrom: 1 to: 10). + self assert: result equals: 'aaaaaaaaaa' +] + +{ #category : #tests } +SimpleCipherTest >> test03_RandomKeyCipherIsReversibleIeIfYouApplyDecodeInAEncodedResultYouMustSeeTheSamePlaintextEncodeParameterAsAResultOfTheDecodeMethod [ + + + + | result | + + result := simpleCipherCalculator decode: (simpleCipherCalculator encode: 'abcdefghij'). + self assert: result equals: 'abcdefghij' +] + +{ #category : #tests } +SimpleCipherTest >> test04_RandomKeyCipherKeyIsMadeOnlyOfLowercaseLetters [ + + + + + self assert: (simpleCipherCalculator key matchesRegex: '^[a-z]+$'). +] + +{ #category : #tests } +SimpleCipherTest >> test05_SubstitutionCipherCanEncode [ + + + + | result | + + result := simpleCipherCalculator key: 'abcdefghij'; encode: 'aaaaaaaaaa'. + self assert: result equals: 'abcdefghij' +] + +{ #category : #tests } +SimpleCipherTest >> test06_SubstitutionCipherCanDecode [ + + + + | result | + + result := simpleCipherCalculator key: 'abcdefghij'; decode: 'abcdefghij'. + self assert: result equals: 'aaaaaaaaaa' +] + +{ #category : #tests } +SimpleCipherTest >> test07_SubstitutionCipherIsReversibleIeIfYouApplyDecodeInAEncodedResultYouMustSeeTheSamePlaintextEncodeParameterAsAResultOfTheDecodeMethod [ + + + + | result | + + result := simpleCipherCalculator key: 'abcdefghij'; encode: 'abcdefghij'. + self assert: (simpleCipherCalculator decode: result) equals: 'abcdefghij' +] + +{ #category : #tests } +SimpleCipherTest >> test08_SubstitutionCipherCanDoubleShiftEncode [ + + + + | result | + + result := simpleCipherCalculator key: 'iamapandabear'; encode: 'iamapandabear' . + self assert: result equals: 'qayaeaagaciai' +] + +{ #category : #tests } +SimpleCipherTest >> test09_SubstitutionCipherCanWrapOnEncode [ + + + + | result | + + result := simpleCipherCalculator key: 'abcdefghij'; encode: 'zzzzzzzzzz'. + self assert: result equals: 'zabcdefghi' +] + +{ #category : #tests } +SimpleCipherTest >> test10_SubstitutionCipherCanWrapOnDecode [ + + + + | result | + + result := simpleCipherCalculator key: 'abcdefghij'; decode: 'zabcdefghi'. + self assert: result equals: 'zzzzzzzzzz' +] + +{ #category : #tests } +SimpleCipherTest >> test11_SubstitutionCipherCanEncodeMessagesLongerThanTheKey [ + + + + | result | + + result := simpleCipherCalculator key: 'abc'; encode: 'iamapandabear'. + self assert: result equals: 'iboaqcnecbfcr' +] + +{ #category : #tests } +SimpleCipherTest >> test12_SubstitutionCipherCanDecodeMessagesLongerThanTheKey [ + + + + | result | + + result := simpleCipherCalculator key: 'abc'; decode: 'iboaqcnecbfcr'. + self assert: result equals: 'iamapandabear' +] diff --git a/dev/src/Exercise@SimpleCipher/package.st b/dev/src/Exercise@SimpleCipher/package.st new file mode 100644 index 00000000..0002e8db --- /dev/null +++ b/dev/src/Exercise@SimpleCipher/package.st @@ -0,0 +1 @@ +Package { #name : #'Exercise@SimpleCipher' } diff --git a/dev/src/ExercismWIP/SimpleCipherTest.class.st b/dev/src/ExercismWIP/SimpleCipherTest.class.st deleted file mode 100644 index a3e1f449..00000000 --- a/dev/src/ExercismWIP/SimpleCipherTest.class.st +++ /dev/null @@ -1,208 +0,0 @@ -" -# Simple Cipher - -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 we are lucky that -generally our little sisters are 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][1] - -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 are no fun though when your kid sister figures it out. Try -amending the code to allow us to specify a key and use that for the -shift distance. This is called a substitution cipher. - -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 alphanumeric 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. - -[1]: 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]: http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange - -## Hint - -TBD - -" -Class { - #name : #SimpleCipherTest, - #superclass : #ExercismTest, - #instVars : [ - 'simpleCipherCalculator' - ], - #category : #'ExercismWIP-SimpleCipher' -} - -{ #category : #config } -SimpleCipherTest class >> uuid [ - "Answer a unique id for this exercise" - ^'3be50a17-9d41-0d00-9015-b90f08866539' -] - -{ #category : #config } -SimpleCipherTest class >> version [ - "Generated from specification: 29 March 2019" - ^'2.0.0' -] - -{ #category : #running } -SimpleCipherTest >> setUp [ - super setUp. - simpleCipherCalculator := SimpleCipher new -] - -{ #category : #tests } -SimpleCipherTest >> test01_RandomKeyCipherCanEncode [ - | result | - - result := simpleCipherCalculator encodePlaintext: 'aaaaaaaaaa' . - self assert: result equals: 'cipher.key.substring(0, plaintext.length)' -] - -{ #category : #tests } -SimpleCipherTest >> test02_RandomKeyCipherCanDecode [ - | result | - - result := simpleCipherCalculator decodeCiphertext: 'cipher.key.substring(0, expected.length)' . - self assert: result equals: 'aaaaaaaaaa' -] - -{ #category : #tests } -SimpleCipherTest >> test03_RandomKeyCipherIsReversibleIeIfYouApplyDecodeInAEncodedResultYouMustSeeTheSamePlaintextEncodeParameterAsAResultOfTheDecodeMethod [ - | result | - - result := simpleCipherCalculator decodeCiphertext: 'cipher.encode' plaintext: 'abcdefghij' . - self assert: result equals: 'abcdefghij' -] - -{ #category : #tests } -SimpleCipherTest >> test04_RandomKeyCipherKeyIsMadeOnlyOfLowercaseLetters [ - | result | - - result := simpleCipherCalculator key. - self assert: result equals: ((Dictionary new) add: ('match'->'^[a-z]+$'); yourself) -] - -{ #category : #tests } -SimpleCipherTest >> test05_SubstitutionCipherCanEncode [ - | result | - - result := simpleCipherCalculator encodeKey: 'abcdefghij' plaintext: 'aaaaaaaaaa' . - self assert: result equals: 'abcdefghij' -] - -{ #category : #tests } -SimpleCipherTest >> test06_SubstitutionCipherCanDecode [ - | result | - - result := simpleCipherCalculator decodeCiphertext: 'abcdefghij' key: 'abcdefghij' . - self assert: result equals: 'aaaaaaaaaa' -] - -{ #category : #tests } -SimpleCipherTest >> test07_SubstitutionCipherIsReversibleIeIfYouApplyDecodeInAEncodedResultYouMustSeeTheSamePlaintextEncodeParameterAsAResultOfTheDecodeMethod [ - | result | - - result := simpleCipherCalculator decodeCiphertext: 'cipher.encode' key: 'abcdefghij' plaintext: 'abcdefghij' . - self assert: result equals: 'abcdefghij' -] - -{ #category : #tests } -SimpleCipherTest >> test08_SubstitutionCipherCanDoubleShiftEncode [ - | result | - - result := simpleCipherCalculator encodeKey: 'iamapandabear' plaintext: 'iamapandabear' . - self assert: result equals: 'qayaeaagaciai' -] - -{ #category : #tests } -SimpleCipherTest >> test09_SubstitutionCipherCanWrapOnEncode [ - | result | - - result := simpleCipherCalculator encodeKey: 'abcdefghij' plaintext: 'zzzzzzzzzz' . - self assert: result equals: 'zabcdefghi' -] - -{ #category : #tests } -SimpleCipherTest >> test10_SubstitutionCipherCanWrapOnDecode [ - | result | - - result := simpleCipherCalculator decodeCiphertext: 'zabcdefghi' key: 'abcdefghij' . - self assert: result equals: 'zzzzzzzzzz' -] - -{ #category : #tests } -SimpleCipherTest >> test11_SubstitutionCipherCanEncodeMessagesLongerThanTheKey [ - | result | - - result := simpleCipherCalculator encodeKey: 'abc' plaintext: 'iamapandabear' . - self assert: result equals: 'iboaqcnecbfcr' -] - -{ #category : #tests } -SimpleCipherTest >> test12_SubstitutionCipherCanDecodeMessagesLongerThanTheKey [ - | result | - - result := simpleCipherCalculator decodeCiphertext: 'iboaqcnecbfcr' key: 'abc' . - self assert: result equals: 'iamapandabear' -] diff --git a/exercises/practice/simple-cipher/.docs/instructions.append.md b/exercises/practice/simple-cipher/.docs/instructions.append.md new file mode 100644 index 00000000..73191dc6 --- /dev/null +++ b/exercises/practice/simple-cipher/.docs/instructions.append.md @@ -0,0 +1,3 @@ +# Instructions append + +Try using arithmetic operations like modulo to wrap indexes, when exceeded. Character, String classes are your friends too. \ No newline at end of file diff --git a/exercises/practice/simple-cipher/.docs/instructions.md b/exercises/practice/simple-cipher/.docs/instructions.md new file mode 100644 index 00000000..475af618 --- /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 00000000..bc631ae3 --- /dev/null +++ b/exercises/practice/simple-cipher/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "Bajger" + ], + "files": { + "solution": [ + "SimpleCipher.class.st" + ], + "test": [ + "SimpleCipherTest.class.st" + ], + "example": [ + ".meta/solution/SimpleCipher.class.st" + ] + }, + "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/solution/SimpleCipher.class.st b/exercises/practice/simple-cipher/.meta/solution/SimpleCipher.class.st new file mode 100644 index 00000000..9c528393 --- /dev/null +++ b/exercises/practice/simple-cipher/.meta/solution/SimpleCipher.class.st @@ -0,0 +1,88 @@ +" +I represent Simple Cipher, I do simple encoding/decoding based on either supplied or own generated key. I support operations encode, decode with input string parameter. Optionally, key can be passed, based on which encoding/decoding should happen. +" +Class { + #name : #SimpleCipher, + #superclass : #Object, + #instVars : [ + 'key', + 'alphabet' + ], + #category : #'Exercise@SimpleCipher' +} + +{ #category : #accessing } +SimpleCipher >> alphabet [ + + "lowercase alphabet, it is cached since we do not want to create copies for every loop iteration" + ^ alphabet ifNil: [ alphabet := Character alphabet ] +] + +{ #category : #exercism } +SimpleCipher >> decode: aString [ + + ^ aString withIndexCollect: [: char :idx| + self alphabet at: (self decodedIndexOf: char atKeyPosition: idx ) + ] +] + +{ #category : #exercism } +SimpleCipher >> decodedIndexOf: aChar atKeyPosition: keyPos [ + + |idx| + idx := (self alphabet indexOf: aChar) - (self keyDistanceAt: keyPos). + idx < 1 ifTrue: [ ^ self alphabet size + idx ]. + ^ idx +] + +{ #category : #exercism } +SimpleCipher >> encode: aString [ + + ^ aString withIndexCollect: [: char :idx| + self alphabet at: (self encodedIndexOf: char atKeyPosition: idx ) + ] +] + +{ #category : #exercism } +SimpleCipher >> encodedIndexOf: aChar atKeyPosition: keyPos [ + + |idx| + idx := (self alphabet indexOf: aChar) + (self keyDistanceAt: keyPos). + idx > self alphabet size ifTrue: [ ^ idx \\ self alphabet size ]. + ^ idx +] + +{ #category : #private } +SimpleCipher >> generateRandomKey [ + + | aKey | + aKey := String new: 100. + 1 to: 100 do: [:idx | aKey at: idx put: self alphabet atRandom ]. + ^ aKey +] + +{ #category : #accessing } +SimpleCipher >> key [ + + ^ key ifNil: [ key := self generateRandomKey ] +] + +{ #category : #accessing } +SimpleCipher >> key: lowerCaseString [ + + key := lowerCaseString +] + +{ #category : #exercism } +SimpleCipher >> keyDistanceAt: keyPos [ + |aPos| + "use modulo to wrap index to size of key" + aPos := keyPos \\ self key size. + + "when current index of encoded/decoded letter is equal to size of key" + aPos isZero ifTrue: [ aPos := self key size ]. + + "returns for how many letters target letter should be shifted" + ^ (self alphabet indexOf: (self key at: aPos)) - 1 + +] diff --git a/exercises/practice/simple-cipher/.meta/tests.toml b/exercises/practice/simple-cipher/.meta/tests.toml new file mode 100644 index 00000000..77e6571e --- /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/SimpleCipher.class.st b/exercises/practice/simple-cipher/SimpleCipher.class.st new file mode 100644 index 00000000..e69de29b diff --git a/exercises/practice/simple-cipher/SimpleCipherTest.class.st b/exercises/practice/simple-cipher/SimpleCipherTest.class.st new file mode 100644 index 00000000..f39ceeb5 --- /dev/null +++ b/exercises/practice/simple-cipher/SimpleCipherTest.class.st @@ -0,0 +1,244 @@ +" +# SimpleCipher + +# Description + +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 + +## Hint + +Try using arithmetic operations like modulo to wrap indexes, when exceeded. Character, String classes are your friends too. +" +Class { + #name : #SimpleCipherTest, + #superclass : #ExercismTest, + #instVars : [ + 'simpleCipherCalculator' + ], + #category : #'Exercise@SimpleCipher' +} + +{ #category : #config } +SimpleCipherTest class >> exercise [ + + ^(ExercismExercise for: self) + isCore: false; + isAutoApproved: true; + difficulty: 4; + topics: #('strings' 'string-methods' 'loops' 'numbers' 'lists' 'randomness'); + yourself +] + +{ #category : #config } +SimpleCipherTest class >> uuid [ + "Answer a unique id for this exercise" + ^'83b2fe48-3b61-4c08-b711-51fe5b654638' +] + +{ #category : #config } +SimpleCipherTest class >> version [ + "Generated from specification: 14 September 2023" + ^'Not specified' +] + +{ #category : #running } +SimpleCipherTest >> setUp [ + super setUp. + simpleCipherCalculator := SimpleCipher new +] + +{ #category : #tests } +SimpleCipherTest >> test01_RandomKeyCipherCanEncode [ + "Tip: Remember to review the class [Comment] tab" + + + + | result | + + result := simpleCipherCalculator encode: 'aaaaaaaaaa'. + "originally: 'cipher.key.substring(0, plaintext.length)'" + self assert: result equals: (simpleCipherCalculator key copyFrom: 1 to: 10). +] + +{ #category : #tests } +SimpleCipherTest >> test02_RandomKeyCipherCanDecode [ + + + + | result | + + "originally: 'cipher.key.substring(0, expected.length)'" + result := simpleCipherCalculator decode: (simpleCipherCalculator key copyFrom: 1 to: 10). + self assert: result equals: 'aaaaaaaaaa' +] + +{ #category : #tests } +SimpleCipherTest >> test03_RandomKeyCipherIsReversibleIeIfYouApplyDecodeInAEncodedResultYouMustSeeTheSamePlaintextEncodeParameterAsAResultOfTheDecodeMethod [ + + + + | result | + + result := simpleCipherCalculator decode: (simpleCipherCalculator encode: 'abcdefghij'). + self assert: result equals: 'abcdefghij' +] + +{ #category : #tests } +SimpleCipherTest >> test04_RandomKeyCipherKeyIsMadeOnlyOfLowercaseLetters [ + + + + + self assert: (simpleCipherCalculator key matchesRegex: '^[a-z]+$'). +] + +{ #category : #tests } +SimpleCipherTest >> test05_SubstitutionCipherCanEncode [ + + + + | result | + + result := simpleCipherCalculator key: 'abcdefghij'; encode: 'aaaaaaaaaa'. + self assert: result equals: 'abcdefghij' +] + +{ #category : #tests } +SimpleCipherTest >> test06_SubstitutionCipherCanDecode [ + + + + | result | + + result := simpleCipherCalculator key: 'abcdefghij'; decode: 'abcdefghij'. + self assert: result equals: 'aaaaaaaaaa' +] + +{ #category : #tests } +SimpleCipherTest >> test07_SubstitutionCipherIsReversibleIeIfYouApplyDecodeInAEncodedResultYouMustSeeTheSamePlaintextEncodeParameterAsAResultOfTheDecodeMethod [ + + + + | result | + + result := simpleCipherCalculator key: 'abcdefghij'; encode: 'abcdefghij'. + self assert: (simpleCipherCalculator decode: result) equals: 'abcdefghij' +] + +{ #category : #tests } +SimpleCipherTest >> test08_SubstitutionCipherCanDoubleShiftEncode [ + + + + | result | + + result := simpleCipherCalculator key: 'iamapandabear'; encode: 'iamapandabear' . + self assert: result equals: 'qayaeaagaciai' +] + +{ #category : #tests } +SimpleCipherTest >> test09_SubstitutionCipherCanWrapOnEncode [ + + + + | result | + + result := simpleCipherCalculator key: 'abcdefghij'; encode: 'zzzzzzzzzz'. + self assert: result equals: 'zabcdefghi' +] + +{ #category : #tests } +SimpleCipherTest >> test10_SubstitutionCipherCanWrapOnDecode [ + + + + | result | + + result := simpleCipherCalculator key: 'abcdefghij'; decode: 'zabcdefghi'. + self assert: result equals: 'zzzzzzzzzz' +] + +{ #category : #tests } +SimpleCipherTest >> test11_SubstitutionCipherCanEncodeMessagesLongerThanTheKey [ + + + + | result | + + result := simpleCipherCalculator key: 'abc'; encode: 'iamapandabear'. + self assert: result equals: 'iboaqcnecbfcr' +] + +{ #category : #tests } +SimpleCipherTest >> test12_SubstitutionCipherCanDecodeMessagesLongerThanTheKey [ + + + + | result | + + result := simpleCipherCalculator key: 'abc'; decode: 'iboaqcnecbfcr'. + self assert: result equals: 'iamapandabear' +]