@irfanshadikrishad/cipher 1.4.1 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -13
- package/dist/Cipher.d.ts +5 -0
- package/dist/ciphers/ECC.d.ts +6 -3
- package/dist/ciphers/ECC.js +16 -10
- package/dist/ciphers/Nihilist.d.ts +12 -0
- package/dist/ciphers/Nihilist.js +109 -0
- package/dist/index.js +2 -0
- package/package.json +12 -11
package/README.md
CHANGED
|
@@ -43,19 +43,20 @@ console.log(caesar.encrypt('hello world')) // Output: "nkrru cuxrj"
|
|
|
43
43
|
|
|
44
44
|
This library provides implementations of various classical and modern ciphers:
|
|
45
45
|
|
|
46
|
-
| Cipher | Type | Key required? | Strength | Used In/Notes
|
|
47
|
-
| --------------------------------------------------- | ---------------------------------------- | ------------- | --------- |
|
|
48
|
-
| [Caesar Cipher](/docs/en/ciphers/CAESAR.md) | Substitution | No | Low | Ancient Rome, Simple Obsfuscation
|
|
49
|
-
| [Atbash Cipher](/docs/en/ciphers/ATBASH.md) | Substitution | No | Low | Hebrew Cipher, Basic Encryption
|
|
50
|
-
| [Playfair Cipher](/docs/en/ciphers/PLAYFAIR.md) | Diagraph-based | Yes | Medium | Used in WWI & WWII
|
|
51
|
-
| [Vigenère Cipher](/docs/en/ciphers/VIGENERE.md) | Polyalphabetic | Yes | Medium | Used in Historical Documents
|
|
52
|
-
| [The Alphabet Cipher](/docs/en/ciphers/ALPHABET.md) | Polyalphabetic | Yes | Medium | Inspired by Vigenere, Cryptography Puzzles
|
|
53
|
-
| [Salsa20](/docs/en/ciphers/SALSA20.md) | Stream Cipher | Yes | High | Modern Cryptography, Secure Communications
|
|
54
|
-
| [ADFGVX](/docs/en/ciphers/ADFGVX.md) | Polybius Square + Columnar Transposition | Yes | Medium | Used in WWI, Known for 6x6 polybius square
|
|
55
|
-
| [AES](/docs/en/ciphers/AES.md) | Symmetric Block Cipher | Yes | High | Also known as, Rijndael
|
|
56
|
-
| [DES](/docs/en/ciphers/DES.md) | Symmetric Block Cipher | Yes | Medium | 56-bit key, Used in legacy systems, replaced by AES
|
|
57
|
-
| [ECC](/docs/en/ciphers/ECC.md) | Asymmetric (Public-Key Cryptography) | Yes | Very High | Used in modern systems like Bitcoin, TLS, JWT, etc.
|
|
58
|
-
| [ROT13](/docs/en/ciphers/ROT13.md) | Substitution (Caesar variant) | No | Very Low | Simple text obfuscation, not secure
|
|
46
|
+
| Cipher | Type | Key required? | Strength | Used In/Notes |
|
|
47
|
+
| --------------------------------------------------- | ---------------------------------------- | ------------- | --------- | ----------------------------------------------------- |
|
|
48
|
+
| [Caesar Cipher](/docs/en/ciphers/CAESAR.md) | Substitution | No | Low | Ancient Rome, Simple Obsfuscation |
|
|
49
|
+
| [Atbash Cipher](/docs/en/ciphers/ATBASH.md) | Substitution | No | Low | Hebrew Cipher, Basic Encryption |
|
|
50
|
+
| [Playfair Cipher](/docs/en/ciphers/PLAYFAIR.md) | Diagraph-based | Yes | Medium | Used in WWI & WWII |
|
|
51
|
+
| [Vigenère Cipher](/docs/en/ciphers/VIGENERE.md) | Polyalphabetic | Yes | Medium | Used in Historical Documents |
|
|
52
|
+
| [The Alphabet Cipher](/docs/en/ciphers/ALPHABET.md) | Polyalphabetic | Yes | Medium | Inspired by Vigenere, Cryptography Puzzles |
|
|
53
|
+
| [Salsa20](/docs/en/ciphers/SALSA20.md) | Stream Cipher | Yes | High | Modern Cryptography, Secure Communications |
|
|
54
|
+
| [ADFGVX](/docs/en/ciphers/ADFGVX.md) | Polybius Square + Columnar Transposition | Yes | Medium | Used in WWI, Known for 6x6 polybius square |
|
|
55
|
+
| [AES](/docs/en/ciphers/AES.md) | Symmetric Block Cipher | Yes | High | Also known as, Rijndael |
|
|
56
|
+
| [DES](/docs/en/ciphers/DES.md) | Symmetric Block Cipher | Yes | Medium | 56-bit key, Used in legacy systems, replaced by AES |
|
|
57
|
+
| [ECC](/docs/en/ciphers/ECC.md) | Asymmetric (Public-Key Cryptography) | Yes | Very High | Used in modern systems like Bitcoin, TLS, JWT, etc. |
|
|
58
|
+
| [ROT13](/docs/en/ciphers/ROT13.md) | Substitution (Caesar variant) | No | Very Low | Simple text obfuscation, not secure |
|
|
59
|
+
| [Nihilist](/docs/en/ciphers/Nihilist.md) | Polybius Square + Addition | Yes | Medium | Used by Russian Nihilists, Polybius + additive cipher |
|
|
59
60
|
|
|
60
61
|
More ciphers coming soon...
|
|
61
62
|
|
package/dist/Cipher.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Atbash } from './ciphers/Atbash.js';
|
|
|
5
5
|
import { Caesar } from './ciphers/Caesar.js';
|
|
6
6
|
import { DES } from './ciphers/DES.js';
|
|
7
7
|
import { ECC } from './ciphers/ECC.js';
|
|
8
|
+
import { Nihilist } from './ciphers/Nihilist.js';
|
|
8
9
|
import { Playfair } from './ciphers/Playfair.js';
|
|
9
10
|
import { ROT13 } from './ciphers/ROT13.js';
|
|
10
11
|
import { Salsa20 } from './ciphers/Salsa20.js';
|
|
@@ -68,6 +69,10 @@ export declare abstract class Cipher {
|
|
|
68
69
|
* ROT13 is a simple letter substitution cipher that replaces a letter with the 13th letter after it in the Latin alphabet.
|
|
69
70
|
*/
|
|
70
71
|
static ROT13: typeof ROT13;
|
|
72
|
+
/**
|
|
73
|
+
* Nihilist cipher is a manually operated symmetric encryption cipher, originally used by Russian Nihilists in the 1880s to organize terrorism against the tsarist regime.
|
|
74
|
+
*/
|
|
75
|
+
static Nihilist: typeof Nihilist;
|
|
71
76
|
abstract encrypt(text: string): string | Promise<string>;
|
|
72
77
|
abstract decrypt(text: string): string | Promise<string>;
|
|
73
78
|
}
|
package/dist/ciphers/ECC.d.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
import { webcrypto } from 'node:crypto';
|
|
1
2
|
import { Cipher } from '../Cipher.js';
|
|
3
|
+
type NodeCryptoKey = webcrypto.CryptoKey;
|
|
2
4
|
export declare class ECC extends Cipher {
|
|
3
5
|
private recipientPublicKey?;
|
|
4
6
|
private ownPrivateKey?;
|
|
5
7
|
static recipientPublicKey: string;
|
|
6
8
|
static ownPrivateKey: string;
|
|
7
|
-
constructor(recipientPublicKey?:
|
|
9
|
+
constructor(recipientPublicKey?: NodeCryptoKey, ownPrivateKey?: NodeCryptoKey);
|
|
8
10
|
static generate(): Promise<ECC>;
|
|
9
11
|
encrypt(plaintext: string): Promise<string>;
|
|
10
12
|
decrypt(data: string): Promise<string>;
|
|
11
13
|
exportPublicKey(): Promise<string>;
|
|
12
|
-
static importPublicKey(hex: string): Promise<
|
|
14
|
+
static importPublicKey(hex: string): Promise<NodeCryptoKey>;
|
|
13
15
|
static bufToHex(buf: Uint8Array): string;
|
|
14
|
-
static hexToBuf(hex: string):
|
|
16
|
+
static hexToBuf(hex: string): ArrayBuffer;
|
|
15
17
|
}
|
|
18
|
+
export {};
|
package/dist/ciphers/ECC.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { webcrypto } from 'node:crypto';
|
|
2
2
|
import { Cipher } from '../Cipher.js';
|
|
3
|
+
const crypto = webcrypto;
|
|
3
4
|
export class ECC extends Cipher {
|
|
4
5
|
constructor(recipientPublicKey, ownPrivateKey) {
|
|
5
6
|
super();
|
|
@@ -11,8 +12,9 @@ export class ECC extends Cipher {
|
|
|
11
12
|
return new ECC(publicKey, privateKey);
|
|
12
13
|
}
|
|
13
14
|
async encrypt(plaintext) {
|
|
14
|
-
if (!this.recipientPublicKey)
|
|
15
|
+
if (!this.recipientPublicKey) {
|
|
15
16
|
throw new Error('Recipient public key not set');
|
|
17
|
+
}
|
|
16
18
|
const ephemeral = await ECC.generate();
|
|
17
19
|
const sharedKey = await crypto.subtle.deriveKey({ name: 'ECDH', public: this.recipientPublicKey }, ephemeral.ownPrivateKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt']);
|
|
18
20
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
@@ -25,31 +27,35 @@ export class ECC extends Cipher {
|
|
|
25
27
|
return JSON.stringify(payload);
|
|
26
28
|
}
|
|
27
29
|
async decrypt(data) {
|
|
28
|
-
if (!this.ownPrivateKey)
|
|
30
|
+
if (!this.ownPrivateKey) {
|
|
29
31
|
throw new Error('Own private key not set');
|
|
32
|
+
}
|
|
30
33
|
const { iv, ciphertext, ephemeralPublicHex } = JSON.parse(data);
|
|
31
34
|
const ephemeralPubKey = await ECC.importPublicKey(ephemeralPublicHex);
|
|
32
35
|
const sharedKey = await crypto.subtle.deriveKey({ name: 'ECDH', public: ephemeralPubKey }, this.ownPrivateKey, { name: 'AES-GCM', length: 256 }, false, ['decrypt']);
|
|
33
|
-
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: ECC.hexToBuf(iv) }, sharedKey, ECC.hexToBuf(ciphertext));
|
|
36
|
+
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: new Uint8Array(ECC.hexToBuf(iv)) }, sharedKey, ECC.hexToBuf(ciphertext));
|
|
34
37
|
return new TextDecoder().decode(decrypted);
|
|
35
38
|
}
|
|
36
39
|
async exportPublicKey() {
|
|
37
|
-
const
|
|
40
|
+
const key = this.recipientPublicKey ?? this.ownPrivateKey;
|
|
41
|
+
if (!key) {
|
|
42
|
+
throw new Error('No key available to export');
|
|
43
|
+
}
|
|
44
|
+
const raw = await crypto.subtle.exportKey('raw', key);
|
|
38
45
|
return ECC.bufToHex(new Uint8Array(raw));
|
|
39
46
|
}
|
|
40
47
|
static async importPublicKey(hex) {
|
|
41
|
-
|
|
42
|
-
return crypto.subtle.importKey('raw', raw, { name: 'ECDH', namedCurve: 'P-256' }, true, []);
|
|
48
|
+
return crypto.subtle.importKey('raw', ECC.hexToBuf(hex), { name: 'ECDH', namedCurve: 'P-256' }, true, []);
|
|
43
49
|
}
|
|
44
|
-
// Helpers
|
|
50
|
+
// ===== Helpers =====
|
|
45
51
|
static bufToHex(buf) {
|
|
46
52
|
return [...buf].map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
47
53
|
}
|
|
48
54
|
static hexToBuf(hex) {
|
|
49
55
|
const bytes = new Uint8Array(hex.length / 2);
|
|
50
56
|
for (let i = 0; i < bytes.length; i++) {
|
|
51
|
-
bytes[i] = parseInt(hex.
|
|
57
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
52
58
|
}
|
|
53
|
-
return bytes;
|
|
59
|
+
return bytes.buffer;
|
|
54
60
|
}
|
|
55
61
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Cipher } from '../Cipher.js';
|
|
2
|
+
export declare class Nihilist extends Cipher {
|
|
3
|
+
private key;
|
|
4
|
+
private keyword;
|
|
5
|
+
constructor(key: string, keyword: string);
|
|
6
|
+
private prepareKey;
|
|
7
|
+
private getCoordinates;
|
|
8
|
+
private textToNumbers;
|
|
9
|
+
private numbersToText;
|
|
10
|
+
encrypt(plaintext: string): string;
|
|
11
|
+
decrypt(ciphertext: string): string;
|
|
12
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Cipher } from '../Cipher.js';
|
|
2
|
+
export class Nihilist extends Cipher {
|
|
3
|
+
constructor(key, keyword) {
|
|
4
|
+
super();
|
|
5
|
+
this.key = this.prepareKey(key.toUpperCase().replace(/[^A-Z]/g, ''));
|
|
6
|
+
this.keyword = keyword.toUpperCase().replace(/[^A-Z]/g, '');
|
|
7
|
+
}
|
|
8
|
+
prepareKey(key) {
|
|
9
|
+
const alphabet = 'ABCDEFGHIKLMNOPQRSTUVWXYZ'; // I/J combined
|
|
10
|
+
let result = '';
|
|
11
|
+
const seen = new Set();
|
|
12
|
+
// Add key letters first
|
|
13
|
+
for (const char of key) {
|
|
14
|
+
const normalizedChar = char === 'J' ? 'I' : char;
|
|
15
|
+
if (!seen.has(normalizedChar)) {
|
|
16
|
+
result += normalizedChar;
|
|
17
|
+
seen.add(normalizedChar);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// Add remaining alphabet letters
|
|
21
|
+
for (const char of alphabet) {
|
|
22
|
+
if (!seen.has(char)) {
|
|
23
|
+
result += char;
|
|
24
|
+
seen.add(char);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
getCoordinates(text) {
|
|
30
|
+
const normalizedText = text
|
|
31
|
+
.toUpperCase()
|
|
32
|
+
.replace(/J/g, 'I')
|
|
33
|
+
.replace(/[^A-Z]/g, '');
|
|
34
|
+
const coordinates = [];
|
|
35
|
+
for (const char of normalizedText) {
|
|
36
|
+
const index = this.key.indexOf(char);
|
|
37
|
+
if (index !== -1) {
|
|
38
|
+
const row = Math.floor(index / 5) + 1;
|
|
39
|
+
const col = (index % 5) + 1;
|
|
40
|
+
coordinates.push(row, col);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return coordinates;
|
|
44
|
+
}
|
|
45
|
+
textToNumbers(text) {
|
|
46
|
+
const coordinates = this.getCoordinates(text);
|
|
47
|
+
const numbers = [];
|
|
48
|
+
for (let i = 0; i < coordinates.length; i += 2) {
|
|
49
|
+
numbers.push(coordinates[i] * 10 + coordinates[i + 1]);
|
|
50
|
+
}
|
|
51
|
+
return numbers;
|
|
52
|
+
}
|
|
53
|
+
numbersToText(numbers) {
|
|
54
|
+
let result = '';
|
|
55
|
+
for (const num of numbers) {
|
|
56
|
+
const row = Math.floor(num / 10) - 1;
|
|
57
|
+
const col = (num % 10) - 1;
|
|
58
|
+
if (row >= 0 && row < 5 && col >= 0 && col < 5) {
|
|
59
|
+
const index = row * 5 + col;
|
|
60
|
+
result += this.key.charAt(index);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
encrypt(plaintext) {
|
|
66
|
+
if (!plaintext)
|
|
67
|
+
return '';
|
|
68
|
+
// Convert plaintext to numbers
|
|
69
|
+
const plaintextNumbers = this.textToNumbers(plaintext);
|
|
70
|
+
if (plaintextNumbers.length === 0)
|
|
71
|
+
return '';
|
|
72
|
+
// Convert keyword to numbers
|
|
73
|
+
const keywordNumbers = this.textToNumbers(this.keyword);
|
|
74
|
+
if (keywordNumbers.length === 0) {
|
|
75
|
+
return plaintextNumbers.join(' ');
|
|
76
|
+
}
|
|
77
|
+
// Add plaintext and repeated keyword numbers
|
|
78
|
+
const cipherNumbers = [];
|
|
79
|
+
for (let i = 0; i < plaintextNumbers.length; i++) {
|
|
80
|
+
const keywordNum = keywordNumbers[i % keywordNumbers.length];
|
|
81
|
+
cipherNumbers.push(plaintextNumbers[i] + keywordNum);
|
|
82
|
+
}
|
|
83
|
+
return cipherNumbers.join(' ');
|
|
84
|
+
}
|
|
85
|
+
decrypt(ciphertext) {
|
|
86
|
+
if (!ciphertext.trim())
|
|
87
|
+
return '';
|
|
88
|
+
// Parse cipher numbers, handle multiple spaces
|
|
89
|
+
const cipherNumbers = ciphertext
|
|
90
|
+
.trim()
|
|
91
|
+
.split(/\s+/)
|
|
92
|
+
.map(Number)
|
|
93
|
+
.filter((n) => !isNaN(n));
|
|
94
|
+
if (cipherNumbers.length === 0)
|
|
95
|
+
return '';
|
|
96
|
+
// Convert keyword to numbers
|
|
97
|
+
const keywordNumbers = this.textToNumbers(this.keyword);
|
|
98
|
+
if (keywordNumbers.length === 0) {
|
|
99
|
+
return this.numbersToText(cipherNumbers);
|
|
100
|
+
}
|
|
101
|
+
// Subtract keyword numbers from cipher numbers
|
|
102
|
+
const plaintextNumbers = [];
|
|
103
|
+
for (let i = 0; i < cipherNumbers.length; i++) {
|
|
104
|
+
const keywordNum = keywordNumbers[i % keywordNumbers.length];
|
|
105
|
+
plaintextNumbers.push(cipherNumbers[i] - keywordNum);
|
|
106
|
+
}
|
|
107
|
+
return this.numbersToText(plaintextNumbers);
|
|
108
|
+
}
|
|
109
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Atbash } from './ciphers/Atbash.js';
|
|
|
6
6
|
import { Caesar } from './ciphers/Caesar.js';
|
|
7
7
|
import { DES } from './ciphers/DES.js';
|
|
8
8
|
import { ECC } from './ciphers/ECC.js';
|
|
9
|
+
import { Nihilist } from './ciphers/Nihilist.js';
|
|
9
10
|
import { Playfair } from './ciphers/Playfair.js';
|
|
10
11
|
import { ROT13 } from './ciphers/ROT13.js';
|
|
11
12
|
import { Salsa20 } from './ciphers/Salsa20.js';
|
|
@@ -21,4 +22,5 @@ Cipher.AES = AES;
|
|
|
21
22
|
Cipher.DES = DES;
|
|
22
23
|
Cipher.ECC = ECC;
|
|
23
24
|
Cipher.ROT13 = ROT13;
|
|
25
|
+
Cipher.Nihilist = Nihilist;
|
|
24
26
|
export { Cipher };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@irfanshadikrishad/cipher",
|
|
3
3
|
"description": "A versatile and secure cryptographic library for implementing various cipher algorithms in Node.js applications with zero/0 dependencies.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.5.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Irfan Shadik Rishad"
|
|
7
7
|
},
|
|
@@ -45,19 +45,20 @@
|
|
|
45
45
|
],
|
|
46
46
|
"prettier": "@irfanshadikrishad/prettier",
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@eslint/js": "^9.
|
|
49
|
-
"@irfanshadikrishad/prettier": "^1.
|
|
48
|
+
"@eslint/js": "^9.39.2",
|
|
49
|
+
"@irfanshadikrishad/prettier": "^1.3.1",
|
|
50
50
|
"@types/jest": "^30.0.0",
|
|
51
|
-
"@
|
|
52
|
-
"@typescript-eslint/
|
|
53
|
-
"eslint": "^
|
|
54
|
-
"
|
|
51
|
+
"@types/node": "^25.0.10",
|
|
52
|
+
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
53
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
54
|
+
"eslint": "^9.39.2",
|
|
55
|
+
"globals": "^17.2.0",
|
|
55
56
|
"husky": "^9.1.7",
|
|
56
57
|
"jest": "^30.2.0",
|
|
57
|
-
"prettier": "^3.
|
|
58
|
-
"ts-jest": "^29.4.
|
|
59
|
-
"typescript": "^5.
|
|
60
|
-
"typescript-eslint": "^8.
|
|
58
|
+
"prettier": "^3.8.1",
|
|
59
|
+
"ts-jest": "^29.4.6",
|
|
60
|
+
"typescript": "^5.9.3",
|
|
61
|
+
"typescript-eslint": "^8.54.0"
|
|
61
62
|
},
|
|
62
63
|
"publishConfig": {
|
|
63
64
|
"access": "public"
|