Skip to content

feat: add Substitution Cipher algorithm and tests #1773

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Next Next commit
feat: add Substitution Cipher algorithm and tests
  • Loading branch information
mmohamedkhaled committed Apr 4, 2025
commit c93cda32b70e2943411c7da7b03e65e255de68c9
59 changes: 59 additions & 0 deletions Ciphers/SubstitutionCipher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Substitution Cipher
*
* A monoalphabetic substitution cipher replaces each letter of the plaintext
* with another letter based on a fixed permutation (key) of the alphabet.
* https://en.wikipedia.org/wiki/Substitution_cipher
*/

const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
const defaultKey = 'QWERTYUIOPASDFGHJKLZXCVBNM'

/**
* Encrypts a string using a monoalphabetic substitution cipher
* @param {string} text - The text to encrypt
* @param {string} key - The substitution key (must be 26 uppercase letters)
* @returns {string}
*/
export function substitutionCipherEncryption(text, key = defaultKey) {
if (key.length !== 26 || !/^[A-Z]+$/.test(key)) {
throw new RangeError('Key must be 26 uppercase English letters.')
}

let result = ''
const textUpper = text.toUpperCase()
for (let i = 0; i < textUpper.length; i++) {
const char = textUpper[i]
const index = alphabet.indexOf(char)
if (index !== -1) {
result += key[index]
} else {
result += char
}
}
return result
}
/**
* Decrypts a string encrypted with the substitution cipher
* @param {string} text - The encrypted text
* @param {string} key - The substitution key used during encryption
* @returns {string}
*/
export function substitutionCipherDecryption(text, key = defaultKey) {
if (key.length !== 26 || !/^[A-Z]+$/.test(key)) {
throw new RangeError('Key must be 26 uppercase English letters.')
}

let result = ''
const textUpper = text.toUpperCase()
for (let i = 0; i < textUpper.length; i++) {
const char = textUpper[i]
const index = key.indexOf(char)
if (index !== -1) {
result += alphabet[index]
} else {
result += char
}
}
return result
}
30 changes: 30 additions & 0 deletions Ciphers/test/SubstitutionCipher.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, it, expect } from 'vitest'
import {
substitutionCipherEncryption,
substitutionCipherDecryption
} from './SubstitutionCipher'

describe('Substitution Cipher', () => {
const key = 'QWERTYUIOPASDFGHJKLZXCVBNM'

it('correctly encrypts a message', () => {
const encrypted = substitutionCipherEncryption('HELLO WORLD', key)
expect(encrypted).toBe('ITSSG VGKSR')
})

it('correctly decrypts a message', () => {
const decrypted = substitutionCipherDecryption('ITSSG VGKSR', key)
expect(decrypted).toBe('HELLO WORLD')
})

it('handles non-alphabetic characters', () => {
const encrypted = substitutionCipherEncryption('Test! 123', key)
expect(encrypted).toBe('ZTLZ! 123')
})

it('throws error for invalid key', () => {
expect(() => substitutionCipherEncryption('HELLO', 'BADKEY')).toThrow(
RangeError
)
})
})
Loading