codepi-package 0.0.0-stage → 1.0.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 +37 -2
- package/bin/codepi.js +29 -0
- package/codepi-1.0.0.tgz +0 -0
- package/lib/codepack.js +36 -0
- package/lib/decrypt.js +67 -0
- package/lib/encrypt.js +54 -0
- package/package.json +21 -4
- package/web/index.html +91 -0
package/README.md
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Code[{π*}]
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Secure encryption toolkit using AES-256-GCM.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g codepi
|
|
9
|
+
|
|
10
|
+
## Encrypt
|
|
11
|
+
|
|
12
|
+
codepi encrypt "Hello" "1234"
|
|
13
|
+
|
|
14
|
+
## Decrypt
|
|
15
|
+
|
|
16
|
+
codepi decrypt "[{...}]" "1234"
|
|
17
|
+
|
|
18
|
+
## Validate
|
|
19
|
+
|
|
20
|
+
codepi validate "[{...}]"
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- AES-256-GCM
|
|
25
|
+
- Random nonce
|
|
26
|
+
- Password authentication
|
|
27
|
+
- [{...}] format
|
|
28
|
+
- .Codepack[{}] validation
|
|
29
|
+
- Browser encryption
|
|
30
|
+
- No code execution during validation
|
|
31
|
+
|
|
32
|
+
## License
|
|
33
|
+
|
|
34
|
+
MIT
|
|
35
|
+
|
|
36
|
+
## GitHub
|
|
37
|
+
|
|
38
|
+
https://github.com/ScriptsSoftware/codepi
|
package/bin/codepi.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const {encrypt}=require('../lib/encrypt');
|
|
3
|
+
const {decrypt}=require('../lib/decrypt');
|
|
4
|
+
const {validateCodepack}=require('../lib/codepack');
|
|
5
|
+
|
|
6
|
+
const [,,cmd,...a]=process.argv;
|
|
7
|
+
const VERSION='1.0.0';
|
|
8
|
+
|
|
9
|
+
try{
|
|
10
|
+
if(cmd==='--version'||cmd==='-v'){console.log(`Code[{π*}] v${VERSION}`);process.exit(0)}
|
|
11
|
+
if(cmd==='encrypt'){
|
|
12
|
+
if(!a[0]||!a[1])throw Error('Usage: codepi encrypt "message" "password"');
|
|
13
|
+
console.log(encrypt(a[0],a[1]));
|
|
14
|
+
}else if(cmd==='decrypt'){
|
|
15
|
+
if(!a[0]||!a[1])throw Error('Usage: codepi decrypt "[{...}]" "password"');
|
|
16
|
+
console.log(decrypt(a[0],a[1]));
|
|
17
|
+
}else if(cmd==='validate'){
|
|
18
|
+
if(!a[0])throw Error('Usage: codepi validate "[{...}]"');
|
|
19
|
+
console.log(validateCodepack(a[0]));
|
|
20
|
+
}else{
|
|
21
|
+
console.log('Code[{π*}]');
|
|
22
|
+
console.log('codepi encrypt "message" "password"');
|
|
23
|
+
console.log('codepi decrypt "[{...}]" "password"');
|
|
24
|
+
console.log('codepi validate "[{...}]"');
|
|
25
|
+
}
|
|
26
|
+
}catch(error){
|
|
27
|
+
console.error('Error:',error.message);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
package/codepi-1.0.0.tgz
ADDED
|
Binary file
|
package/lib/codepack.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const FORMAT = '[{...}]';
|
|
2
|
+
|
|
3
|
+
function isCodepack(value) {
|
|
4
|
+
if (typeof value !== 'string') return false;
|
|
5
|
+
if (!value.startsWith('[{') || !value.endsWith('}]')) return false;
|
|
6
|
+
|
|
7
|
+
const data = value.slice(2, -2);
|
|
8
|
+
|
|
9
|
+
if (!data || !/^[A-Za-z0-9_-]+$/.test(data)) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function validateCodepack(value) {
|
|
17
|
+
if (!isCodepack(value)) {
|
|
18
|
+
return {
|
|
19
|
+
valid: false,
|
|
20
|
+
safe: false,
|
|
21
|
+
status: 'INVALID'
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
valid: true,
|
|
27
|
+
safe: true,
|
|
28
|
+
status: 'VALID'
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
FORMAT,
|
|
34
|
+
isCodepack,
|
|
35
|
+
validateCodepack
|
|
36
|
+
};
|
package/lib/decrypt.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
const PI = '3.14159265358979323846264338327950288419716939937510';
|
|
4
|
+
const FORMAT = `π+[${PI}]{}\/`;
|
|
5
|
+
|
|
6
|
+
function deriveKey(password) {
|
|
7
|
+
return crypto
|
|
8
|
+
.createHash('sha256')
|
|
9
|
+
.update(FORMAT + password, 'utf8')
|
|
10
|
+
.digest();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function decrypt(packet, password) {
|
|
14
|
+
if (typeof packet !== 'string') {
|
|
15
|
+
throw new TypeError('Codepack must be a string');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!packet.startsWith('[{') || !packet.endsWith('}]')) {
|
|
19
|
+
throw new Error('Invalid Codepack format');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeof password !== 'string' || password.length === 0) {
|
|
23
|
+
throw new Error('Password is required');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const encoded = packet.slice(2, -2);
|
|
27
|
+
|
|
28
|
+
let data;
|
|
29
|
+
try {
|
|
30
|
+
data = Buffer.from(encoded, 'base64url');
|
|
31
|
+
} catch {
|
|
32
|
+
throw new Error('Invalid Codepack data');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 12-byte nonce + 16-byte authentication tag + ciphertext
|
|
36
|
+
if (data.length < 28) {
|
|
37
|
+
throw new Error('Invalid Codepack data');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const nonce = data.subarray(0, 12);
|
|
41
|
+
const authTag = data.subarray(12, 28);
|
|
42
|
+
const encrypted = data.subarray(28);
|
|
43
|
+
|
|
44
|
+
const key = deriveKey(password);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const decipher = crypto.createDecipheriv(
|
|
48
|
+
'aes-256-gcm',
|
|
49
|
+
key,
|
|
50
|
+
nonce
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
decipher.setAAD(Buffer.from(FORMAT, 'utf8'));
|
|
54
|
+
decipher.setAuthTag(authTag);
|
|
55
|
+
|
|
56
|
+
return Buffer.concat([
|
|
57
|
+
decipher.update(encrypted),
|
|
58
|
+
decipher.final()
|
|
59
|
+
]).toString('utf8');
|
|
60
|
+
} catch {
|
|
61
|
+
throw new Error('Authentication failed');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
decrypt
|
|
67
|
+
};
|
package/lib/encrypt.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
const PI = '3.14159265358979323846264338327950288419716939937510';
|
|
4
|
+
const FORMAT = `π+[${PI}]{}\/`;
|
|
5
|
+
|
|
6
|
+
function deriveKey(password) {
|
|
7
|
+
return crypto
|
|
8
|
+
.createHash('sha256')
|
|
9
|
+
.update(FORMAT + password, 'utf8')
|
|
10
|
+
.digest();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function encrypt(message, password) {
|
|
14
|
+
if (typeof message !== 'string') {
|
|
15
|
+
throw new TypeError('Message must be a string');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (typeof password !== 'string' || password.length === 0) {
|
|
19
|
+
throw new Error('Password is required');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const key = deriveKey(password);
|
|
23
|
+
|
|
24
|
+
// Random 96-bit nonce
|
|
25
|
+
const nonce = crypto.randomBytes(12);
|
|
26
|
+
|
|
27
|
+
const cipher = crypto.createCipheriv(
|
|
28
|
+
'aes-256-gcm',
|
|
29
|
+
key,
|
|
30
|
+
nonce
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
cipher.setAAD(Buffer.from(FORMAT, 'utf8'));
|
|
34
|
+
|
|
35
|
+
const encrypted = Buffer.concat([
|
|
36
|
+
cipher.update(message, 'utf8'),
|
|
37
|
+
cipher.final()
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
const authTag = cipher.getAuthTag();
|
|
41
|
+
|
|
42
|
+
// nonce + authentication tag + encrypted data
|
|
43
|
+
const packet = Buffer.concat([
|
|
44
|
+
nonce,
|
|
45
|
+
authTag,
|
|
46
|
+
encrypted
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
return `[{${packet.toString('base64url')}}]`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
encrypt
|
|
54
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codepi-package",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Secure encryption toolkit with the .Codepack[{}] format",
|
|
5
|
+
"main": "lib/codepack.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"codepi": "bin/codepi.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node --check bin/codepi.js && node --check lib/encrypt.js && node --check lib/decrypt.js && node --check lib/codepack.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"encryption",
|
|
14
|
+
"aes-256-gcm",
|
|
15
|
+
"codepack",
|
|
16
|
+
"security",
|
|
17
|
+
"termux"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/web/index.html
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Code[{π*}]</title>
|
|
7
|
+
<style>
|
|
8
|
+
*{box-sizing:border-box}
|
|
9
|
+
body{margin:0;background:#0b0d12;color:#eee;font:15px Arial}
|
|
10
|
+
main{max-width:700px;margin:auto;padding:35px 18px}
|
|
11
|
+
header{text-align:center;margin-bottom:30px}
|
|
12
|
+
h1{font-size:34px;margin:0}
|
|
13
|
+
header p{color:#888}
|
|
14
|
+
.card{background:#13161d;border:1px solid #252a35;border-radius:14px;padding:18px;margin:15px 0}
|
|
15
|
+
h2{margin-top:0}
|
|
16
|
+
textarea,input,button{width:100%;padding:12px;margin:6px 0;border-radius:9px;border:1px solid #303642;background:#0d1016;color:#fff}
|
|
17
|
+
textarea{min-height:110px;resize:vertical}
|
|
18
|
+
button{cursor:pointer;font-weight:bold;background:#eee;color:#111}
|
|
19
|
+
.status{color:#888;font-size:13px}
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<main>
|
|
24
|
+
<header>
|
|
25
|
+
<h1>Code[{π*}]</h1>
|
|
26
|
+
<p>Secure encryption toolkit</p>
|
|
27
|
+
</header>
|
|
28
|
+
|
|
29
|
+
<section class="card">
|
|
30
|
+
<h2>Encrypt</h2>
|
|
31
|
+
<textarea id="m" placeholder="Enter your message"></textarea>
|
|
32
|
+
<input id="ep" type="password" placeholder="Password">
|
|
33
|
+
<button onclick="encrypt()">Encrypt</button>
|
|
34
|
+
<textarea id="out" readonly placeholder="[{...}]"></textarea>
|
|
35
|
+
</section>
|
|
36
|
+
|
|
37
|
+
<section class="card">
|
|
38
|
+
<h2>Decrypt</h2>
|
|
39
|
+
<textarea id="in" placeholder="Paste [{...}]"></textarea>
|
|
40
|
+
<input id="dp" type="password" placeholder="Password">
|
|
41
|
+
<button onclick="decrypt()">Decrypt</button>
|
|
42
|
+
<textarea id="result" readonly placeholder="Decrypted message"></textarea>
|
|
43
|
+
</section>
|
|
44
|
+
|
|
45
|
+
<p class="status">AES-256-GCM · Local browser encryption</p>
|
|
46
|
+
<script>
|
|
47
|
+
const F="π+[3.14159265358979323846264338327950288419716939937510]{}/";
|
|
48
|
+
const E=new TextEncoder(),D=new TextDecoder();
|
|
49
|
+
|
|
50
|
+
async function key(p){
|
|
51
|
+
const h=await crypto.subtle.digest("SHA-256",E.encode(F+p));
|
|
52
|
+
return crypto.subtle.importKey("raw",h,{name:"AES-GCM"},false,["encrypt","decrypt"]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function b64(a){
|
|
56
|
+
return btoa(String.fromCharCode(...a))
|
|
57
|
+
.replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function unb64(s){
|
|
61
|
+
s=s.replace(/-/g,"+").replace(/_/g,"/");
|
|
62
|
+
while(s.length%4)s+="=";
|
|
63
|
+
return Uint8Array.from(atob(s),x=>x.charCodeAt(0));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function encrypt(){
|
|
67
|
+
const n=crypto.getRandomValues(new Uint8Array(12));
|
|
68
|
+
const c=new Uint8Array(await crypto.subtle.encrypt(
|
|
69
|
+
{name:"AES-GCM",iv:n,additionalData:E.encode(F)},
|
|
70
|
+
await key(ep.value),E.encode(m.value)
|
|
71
|
+
));
|
|
72
|
+
const a=new Uint8Array(12+c.length);
|
|
73
|
+
a.set(n);a.set(c,12);
|
|
74
|
+
out.value="[{"+b64(a)+"}]";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function decrypt(){
|
|
78
|
+
try{
|
|
79
|
+
const a=unb64(in.value.slice(2,-2));
|
|
80
|
+
result.value=D.decode(await crypto.subtle.decrypt(
|
|
81
|
+
{name:"AES-GCM",iv:a.slice(0,12),additionalData:E.encode(F)},
|
|
82
|
+
await key(dp.value),a.slice(12)
|
|
83
|
+
));
|
|
84
|
+
}catch{
|
|
85
|
+
result.value="Authentication failed.";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
</script>
|
|
89
|
+
</main>
|
|
90
|
+
</body>
|
|
91
|
+
</html>
|