add file name support for encryptFile & decryptFile

also adds supporting methods l/e int32 byteArray2int & int2byteArray
creates TextEncoder, TextDecoder objects in global module scope
creates magic value METADATA_LENGTH of 1020 + 4 (4 bytes * 255 chars, 4
bytes for int32)
This commit is contained in:
yigid balaban 2024-09-10 18:31:28 +03:00
parent fa44bfe2eb
commit 4fcdcb65bd
Signed by: fyb
GPG Key ID: E21FEB2C244CB7EB
4 changed files with 114 additions and 49 deletions

View File

@ -2,23 +2,37 @@ import init, * as ecies from "ecies-wasm";
import { Key } from "./zkl-kds/key";
init();
const td = new TextDecoder();
const te = new TextEncoder();
const METADATA_LENGTH = 4 + 1020; // [byte length, 255 chars * 4 bytes]
/**
* Encrypts given data for the recipient
*
* @param {Key} publicKey The public key of the recipient
* @param {string} fileName Name of the file
* @param {Uint8Array} data The plaintext data
* @returns {Uint8Array} The ciphertext data
* @throws {TypeError} If arguments are of incorrect types
*/
export function encryptFile(publicKey, data) {
export function encryptFile(publicKey, fileName, data) {
if (!(publicKey instanceof Key)) {
throw new TypeError("publicKey must be an instance of Key");
}
if (!(data instanceof Uint8Array)) {
throw new TypeError("data must be an instance of Uint8Array");
}
return encrypt(publicKey, data);
if (!fileName)
fileName = "Unknown file";
const _data = new Uint8Array(METADATA_LENGTH + data.length);
const fnBytes = te.encode(fileName);
_data.set(int2byteArray(fnBytes.length));
_data.set(fnBytes, 4);
_data.set(data, METADATA_LENGTH);
return encrypt(publicKey, _data);
}
/**
@ -26,7 +40,7 @@ export function encryptFile(publicKey, data) {
*
* @param {Key} privateKey The private key of the recipient
* @param {Uint8Array} data The ciphertext data
* @returns {Uint8Array} The plaintext data
* @returns {{data: Uint8Array, fileName: string}} The plaintext data
* @throws {TypeError} If arguments are of incorrect types
*/
export function decryptFile(privateKey, data) {
@ -36,7 +50,17 @@ export function decryptFile(privateKey, data) {
if (!(data instanceof Uint8Array)) {
throw new TypeError("data must be an instance of Uint8Array");
}
return decrypt(privateKey, data);
const plaintext = decrypt(privateKey, data);
const fnLength = byteArray2int(plaintext.slice(0, 4));
const fnBytes = plaintext.slice(4, 4 + fnLength);
const fdBytes = plaintext.slice(METADATA_LENGTH, plaintext.length);
const fileName = td.decode(fnBytes);
return {
data: fdBytes,
fileName: fileName
};
}
/**
@ -55,8 +79,7 @@ export function encryptString(publicKey, string) {
throw new TypeError("string must be of type string");
}
const encoder = new TextEncoder();
const byteArray = encoder.encode(string);
const byteArray = te.encode(string);
const encryptedData = encrypt(publicKey, byteArray);
return encryptedData.asHexString();
}
@ -84,8 +107,7 @@ export function decryptString(privateKey, string) {
string.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
);
const decryptedData = decrypt(privateKey, byteArray);
const decoder = new TextDecoder();
return decoder.decode(decryptedData);
return td.decode(decryptedData);
}
function encrypt(publicKey, plaintext) {
@ -108,6 +130,46 @@ function decrypt(privateKey, ciphertext) {
return ecies.decrypt(privateKey.asByteArray, ciphertext);
}
/**
* Converts a 32-bit signed integer into an array of 4 bytes (little-endian).
*
* @param {number} int - The 32-bit signed integer to convert. Must be in the range of a signed 32-bit integer (-2^31 to 2^31-1).
* @returns {number[]} An array of 4 bytes, where the least significant byte is the first element (little-endian).
* @example
* // Convert 305419896 (0x12345678) to bytes
* int2byteArray(305419896); // [120, 86, 52, 18]
*/
function int2byteArray(int) {
return [
int & 0xff ? int & 0xff : 0,
(int >> 8) & 0xff ? (int >> 8) & 0xff : 0,
(int >> 16) & 0xff ? (int >> 16) & 0xff : 0,
(int >> 24) & 0xff ? (int >> 24) & 0xff : 0
]
}
/**
* Converts an array of 4 bytes (little-endian) into a 32-bit signed integer.
*
* @param {number[]} bytes - An array of 4 bytes where the least significant byte is the first element (little-endian).
* @returns {number} The reconstructed 32-bit signed integer.
* @example
* // Convert [120, 86, 52, 18] back to an integer
* byteArray2int([120, 86, 52, 18]); // 305419896 (0x12345678)
*/
function byteArray2int(bytes) {
return (bytes[0]) |
(bytes[1] << 8) |
(bytes[2] << 16) |
(bytes[3] << 24);
}
function asByteArray() {
return Uint8Array.from(
this.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
);
}
function asHexString() {
return this.reduce(
(str, byte) => str + byte.toString(16).padStart(2, "0"),

View File

@ -7,9 +7,9 @@ import {
} from "./crypto.js";
let keypair;
const el_fileInput = document.querySelector("#fileInput");
document
.getElementById("fileInput")
el_fileInput
.addEventListener("change", function (event) {
const file = event.target.files[0];
if (file) {
@ -23,8 +23,9 @@ document
return;
}
const cipherFile = encryptFile(keypair.pkey, byteArray);
console.log(cipherFile);
console.log('File to be encrypted:', el_fileInput.files[0].name);
const cipherFile = encryptFile(keypair.pkey, el_fileInput.files[0].name, byteArray);
console.log('Ciphertext bytes:', cipherFile);
{
const numPixels = cipherFile.length / 4;
@ -47,7 +48,9 @@ document
}
const plainFile = decryptFile(keypair.skey, cipherFile);
console.log(plainFile);
console.log("Decrypted raw data:", plainFile.data);
console.log("Decrypted decoded:", (new TextDecoder()).decode(plainFile.data));
console.log("Decrypted file name:", plainFile.fileName);
};
reader.readAsArrayBuffer(file);

70
pnpm-lock.yaml generated
View File

@ -130,8 +130,8 @@ packages:
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@types/node@22.5.1':
resolution: {integrity: sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==}
'@types/node@22.5.4':
resolution: {integrity: sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==}
'@webassemblyjs/ast@1.12.1':
resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==}
@ -262,8 +262,8 @@ packages:
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
caniuse-lite@1.0.30001653:
resolution: {integrity: sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==}
caniuse-lite@1.0.30001660:
resolution: {integrity: sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==}
chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
@ -303,8 +303,8 @@ packages:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
debug@4.3.6:
resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==}
debug@4.3.7:
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
@ -315,8 +315,8 @@ packages:
ecies-wasm@0.2.0:
resolution: {integrity: sha512-T0wkoz2iOu3IN0wugO3gzCn3ADAafA8FRDQUWWST31InPWlUSvH5JH5akegAZw48or1kwQsWE5jGiNqh5woxhg==}
electron-to-chromium@1.5.13:
resolution: {integrity: sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==}
electron-to-chromium@1.5.18:
resolution: {integrity: sha512-1OfuVACu+zKlmjsNdcJuVQuVE61sZOLbNM4JAQ1Rvh6EOj0/EUKhMJjRH73InPlXSh8HIJk1cVZ8pyOV/FMdUQ==}
enhanced-resolve@5.17.1:
resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==}
@ -330,8 +330,8 @@ packages:
es-module-lexer@1.5.4:
resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==}
escalade@3.1.2:
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
escape-string-regexp@1.0.5:
@ -496,8 +496,8 @@ packages:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
@ -540,8 +540,8 @@ packages:
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
picocolors@1.1.0:
resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==}
pkg-dir@4.2.0:
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
@ -647,8 +647,8 @@ packages:
uglify-js:
optional: true
terser@5.31.6:
resolution: {integrity: sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==}
terser@5.32.0:
resolution: {integrity: sha512-v3Gtw3IzpBJ0ugkxEX8U0W6+TnPKRRCWGh1jC/iM/e3Ki5+qvO1L1EAZ56bZasc64aXHwRHNIQEzm6//i5cemQ==}
engines: {node: '>=10'}
hasBin: true
@ -732,7 +732,7 @@ snapshots:
'@babel/code-frame@7.24.7':
dependencies:
'@babel/highlight': 7.24.7
picocolors: 1.0.1
picocolors: 1.1.0
'@babel/compat-data@7.25.4': {}
@ -749,7 +749,7 @@ snapshots:
'@babel/traverse': 7.25.6
'@babel/types': 7.25.6
convert-source-map: 2.0.0
debug: 4.3.6
debug: 4.3.7
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@ -811,7 +811,7 @@ snapshots:
'@babel/helper-validator-identifier': 7.24.7
chalk: 2.4.2
js-tokens: 4.0.0
picocolors: 1.0.1
picocolors: 1.1.0
'@babel/parser@7.25.6':
dependencies:
@ -830,7 +830,7 @@ snapshots:
'@babel/parser': 7.25.6
'@babel/template': 7.25.0
'@babel/types': 7.25.6
debug: 4.3.6
debug: 4.3.7
globals: 11.12.0
transitivePeerDependencies:
- supports-color
@ -869,7 +869,7 @@ snapshots:
'@types/json-schema@7.0.15': {}
'@types/node@22.5.1':
'@types/node@22.5.4':
dependencies:
undici-types: 6.19.8
@ -1014,14 +1014,14 @@ snapshots:
browserslist@4.23.3:
dependencies:
caniuse-lite: 1.0.30001653
electron-to-chromium: 1.5.13
caniuse-lite: 1.0.30001660
electron-to-chromium: 1.5.18
node-releases: 2.0.18
update-browserslist-db: 1.1.0(browserslist@4.23.3)
buffer-from@1.1.2: {}
caniuse-lite@1.0.30001653: {}
caniuse-lite@1.0.30001660: {}
chalk@2.4.2:
dependencies:
@ -1059,13 +1059,13 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
debug@4.3.6:
debug@4.3.7:
dependencies:
ms: 2.1.2
ms: 2.1.3
ecies-wasm@0.2.0: {}
electron-to-chromium@1.5.13: {}
electron-to-chromium@1.5.18: {}
enhanced-resolve@5.17.1:
dependencies:
@ -1076,7 +1076,7 @@ snapshots:
es-module-lexer@1.5.4: {}
escalade@3.1.2: {}
escalade@3.2.0: {}
escape-string-regexp@1.0.5: {}
@ -1159,7 +1159,7 @@ snapshots:
jest-worker@27.5.1:
dependencies:
'@types/node': 22.5.1
'@types/node': 22.5.4
merge-stream: 2.0.0
supports-color: 8.1.1
@ -1199,7 +1199,7 @@ snapshots:
dependencies:
mime-db: 1.52.0
ms@2.1.2: {}
ms@2.1.3: {}
neo-async@2.6.2: {}
@ -1231,7 +1231,7 @@ snapshots:
path-parse@1.0.7: {}
picocolors@1.0.1: {}
picocolors@1.1.0: {}
pkg-dir@4.2.0:
dependencies:
@ -1321,10 +1321,10 @@ snapshots:
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.2
terser: 5.31.6
terser: 5.32.0
webpack: 5.94.0(webpack-cli@5.1.4)
terser@5.31.6:
terser@5.32.0:
dependencies:
'@jridgewell/source-map': 0.3.6
acorn: 8.12.1
@ -1338,8 +1338,8 @@ snapshots:
update-browserslist-db@1.1.0(browserslist@4.23.3):
dependencies:
browserslist: 4.23.3
escalade: 3.1.2
picocolors: 1.0.1
escalade: 3.2.0
picocolors: 1.1.0
uri-js@4.4.1:
dependencies:

View File

@ -7,7 +7,7 @@ module.exports = {
path: path.resolve(__dirname, "dist"),
},
experiments: {
asyncWebAssembly: true,
syncWebAssembly: true,
},
mode: "development",
};