Reputation: 11
So I have a project where I need to create a cipher which has 3 different types of ciphers built into it. In my case I have a Caesar cipher, Atbash cipher and ROT13. Whenever I try to decrypt a message however, some characters don't show up, while some show up as symbols.
Below is my code in Javascript
function decryptROT13() {
var dmsg = document.getElementById('message').value;
var dnewMsg2 = '';
for(var i = 0; i < dmsg.length; i++){
var n = dmsg.charCodeAt(i)
if(n == 32) {
dnewMsg2 += String.fromCharCode(n);
}
else if(n - 13 > 90){
dnewMsg2 += String.fromCharCode(n-13-26)
}
else{
dnewMsg2 += String.fromCharCode(n - 13)
}
}
decryptAtbash(dnewMsg2);
}
function decryptAtbash(dval) {
var dmsg = dval;
var dnewMsg1 = '';
var dtebahpla = 'ZYXWVUTSRQPONMLKJIHGFEDCBA ';
var dalphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ';
for (i = 0; i < dmsg.length; i++) {
var dcodedLetter = dmsg.charAt(i);
var dletterIndex = dalphabet.indexOf(dcodedLetter);
dnewMsg1 += dalphabet.charAt(dletterIndex);
}
decryptCaesar(dnewMsg1);
}
function decryptCaesar(dval) {
var dnewMsg = '';
var dmsg = dval;
var dmsg1 = dmsg.toUpperCase();
for(var i = 0; i < dmsg1.length; i++){
var n = dmsg1.charCodeAt(i)
if(n == 32) {
dnewMsg += String.fromCharCode(n);
}
else if(n - 3 > 90){
dnewMsg += String.fromCharCode(n - 3 - 26)
}
else{
dnewMsg += String.fromCharCode(n - 3)
}
}
dnewMsg.toUpperCase()
document.getElementById('decryptedMessage').innerHTML = dnewMsg;
}
This what the output looks like:
Upvotes: 1
Views: 282
Reputation: 135397
character codes
Our simple ciphers will work on capital letters A-Z and perform simple transformations of their ASCII character codes. We can go from a letter to a character code -
console.log("A".charCodeAt(0)) // 65
console.log("B".charCodeAt(0)) // 66
console.log("C".charCodeAt(0)) // 67
console.log("Z".charCodeAt(0)) // 90
Or from a character code to a letter -
console.log(String.fromCharCode(65)) // "A"
console.log(String.fromCharCode(66)) // "B"
console.log(String.fromCharCode(67)) // "C"
console.log(String.fromCharCode(90)) // "Z"
We will write reusable functions toCode
and fromCode
to simplify the rest of our program -
const toCode = s =>
s.toUpperCase().charCodeAt(0)
const fromCode = n =>
String.fromCharCode(n)
cipher
A cipher
is an isomorphism - ie, it is a pair of functions whereby one function can reverse the effect of the other. Each cipher we write will have an encode
and decode
function -
const cipher = (encode, decode) =>
({ encode, decode })
Character A
starts at offset 65
, Z
ends at 90
, and we don't want to apply our cipher to characters out of this range. Instead of handling this logic in each cipher, it would be nice if we could write our ciphers that work on a simple A-Z charset that goes from 0-25 -
(0,A) (1,B) (2,C) (3,D) (4,E)
(5,F) (6,G) (7,H) (8,I) (9,J)
(10,K) (11,L) (12,M) (13,N) (14,O)
(15,P) (16,Q) (17,R) (18,S) (19,T)
(20,U) (21,V) (22,W) (23,X) (24,Y)
(25,Z)
For any given alg
orithm and character code n
, we can filter
characters that are out of the 65-90 range, and automatically apply appropriate offset to the algorithm's response -
const filter = alg => n =>
n < 65 || n > 90 // if n is out or range
? n // simply return n
: 65 + alg(n - 65) // apply offset to alg's response
atbash
Now let's write our first cipher, atbash
-
atbash(n) | cipher |
---|---|
25 - 0 = 25 | A → Z |
25 - 1 = 24 | B → Y |
25 - 2 = 23 | C → X |
25 - 23 = 2 | X → C |
25 - 24 = 1 | Y → B |
25 - 25 = 0 | Z → A |
The implementation is simple. As the table above reveals, the process to encode atbash is exactly the same as the decoding process. In other words, atbash
is a pair of identical functions -
const atbash =
cipher(n => 25 - n, n => 25 - n)
rot13
Next we look at our second cipher, rot13
-
rot13(n) | cipher |
---|---|
rot13(0) | A → N |
rot13(1) | B → O |
rot13(2) | C → P |
rot13(23) | X → K |
rot13(24) | Y → L |
rot13(25) | Z → M |
Rot13 is a Caesar shift of 13, so we can simply define it as
const rot13 =
caesar(13)
caesar
And lastly Caesar allows us shift the character code by a specified amount -
caesar(shift,n) | cipher |
---|---|
caesar(-3,0) | A → X |
caesar(-2,0) | A → Y |
caesar(-1,0) | A → Z |
caesar(0,0) | A → A |
caesar(1,0) | A → B |
caesar(2,0) | A → C |
caesar(3,0) | A → D |
caesar(-100,0) | A → E |
caesar(100,0) | A → W |
We can implement caesar
by parameterizing a cipher
with a shift
amount. period
is used to perform basic modular arithmetic and support any positive or negative shift amount -
const caesar = shift =>
cipher
( n => period(26, n + shift) // plus shift for encode
, n => period(26, n - shift) // minus shift for decode
)
const period = (z, n) =>
(z + n % z) % z
encode and decode
The ciphers we defined operate on character codes but we don't expect the user to do handle that conversion manually. We will provide a simple encode
function to work with -
encode(atbash, "A") // "Z"
encode(atbash, "B") // "Y"
encode(atbash, "C") // "Z"
encode(caesar(1), "A") // "B"
encode(caesar(2), "A") // "C"
encode(caesar(3), "A") // "D"
As well as the reversing decode
function -
decode(atbash, "Z") // "A"
decode(atbash, "Y") // "B"
decode(atbash, "Z") // "C"
decode(caesar(1), "B") // "A"
decode(caesar(2), "C") // "A"
decode(caesar(3), "D") // "A"
encode
and decode
accept a cipher, c
, and an input string, s
-
const encode = (c, s) =>
transform(c.encode, s)
const decode = (c, s) =>
transform(c.decode, s)
The transform
procedure is the same whether you are encoding or decoding. The only things that changes is the alg
orithm being used -
const transform = (alg, s) =>
Array
.from(s, composeL(toCode, filter(alg), fromCode))
.join("")
myCipher
Finally we write composeCipher
which allows you to construct your own complex cipher
from a sequence of ciphers. The sequence of encoders runs from left-to-right using composeL
. Naturally, the sequence of decoders runs in reverse from right-to-left using composeR
-
const composeCipher = (...ciphers) =>
cipher
( composeL(...ciphers.map(c => c.encode)) // encode
, composeR(...ciphers.map(c => c.decode)) // decode
)
const myCipher =
composeCipher(rot13, atbash, caesar(3))
console.log(encode(myCipher, "DON'T CAUSE PANIC!"))
// MBC'W NPVXL APCHN!
console.log(decode(myCipher, "MBC'W NPVXL APCHN!"))
// DON'T CAUSE PANIC!
reusable functions
Above we use composeL
(left) and composeR
(right) which are generic function composition procedures. These allow us to building a single function out of a sequence of input functions. You don't need to understand their implementation in order to use them -
// func.js
const composeL = (...fs) =>
x => fs.reduce((r, f) => f(r), x)
const composeR = (...fs) =>
x => fs.reduceRight((r, f) => f(r), x)
// ...
export { composeL, composeR, ... }
modules
Like we did with the func
module above, you should bundle your very own cipher
module to keep your code nice and tidy. This also allows us to choose which parts of the module should be accessible to the user -
// cipher.js
import { composeL, composeR } from "./func.js"
const atbash = ...
const caesar = ...
const cipher = ...
const compose = ...
const decode = ...
const encode = ...
const fromCode = ...
const rot13 = ...
const toCode = ...
const transform = ...
export { atbash, caesar, cipher, compose, decode, encode, rot13 }
When we write our program, we only import the parts we need -
// main.js
import { compose, atbash, caesar, rot13 } from "./cipher.js"
const superSecret =
compose(rot13, atbash, caesar(3))
console.log(encode(superSecret, "DON'T CAUSE PANIC!"))
console.log(decode(superSecret, "MBC'W NPVXL APCHN!"))
MBC'W NPVXL APCHN!
DON'T CAUSE PANIC!
demo
Expand the snippet below to verify the result in your own browser -
// cipher.js
const fromCode = n => String.fromCharCode(n)
const toCode = s => s.toUpperCase().charCodeAt(0)
const cipher = (encode, decode) =>
({ encode, decode })
const atbash =
cipher(n => 25 - n, n => 25 - n)
const caesar = shift =>
cipher
( n => period(26, n + shift)
, n => period(26, n - shift)
)
const rot13 =
caesar(13)
const filter = alg => n =>
n < 65 || n > 90
? n
: 65 + alg(n - 65)
const period = (z, n) =>
(z + n % z) % z
const transform = (f, s) =>
Array
.from(s, composeL(toCode, filter(f), fromCode))
.join("")
const encode = (alg, s) =>
transform(alg.encode, s)
const decode = (alg, s) =>
transform(alg.decode, s)
const composeCipher = (...ciphers) =>
cipher
( composeL(...ciphers.map(c => c.encode))
, composeR(...ciphers.map(c => c.decode))
)
// func.js
const composeL = (...fs) =>
x => fs.reduce((r, f) => f(r), x)
const composeR = (...fs) =>
x => fs.reduceRight((r, f) => f(r), x)
// main.js
const myCipher =
composeCipher(rot13, atbash, caesar(3))
console.log(encode(myCipher, "DON'T CAUSE PANIC!"))
console.log(decode(myCipher, "MBC'W NPVXL APCHN!"))
MBC'W NPVXL APCHN!
DON'T CAUSE PANIC!
Upvotes: 1