Skip to content

Commit 8a66689

Browse files
authored
Merge pull request #5 from OmniacDev/encryption
Encryption
2 parents 1cfe012 + cffc381 commit 8a66689

File tree

1 file changed

+136
-38
lines changed

1 file changed

+136
-38
lines changed

src/ipc.ts

+136-38
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,95 @@
2525
import { world, system } from '@minecraft/server'
2626

2727
namespace IPC {
28-
const MAX_STR_LENGTH = 1024
2928
let ID = 0
3029

30+
export namespace CONFIG {
31+
export namespace ENCRYPTION {
32+
/**
33+
* @description Used to generate secrets, must be a prime number
34+
* @default 19893121
35+
* @warning Modify only if you know what you're doing, incorrect values can cause issues
36+
*/
37+
export let PRIME: number = 19893121
38+
/**
39+
* @description Used to generate secrets, must be a prime root of {@link PRIME}
40+
* @default 341
41+
* @warning Modify only if you know what you're doing, incorrect values can cause issues
42+
*/
43+
export let MOD: number = 341
44+
}
45+
export namespace FRAGMENTATION {
46+
/**
47+
* @description Used when fragmenting data strings
48+
* @default 1024
49+
* @warning Modify only if you know what you're doing, incorrect values can cause issues
50+
*/
51+
export let MAX_STR_LENGTH: number = 1024
52+
}
53+
}
54+
55+
namespace ENCRYPTION {
56+
export function generate_secret(mod: number = CONFIG.ENCRYPTION.MOD): number {
57+
return Math.floor(Math.random() * (mod - 1)) + 1
58+
}
59+
60+
export function generate_public(
61+
secret: number,
62+
mod: number = CONFIG.ENCRYPTION.MOD,
63+
prime: number = CONFIG.ENCRYPTION.PRIME
64+
): string {
65+
return HEX(mod_exp(mod, secret, prime))
66+
}
67+
68+
export function generate_shared(
69+
secret: number,
70+
other_key: string,
71+
prime: number = CONFIG.ENCRYPTION.PRIME
72+
): string {
73+
return HEX(mod_exp(NUM(other_key), secret, prime))
74+
}
75+
76+
export function encrypt(raw: string, key: string): string {
77+
let encrypted = ''
78+
for (let i = 0; i < raw.length; i++) {
79+
encrypted += String.fromCharCode(raw.charCodeAt(i) ^ key.charCodeAt(i % key.length))
80+
}
81+
return encrypted
82+
}
83+
84+
export function decrypt(encrypted: string, key: string): string {
85+
let decrypted = ''
86+
for (let i = 0; i < encrypted.length; i++) {
87+
decrypted += String.fromCharCode(encrypted.charCodeAt(i) ^ key.charCodeAt(i % key.length))
88+
}
89+
return decrypted
90+
}
91+
92+
function mod_exp(base: number, exp: number, mod: number): number {
93+
let result = 1
94+
base = base % mod
95+
while (exp > 0) {
96+
if (exp % 2 === 1) {
97+
result = (result * base) % mod
98+
}
99+
exp = Math.floor(exp / 2)
100+
base = (base * base) % mod
101+
}
102+
return result
103+
}
104+
105+
function HEX(num: number): string {
106+
return num.toString(16).toUpperCase()
107+
}
108+
function NUM(hex: string): number {
109+
return parseInt(hex, 16)
110+
}
111+
}
112+
31113
export class Connection {
32114
private readonly _from: string
33115
private readonly _to: string
116+
private readonly _enc: string | false
34117

35118
get from() {
36119
return this._from
@@ -40,20 +123,24 @@ namespace IPC {
40123
return this._to
41124
}
42125

43-
constructor(from: string, to: string) {
126+
constructor(from: string, to: string, encryption: string | false) {
44127
this._from = from
45128
this._to = to
129+
this._enc = encryption
46130
}
47131

48132
send(channel: string, ...args: any[]): void {
49-
emit('send', `${this._to}:${channel}`, [this._from, args])
133+
const data = this._enc !== false ? ENCRYPTION.encrypt(JSON.stringify(args), this._enc) : args
134+
emit('send', `${this._to}:${channel}`, [this._from, data])
50135
}
51136

52137
invoke(channel: string, ...args: any[]): Promise<any> {
53-
emit('invoke', `${this._to}:${channel}`, [this._from, args])
138+
const data = this._enc !== false ? ENCRYPTION.encrypt(JSON.stringify(args), this._enc) : args
139+
emit('invoke', `${this._to}:${channel}`, [this._from, data])
54140
return new Promise(resolve => {
55141
const listener = listen('handle', `${this._from}:${channel}`, args => {
56-
resolve(args[1])
142+
const data = this._enc !== false ? JSON.parse(ENCRYPTION.decrypt(args[1] as string, this._enc)) : args[1]
143+
resolve(data)
57144
system.afterEvents.scriptEventReceive.unsubscribe(listener)
58145
})
59146
})
@@ -62,66 +149,76 @@ namespace IPC {
62149

63150
export class ConnectionManager {
64151
private readonly _id: string
152+
private readonly _enc_map: Map<string, string | false>
153+
private readonly _enc_force: boolean
65154

66155
get id() {
67156
return this._id
68157
}
69158

70-
constructor(id: string) {
159+
constructor(id: string, force_encryption: boolean = false) {
71160
this._id = id
72-
161+
this._enc_map = new Map<string, string | false>()
162+
this._enc_force = force_encryption
73163
listen('handshake', `${this._id}:SYN`, args => {
74-
emit('handshake', `${args[0]}:ACK`, [this._id])
164+
const secret = ENCRYPTION.generate_secret(args[4])
165+
const public_key = ENCRYPTION.generate_public(secret, args[4], args[3])
166+
const enc = args[1] === 1 || this._enc_force ? ENCRYPTION.generate_shared(secret, args[2], args[3]) : false
167+
this._enc_map.set(args[0], enc)
168+
emit('handshake', `${args[0]}:ACK`, [this._id, this._enc_force ? 1 : 0, public_key])
75169
})
76170
}
77171

78-
connect(to: string, timeout?: number): Promise<Connection> {
79-
return new Promise<Connection>((resolve, reject) => {
80-
this.handshake(to, timeout).then(success => {
81-
if (success) {
82-
resolve(new Connection(this._id, to))
83-
} else {
84-
reject()
172+
connect(to: string, encrypted: boolean = false, timeout: number = 20): Promise<Connection> {
173+
const secret = ENCRYPTION.generate_secret()
174+
const public_key = ENCRYPTION.generate_public(secret)
175+
const enc_flag = encrypted ? 1 : 0
176+
emit('handshake', `${to}:SYN`, [this._id, enc_flag, public_key, CONFIG.ENCRYPTION.PRIME, CONFIG.ENCRYPTION.MOD])
177+
return new Promise((resolve, reject) => {
178+
function clear() {
179+
system.afterEvents.scriptEventReceive.unsubscribe(listener)
180+
system.clearRun(timeout_handle)
181+
}
182+
const timeout_handle = system.runTimeout(() => {
183+
reject()
184+
clear()
185+
}, timeout)
186+
const listener = listen('handshake', `${this._id}:ACK`, args => {
187+
if (args[0] === to) {
188+
const enc = args[1] === 1 || encrypted ? ENCRYPTION.generate_shared(secret, args[2]) : false
189+
resolve(new Connection(this._id, to, enc))
190+
clear()
85191
}
86192
})
87193
})
88194
}
89195

90196
handle(channel: string, listener: (...args: any[]) => any) {
91197
listen('invoke', `${this._id}:${channel}`, args => {
92-
const result = listener(...args[1])
93-
emit('handle', `${args[0]}:${channel}`, [this._id, result])
198+
const enc = this._enc_map.get(args[0]) as string | false
199+
const data: any[] = enc !== false ? JSON.parse(ENCRYPTION.decrypt(args[1] as string, enc)) : args[1]
200+
const result = listener(...data)
201+
const return_data = enc !== false ? ENCRYPTION.encrypt(JSON.stringify(result), enc) : result
202+
emit('handle', `${args[0]}:${channel}`, [this._id, return_data])
94203
})
95204
}
96205

97206
on(channel: string, listener: (...args: any[]) => void) {
98-
listen('send', `${this._id}:${channel}`, args => listener(...args[1]))
207+
listen('send', `${this._id}:${channel}`, args => {
208+
const enc = this._enc_map.get(args[0]) as string | false
209+
const data: any[] = enc !== false ? JSON.parse(ENCRYPTION.decrypt(args[1] as string, enc)) : args[1]
210+
listener(...data)
211+
})
99212
}
100213

101214
once(channel: string, listener: (...args: any[]) => void) {
102215
const event = listen('send', `${this._id}:${channel}`, args => {
103-
listener(...args[1])
216+
const enc = this._enc_map.get(args[0]) as string | false
217+
const data: any[] = enc !== false ? JSON.parse(ENCRYPTION.decrypt(args[1] as string, enc)) : args[1]
218+
listener(...data)
104219
system.afterEvents.scriptEventReceive.unsubscribe(event)
105220
})
106221
}
107-
108-
private handshake(receiver: string, timeout: number = 20): Promise<boolean> {
109-
emit('handshake', `${receiver}:SYN`, [this._id])
110-
return new Promise(resolve => {
111-
const run_timeout = system.runTimeout(() => {
112-
resolve(false)
113-
system.afterEvents.scriptEventReceive.unsubscribe(listener)
114-
system.clearRun(run_timeout)
115-
}, timeout)
116-
const listener = listen('handshake', `${this._id}:ACK`, args => {
117-
if (args[0] === receiver) {
118-
resolve(true)
119-
system.afterEvents.scriptEventReceive.unsubscribe(listener)
120-
system.clearRun(run_timeout)
121-
}
122-
})
123-
})
124-
}
125222
}
126223

127224
interface Payload {
@@ -191,7 +288,8 @@ namespace IPC {
191288
}
192289

193290
function emit(event_id: string, channel: string, args: any[]) {
194-
const data_fragments: string[] = JSON.stringify(args).match(new RegExp(`.{1,${MAX_STR_LENGTH}}`, 'g')) ?? []
291+
const data_fragments: string[] =
292+
JSON.stringify(args).match(new RegExp(`.{1,${CONFIG.FRAGMENTATION.MAX_STR_LENGTH}}`, 'g')) ?? []
195293
const payload_strings = data_fragments
196294
.map((data, index) => {
197295
return data_fragments.length > 1

0 commit comments

Comments
 (0)