RainingChain RainingChain - 6 months ago 22
Node.js Question

Crypto Cipher knowning Part of Secret

Assuming this is my encrypt and decrypt function using native

crypto
from nodejs.

var algo = 'aes-256-cbc';
var algoSecret = 'mySecret';

var encrypt = function(secret){
var cipher = require('crypto').createCipher(algo,algoSecret);
var crypted = cipher.update(secret,'utf8','hex')
crypted += cipher.final('hex');
return crypted;
}
var decrypt = function(text){
var decipher = require('crypto').createDecipher(algo,algoSecret);
var dec = decipher.update(text,'hex','utf8');
dec += decipher.final('utf8');
return dec;
}


I have to encrypt data of length 20. However, the first 8 chars are always the same and known by everyone. Ex: Always starts with
api-key=
. Does including or removing the 8 first chars affect the security of the system?

Ex:
encrypt('api-key=askjdhaskdhaskd')
vs
'api-key=' + encrypt('askjdhaskdhaskd')

Answer

Does including or removing the 8 first chars affect the security of the system?

Yes, but only slightly.

CBC mode with a static IV is deterministic, which means that an attacker who only observes ciphertexts can determine if the same plaintext prefix was sent before. Since the first 8 bytes are known to be static, the chance for the next 8 bytes of the first block are much more probable to equal another API key, which does not necessarily match in its entirety. Whether this information is useful to the attacker is a completely different question.

It would be better to always generate an unpredictable (read: random) IV for CBC mode instead of relying on the same IV that is derived from a password. An IV is always 16 bytes or 32 hex-encoded characters long for AES regardless of key size.

Some example code:

var crypto = require('crypto');
var algo = 'aes-256-cbc';
var algoSecret = 'mySecret';
var key = crypto.pbkdf2Sync(algoSecret, 'salt', 1000, 256, 'sha256'); 
// the key can also be stored in Hex in order to prevent PBKDF2 invocation

var encrypt = function(secret){
    var iv = crypto.randomBytes(16);
    var cipher = crypto.createCipheriv(algo, key, iv);
    var crypted = cipher.update(secret,'utf8','hex')
    crypted += cipher.final('hex');
    return iv.toString('hex') + crypted;
}
var decrypt = function(text){
    var iv = new Buffer(text.slice(0, 32), 'hex');
    text = text.slice(32);
    var decipher = crypto.createDecipheriv(algo, key, iv);
    var dec = decipher.update(text,'hex','utf8');
    dec += decipher.final('utf8');
    return dec;
}

This is still not enough, because this code might be vulnerable to the padding oracle attack depending on your communication architecture. You should authenticate the ciphertexts with a message authentication code (MAC). A popular choice is HMAC-SHA256 for "encrypt-then-MAC".

var crypto = require('crypto');
var algo = 'aes-256-cbc';
var algoSecret = 'mySecret';
var key = crypto.pbkdf2Sync(algoSecret, 'salt', 1000, 512, 'sha512');
var keyMac = key.slice(32);
var keyEnc = key.slice(0, 32);

var encrypt = function(secret){
    var iv = crypto.randomBytes(16);
    var cipher = crypto.createCipheriv(algo, keyEnc, iv);
    var crypted = cipher.update(secret,'utf8','hex')
    crypted += cipher.final('hex');
    var ct = iv.toString('hex') + crypted;

    var hmac = crypto.createHmac('sha256', keyMac);
    hmac.update(ct);
    return ct + hmac.digest('hex');
}
var decrypt = function(text){
    var hmac = crypto.createHmac('sha256', keyMac);
    hmac.update(text.slice(0, -64));
    if (hmac.digest('hex') !== text.slice(-64)) { // TODO: contant-time comparison
        // TODO: do some decoy decryption
        return false;
    }

    var iv = new Buffer(text.slice(0, 32), 'hex');
    text = text.slice(32, -64);
    var decipher = crypto.createDecipheriv(algo, keyEnc, iv);
    var dec = decipher.update(text,'hex','utf8');
    dec += decipher.final('utf8');
    return dec;
}