Compare commits

...

4 commits

Author SHA1 Message Date
duskflower
85621ce3dc chore: increment version 2025-01-24 17:24:02 +01:00
duskflower
99774ea523 feat: add partly returning statements and remove DecryptionResult 2025-01-24 17:21:08 +01:00
Samuel
6bb11e60b7 fix: reset position before demanding new data for frame decryption 2025-01-19 18:56:43 +01:00
Samuel
8ff12e49bb chore: increment version 2024-12-26 12:13:05 +01:00
5 changed files with 44 additions and 58 deletions

2
Cargo.lock generated
View file

@ -495,7 +495,7 @@ dependencies = [
[[package]] [[package]]
name = "signal-decrypt-backup-wasm" name = "signal-decrypt-backup-wasm"
version = "0.1.1" version = "0.3.0"
dependencies = [ dependencies = [
"aes", "aes",
"base64", "base64",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "signal-decrypt-backup-wasm" name = "signal-decrypt-backup-wasm"
version = "0.1.1" version = "0.3.0"
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"

View file

@ -1,3 +1,3 @@
#!/bin/bash #!/bin/bash
wasm-pack build --scope duskflower --release --target bundler wasm-pack build --scope duskflower --release --weak-refs --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

View file

@ -39,19 +39,6 @@ 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>,
@ -356,6 +343,8 @@ 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,
@ -380,6 +369,7 @@ 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,
@ -513,8 +503,6 @@ 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,
@ -524,6 +512,8 @@ 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)) => {
@ -622,9 +612,17 @@ impl BackupDecryptor {
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn finish(self) -> Result<DecryptionResult, JsValue> { pub fn get_new_decrypted_statements(&mut self) -> Result<Vec<String>, JsValue> {
Ok(DecryptionResult { let result =
database_statements: self.database_statements, Ok(self.database_statements[self.returned_database_statements_length..].to_vec());
})
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())
} }
} }

View file

@ -6,7 +6,8 @@ import { db } from "./db";
export async function decryptBackup( export async function decryptBackup(
file: File, file: File,
passphrase: string, passphrase: string,
progressCallback: (progres: number) => void, progressCallback: (progress: 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);
@ -30,53 +31,30 @@ export async function decryptBackup(
while (!done) { while (!done) {
try { try {
done = decryptor.process_chunk(passphrase); done = decryptor.process_chunk(passphrase);
} catch (e) { } catch (error) {
console.error("Error processing chunk:", e); console.error("Error processing chunk:", error);
throw e; throw error;
} }
} }
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 (e) { } catch (error) {
console.error("Decryption failed:", e); console.error("Decryption failed:", error);
throw e; throw error;
} }
} }
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);
} }
@ -86,14 +64,19 @@ 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) {
decrypt(currentBackupFile, passphrase()).then((result) => { decryptBackup(
if (result) { currentBackupFile,
for (const statement of result.database_statements) { passphrase(),
console.info,
(statements) => {
for (const statement of statements) {
try { try {
console.log("executing"); console.log("executing");
db.exec(statement); db.exec(statement);
@ -104,7 +87,11 @@ const App: Component = () => {
} }
} }
console.log("All statements executed"); executed += statements.length;
},
).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", {
@ -133,6 +120,7 @@ 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]);
}} }}