dchest / tweetnacl-js

Port of TweetNaCl cryptographic library to JavaScript
https://tweetnacl.js.org
The Unlicense
1.77k stars 293 forks source link

Error: bad key size #162

Closed heniotierra closed 5 years ago

heniotierra commented 5 years ago

I'm getting the error below when I try to run the example at https://github.com/dchest/tweetnacl-js/wiki/Examples .

(node:295) UnhandledPromiseRejectionWarning: Error: bad key size at checkLengths (/home/node/app/node_modules/tweetnacl/nacl-fast.js:2139:53) at Object.nacl.secretbox (/home/node/app/node_modules/tweetnacl/nacl-fast.js:2167:3) at Crypt.encrypt (/home/node/app/src/DatabaseManager/Crypt.ts:51:21) at DataImportService.jsonToEmployee (/home/node/app/src/DatabaseManager/DataImport.service.ts:35:49) at DataImportService. (/home/node/app/src/DatabaseManager/DataImport.service.ts:51:44) at Generator.next () at fulfilled (/home/node/app/src/DatabaseManager/DataImport.service.ts:13:58) at at process._tickDomainCallback (internal/process/next_tick.js:229:7) (node:295) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1) (node:295) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. (node:295) UnhandledPromiseRejectionWarning: Error: bad key size at checkLengths (/home/node/app/node_modules/tweetnacl/nacl-fast.js:2139:53) at Object.nacl.secretbox (/home/node/app/node_modules/tweetnacl/nacl-fast.js:2167:3) at Crypt.encrypt (/home/node/app/src/DatabaseManager/Crypt.ts:51:21) at DataImportService.jsonToEmployee (/home/node/app/src/DatabaseManager/DataImport.service.ts:35:49) at DataImportService. (/home/node/app/src/DatabaseManager/DataImport.service.ts:51:44) at Generator.next () at fulfilled (/home/node/app/src/DatabaseManager/DataImport.service.ts:13:58) at at process._tickDomainCallback (internal/process/next_tick.js:229:7) (node:295) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2) (node:295) UnhandledPromiseRejectionWarning: Error: bad key size at checkLengths (/home/node/app/node_modules/tweetnacl/nacl-fast.js:2139:53) at Object.nacl.secretbox (/home/node/app/node_modules/tweetnacl/nacl-fast.js:2167:3) at Crypt.encrypt (/home/node/app/src/DatabaseManager/Crypt.ts:51:21) at DataImportService.jsonToEmployee (/home/node/app/src/DatabaseManager/DataImport.service.ts:35:49) at DataImportService. (/home/node/app/src/DatabaseManager/DataImport.service.ts:51:44) at Generator.next () at fulfilled (/home/node/app/src/DatabaseManager/DataImport.service.ts:13:58) at at process._tickDomainCallback (internal/process/next_tick.js:229:7) (node:295) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 3) (node:295) UnhandledPromiseRejectionWarning: Error: bad key size at checkLengths (/home/node/app/node_modules/tweetnacl/nacl-fast.js:2139:53) at Object.nacl.secretbox (/home/node/app/node_modules/tweetnacl/nacl-fast.js:2167:3) at Crypt.encrypt (/home/node/app/src/DatabaseManager/Crypt.ts:51:21) at DataImportService.jsonToEmployee (/home/node/app/src/DatabaseManager/DataImport.service.ts:35:49) at DataImportService. (/home/node/app/src/DatabaseManager/DataImport.service.ts:51:44) at Generator.next () at fulfilled (/home/node/app/src/DatabaseManager/DataImport.service.ts:13:58) at at process._tickDomainCallback (internal/process/next_tick.js:229:7) (node:295) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 4) (node:295) UnhandledPromiseRejectionWarning: Error: bad key size at checkLengths (/home/node/app/node_modules/tweetnacl/nacl-fast.js:2139:53) at Object.nacl.secretbox (/home/node/app/node_modules/tweetnacl/nacl-fast.js:2167:3) at Crypt.encrypt (/home/node/app/src/DatabaseManager/Crypt.ts:51:21) at DataImportService.jsonToEmployee (/home/node/app/src/DatabaseManager/DataImport.service.ts:35:49) at DataImportService. (/home/node/app/src/DatabaseManager/DataImport.service.ts:51:44) at Generator.next () at fulfilled (/home/node/app/src/DatabaseManager/DataImport.service.ts:13:58) at at process._tickDomainCallback (internal/process/next_tick.js:229:7) (node:295) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 5)

Can anybody help?

dchest commented 5 years ago

Example runs fine for me, so you probably provided wrong sized key. It should have 32 bytes (in the example it decodes base64 encoded key from string to 32 key bytes). Note that key should be uniform random byte array, not password. If you want to use password, you should use key derivation function, such as https://github.com/dchest/scrypt-async-js/ to turn it into a 32-byte key.

heniotierra commented 5 years ago

Thank you for the quick reply. And I'm sorry, you're right, the example runs fine.

Actually what I'm doing is adapting the example to my need, but it seems strange to me that it raises an error because I followed the same exact flow. I'm not using a password.

I basically wrapped the example in a class. The difference is that I save the generated key in the file system for later use.

import { secretbox, randomBytes } from 'tweetnacl';
import {
  decodeUTF8,
  encodeUTF8,
  encodeBase64,
  decodeBase64
} from 'tweetnacl-util';
import * as fs from 'fs';
import * as path from 'path';

const naclKeyFilename = 'nacl.key';

export class Crypt {

    private naclKey = '';

    constructor(){
        this.readKey();
    }

    private setNaclKey(key){
        this.naclKey = key;
    }

    private readKey(){
        const filePath = path.join(__dirname,`./${naclKeyFilename}`); 
        if(fs.existsSync(filePath)){          
            const data = fs.readFileSync(filePath);
            this.setNaclKey(data);
        }else{
            const newKey = this.genKey();
            this.setNaclKey(newKey);
            fs.writeFileSync(filePath, newKey);
        }
    }

    private newNonce() { 
        return randomBytes(secretbox.nonceLength);
    }

    private genKey(){
        return encodeBase64(randomBytes(secretbox.keyLength));
    }

    encrypt (json) {
        const key = this.naclKey;
        console.log("chave:", key);
        const keyUint8Array = decodeBase64(key);

        const nonce = this.newNonce();
        const messageUint8 = decodeUTF8(JSON.stringify(json));
        const box = secretbox(messageUint8, nonce, keyUint8Array);

        const fullMessage = new Uint8Array(nonce.length + box.length);
        fullMessage.set(nonce);
        fullMessage.set(box, nonce.length);

        const base64FullMessage = encodeBase64(fullMessage);
        return base64FullMessage;
    };

    decrypt (messageWithNonce) {
        const key = this.naclKey;
        const keyUint8Array = decodeBase64(key);
        const messageWithNonceAsUint8Array = decodeBase64(messageWithNonce);
        const nonce = messageWithNonceAsUint8Array.slice(0, secretbox.nonceLength);
        const message = messageWithNonceAsUint8Array.slice(
            secretbox.nonceLength,
            messageWithNonce.length
        );

        const decrypted = secretbox.open(message, nonce, keyUint8Array);

        if (!decrypted) {
            throw new Error("Could not decrypt message");
        }

        const base64DecryptedMessage = encodeUTF8(decrypted);
        return JSON.parse(base64DecryptedMessage);
    }

}    
dchest commented 5 years ago

I think the bug is that in readFileSync you're not reading a string, but a buffer. If you replace it with const data = fs.readFileSync(filePath, { encoding: 'utf8' });, it will return a string. Alternatively, remove all those encode/decodeBase64 and just use encoding: 'base64' when saving and reading files or read/write binary files.

Anyway, this is not a tweetnacl problem, so you'll have to debug it yourself :)

heniotierra commented 5 years ago

Thanks! Using const data = fs.readFileSync(filePath, { encoding: 'utf8' }); works!