@irfanshadikrishad/cipher 1.4.0 → 1.5.0

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 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
  }
@@ -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?: CryptoKey, ownPrivateKey?: CryptoKey);
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<CryptoKey>;
14
+ static importPublicKey(hex: string): Promise<NodeCryptoKey>;
13
15
  static bufToHex(buf: Uint8Array): string;
14
- static hexToBuf(hex: string): Uint8Array;
16
+ static hexToBuf(hex: string): ArrayBuffer;
15
17
  }
18
+ export {};
@@ -1,5 +1,6 @@
1
- import crypto from 'crypto';
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 raw = await crypto.subtle.exportKey('raw', this.recipientPublicKey || this.ownPrivateKey);
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
- const raw = ECC.hexToBuf(hex);
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.substr(i * 2, 2), 16);
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
+ }
@@ -1,4 +1,4 @@
1
- import { Cipher } from '../Cipher';
1
+ import { Cipher } from '../Cipher.js';
2
2
  export declare class ROT13 extends Cipher {
3
3
  constructor();
4
4
  private rotate;
@@ -1,4 +1,4 @@
1
- import { Cipher } from '../Cipher';
1
+ import { Cipher } from '../Cipher.js';
2
2
  export class ROT13 extends Cipher {
3
3
  constructor() {
4
4
  super();
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.0",
4
+ "version": "1.5.0",
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.32.0",
49
- "@irfanshadikrishad/prettier": "^1.2.3",
48
+ "@eslint/js": "^9.39.2",
49
+ "@irfanshadikrishad/prettier": "^1.2.5",
50
50
  "@types/jest": "^30.0.0",
51
- "@typescript-eslint/eslint-plugin": "^8.38.0",
52
- "@typescript-eslint/parser": "^8.38.0",
53
- "eslint": "^9.34.0",
51
+ "@types/node": "^25.0.3",
52
+ "@typescript-eslint/eslint-plugin": "^8.50.1",
53
+ "@typescript-eslint/parser": "^8.50.1",
54
+ "eslint": "^9.39.2",
54
55
  "globals": "^16.5.0",
55
56
  "husky": "^9.1.7",
56
57
  "jest": "^30.2.0",
57
- "prettier": "^3.6.2",
58
- "ts-jest": "^29.4.5",
59
- "typescript": "^5.8.3",
60
- "typescript-eslint": "^8.38.0"
58
+ "prettier": "^3.7.4",
59
+ "ts-jest": "^29.4.6",
60
+ "typescript": "^5.9.3",
61
+ "typescript-eslint": "^8.50.1"
61
62
  },
62
63
  "publishConfig": {
63
64
  "access": "public"