Skip to content

Commit eafb248

Browse files
committed
spaces -> tabs, better generateOTP api
1 parent d4d9e70 commit eafb248

4 files changed

Lines changed: 444 additions & 445 deletions

File tree

crypto.js

Lines changed: 164 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -1,217 +1,217 @@
11
// crypto.js
22

33
function serializeArg(arg) {
4-
if (arg instanceof Uint8Array || arg instanceof ArrayBuffer) return arrayBufferToBase64(arg);
5-
if (arg && typeof arg === 'object') return JSON.stringify(arg, Object.keys(arg).sort());
6-
return String(arg);
4+
if (arg instanceof Uint8Array || arg instanceof ArrayBuffer) return arrayBufferToBase64(arg);
5+
if (arg && typeof arg === 'object') return JSON.stringify(arg, Object.keys(arg).sort());
6+
return String(arg);
77
}
88
function getSize(value) {
9-
if (value instanceof ArrayBuffer) return value.byteLength;
10-
if (value instanceof Uint8Array) return value.byteLength;
11-
if (typeof value === 'string') return value.length * 2;
12-
if (typeof value === 'object' && value !== null) {
13-
return new TextEncoder().encode(JSON.stringify(value)).length;
14-
}
15-
return 0;
9+
if (value instanceof ArrayBuffer) return value.byteLength;
10+
if (value instanceof Uint8Array) return value.byteLength;
11+
if (typeof value === 'string') return value.length * 2;
12+
if (typeof value === 'object' && value !== null) {
13+
return new TextEncoder().encode(JSON.stringify(value)).length;
14+
}
15+
return 0;
1616
}
1717

1818
function cacheAsync(fn) {
19-
const cache = new Map();
20-
return async (...args) => {
21-
const key = args.map(serializeArg).join('|');
22-
const start = performance.now();
23-
if (cache.has(key)) {
24-
const result = await cache.get(key);
25-
let totalUnits = 0;
26-
for (const v of cache.values()) totalUnits += getSize(await v);
27-
console.log(`[Cached - ${fn.name}]: ${performance.now() - start} ms, cache size: ${totalUnits} units`);
28-
return result;
29-
}
30-
const promise = fn(...args);
31-
cache.set(key, promise);
32-
const result = await promise;
33-
let totalUnits = 0;
34-
for (const v of cache.values()) totalUnits += getSize(await v);
35-
console.log(`[Executed - ${fn.name}]: ${performance.now() - start} ms, cache size: ${totalUnits} units`);
36-
return result;
37-
};
19+
const cache = new Map();
20+
return async (...args) => {
21+
const key = args.map(serializeArg).join('|');
22+
const start = performance.now();
23+
if (cache.has(key)) {
24+
const result = await cache.get(key);
25+
let totalUnits = 0;
26+
for (const v of cache.values()) totalUnits += getSize(await v);
27+
console.log(`[Cached - ${fn.name}]: ${performance.now() - start} ms, cache size: ${totalUnits} units`);
28+
return result;
29+
}
30+
const promise = fn(...args);
31+
cache.set(key, promise);
32+
const result = await promise;
33+
let totalUnits = 0;
34+
for (const v of cache.values()) totalUnits += getSize(await v);
35+
console.log(`[Executed - ${fn.name}]: ${performance.now() - start} ms, cache size: ${totalUnits} units`);
36+
return result;
37+
};
3838
}
3939

4040
function buf2hex(buffer) {
41-
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
41+
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
4242
}
4343

4444
async function sha256(message) {
45-
const msgBuffer = new TextEncoder().encode(message);
46-
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
47-
return buf2hex(hashBuffer);
45+
const msgBuffer = new TextEncoder().encode(message);
46+
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
47+
return buf2hex(hashBuffer);
4848
}
4949
sha256 = cacheAsync(sha256)
5050

5151
function base64EncodeUnicode(str) {
52-
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (m, p1) =>
53-
String.fromCharCode(Number.parseInt(p1, 16))
54-
));
52+
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (m, p1) =>
53+
String.fromCharCode(Number.parseInt(p1, 16))
54+
));
5555
}
5656

5757
function base64DecodeUnicode(str) {
58-
return decodeURIComponent(atob(str).split('').map(c =>
59-
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
60-
).join(''));
58+
return decodeURIComponent(atob(str).split('').map(c =>
59+
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
60+
).join(''));
6161
}
6262

6363
function arrayBufferToBase64(buffer) {
64-
let binary = '';
65-
const bytes = new Uint8Array(buffer);
66-
for (let i = 0; i < bytes.byteLength; i++) {
67-
binary += String.fromCharCode(bytes[i]);
68-
}
69-
return globalThis.btoa(binary);
64+
let binary = '';
65+
const bytes = new Uint8Array(buffer);
66+
for (let i = 0; i < bytes.byteLength; i++) {
67+
binary += String.fromCharCode(bytes[i]);
68+
}
69+
return globalThis.btoa(binary);
7070
}
7171

7272
function base64ToArrayBuffer(base64) {
73-
const binary = globalThis.atob(base64);
74-
const bytes = new Uint8Array(binary.length);
75-
for (let i = 0; i < binary.length; i++) {
76-
bytes[i] = binary.charCodeAt(i);
77-
}
78-
return bytes.buffer;
73+
const binary = globalThis.atob(base64);
74+
const bytes = new Uint8Array(binary.length);
75+
for (let i = 0; i < binary.length; i++) {
76+
bytes[i] = binary.charCodeAt(i);
77+
}
78+
return bytes.buffer;
7979
}
8080

8181
function base64ToUint8Array(base64) {
82-
const binaryString = atob(base64);
83-
const len = binaryString.length;
84-
const bytes = new Uint8Array(len);
85-
for (let i = 0; i < len; i++) {
86-
bytes[i] = binaryString.charCodeAt(i);
87-
}
88-
return bytes;
82+
const binaryString = atob(base64);
83+
const len = binaryString.length;
84+
const bytes = new Uint8Array(len);
85+
for (let i = 0; i < len; i++) {
86+
bytes[i] = binaryString.charCodeAt(i);
87+
}
88+
return bytes;
8989
}
9090

9191
async function deriveKey(password, salt) {
92-
const encoder = new TextEncoder();
93-
const keyMaterial = await crypto.subtle.importKey('raw', encoder.encode(password), 'PBKDF2', false, ['deriveKey']);
94-
return crypto.subtle.deriveKey(
95-
{ name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' },
96-
keyMaterial,
97-
{ name: 'AES-GCM', length: 256 },
98-
false,
99-
['encrypt', 'decrypt']
100-
);
92+
const encoder = new TextEncoder();
93+
const keyMaterial = await crypto.subtle.importKey('raw', encoder.encode(password), 'PBKDF2', false, ['deriveKey']);
94+
return crypto.subtle.deriveKey(
95+
{ name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' },
96+
keyMaterial,
97+
{ name: 'AES-GCM', length: 256 },
98+
false,
99+
['encrypt', 'decrypt']
100+
);
101101
}
102102
deriveKey = cacheAsync(deriveKey);
103103

104104
async function encryptText(plainText, password) {
105-
const encoder = new TextEncoder();
106-
const salt = crypto.getRandomValues(new Uint8Array(16));
107-
const iv = crypto.getRandomValues(new Uint8Array(12));
108-
const key = await deriveKey(password, salt);
109-
const encrypted = await crypto.subtle.encrypt(
110-
{ name: 'AES-GCM', iv: iv },
111-
key,
112-
encoder.encode(plainText)
113-
);
114-
return JSON.stringify({
115-
salt: arrayBufferToBase64(salt),
116-
iv: arrayBufferToBase64(iv),
117-
data: arrayBufferToBase64(encrypted)
118-
});
105+
const encoder = new TextEncoder();
106+
const salt = crypto.getRandomValues(new Uint8Array(16));
107+
const iv = crypto.getRandomValues(new Uint8Array(12));
108+
const key = await deriveKey(password, salt);
109+
const encrypted = await crypto.subtle.encrypt(
110+
{ name: 'AES-GCM', iv: iv },
111+
key,
112+
encoder.encode(plainText)
113+
);
114+
return JSON.stringify({
115+
salt: arrayBufferToBase64(salt),
116+
iv: arrayBufferToBase64(iv),
117+
data: arrayBufferToBase64(encrypted)
118+
});
119119
}
120120

121121
async function decryptText(encryptedData, password) {
122-
try {
123-
const obj = JSON.parse(encryptedData);
124-
const salt = new Uint8Array(base64ToArrayBuffer(obj.salt));
125-
const iv = new Uint8Array(base64ToArrayBuffer(obj.iv));
126-
const data = base64ToArrayBuffer(obj.data);
127-
const key = await deriveKey(password, salt);
128-
const decrypted = await crypto.subtle.decrypt(
129-
{ name: 'AES-GCM', iv: iv },
130-
key,
131-
data
132-
);
133-
return new TextDecoder().decode(decrypted);
134-
} catch (e) {
135-
console.error(e);
136-
throw new Error("Invalid password or corrupted data");
137-
}
122+
try {
123+
const obj = JSON.parse(encryptedData);
124+
const salt = new Uint8Array(base64ToArrayBuffer(obj.salt));
125+
const iv = new Uint8Array(base64ToArrayBuffer(obj.iv));
126+
const data = base64ToArrayBuffer(obj.data);
127+
const key = await deriveKey(password, salt);
128+
const decrypted = await crypto.subtle.decrypt(
129+
{ name: 'AES-GCM', iv: iv },
130+
key,
131+
data
132+
);
133+
return new TextDecoder().decode(decrypted);
134+
} catch (e) {
135+
console.error(e);
136+
throw new Error("Invalid password or corrupted data");
137+
}
138138
}
139139
decryptText = cacheAsync(decryptText);
140140

141-
async function generateOTP(keyObj) {
142-
const digits = keyObj.digits;
143-
const period = keyObj.period;
144-
const algorithm = (() => {
145-
switch (keyObj.algorithm) {
146-
case "SHA1": return "SHA-1";
147-
case "SHA256": return "SHA-256";
148-
case "SHA512": return "SHA-512";
149-
default:
150-
console.error(`generateOTP: Unsupported algorithm "${keyObj.algorithm}"`);
151-
throw new Error("Unsupported algorithm");
152-
}
153-
})();
154-
155-
const epoch = Math.floor(Date.now() / 1000);
156-
const counter = BigInt(Math.floor(epoch / period));
157-
const buffer = new ArrayBuffer(8);
158-
new DataView(buffer).setBigUint64(0, counter, false);
159-
160-
const keyData = base32ToUint8Array(keyObj.secret);
161-
162-
const cryptoKey = await crypto.subtle.importKey(
163-
'raw',
164-
keyData,
165-
{ name: 'HMAC', hash: { name: algorithm } },
166-
false,
167-
['sign']
168-
);
169-
170-
const hmac = await crypto.subtle.sign('HMAC', cryptoKey, buffer);
171-
const hmacBytes = new Uint8Array(hmac);
172-
const offset = hmacBytes[hmacBytes.length - 1] & 0xf;
173-
174-
const binary =
175-
(BigInt(hmacBytes[offset] & 0x7f) << 24n) |
176-
(BigInt(hmacBytes[offset + 1] & 0xff) << 16n) |
177-
(BigInt(hmacBytes[offset + 2] & 0xff) << 8n) |
178-
BigInt(hmacBytes[offset + 3] & 0xff);
179-
180-
const otp = (binary % 10n ** BigInt(digits)).toString().padStart(digits, '0');
181-
182-
return otp;
141+
async function generateOTP(keyObj, offset = 0) {
142+
const digits = keyObj.digits;
143+
const period = keyObj.period;
144+
const algorithm = (() => {
145+
switch (keyObj.algorithm) {
146+
case "SHA1": return "SHA-1";
147+
case "SHA256": return "SHA-256";
148+
case "SHA512": return "SHA-512";
149+
default:
150+
console.error(`generateOTP: Unsupported algorithm "${keyObj.algorithm}"`);
151+
throw new Error("Unsupported algorithm");
152+
}
153+
})();
154+
155+
const epoch = Math.floor(Date.now() / 1000) + offset * period;
156+
const counter = BigInt(Math.floor(epoch / period));
157+
const buffer = new ArrayBuffer(8);
158+
new DataView(buffer).setBigUint64(0, counter, false);
159+
160+
const keyData = base32ToUint8Array(keyObj.secret);
161+
162+
const cryptoKey = await crypto.subtle.importKey(
163+
'raw',
164+
keyData,
165+
{ name: 'HMAC', hash: { name: algorithm } },
166+
false,
167+
['sign']
168+
);
169+
170+
const hmac = await crypto.subtle.sign('HMAC', cryptoKey, buffer);
171+
const hmacBytes = new Uint8Array(hmac);
172+
const offsetByte = hmacBytes[hmacBytes.length - 1] & 0xf;
173+
174+
const binary =
175+
(BigInt(hmacBytes[offsetByte] & 0x7f) << 24n) |
176+
(BigInt(hmacBytes[offsetByte + 1] & 0xff) << 16n) |
177+
(BigInt(hmacBytes[offsetByte + 2] & 0xff) << 8n) |
178+
BigInt(hmacBytes[offsetByte + 3] & 0xff);
179+
180+
const otp = (binary % 10n ** BigInt(digits)).toString().padStart(digits, '0');
181+
182+
return otp;
183183
}
184184

185185
function base32ToUint8Array(base32) {
186-
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
187-
let bits = '', output = [];
188-
189-
for (let char of base32) {
190-
let val = alphabet.indexOf(char.toUpperCase());
191-
if (val === -1) continue;
192-
bits += val.toString(2).padStart(5, '0');
193-
}
194-
195-
for (let i = 0; i + 8 <= bits.length; i += 8) {
196-
output.push(Number.parseInt(bits.substring(i, i + 8), 2));
197-
}
198-
199-
return new Uint8Array(output);
186+
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
187+
let bits = '', output = [];
188+
189+
for (let char of base32) {
190+
let val = alphabet.indexOf(char.toUpperCase());
191+
if (val === -1) continue;
192+
bits += val.toString(2).padStart(5, '0');
193+
}
194+
195+
for (let i = 0; i + 8 <= bits.length; i += 8) {
196+
output.push(Number.parseInt(bits.substring(i, i + 8), 2));
197+
}
198+
199+
return new Uint8Array(output);
200200
}
201201

202202
function base64ToBase32(base64) {
203-
const bytes = base64ToUint8Array(base64);
204-
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
205-
let bits = '', base32 = '';
203+
const bytes = base64ToUint8Array(base64);
204+
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
205+
let bits = '', base32 = '';
206206

207-
for (let byte of bytes) {
208-
bits += byte.toString(2).padStart(8, '0');
209-
}
207+
for (let byte of bytes) {
208+
bits += byte.toString(2).padStart(8, '0');
209+
}
210210

211-
for (let i = 0; i < bits.length; i += 5) {
212-
const chunk = bits.substring(i, i + 5);
213-
base32 += alphabet[Number.parseInt(chunk.padEnd(5, '0'), 2)];
214-
}
211+
for (let i = 0; i < bits.length; i += 5) {
212+
const chunk = bits.substring(i, i + 5);
213+
base32 += alphabet[Number.parseInt(chunk.padEnd(5, '0'), 2)];
214+
}
215215

216-
return base32;
216+
return base32;
217217
}

0 commit comments

Comments
 (0)