Libsodium crypto_secretstream_xchacha20poly1305_init_pull löst trotz korrektem Schlüssel und Header keinen InitialisieruJavaScript

Javascript-Forum
Anonymous
 Libsodium crypto_secretstream_xchacha20poly1305_init_pull löst trotz korrektem Schlüssel und Header keinen Initialisieru

Post by Anonymous »

Ich implementiere eine Ende-zu-Ende-Verschlüsselung für große Dateien mithilfe von libsodium.js mit crypto_secretstream_xchacha20poly1305. Die Verschlüsselung funktioniert einwandfrei, aber die Entschlüsselung schlägt mit „State konnte nicht initialisiert werden“ fehl.
Der Versuch, den Entschlüsselungsstatus mit crypto_secretstream_xchacha20poly1305_init_pull() zu initialisieren, schlägt fehl, obwohl:
  • Der Schlüssel ist 32 Bytes (richtige Länge für crypto_secretstream_xchacha20poly1305_KEYBYTES)
    />

Code: Select all

Encryptor: Crypto Header
Uint8Array(24) [ 119, 236, 242, 100, 144, 128, 29, 94, 227, 167, … ]

Generierter Key (Base64URL): dZqmIY8RjO_flrBXm71jbg5VeZfl5EYhZNICo5qw0fM ed:17:21
Starte Entschlüsselung...
CryptoHeader:

Decryptor: CryptoHeader:
Uint8Array(24) [ 119, 236, 242, 100, 144, 128, 29, 94, 227, 167, … ]
:1:147461

Decryptor: Key bytes:
Uint8Array(32) [ 117, 154, 166, 33, 143, 17, 140, 239, 223, 150, … ]

Decryptor: Header length: 24 ed:207:25

Decryptor: Key length: 32

Decryptor init error: Error: State konnte nicht initialisiert werden
init /ed:218
decryptAll /ed:258
 /ed:444
async* /ed:420
:1:147461

decryptAll error: Error: State konnte nicht initialisiert werden
init /ed:218
decryptAll /ed:258
 /ed:444
async* /ed:420
:1:147461

Uncaught (in promise) Error: State konnte nicht initialisiert werden
Mein minimal realisierbares Beispiel:

Code: Select all





Datei Encrypt/Decrypt Test



Datei Encrypt/Decrypt Test

Start Verschlüsselung/Entschlüsselung


const log = (msg) => {
console.log(msg);
const outDiv = document.getElementById("output");
outDiv.innerHTML += msg + "
";
};

const DEBUG_ENCRYPTOR = true; // Setze auf false, wenn kein Logging

class Encryptor {
sodium = null;
key = null;
state = null;
cryptoHeader = null;
encoder = new TextEncoder();
CHUNK_SIZE = 64 * 1024;  // 64 KB, kann auf 1MB hochgesetzt werden

constructor(keyString = null) {
this.userKeyString = keyString;
}

async init() {
this.sodium = await SodiumLoader.getInstance();

if (DEBUG_ENCRYPTOR) console.log("Encryptor: Zufälliger Key erzeugt");
this.key = this.sodium.randombytes_buf(this.sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES);

const result = this.sodium.crypto_secretstream_xchacha20poly1305_init_push(this.key);
this.state = result.state;
this.cryptoHeader = result.header;

if (DEBUG_ENCRYPTOR) console.log("Encryptor: Crypto-State initialisiert");
}

__encryptChunk(data) {
return this.sodium.crypto_secretstream_xchacha20poly1305_push(
this.state,
data,
null,
this.sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
);
}

async __streamFile(file, outputChunks, progressCallback, offsetStart, totalSize) {
if (DEBUG_ENCRYPTOR) console.log(`Encryptor: Verschlüssele Datei ${file.name}, Größe: ${file.size}`);

const reader = file.stream().getReader();
let leftover = new Uint8Array(0);
let processed = 0;

while (true) {
const { value, done } = await reader.read();
if (done) break;

let buffer = new Uint8Array(leftover.length + value.length);
buffer.set(leftover, 0);
buffer.set(value, leftover.length);

let offset = 0;
while (offset + this.CHUNK_SIZE   ({ name: f.name, size: f.size }))
}));
const headerLengthBytes = new Uint8Array(4);
new DataView(headerLengthBytes.buffer).setUint32(0, headerBytes.length, false);

const [headerLenFrame, headerLenData] = this.__encryptChunk(headerLengthBytes);
outputChunks.push(headerLenFrame, headerLenData);

const [headerJsonFrameLen, headerJsonFrame] = this.__encryptChunk(headerBytes);
outputChunks.push(headerJsonFrameLen, headerJsonFrame);

// Dateien verschlüsseln
let offsetStart = 0;
for (const file of fileRefs) {
await this.__streamFile(file, outputChunks, progressCallback, offsetStart, totalSize);
offsetStart += file.size;
}

if (DEBUG_ENCRYPTOR) console.log("Encryptor: Verschlüsselung abgeschlossen, Blob erzeugen");

// Blob / File erzeugen
const result = new File(outputChunks, 'container.mycnt', { type: 'application/octet-stream' });
return result;
}

getKey() {
return this.key;
}

getKeyB64() {
return btoa(String.fromCharCode(...this.key));
}

getKeyB64URL() {
return sodium.to_base64(
this.key,
sodium.base64_variants.URLSAFE_NO_PADDING
);
}
}

const DEBUG_DECRYPTOR = true; // Setze auf false, um Logs auszuschalten
class Decryptor {
sodium = null;
key = null;
state = null;
cryptoHeader = null;
decoder = new TextDecoder();
reader = null;
leftover = new Uint8Array(0);

constructor(file, keyInput) {
this.payload = file;
this.keyInput = keyInput;  // nur speichern, NICHT dekodieren
}

async init() {
try {
this.sodium = await SodiumLoader.getInstance();

if (typeof this.keyInput === "string") {
this.key = this.sodium.from_base64(
this.keyInput,
this.sodium.base64_variants.URLSAFE_NO_PADDING
);
} else if (this.keyInput instanceof Uint8Array) {
this.key = this.keyInput;
} else {
throw new Error("Key muss Base64URL-String oder Uint8Array sein");
}

const expectedKeyLength = this.sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES;
if (this.key.length !== expectedKeyLength) {
throw new Error(`Falsche Key-Länge: ${this.key.length} Bytes (erwarte ${expectedKeyLength})`);
}

// Header direkt aus File lesen
const headerSize = this.sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES;
const arrayBuffer = await this.payload.slice(0, headerSize).arrayBuffer();
this.cryptoHeader = new Uint8Array(arrayBuffer);

if (DEBUG_DECRYPTOR) console.warn("Decryptor: CryptoHeader: ", this.cryptoHeader);

// Reader starten
this.reader = this.payload.slice(headerSize).stream().getReader();

if (DEBUG_DECRYPTOR) {
console.log("Decryptor: Key bytes:", this.key);
console.log("Decryptor: Header length:", this.cryptoHeader.length);
console.log("Decryptor: Key length:", this.key.length);
}

// State initialisieren
const initResult = this.sodium.crypto_secretstream_xchacha20poly1305_init_pull(
this.cryptoHeader,
this.key
);

if (!initResult || !initResult.state) {
throw new Error("State konnte nicht initialisiert werden");
}

this.state = initResult.state;

if (DEBUG_DECRYPTOR) console.log("Decryptor: Erfolgreich initialisiert");

} catch (error) {
console.error("Decryptor init error:", error);
throw error;
}
}

async readNextFrame() {
const { value, done } = await this.reader.read();
if (done) return null;
return value;
}

async __decryptChunk(chunk) {
if (!chunk || chunk.length === 0) return null;

try {
if (!this.state) throw new Error("State nicht initialisiert");

const result = this.sodium.crypto_secretstream_xchacha20poly1305_pull(this.state, chunk);
i
  • Die Schlüssellänge beträgt genau 32 Bytes
  • Die Headerlänge beträgt genau 24 Bytes
  • Die Schlüsselkodierung/-dekodierung verwendet konsistent URLSAFE_NO_PADDING
  • Keine Datenbeschädigung bei der Dateiübertragung
  • Verwendung derselben libsodium.js-Version für beide Vorgänge

    libsodium.js (neueste)
  • Chrome 120+, Firefox 146+
  • Dateigrößen: 1 MB – 100 MB

Abhängigkeiten:
  • LibSodium-Version
Was könnte dazu führen, dass crypto_secretstream_xchacha20poly1305_init_pull() selbst bei korrekten Schlüssel- und Headerlängen fehlschlägt? Gibt es häufige Fallstricke bei der Header-Extraktion oder Schlüsseldekodierung, die mir möglicherweise entgangen sind?

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post