Skip to content

Commit ed921e6

Browse files
committed
add offline decryptor
1 parent c88829d commit ed921e6

File tree

2 files changed

+317
-0
lines changed

2 files changed

+317
-0
lines changed

tools/crypto-helper.js

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
var modules = (function (exports) {
2+
'use strict';
3+
4+
/*! *****************************************************************************
5+
Copyright (c) Microsoft Corporation.
6+
7+
Permission to use, copy, modify, and/or distribute this software for any
8+
purpose with or without fee is hereby granted.
9+
10+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
12+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
15+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16+
PERFORMANCE OF THIS SOFTWARE.
17+
***************************************************************************** */
18+
19+
function __awaiter(thisArg, _arguments, P, generator) {
20+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
21+
return new (P || (P = Promise))(function (resolve, reject) {
22+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
23+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
24+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
25+
step((generator = generator.apply(thisArg, _arguments || [])).next());
26+
});
27+
}
28+
29+
const vectorSize = 16;
30+
const utf8Encoder = new TextEncoder();
31+
const utf8Decoder = new TextDecoder();
32+
const iterations = 1000;
33+
const salt = utf8Encoder.encode('XHWnDAT6ehMVY2zD');
34+
class CryptoHelperV2 {
35+
deriveKey(password) {
36+
return __awaiter(this, void 0, void 0, function* () {
37+
const buffer = utf8Encoder.encode(password);
38+
const key = yield crypto.subtle.importKey('raw', buffer, { name: 'PBKDF2' }, false, ['deriveKey']);
39+
const privateKey = crypto.subtle.deriveKey({
40+
name: 'PBKDF2',
41+
hash: { name: 'SHA-256' },
42+
iterations,
43+
salt
44+
}, key, {
45+
name: 'AES-GCM',
46+
length: 256
47+
}, false, ['encrypt', 'decrypt']);
48+
return privateKey;
49+
});
50+
}
51+
encryptToBytes(text, password) {
52+
return __awaiter(this, void 0, void 0, function* () {
53+
const key = yield this.deriveKey(password);
54+
const textBytesToEncrypt = utf8Encoder.encode(text);
55+
const vector = crypto.getRandomValues(new Uint8Array(vectorSize));
56+
// encrypt into bytes
57+
const encryptedBytes = new Uint8Array(yield crypto.subtle.encrypt({ name: 'AES-GCM', iv: vector }, key, textBytesToEncrypt));
58+
const finalBytes = new Uint8Array(vector.byteLength + encryptedBytes.byteLength);
59+
finalBytes.set(vector, 0);
60+
finalBytes.set(encryptedBytes, vector.byteLength);
61+
return finalBytes;
62+
});
63+
}
64+
encryptToBase64(text, password) {
65+
return __awaiter(this, void 0, void 0, function* () {
66+
// const key = await this.deriveKey(password);
67+
// const textBytesToEncrypt = utf8Encoder.encode(text);
68+
// const vector = crypto.getRandomValues(new Uint8Array(vectorSize));
69+
// // encrypt into bytes
70+
// const encryptedBytes = new Uint8Array(
71+
// await crypto.subtle.encrypt(
72+
// {name: 'AES-GCM', iv: vector},
73+
// key,
74+
// textBytesToEncrypt
75+
// )
76+
// );
77+
// const finalBytes = new Uint8Array( vector.byteLength + encryptedBytes.byteLength );
78+
// finalBytes.set( vector, 0 );
79+
// finalBytes.set( encryptedBytes, vector.byteLength );
80+
const finalBytes = yield this.encryptToBytes(text, password);
81+
//convert array to base64
82+
const base64Text = btoa(String.fromCharCode(...finalBytes));
83+
return base64Text;
84+
});
85+
}
86+
stringToArray(str) {
87+
var result = [];
88+
for (var i = 0; i < str.length; i++) {
89+
result.push(str.charCodeAt(i));
90+
}
91+
return new Uint8Array(result);
92+
}
93+
decryptFromBytes(encryptedBytes, password) {
94+
return __awaiter(this, void 0, void 0, function* () {
95+
try {
96+
// extract iv
97+
const vector = encryptedBytes.slice(0, vectorSize);
98+
// extract encrypted text
99+
const encryptedTextBytes = encryptedBytes.slice(vectorSize);
100+
const key = yield this.deriveKey(password);
101+
// decrypt into bytes
102+
let decryptedBytes = yield crypto.subtle.decrypt({ name: 'AES-GCM', iv: vector }, key, encryptedTextBytes);
103+
// convert bytes to text
104+
let decryptedText = utf8Decoder.decode(decryptedBytes);
105+
return decryptedText;
106+
}
107+
catch (e) {
108+
//console.error(e);
109+
return null;
110+
}
111+
});
112+
}
113+
decryptFromBase64(base64Encoded, password) {
114+
return __awaiter(this, void 0, void 0, function* () {
115+
try {
116+
let bytesToDecode = this.stringToArray(atob(base64Encoded));
117+
return yield this.decryptFromBytes(bytesToDecode, password);
118+
// // extract iv
119+
// const vector = bytesToDecode.slice(0,vectorSize);
120+
// // extract encrypted text
121+
// const encryptedTextBytes = bytesToDecode.slice(vectorSize);
122+
// const key = await this.deriveKey(password);
123+
// // decrypt into bytes
124+
// let decryptedBytes = await crypto.subtle.decrypt(
125+
// {name: 'AES-GCM', iv: vector},
126+
// key,
127+
// encryptedTextBytes
128+
// );
129+
// // convert bytes to text
130+
// let decryptedText = utf8Decoder.decode(decryptedBytes);
131+
// return decryptedText;
132+
}
133+
catch (e) {
134+
//console.error(e);
135+
return null;
136+
}
137+
});
138+
}
139+
}
140+
const algorithmObsolete = {
141+
name: 'AES-GCM',
142+
iv: new Uint8Array([196, 190, 240, 190, 188, 78, 41, 132, 15, 220, 84, 211]),
143+
tagLength: 128
144+
};
145+
class CryptoHelperObsolete {
146+
buildKey(password) {
147+
return __awaiter(this, void 0, void 0, function* () {
148+
let utf8Encode = new TextEncoder();
149+
let passwordBytes = utf8Encode.encode(password);
150+
let passwordDigest = yield crypto.subtle.digest({ name: 'SHA-256' }, passwordBytes);
151+
let key = yield crypto.subtle.importKey('raw', passwordDigest, algorithmObsolete, false, ['encrypt', 'decrypt']);
152+
return key;
153+
});
154+
}
155+
encryptToBase64(text, password) {
156+
return __awaiter(this, void 0, void 0, function* () {
157+
let key = yield this.buildKey(password);
158+
let utf8Encode = new TextEncoder();
159+
let bytesToEncrypt = utf8Encode.encode(text);
160+
// encrypt into bytes
161+
let encryptedBytes = new Uint8Array(yield crypto.subtle.encrypt(algorithmObsolete, key, bytesToEncrypt));
162+
//convert array to base64
163+
let base64Text = btoa(String.fromCharCode(...encryptedBytes));
164+
return base64Text;
165+
});
166+
}
167+
stringToArray(str) {
168+
var result = [];
169+
for (var i = 0; i < str.length; i++) {
170+
result.push(str.charCodeAt(i));
171+
}
172+
return new Uint8Array(result);
173+
}
174+
decryptFromBase64(base64Encoded, password) {
175+
return __awaiter(this, void 0, void 0, function* () {
176+
try {
177+
// convert base 64 to array
178+
let bytesToDecrypt = this.stringToArray(atob(base64Encoded));
179+
let key = yield this.buildKey(password);
180+
// decrypt into bytes
181+
let decryptedBytes = yield crypto.subtle.decrypt(algorithmObsolete, key, bytesToDecrypt);
182+
// convert bytes to text
183+
let utf8Decode = new TextDecoder();
184+
let decryptedText = utf8Decode.decode(decryptedBytes);
185+
return decryptedText;
186+
}
187+
catch (e) {
188+
return null;
189+
}
190+
});
191+
}
192+
}
193+
194+
exports.CryptoHelperObsolete = CryptoHelperObsolete;
195+
exports.CryptoHelperV2 = CryptoHelperV2;
196+
197+
Object.defineProperty(exports, '__esModule', { value: true });
198+
199+
return exports;
200+
201+
})({});

tools/decrypt.html

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Decryptor Tool for Obsidian Meld Encrypt Plugin</title>
7+
<script src="crypto-helper.js"></script>
8+
<style>
9+
:root{
10+
font-family: Arial, Helvetica, sans-serif;
11+
}
12+
13+
body{
14+
display: flex;
15+
flex-direction: column;
16+
align-items: center;
17+
background-color: #eee;
18+
}
19+
20+
h1{
21+
text-align: center;
22+
}
23+
24+
.row-500{
25+
display: flex;
26+
flex-direction: column;
27+
max-width: 500px;
28+
}
29+
30+
.col{
31+
display: flex;
32+
flex-direction: row;
33+
align-items: baseline;
34+
}
35+
36+
label{
37+
color: blue;
38+
margin-top: 1em;
39+
}
40+
41+
textarea{
42+
height: 5em;
43+
resize: vertical;
44+
padding: 0.5em;
45+
}
46+
47+
button, input{
48+
padding: 0.5em;
49+
}
50+
51+
#pw{
52+
margin-right: 1em;
53+
}
54+
55+
#m{
56+
margin-left: 1em;
57+
}
58+
59+
.footnote{
60+
margin-top: 4em;
61+
font-size: 0.8em;
62+
}
63+
</style>
64+
<script defer>
65+
function decryptHandler(){
66+
const eTextEncrypted = document.querySelector('#et');
67+
const ePassword = document.querySelector('#pw');
68+
const eTextDecrypted = document.querySelector('#dt');
69+
const eMessage = document.querySelector('#m');
70+
71+
eMessage.innerHTML = '';
72+
73+
const ch = new modules.CryptoHelperV2();
74+
75+
ch.decryptFromBase64(
76+
eTextEncrypted.value,
77+
ePassword.value
78+
).then( result => {
79+
if (result === null){
80+
eMessage.innerHTML = '👎 Decryption failed';
81+
eTextDecrypted.value = '';
82+
}else{
83+
eMessage.innerHTML = '👍 Decrypted';
84+
eTextDecrypted.value = result
85+
}
86+
});
87+
}
88+
</script>
89+
</head>
90+
<body>
91+
92+
<div class="row-500">
93+
<h1>🔐 Decryptor Tool for Obsidian Meld Encrypt Plugin 🔐</h1>
94+
95+
<p>Use this tool to decrypt notes without using the Obsidian Meld Encrypt Plugin.</p>
96+
97+
<label for="et">Encrypted Text</label>
98+
<textarea id="et"></textarea>
99+
100+
<label for="pw">Password</label>
101+
<div class="col">
102+
<input id="pw" type="password">
103+
<button type="button" onclick="decryptHandler()">🔓 Decrypt</button>
104+
<span id="m"></span>
105+
</div>
106+
107+
<label for="dt">Decrypted Text</label>
108+
<textarea id="dt" readonly=""></textarea>
109+
110+
<p class="footnote"><b>Note:</b> As an offline backup, you can save your own copy of this tool. Right click and choose 'Save link as' for: <a href="decrypt.html" download>this page</a> and the <a href="crypto-helper.js">decryptor code</a></p>
111+
112+
</div>
113+
114+
115+
</body>
116+
</html>

0 commit comments

Comments
 (0)