For this article I have decided to write a Caesar Cipher algorithm.
First let me give you a brief intro to this subject. A Caesar Cipher is a basic encryption algorithm that takes all the letters of a message and encrypts them by shifting them over some fixed amount. For example a Caesar Cipher with an offset of 15 would map the alphabet as followed:
Alphabet
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Shifted Alphabet
PQRSTUVWXYZABCDEFGHIJKLMNO
Message Example
Input: WELCOME TO RISING GALAXY
Output: LTARDBT ID GXHXCV VPAPMN
Let’s get started and find out whether we are up for this challenge.
STEP ONE – let’s write an enumeration and define our Encryption modes.
import Foundation
//YOU ARE NOW HERE
enum Encryption { case encrypting, decrypting }
STEP TWO – Now, go ahead and create a class, name this class CCipher. Soon we are going to define our encryption method within this class. Let’s add a single private alphabet property of type String to our class. As we are going to initialise this property through the init method, you just have to declare this variable, there is no need to initialise it with a default value.
//YOU ARE NOW HERE
class CCipher {
private var alphabet: String
init(_ alphabet: String) {
self.alphabet = alphabet
}
}
STEP THREE – Next, we are going to write our encryption algorithm. Our function should take three input parameters and have a String return type. The first input parameter is going to accept our message and should be of type String.
The second input parameter will be an Int, we are going to use this parameter to define the amount of time that we desire to shift each character.
The last input parameter is going to be of type Encryption, the handy dandy enumeration type that we created at the start of this article, remember?. This last input parameter should allow us to specify what we are expecting from our algorithm to do for us, e.g are we encrypting or decrypting a message?
//YOU ARE NOW HERE
func encryption(_ message: String, _ n: Int = 15, mode: Encryption) -> String {
//Just for now let's satisfy our method and return an empty String
return " "
}
STEP FOUR -Now Inside the encryption method, let’s go ahead and create a result property of type String. Later we are going to append all of the shifted characters to this variable, and return the final result as our method’s return type.
func encryption(_ message: String, _ n: Int = 15, mode: Encryption) -> String {
//YOU ARE NOW HERE
var result: String = ""
return restul
}
STEP FIVE – Let’s create two more properties. These are going to be two Arrays of type Character. The first Array will be used to chop our message string into individual letters of type Character. And the other one will be used to store alphabet letters. Later we are going to use the alphabet array as a lookup table, this will enable us to find the correct substitute for each character from the message array.
func encryption(_ message: String, _ n: Int = 15, mode: Encryption) -> String {
//YOU ARE NOW HERE
var result: String = ""
let messageChars = Array(message)
let lookup = Array(self.alphabet)
return restul
}
STEP SIX – Things are about to get exciting 😎 are you ready?. Let’s write a simple for-loop and loop through our message characters (messageChars). Inside this loop we should create a reference property of type character, this variable is going to refer to the shifted character.
Let me explain this to you in an expanded manner. During each loop iteration we are looking at the current letter from the messages array, then through some magic, which we’ll be implementing shortly. We’ll try to find the correct shifted substitute for the current letter. Once we have found the correct letter we are going to assign that character to the reference variable that we just created, and at the end of each loop append the shifted character to the result variable.
func encryption(_ message: String, _ n: Int = 15, mode: Encryption) -> String {
var result: String = ""
let messageChars = Array(message)
let lookup = Array(self.alphabet)
//YOU ARE NOW HERE
for c in messageChars {
var encryptedMessage: Character = " "
}
return restul
}
STEP SEVEN – Next, we would like to find out the index of the current character from the messageChars within the alphabet. So, if the current letter is B, its index would be 1.
We could use the firstIndex(of:) method, which is offered by Apple/Swift for free. But hey, we are good software engineers and love to write thing from scratch, right?
//First input takes the current Character
//Second input takes the lookup array i.e Alphabet array
//This method returns an Integer
private func findIndex(of c: Character, _ arr: [String.Element]) -> Int {
var n: Int = .zero
let m = arr.count
var found: Bool = false
//While Character is not found run this loop
while !found {
//If the index that we are trying to get is less than,
//array count continue
if n < m {
//Using a simple ternary operation to determine found value
//if c i equal to the n(th) array element found becomes true
//else found remains false
found = c == arr[n] ? true : false
//As long is found remains false, we keep adding one to n
//as soon as found becomes true we assign the current n value
//to n and exit the loop
n = !found ? (n + 1) : n
}
}
return n
}
STEP EIGHT – Let’s head back to our encryption algorithm. Since we are only interested in Characters/ Letters and not any whitespaces, we should write a simple if statement in which we clearly define if Character is not a whitespace. If the current Character is in fact a whitespace, we would like to append it to the result variable immediately.
func encryption(_ message: String, _ n: Int = 15, mode: Encryption) -> String {
var result: String = ""
let messageChars = Array(message)
let lookup = Array(self.alphabet)
for c in messageChars {
var encryptedMessage: Character = " "
//YOU ARE NOW HERE
if c != " " {
}
result.append(encryptedMessage)
}
return restul
}
STEP NINE – During this step we are going to finalize our algorithm. Therefore we need two more new properties and a Switch statement. Without further ado, within the if statement go ahead and create the first property and grab the index of the current Character, this should be done through using our own findIndex method that we created minutes ago.
Next we should create an Integer variable, which is going to be used as an index reference for the shifted character.
Lastly, go ahead and create a Switch statements which gives us access to the encryption cases of the mode parameter.
case . encrypting: key = ((index + n) % lookup.count)
case . decrypting: key = ((index – n) % lookup.count)
func encryption(_ message: String, _ n: Int = 15, mode: Encryption) -> String {
var result: String = ""
let messageChars = Array(message)
let lookup = Array(self.alphabet)
for c in messageChars {
var encryptedMessage: Character = " "
if c != " " {
//YOU ARE NOW HERE
let index = findIndex(of: c, lookup)
var key: Int = .zero
switch mode {
case .encrypting: key = ((index + n) % lookup.count)
case .decrypting: key = ((index - n) % lookup.count)
}
encryptedMessage = lookup[key]
}
result.append(encryptedMessage)
}
return result
}
Let me explain to you what is happening inside these cases. Inside each case we are calculating the offset of each Character and assigning the calculated value to the key property. Then we are using the same key property to look into our lookup array and find out the correct substitute for the current Character.
Right now our algorithm is shifting Characters and and is correctly appending the final result to our result property. However we are not done yet. Our encryption algorithm as it is written right now has two huge bugs 🐞🦟.
You may ask yourself what those bugs are. Well, we are able to encrypte our messages, but do you think we’d get the same message back when decrypting?.
The Answer is NO.
Imagine we would like to encrypt a single letter, for instance letter Y. This letter would have an index of 24 within the alphabet array. Let’s say the offset is 15 and for the length we are using the alphabet letters count. (24 + 15) % 26 will give us 13. If we’d take a quick look inside our lookup table we’ll see that letter N becomes our encrypted equivalent of Y.
Now let see whether we are able to get Y back by applying almost the same calculation. With the only difference, this time we should subtract the letter’s index from the offset value. (13 – 15) % 26 will give us -2. This will definitely produce an out of range error. Even if we convert this value to a positive value, we’d get the wrong result,
turning -2 into 2 will give us C, and that’s not what we are looking for.
Let me explain it to you in code
//Letter Y has an index of 24
// Offset is 15
// Length is 26
//Let's see what index we'd get, through a simple calculation
let yIndex = 24
let offset = 15
let length = 26
let encryptedIndex = (yIndex + offset) % length
print(encryptedIndex) // 13
//Encrypted letter has the index 13 (N)
// Offset is 15
// Length is 26
let nIndex = 13
let decryptedIndex = (nIndex - offset) % length
print(decryptedIndex) // -2
//A. This will produce an out of range error
//B. Even if we convert this value into a positive value
// it would become 2 which will give us letter C and not Y
Also one of our cases may produce a negative key, e.g -1 or -15 or -25, since we don’t have any negative indices this will definitely cause an out of range error and crash our algorithm.
So we need to write two more methods to prevent those bugs from causing our algorithm to fail.
One method to normalize the index range, and another method to convert negative values into positive values. For the last functionality we could use the standard Swift abs() method. But again, as good iOS engineers we should be able to write our own methods, right!?
private func _abs( _ n: Int ) -> Int {
//If n is less than zero, it's negative
return n < 0 ? n * -1 : n
}
private func normalize( _ key: Int, _ length: Int ) -> Int {
//If n is less than zero, it's negative and out of range
// So we are adding key to the length to get a normalized index
return key < 0 ? (key + length) : key
}
FINAL STEP – let’s head back to our encryption algorithm for the last time. After the closing curly brace of the Switch statement, go ahead and use both _abs and normalize methods and assign the correct Character to the encryptedMessage property.
func encryption(_ message: String, _ n: Int = 15, mode: Encryption) -> String {
var result: String = ""
let messageChars = Array(message)
let lookup = Array(self.alphabet)
for c in messageChars {
var encryptedMessage: Character = " "
if c != " " {
let index = findIndex(of: c, lookup)
var key: Int = .zero
switch mode {
case .encrypting: key = ((index + n) % lookup.count)
case .decrypting: key = ((index - n) % lookup.count)
}
//YOU ARE NOW HERE
encryptedMessage = lookup[_abs(normalize(key, lookup.count))]
}
result.append(encryptedMessage)
}
return result
}
Well folks, we managed to write a Caesar Cipher algorithm from scratch. I hope you liked this article, Thank you for your time.
import Foundation
enum Encryption { case encrypting, decrypting }
class CCipher {
private var alphabet: String
init(_ alphabet: String) {
self.alphabet = alphabet
}
func encryption(_ message: String, _ n: Int = 15, mode: Encryption) -> String {
var result: String = ""
let messageChars = Array(message)
let lookup = Array(self.alphabet)
for c in messageChars {
var encryptedMessage: Character = " "
if c != " " {
let index = findIndex(of: c, lookup)
var key: Int = .zero
switch mode {
case .encrypting: key = ((index + n) % lookup.count)
case .decrypting: key = ((index - n) % lookup.count)
}
encryptedMessage = lookup[_abs(normalize(key, lookup.count))]
}
result.append(encryptedMessage)
}
return result
}
private func _abs( _ n: Int ) -> Int {
return n < 0 ? n * -1 : n
}
private func normalize( _ key: Int, _ length: Int ) -> Int {
return key < 0 ? (key + length) : key
}
private func findIndex(of c: Character, _ arr: [String.Element]) -> Int {
var n: Int = .zero
let m = arr.count
var found: Bool = false
while !found {
if n < m {
found = c == arr[n] ? true : false
n = !found ? (n + 1) : n
}
}
return n
}
}
let ccipher = CCipher("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
ccipher.encryption("WELCOME TO RISING GALAXY", mode: .encrypting)
//LTARDBT ID GXHXCV VPAPMN
ccipher.encryption("LTARDBT ID GXHXCV VPAPMN", mode: .decrypting)
//WELCOME TO RISING GALAXY