Skip to content

Commit 52fbc8a

Browse files
committedDec 17, 2020
Add Hill Cipher.
1 parent d899ae1 commit 52fbc8a

File tree

3 files changed

+75
-37
lines changed

3 files changed

+75
-37
lines changed
 

‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ a set of rules that precisely define a sequence of operations.
141141
* **Cryptography**
142142
* `B` [Polynomial Hash](src/algorithms/cryptography/polynomial-hash) - rolling hash function based on polynomial
143143
* `B` [Caesar Cipher](src/algorithms/cryptography/caesar-cipher) - simple substitution cipher
144+
* `B` [Hill Cipher](src/algorithms/cryptography/hill-cipher) - polygraphic substitution cipher
144145
* **Machine Learning**
145146
* `B` [NanoNeuron](https://github.com/trekhleb/nano-neuron) - 7 simple JS functions that illustrate how machines can actually learn (forward/backward propagation)
146147
* `B` [k-NN](src/algorithms/ml/knn) - k-nearest neighbors classification algorithm
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,46 @@
1-
import hillCipherEncrypt from '../hillCipher';
1+
import { hillCipherEncrypt, hillCipherDecrypt } from '../hillCipher';
22

33
describe('hillCipher', () => {
4-
it('should throw an error when the length of the keyString does not equal to the power of length of the message ', () => {
5-
const invalidLenghOfkeyString = () => {
6-
hillCipherEncrypt('hello', 'helloworld');
7-
};
8-
9-
expect(invalidLenghOfkeyString).toThrowError();
4+
it('should throw an exception when trying to decipher', () => {
5+
expect(hillCipherDecrypt).toThrowError('This method is not implemented yet');
106
});
7+
118
it('should throw an error when message or keyString contains none letter character', () => {
129
const invalidCharacterInMessage = () => {
1310
hillCipherEncrypt('hell3', 'helloworld');
1411
};
1512
const invalidCharacterInKeyString = () => {
1613
hillCipherEncrypt('hello', 'hel12world');
1714
};
18-
expect(invalidCharacterInMessage).toThrowError();
19-
expect(invalidCharacterInKeyString).toThrowError();
15+
expect(invalidCharacterInMessage).toThrowError(
16+
'The message and key string can only contain letters',
17+
);
18+
expect(invalidCharacterInKeyString).toThrowError(
19+
'The message and key string can only contain letters',
20+
);
21+
});
22+
23+
it('should throw an error when the length of the keyString has a square root which is not integer', () => {
24+
const invalidLengthOfKeyString = () => {
25+
hillCipherEncrypt('ab', 'ab');
26+
};
27+
expect(invalidLengthOfKeyString).toThrowError(
28+
'Invalid key string length. The square root of the key string must be an integer',
29+
);
2030
});
31+
32+
it('should throw an error when the length of the keyString does not equal to the power of length of the message', () => {
33+
const invalidLengthOfKeyString = () => {
34+
hillCipherEncrypt('ab', 'aaabbbccc');
35+
};
36+
expect(invalidLengthOfKeyString).toThrowError(
37+
'Invalid key string length. The key length must be a square of message length',
38+
);
39+
});
40+
2141
it('should encrypt passed message using Hill Cipher', () => {
2242
expect(hillCipherEncrypt('ACT', 'GYBNQKURP')).toBe('POH');
43+
expect(hillCipherEncrypt('CAT', 'GYBNQKURP')).toBe('FIN');
2344
expect(hillCipherEncrypt('GFG', 'HILLMAGIC')).toBe('SWK');
2445
});
2546
});
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
1+
// The code of an 'A' character (equals to 65).
2+
const alphabetCodeShift = 'A'.codePointAt(0);
3+
const englishAlphabetSize = 26;
14

25
/**
3-
* generate key matrix from given keyString
6+
* Generates key matrix from given keyString.
47
*
5-
* @param {integer} length
6-
* @param {string} keyString
7-
* @return {Array[][]} keyMatrix
8+
* @param {string} keyString - a string to build a key matrix (must be of matrixSize^2 length).
9+
* @return {number[][]} keyMatrix
810
*/
9-
const generateKeyMatrix = (length, keyString) => {
11+
const generateKeyMatrix = (keyString) => {
12+
const matrixSize = Math.sqrt(keyString.length);
13+
if (!Number.isInteger(matrixSize)) {
14+
throw new Error(
15+
'Invalid key string length. The square root of the key string must be an integer',
16+
);
17+
}
1018
const keyMatrix = [];
1119
let keyStringIndex = 0;
12-
for (let i = 0; i < length; i += 1) {
20+
for (let i = 0; i < matrixSize; i += 1) {
1321
const keyMatrixRow = [];
14-
for (let j = 0; j < length; j += 1) {
15-
keyMatrixRow.push((keyString.codePointAt(keyStringIndex)) % 65);
22+
for (let j = 0; j < matrixSize; j += 1) {
23+
// A → 0, B → 1, ..., a → 32, b → 33, ...
24+
const charCodeShifted = (keyString.codePointAt(keyStringIndex)) % alphabetCodeShift;
25+
keyMatrixRow.push(charCodeShifted);
1626
keyStringIndex += 1;
1727
}
1828
keyMatrix.push(keyMatrixRow);
@@ -21,48 +31,54 @@ const generateKeyMatrix = (length, keyString) => {
2131
};
2232

2333
/**
24-
* generate message vector from given message
34+
* Generates a message vector from a given message.
2535
*
26-
* @param {*} message
27-
* @return {Array} messageVector
36+
* @param {string} message - the message to encrypt.
37+
* @return {number[]} messageVector
2838
*/
2939
const generateMessageVector = (message) => {
3040
const messageVector = [];
3141
for (let i = 0; i < message.length; i += 1) {
32-
messageVector.push(message.codePointAt(i) % 65);
42+
messageVector.push(message.codePointAt(i) % alphabetCodeShift);
3343
}
3444
return messageVector;
3545
};
3646

3747
/**
38-
* validate data and encrypt message from given message and keyString
48+
* Encrypts the given message using Hill Cipher.
3949
*
4050
* @param {string} message plaintext
4151
* @param {string} keyString
4252
* @return {string} cipherString
43-
*
4453
*/
54+
export function hillCipherEncrypt(message, keyString) {
55+
// The keyString and message can only contain letters.
56+
const onlyLettersRegExp = /^[a-zA-Z]+$/;
57+
if (!onlyLettersRegExp.test(message) || !onlyLettersRegExp.test(keyString)) {
58+
throw new Error('The message and key string can only contain letters');
59+
}
60+
61+
const keyMatrix = generateKeyMatrix(keyString);
4562

46-
export default function hillCipherEncrypt(message, keyString) {
47-
const length = keyString.length ** (0.5);
4863
// keyString.length must equal to square of message.length
49-
if (!Number.isInteger(length) && length !== message.length) {
50-
throw new Error('invalid key string length');
51-
}
52-
// keyString and messange can only contain letters
53-
if (!(/^[a-zA-Z]+$/.test(message)) || !(/^[A-Za-z]+$/.test(keyString))) {
54-
throw new Error('messange and key string can only contain letters');
64+
if (keyMatrix.length !== message.length) {
65+
throw new Error('Invalid key string length. The key length must be a square of message length');
5566
}
5667

57-
const keyMatrix = generateKeyMatrix(length, keyString);
5868
const messageVector = generateMessageVector(message);
59-
let ciperString = '';
60-
for (let row = 0; row < length; row += 1) {
69+
let cipherString = '';
70+
for (let row = 0; row < keyMatrix.length; row += 1) {
6171
let item = 0;
62-
for (let column = 0; column < length; column += 1) {
72+
for (let column = 0; column < keyMatrix.length; column += 1) {
6373
item += keyMatrix[row][column] * messageVector[column];
6474
}
65-
ciperString += String.fromCharCode((item % 26) + 65);
75+
cipherString += String.fromCharCode((item % englishAlphabetSize) + alphabetCodeShift);
6676
}
67-
return ciperString;
77+
78+
return cipherString;
6879
}
80+
81+
// @TODO: Implement this method.
82+
export const hillCipherDecrypt = () => {
83+
throw new Error('This method is not implemented yet');
84+
};

0 commit comments

Comments
 (0)
Please sign in to comment.