Skip to content

Commit a825949

Browse files
committed
feat(encryption): add multi-algorithm support with factory pattern 🔑
- Add AES-256-GCM and ChaCha20-Poly1305 encryption algorithms - Add unit tests for both algorithm implementations - Add error handling for invalid algorithm selection - Configure TypeScript and Jest for new algorithm modules - Implement factory pattern for scalable algorithm management - Refactor SecureJWT class to use algorithm abstraction layer - Update coverage badge to 98.28% and add algorithm comparison - Update README with algorithm selection and performance documentation
1 parent 2bf247b commit a825949

File tree

15 files changed

+728
-74
lines changed

15 files changed

+728
-74
lines changed

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
---
99

10+
## [1.3.0] - 2025-09-06
11+
12+
### Added
13+
- **Dual encryption algorithms**: AES-256-GCM and ChaCha20-Poly1305 support
14+
- **Algorithm selection**: Choose between AES-256-GCM (default) or ChaCha20-Poly1305
15+
- **Factory pattern**: Scalable algorithm management system
16+
- **Algorithm unit tests**: Unit test coverage for both algorithms
17+
- **Performance comparison**: ChaCha20-Poly1305 is 2-3x faster for verification
18+
- **Algorithm documentation**: Updated README with algorithm selection guide
19+
20+
### Changed
21+
- **SecureJWT constructor**: Added optional `algorithm` parameter
22+
- **Encryption abstraction**: Refactored to use algorithm factory pattern
23+
- **README updates**: Enhanced with dual algorithm documentation and performance metrics
24+
- **Coverage badge**: Updated to 98.28%
25+
26+
### Technical
27+
- **Algorithm interface**: Created `IEncryptionAlgo` interface for algorithm abstraction
28+
- **Factory implementation**: `Algorithms` class for algorithm instance management
29+
- **TypeScript support**: Added path aliases for algorithm modules
30+
- **Jest configuration**: Updated to support new algorithm test files
31+
32+
---
33+
1034
## [1.2.0] - 2025-09-06
1135

1236
### Breaking Changes

README.md

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
![npm version](https://img.shields.io/npm/v/@neabyte/secure-jwt)
44
![node version](https://img.shields.io/node/v/@neabyte/secure-jwt)
55
![typescript version](https://img.shields.io/badge/typeScript-5.9.2-blue.svg)
6-
![coverage](https://img.shields.io/badge/coverage-98%25-brightgreen)
6+
![coverage](https://img.shields.io/badge/coverage-98.28%25-brightgreen)
77
![license](https://img.shields.io/npm/l/@neabyte/secure-jwt.svg)
88

9-
A secure JWT implementation with AES-256-GCM encryption for Node.js applications.
9+
A secure JWT implementation with **AES-256-GCM** & **ChaCha20-Poly1305** algorithms for Node.js applications.
1010

1111
## ✨ Features
1212

13-
- 🔒 **AES-256-GCM encryption** - Industry-standard security
13+
- 🔒 **Multi algorithms** - AES-256-GCM & ChaCha20-Poly1305
14+
- ⚙️ **Algorithm selection** - Choose the best encryption for your use case
1415
- 🛡️ **Tamper detection** - Authentication tags prevent modification
1516
-**Automatic expiration** - Built-in token lifecycle management
1617
- 🔄 **Version compatibility** - Prevents downgrade attacks
@@ -111,11 +112,36 @@ const arrayToken: string = jwt.sign([1, 2, 3])
111112
const jwt = new SecureJWT({
112113
secret: 'your-secret-key', // Required: 8-255 characters
113114
expireIn: '1h', // Required: Time string
115+
algorithm: 'aes-256-gcm', // Optional: default: 'aes-256-gcm'
114116
version: '1.0.0', // Optional: Default '1.0.0'
115117
cached: 1000 // Optional: Cache size (default: 1000)
116118
})
117119
```
118120

121+
### 🔧 Algorithm Options
122+
123+
Choose the encryption algorithm that best fits your needs:
124+
125+
```javascript
126+
// AES-256-GCM (default) - Hardware accelerated, industry standard
127+
const jwtAES = new SecureJWT({
128+
algorithm: 'aes-256-gcm',
129+
secret: 'key',
130+
expireIn: '1h'
131+
})
132+
133+
// ChaCha20-Poly1305 - Maximum performance, 2-3x faster
134+
const jwtChaCha = new SecureJWT({
135+
algorithm: 'chacha20-poly1305',
136+
secret: 'key',
137+
expireIn: '1h'
138+
})
139+
```
140+
141+
**Algorithm Comparison:**
142+
- **AES-256-GCM**: Hardware accelerated, widely supported, industry standard
143+
- **ChaCha20-Poly1305**: Software optimized, 2-3x faster, perfect for high-throughput applications
144+
119145
### Time Format
120146

121147
```javascript
@@ -138,8 +164,12 @@ const jwt = new SecureJWT({
138164
```javascript
139165
new SecureJWT(options: JWTOptions)
140166
```
167+
**Available Algorithm:**
168+
- `aes-256-gcm`
169+
- `chacha20-poly1305`
141170

142171
**Options:**
172+
- `algorithm?:` - Encryption algorithm (default: 'aes-256-gcm')
143173
- `secret: string` - Secret key (8-255 chars, required for security)
144174
- `expireIn: string` - Token expiration time (required for security)
145175
- `version?: string` - Token version (default: '1.0.0')

jest.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export default {
2424
coverageReporters: ['text', 'lcov', 'html'],
2525
moduleNameMapper: {
2626
'^@/(.*)$': '<rootDir>/src/$1',
27+
'^@algorithms/(.*)$': '<rootDir>/src/algorithms/$1',
28+
'^@interfaces/(.*)$': '<rootDir>/src/interfaces/$1',
2729
'^@types/(.*)$': '<rootDir>/src/types/$1',
2830
'^@utils/(.*)$': '<rootDir>/src/utils/$1'
2931
},

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@neabyte/secure-jwt",
3-
"description": "Secure JWT with AES-256-GCM encryption, built-in caching, tamper detection, and TypeScript support",
4-
"version": "1.2.0",
3+
"description": "Secure JWT with AES-256-GCM & ChaCha20-Poly1305 encryption, built-in caching, tamper detection, and TypeScript support",
4+
"version": "1.3.0",
55
"type": "module",
66
"main": "./dist/index.js",
77
"types": "./dist/index.d.ts",

src/algorithms/AES256.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { createCipheriv, createDecipheriv } from 'node:crypto'
2+
import { ErrorHandler } from '@utils/index'
3+
import type { TokenEncrypted, EncryptionAlgo, IEncryptionAlgo } from '@interfaces/index'
4+
5+
/**
6+
* AES-256-GCM encryption class
7+
* Provides encryption and decryption using AES-256-GCM algorithm
8+
*/
9+
export default class AES256 implements IEncryptionAlgo {
10+
/** Algorithm name constant */
11+
private readonly algorithm = 'aes-256-gcm'
12+
13+
/**
14+
* Encrypts data using AES-256-GCM
15+
* @param data - String data to encrypt
16+
* @param key - 32-byte encryption key
17+
* @param iv - 16-byte initialization vector
18+
* @param version - Token version for additional authentication data
19+
* @returns Object containing encrypted data, IV, and authentication tag
20+
*/
21+
encrypt(data: string, key: Buffer, iv: Buffer, version: string): TokenEncrypted {
22+
const cipher = createCipheriv(this.algorithm, key, iv)
23+
cipher.setAAD(Buffer.from(`secure-jwt-${version}`, 'utf8'))
24+
let encrypted = cipher.update(data, 'utf8', 'hex')
25+
encrypted += cipher.final('hex')
26+
const tag = cipher.getAuthTag()
27+
ErrorHandler.validateAuthTag(tag)
28+
return {
29+
encrypted,
30+
iv: iv.toString('hex'),
31+
tag: tag.toString('hex')
32+
}
33+
}
34+
35+
/**
36+
* Decrypts data using AES-256-GCM
37+
* @param tokenEncrypted - Object containing encrypted data, IV, and authentication tag
38+
* @param key - 32-byte decryption key
39+
* @param version - Token version for additional authentication data
40+
* @returns Decrypted string data
41+
*/
42+
decrypt(tokenEncrypted: TokenEncrypted, key: Buffer, version: string): string {
43+
const decipher = createDecipheriv(this.algorithm, key, Buffer.from(tokenEncrypted.iv, 'hex'))
44+
decipher.setAAD(Buffer.from(`secure-jwt-${version}`, 'utf8'))
45+
decipher.setAuthTag(Buffer.from(tokenEncrypted.tag, 'hex'))
46+
let decrypted = decipher.update(tokenEncrypted.encrypted, 'hex', 'utf8')
47+
decrypted += decipher.final('utf8')
48+
return decrypted
49+
}
50+
51+
/**
52+
* Gets the required IV length for AES-256-GCM
53+
* @returns IV length in bytes (16)
54+
*/
55+
getIVLength(): number {
56+
return 16
57+
}
58+
59+
/**
60+
* Gets the algorithm name
61+
* @returns Algorithm name string
62+
*/
63+
getAlgoName(): EncryptionAlgo {
64+
return this.algorithm
65+
}
66+
}

src/algorithms/ChaCha20.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { createCipheriv, createDecipheriv } from 'node:crypto'
2+
import { ErrorHandler } from '@utils/index'
3+
import type { TokenEncrypted, EncryptionAlgo, IEncryptionAlgo } from '@interfaces/index'
4+
5+
/**
6+
* ChaCha20-Poly1305 encryption class
7+
* Provides encryption and decryption using ChaCha20-Poly1305 algorithm
8+
*/
9+
export default class ChaCha20 implements IEncryptionAlgo {
10+
/** Algorithm name constant */
11+
private readonly algorithm = 'chacha20-poly1305'
12+
13+
/**
14+
* Encrypts data using ChaCha20-Poly1305
15+
* @param data - String data to encrypt
16+
* @param key - 32-byte encryption key
17+
* @param iv - 12-byte initialization vector
18+
* @param version - Token version for additional authentication data
19+
* @returns Object containing encrypted data, IV, and authentication tag
20+
*/
21+
encrypt(data: string, key: Buffer, iv: Buffer, version: string): TokenEncrypted {
22+
const cipher = createCipheriv(this.algorithm, key, iv)
23+
cipher.setAAD(Buffer.from(`secure-jwt-${version}`, 'utf8'), { plaintextLength: data.length })
24+
let encrypted = cipher.update(data, 'utf8', 'hex')
25+
encrypted += cipher.final('hex')
26+
const tag = cipher.getAuthTag()
27+
ErrorHandler.validateAuthTag(tag)
28+
return {
29+
encrypted,
30+
iv: iv.toString('hex'),
31+
tag: tag.toString('hex')
32+
}
33+
}
34+
35+
/**
36+
* Decrypts data using ChaCha20-Poly1305
37+
* @param tokenEncrypted - Object containing encrypted data, IV, and authentication tag
38+
* @param key - 32-byte decryption key
39+
* @param version - Token version for additional authentication data
40+
* @returns Decrypted string data
41+
*/
42+
decrypt(tokenEncrypted: TokenEncrypted, key: Buffer, version: string): string {
43+
const decipher = createDecipheriv(this.algorithm, key, Buffer.from(tokenEncrypted.iv, 'hex'))
44+
decipher.setAAD(Buffer.from(`secure-jwt-${version}`, 'utf8'), {
45+
plaintextLength: tokenEncrypted.encrypted.length / 2
46+
})
47+
decipher.setAuthTag(Buffer.from(tokenEncrypted.tag, 'hex'))
48+
let decrypted = decipher.update(tokenEncrypted.encrypted, 'hex', 'utf8')
49+
decrypted += decipher.final('utf8')
50+
return decrypted
51+
}
52+
53+
/**
54+
* Gets the required IV length for ChaCha20-Poly1305
55+
* @returns IV length in bytes (12)
56+
*/
57+
getIVLength(): number {
58+
return 12
59+
}
60+
61+
/**
62+
* Gets the algorithm name
63+
* @returns Algorithm name string
64+
*/
65+
getAlgoName(): EncryptionAlgo {
66+
return this.algorithm
67+
}
68+
}

src/algorithms/index.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { randomBytes } from 'node:crypto'
2+
import AES256 from '@algorithms/AES256'
3+
import ChaCha20 from '@algorithms/ChaCha20'
4+
import { EncryptionError, getErrorMessage } from '@utils/index'
5+
import type { EncryptionAlgo, IEncryptionAlgo } from '@interfaces/index'
6+
7+
/**
8+
* Algorithms factory class
9+
* Creates encryption algorithm instances and generates random bytes
10+
*/
11+
export default class Algorithms {
12+
/**
13+
* Generates cryptographically secure random bytes
14+
* @param length - Number of bytes to generate
15+
* @returns Buffer containing random bytes
16+
*/
17+
static getRandomBytes(length: number): Buffer {
18+
return randomBytes(length)
19+
}
20+
21+
/**
22+
* Creates an encryption algorithm instance
23+
* @param algorithm - Algorithm type to create
24+
* @returns Encryption algorithm instance
25+
* @throws EncryptionError when algorithm is not supported
26+
*/
27+
static getInstance(algorithm: EncryptionAlgo): IEncryptionAlgo {
28+
if (algorithm === 'aes-256-gcm') {
29+
return new AES256()
30+
} else if (algorithm === 'chacha20-poly1305') {
31+
return new ChaCha20()
32+
} else {
33+
throw new EncryptionError(getErrorMessage('INVALID_ALGORITHM'))
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)