При попытке инициализировать состояние дешифрования с помощью crypto_secretstream_xchacha20poly1305_init_pull() происходит сбой, хотя:
- Ключ имеет длину 32 байта (правильная длина для crypto_secretstream_xchacha20poly1305_KEYBYTES)
- Заголовок составляет 24 байта (правильная длина для crypto_secretstream_xchacha20poly1305_HEADERBYTES)
- И ключ, и заголовок передаются как Uint8Array
Код: Выделить всё
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
Код: Выделить всё
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
- Длина ключа составляет ровно 32 байта.
- Длина заголовка составляет ровно 24 байта.
- Кодирование/декодирование ключей последовательно использует URLSAFE_NO_PADDING.
- При передаче файлов данные не повреждаются.
- Использование одной и той же версии libsodium.js для обеих операций
libsodium.js (последняя версия) - Chrome 120+, Firefox 146+
- Размеры файлов: 1–100 МБ
Зависимости:
- Версия LibSodium
Подробнее здесь: https://stackoverflow.com/questions/798 ... -to-initla