Reputation: 957
I know AES 256 CBC with buffer to give different encrypt but its length 66. here is my code
const crypto = require('crypto');
const ENCRYPTION_KEY = 'Must256bytes(32characters)secret';
const IV_LENGTH = 16;
function encrypt(text) {
let iv = crypto.randomBytes(IV_LENGTH);
let cipher = crypto.createCipheriv('aes-256-cbc', new Buffer(ENCRYPTION_KEY), iv);
let encrypted = cipher.update(text.toString());
encrypted = Buffer.concat([encrypted, cipher.final()]);
return iv.toString('hex') + 'XX' + encrypted.toString('hex');
}
function decrypt(text) {
let textParts = text.split('XX');
let iv = new Buffer(textParts.shift(), 'hex');
let encryptedText = new Buffer(textParts.join('XX'), 'hex');
let decipher = crypto.createDecipheriv('aes-256-cbc', new Buffer(ENCRYPTION_KEY), iv);
let decrypted = decipher.update(encryptedText);
try{
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}catch(Err){
return 'NULL';
}
}
Problem is encryption data length is 66 even for text is 1
So is there any encryption and decryption method should give different encryption data at every time with less than 10 characters for text is 1(according to my example)
Thank you
Upvotes: 1
Views: 3303
Reputation: 299663
Maarten covers most of the major points that I wanted to make, so this is just some elaboration and an example of it in Node.
The changes from your code are:
As Maarten notes, it is quite dangerous for CTR mode to duplicate a Key+Nonce pair. If someone does that, they can learn the XOR of the two original messages. With that, they have a good chance of decrypting both messages. For example, if you duplicated your key+nonce on two messages and the attacker used that to discover that their XOR was 3 and knew that the encrypted text was a capital letter, they would know that the two messages had to be one of these:
[('A', 'B'), ('D', 'G'), ('E', 'F'), ('H', 'K'), ('I', 'J'), ('L', 'O'),
('M', 'N'), ('P', 'S'), ('Q', 'R'), ('T', 'W'), ('U', 'V')]
This kind of information is devastating for structured data like human language or computer protocols. It can very quickly be used to decrypt the whole message. Key+nonce reuse is how WEP was broken. (When you do this by hand, it's basically identical to solving a cryptogram puzzle you'd find in the newspaper.) It is less powerful the more random the encrypted data is, and the less context it provides.
With a random 5-byte nonce, there is a 50% likelihood of a collision after about 1.3M encryptions. With a random 8-byte nonce, there is a 50% likelihood of a collision after about 5.3B encryptions. sqrt(pi/2 * 2^bits)
In cryptographic terms, this is a completely broken. It may or may not be sufficient for your purposes. To do it correctly (which I do not do below), as Maarten notes, you should keep track of your counter and increment it for every encryption rather than using a random one. After 2^40 encryptions (~1T), you change your key.
Assuming that leaking information about two messages per million is acceptable, this is how you would implement that.
const crypto = require('crypto');
const ENCRYPTION_KEY = 'Must256bytes(32characters)secret';
const SALT = 'somethingrandom';
const IV_LENGTH = 16;
const NONCE_LENGTH = 5; // Gives us 8-character Base64 output. The higher this number, the better
function encrypt(key, text) {
let nonce = crypto.randomBytes(NONCE_LENGTH);
let iv = Buffer.alloc(IV_LENGTH)
nonce.copy(iv)
let cipher = crypto.createCipheriv('aes-256-ctr', key, iv);
let encrypted = cipher.update(text.toString());
message = Buffer.concat([nonce, encrypted, cipher.final()]);
return message.toString('base64')
}
function decrypt(key, text) {
let message = Buffer.from(text, 'base64')
let iv = Buffer.alloc(IV_LENGTH)
message.copy(iv, 0, 0, NONCE_LENGTH)
let encryptedText = message.slice(NONCE_LENGTH)
let decipher = crypto.createDecipheriv('aes-256-ctr', key, iv);
let decrypted = decipher.update(encryptedText);
try{
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}catch(Err){
return 'NULL';
}
}
// You could do this one time and record the result. Or you could just
// generate a random 32-byte key and record that. But you should never
// pass an ASCII string to the encryption function.
let key = crypto.pbkdf2Sync(ENCRYPTION_KEY, SALT, 10000, 32, 'sha512')
let encrypted = encrypt(key, "X")
console.log(encrypted + " : " + encrypted.length)
let decrypted = decrypt(key, encrypted)
console.log(decrypted)
Upvotes: 2
Reputation: 94108
Yes. You can get a ciphertext of 5 bytes (or 10 hexadecimal characters) or less. But there are catches for such short ciphertexts.
Basically there are two ways. I'll start with the easier one.
You can use CTR (counter) mode with a nonce. Encrypting X bytes with CTR mode gives you exactly X bytes as a result. CTR mode does not require padding of the plaintext to N times the block size.
This nonce (number-used-once) must be a unique number, or your plaintext is in direct danger of being exposed. You cannot just rely on a random number; a 4 byte random number has a high probability of repetition because of the birthday bound.
So you either need to store the nonce separately or include a 4 byte nonce in your ciphertext. However, if you ever manage to reuse the nonce then you're screwed. For such a low amount of bytes that means that you basically have to keep a 4 byte counter, which means having to store state.
Generally CTR mode encryption routines require you to supply an IV rather than a nonce. This is simply the initial counter value. You must construct this value by getting the nonce, and then right-padding it with zero valued bytes until you get to 16 bytes, the block size of AES.
You can find sample code here, don't forget to upvote. Rob also has provided some sample code in his answer.
With format preserving encryption the output of the encryption uses the exact number of bits or even values as the possible values of the input. That sounds great, but FPE consists of a bunch of relatively complex algorithms. Basically you'd have to find a crypto library in JavaScript to implement it.
Note that with FPE the same message will always encrypt to the same ciphertext. Depending on the application this may or may not be a problem.
Notes:
Upvotes: 3
Reputation: 91
Your code works fine on my end, and the long sequence of hex characters seems perfectly normal to me.
Your ciphertext is always 66 characters long (in hex) because you're storing the 32 character IV, the 2 character delimiter ("AP"), and the 32 character (256 bit) ciphertext. Your code uses padding, and as such, the ciphertext will always be the next multiple of 16 bytes (32 hex characters) up from the length of the plaintext, and that's why you're getting such a long string of hex characters for a plaintext string that's only 1 character.
Unfortunately for you, padding is required for AES (at least in the mode you're using), otherwise it wouldn't function.
Upvotes: 1