Compare commits
No commits in common. "85621ce3dc086713b38a7dbebf56b27e80c48df3" and "b59f40830b591575d038dea76a78121b4933a265" have entirely different histories.
85621ce3dc
...
b59f40830b
5 changed files with 58 additions and 44 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -495,7 +495,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-decrypt-backup-wasm"
|
name = "signal-decrypt-backup-wasm"
|
||||||
version = "0.3.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"base64",
|
"base64",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "signal-decrypt-backup-wasm"
|
name = "signal-decrypt-backup-wasm"
|
||||||
version = "0.3.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Get the raw database from your Signal backup. Written for webassembly"
|
description = "Get the raw database from your Signal backup. Written for webassembly"
|
||||||
repository = "https://git.duskflower.dev/duskflower/signal-decrypt-backup-wasm"
|
repository = "https://git.duskflower.dev/duskflower/signal-decrypt-backup-wasm"
|
||||||
|
|
2
build.sh
2
build.sh
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
wasm-pack build --scope duskflower --release --weak-refs --target bundler
|
wasm-pack build --scope duskflower --release --target bundler
|
||||||
jq '. |= . + {"publishConfig": {"registry": "https://git.duskflower.dev/api/packages/duskflower/npm/"}}' pkg/package.json > pkg/package.json.tmp && mv pkg/package.json.tmp pkg/package.json
|
jq '. |= . + {"publishConfig": {"registry": "https://git.duskflower.dev/api/packages/duskflower/npm/"}}' pkg/package.json > pkg/package.json.tmp && mv pkg/package.json.tmp pkg/package.json
|
||||||
|
|
36
src/lib.rs
36
src/lib.rs
|
@ -39,6 +39,19 @@ pub mod signal {
|
||||||
|
|
||||||
type HmacSha256 = Hmac<Sha256>;
|
type HmacSha256 = Hmac<Sha256>;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct DecryptionResult {
|
||||||
|
database_statements: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl DecryptionResult {
|
||||||
|
#[wasm_bindgen(getter)]
|
||||||
|
pub fn database_statements(&self) -> Vec<String> {
|
||||||
|
self.database_statements.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add position field to ByteReader
|
// Add position field to ByteReader
|
||||||
struct ByteReader {
|
struct ByteReader {
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
|
@ -343,8 +356,6 @@ pub struct BackupDecryptor {
|
||||||
header_data: Option<HeaderData>,
|
header_data: Option<HeaderData>,
|
||||||
initialisation_vector: Option<Vec<u8>>,
|
initialisation_vector: Option<Vec<u8>>,
|
||||||
database_statements: Vec<String>,
|
database_statements: Vec<String>,
|
||||||
// how many statements were already passed back to JS for partial passing
|
|
||||||
returned_database_statements_length: usize,
|
|
||||||
ciphertext_buf: Vec<u8>,
|
ciphertext_buf: Vec<u8>,
|
||||||
plaintext_buf: Vec<u8>,
|
plaintext_buf: Vec<u8>,
|
||||||
total_file_size: usize,
|
total_file_size: usize,
|
||||||
|
@ -369,7 +380,6 @@ impl BackupDecryptor {
|
||||||
header_data: None,
|
header_data: None,
|
||||||
initialisation_vector: None,
|
initialisation_vector: None,
|
||||||
database_statements: Vec::new(),
|
database_statements: Vec::new(),
|
||||||
returned_database_statements_length: 0,
|
|
||||||
ciphertext_buf: Vec::new(),
|
ciphertext_buf: Vec::new(),
|
||||||
plaintext_buf: Vec::new(),
|
plaintext_buf: Vec::new(),
|
||||||
total_file_size: 0,
|
total_file_size: 0,
|
||||||
|
@ -503,6 +513,8 @@ impl BackupDecryptor {
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// if we got to an attachment, but there we demand more data, it will be faulty, because we try to decrypt the frame although we would need
|
||||||
|
// to decrypt the attachment
|
||||||
match decrypt_frame(
|
match decrypt_frame(
|
||||||
&mut self.reader,
|
&mut self.reader,
|
||||||
hmac,
|
hmac,
|
||||||
|
@ -512,8 +524,6 @@ impl BackupDecryptor {
|
||||||
frame_length,
|
frame_length,
|
||||||
) {
|
) {
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
// here we have to reset as well because the hmac depends on the length decryption which should therefore happen next time on the correct bits
|
|
||||||
self.reader.set_position(initial_reader_position);
|
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
Ok(Some(backup_frame)) => {
|
Ok(Some(backup_frame)) => {
|
||||||
|
@ -612,17 +622,9 @@ impl BackupDecryptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn get_new_decrypted_statements(&mut self) -> Result<Vec<String>, JsValue> {
|
pub fn finish(self) -> Result<DecryptionResult, JsValue> {
|
||||||
let result =
|
Ok(DecryptionResult {
|
||||||
Ok(self.database_statements[self.returned_database_statements_length..].to_vec());
|
database_statements: self.database_statements,
|
||||||
|
})
|
||||||
self.returned_database_statements_length = self.database_statements.len();
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn finish(self) -> Result<Vec<String>, JsValue> {
|
|
||||||
Ok(self.database_statements.clone())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,7 @@ import { db } from "./db";
|
||||||
export async function decryptBackup(
|
export async function decryptBackup(
|
||||||
file: File,
|
file: File,
|
||||||
passphrase: string,
|
passphrase: string,
|
||||||
progressCallback: (progress: number) => void,
|
progressCallback: (progres: number) => void,
|
||||||
statementsCallback?: (statements: string[]) => void,
|
|
||||||
) {
|
) {
|
||||||
const fileSize = file.size;
|
const fileSize = file.size;
|
||||||
console.log("Starting decryption of file size:", fileSize);
|
console.log("Starting decryption of file size:", fileSize);
|
||||||
|
@ -31,30 +30,53 @@ export async function decryptBackup(
|
||||||
while (!done) {
|
while (!done) {
|
||||||
try {
|
try {
|
||||||
done = decryptor.process_chunk(passphrase);
|
done = decryptor.process_chunk(passphrase);
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
console.error("Error processing chunk:", error);
|
console.error("Error processing chunk:", e);
|
||||||
throw error;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statementsCallback?.(decryptor.get_new_decrypted_statements());
|
|
||||||
|
|
||||||
offset += chunkSize;
|
offset += chunkSize;
|
||||||
|
// console.log(`Completed chunk, new offset: ${offset}`);
|
||||||
|
// if (performance.memory) {
|
||||||
|
// const memoryInfo = performance.memory;
|
||||||
|
// console.log(`Total JS Heap Size: ${memoryInfo.totalJSHeapSize} bytes`);
|
||||||
|
// console.log(`Used JS Heap Size: ${memoryInfo.usedJSHeapSize} bytes`);
|
||||||
|
// console.log(`JS Heap Size Limit: ${memoryInfo.jsHeapSizeLimit} bytes`);
|
||||||
|
// } else {
|
||||||
|
// console.log("Memory information is not available in this environment.");
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log("All chunks processed, finishing up");
|
||||||
const result = decryptor.finish();
|
const result = decryptor.finish();
|
||||||
|
|
||||||
|
// decryptor.free();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
console.error("Decryption failed:", error);
|
console.error("Decryption failed:", e);
|
||||||
throw error;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function decrypt(file: File, passphrase: string) {
|
async function decrypt(file: File, passphrase: string) {
|
||||||
try {
|
try {
|
||||||
const result = await decryptBackup(file, passphrase, console.info);
|
const result = await decryptBackup(file, passphrase, console.info);
|
||||||
|
|
||||||
|
// console.log(result, result.database_bytes);
|
||||||
|
|
||||||
|
// console.log("Database bytes length:", result.databaseBytes.length);
|
||||||
|
// console.log(
|
||||||
|
// "Database bytes as string (partly)",
|
||||||
|
// new TextDecoder().decode(result.database_bytes.slice(0, 1024 * 50)),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// console.log(result.database_statements);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
// console.log("Preferences:", result.preferences);
|
||||||
|
// console.log("Key values:", result.keyValues);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Decryption failed:", error);
|
console.error("Decryption failed:", error);
|
||||||
}
|
}
|
||||||
|
@ -64,19 +86,14 @@ const App: Component = () => {
|
||||||
const [passphrase, setPassphrase] = createSignal("");
|
const [passphrase, setPassphrase] = createSignal("");
|
||||||
const [backupFile, setBackupFile] = createSignal<File>();
|
const [backupFile, setBackupFile] = createSignal<File>();
|
||||||
|
|
||||||
let executed = 0;
|
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
backupFile,
|
backupFile,
|
||||||
(currentBackupFile) => {
|
(currentBackupFile) => {
|
||||||
if (currentBackupFile) {
|
if (currentBackupFile) {
|
||||||
decryptBackup(
|
decrypt(currentBackupFile, passphrase()).then((result) => {
|
||||||
currentBackupFile,
|
if (result) {
|
||||||
passphrase(),
|
for (const statement of result.database_statements) {
|
||||||
console.info,
|
|
||||||
(statements) => {
|
|
||||||
for (const statement of statements) {
|
|
||||||
try {
|
try {
|
||||||
console.log("executing");
|
console.log("executing");
|
||||||
db.exec(statement);
|
db.exec(statement);
|
||||||
|
@ -87,11 +104,7 @@ const App: Component = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
executed += statements.length;
|
console.log("All statements executed");
|
||||||
},
|
|
||||||
).then((result) => {
|
|
||||||
if (result) {
|
|
||||||
console.log("All statements executed: ", result.length, executed);
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
db.exec("SELECT * from message", {
|
db.exec("SELECT * from message", {
|
||||||
|
@ -120,7 +133,6 @@ const App: Component = () => {
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
id="backup-input"
|
id="backup-input"
|
||||||
accept=".backup"
|
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setBackupFile(event.currentTarget.files?.[0]);
|
setBackupFile(event.currentTarget.files?.[0]);
|
||||||
}}
|
}}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue