OC: Original Cipher (Shift Cipher aka Caesar Cipher)

I am deepening my understanding of cryptography. While I probably should have started this 20 years ago (or even in 2014 as Bit Coin started making noise), there is no time like the present to continue learning. One of the texts I have selected to start with is Understanding Cryptography. The plan is to get my hands dirty and code along with the text (to prove to myself a minimal competency). My weapon of choice (over the past three years) is JavaScript — and while it was up to the task, it wasn’t without some challenges.

The Caesar” algorithm takes an alphabet, an offset, and (plain) text and replaces each character in the text with a value from the alphabet shifted by the number of characters in the offset. If the offset pushes past the end of the alphabet it wraps back around to the beginning (in mathematics this would be referred to as a ring). To encrypt we take a character and find its numerical value (let’s call that x) add to that the offset (let’s call that k) and modulus divide by the number of characters in the alphabet (let’s call that m). Therefore

encrypt(x) = (x + k) mod m

Decrypt is similar

decrypt(x) = (x — k) mod m

The apparent triviality in the solution (which I confirmed by modelling in Excel first) was completely exposed approximately 10 minutes into the exercise. It turns out there is some ambiguity around modulo with regard to negative numbers. There are two implementations and if you are not using the right one things don’t work. As luck would have it the default javascript modulo operator isn’t the correct implementation for this use case.

However rolling your own modulo operator isn’t difficult:

const quotient = (dividend,divisor) =>
Math.floor(dividend/divisor)
const floor_modulo = (dividend,divisor) =>
dividend — divisor * quotient(dividend,divisor)

with a host of other helpers (to keep my algorithm easy to read) I ended up with this solution:

const applyAlgoToString = algo => pipe([
map(algo),
join('')
])

const encryptCharacter = curry((alphabetMap, k, plainCharacter) => {
const alphabetLength = numberOfKeys(alphabetMap)
const x = alphabetMap[plainCharacter]
const y = floor_modulo(x + k, alphabetLength)
const cipherCharacter = findKeyByValue(alphabetMap, y)
return cipherCharacter
})

const decryptCharacter = curry((alphabetMap, k, cipherCharacter) => {
const alphabetLength = numberOfKeys(alphabetMap)
const y = alphabetMap[cipherCharacter]
const x = floor_modulo(y - k, alphabetLength)
const plainCharacter = findKeyByValue(alphabetMap, x)
return plainCharacter
})
const shiftCipher = (alphabet, k) => {
const alphabetArray = alphabet.split('')
const alphabetMap = arrayToAlphabetMap(alphabetArray)

const encryptString =
applyAlgoToString(encryptCharacter(alphabetMap, k))
const decryptString =
applyAlgoToString(decryptCharacter(alphabetMap, k))

return {
encrypt: encryptString,
decrypt: decryptString
}
}

And then execute it with

const alphabet = 'abcdefghijklmnopqrstuvwxyz'
const plain = 'attack'
const cipher = shiftCipher(alphabet, 17)
const secret = cipher.encrypt(plain)
//=> rkkrtb
const plain2 = cipher.decrypt(secret)
//=> attack

The Tests

describe('encryptCharacter', () => {
it('should encrypt a character that does not need to wrap around
the end of the alphabet', () => {
const simpleMap = {'a': 0, 'b': 1}
const k = 1
encryptCharacter(simpleMap, k, 'a').should.equal('b')
}
it('should encrypt a character that needs to wrap around the end
of the alphabet', () => {
const simpleMap = {'a': 0, 'z': 1}
const k = 1
encryptCharacter(simpleMap, k, 'z').should.equal('a')
})
})

describe('decryptCharacter', () => {
it('should decrypt a character that does not need to wrap around
the end of the alphabet', () => {
const simpleMap = {'a': 0, 'b': 1}
const k = 1
decryptCharacter(simpleMap, k, 'b').should.equal('a')
})
it('should decrypt a character that needs to wrap around the end
of the alphabet', () => {
const simpleMap = {'a': 0, 'z': 1}
const k = 1
decryptCharacter(simpleMap, k, 'a').should.equal('z')
})
})

describe('shiftCipher', () => {
describe('encrypt', () => {

it('should encrypt strings without wrapping the alphabet',
() => {
const encrypt = shiftCipher(englishAlphabet, 1).encrypt
encrypt('abba').should.equal('bccb')
})
it('should encrypt strings that will wrap the alphabet', ()
=> {
const encrypt = shiftCipher(englishAlphabet, 17).encrypt
encrypt('venom').should.equal('mvefd')
})
})

describe('decrypt', () => {
it('should decrypt strings without wrappig the alphabet', ()
=> {
const decrypt= shiftCipher(englishAlphabet, 1).decrypt
decrypt('bccb').should.equal('abba')
})

it('should decrypt strings that will wrap the alphabet', ()
=> {
const decrypt = shiftCipher(englishAlphabet, 17).decrypt
decrypt('mvefd').should.equal('venom')
})
})
})

Please note: This is not a secure cryptographic algorithm.

A 25 year software industry veteran with a passion for functional programming, architecture, mentoring / team development, xp/agile and doing the right thing.

A 25 year software industry veteran with a passion for functional programming, architecture, mentoring / team development, xp/agile and doing the right thing.