I had a chance to do AES encryption/decryption in Node.js, so here are my notes.
The encryption algorithm "AES-256-CBC" was used.
About AES CBC encryption in a nutshell
AES CBC is an encryption algorithm that encrypts binary data of arbitrary length using a key and decrypts it using the same key.
However, if encryption is done with just a key, if the original data is the same, the encrypted data will also be the same each time, making it easier to decrypt.
Therefore, in AES CBC, encryption is performed using not only the key but also "the key + an arbitrary value set for each encryption" and decryption is performed using the "key + an arbitrary value set for each encryption" so that the same data can be decrypted with different encryption results each time, making it difficult to decipher.
The "arbitrary value to be set for each encryption" is then called the IV (initialization vector).
About the Key
The key must be shared in advance between the encryptor and decryptor, and the key must not be divulged to outside parties.
The content of the key can be any value of 128 bits (16 bytes) for AES128 or 256 bits (32 bytes) for AES256.
About IV
Unlike keys, IVs have no problem leaking to the outside world.
The size of IV is equal to the block size of the encryption, and since the block size of AES is 128 bits, IV can be any value of 128 bits (16 bytes).
The specifications of how the IV value is generated and passed from the encrypting side to the decrypting side are not fixed and are left to the implementation.
It is often the case that IV is generated with a random value at the time of encryption, and IV is given at the beginning of the encrypted data and passed to the decryptor. (This is what we did in this case).
Passing Encrypted Data
The encrypted data is binary data, and there is no set specification for how it is conveyed to the decryptor, leaving this to the implementation.
Since character strings are easier to handle than binary data, encrypted data is converted into character strings using Base64 and sent to the decryption side, Therefore, it is often the case that the encrypted data is converted into a string using Base64 and sent to the decryption side, and the decryption side converts the string back into binary data and decrypts it. (This is what we did in this case).
Node.js Implementation Example
Based on the above, this is an example implementation in Node.js.
import * as crypto from 'crypto' const ALGORITHM = 'aes-256-cbc'; const KEY = Buffer.from([ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff ]); // Use a 256-bit value with all bits 1 as the key. function encodeBase64(data: string): string { // Generate a 16-byte random value and set it as IV const iv = crypto.randomBytes(16); // Cryptograph Creation const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv); // Binary encryption of data const encData = cipher.update(Buffer.from(data)); // End processing & add iv at the beginning and return binary as base64 (string) return Buffer.concat([iv, encData, cipher.final()]).toString('base64'); } function decodeBase64(data: string): string { // Convert received encrypted string to binary const buff = Buffer.from(data, 'base64'); // Extract the first 16 bytes, which is the iv value. const iv = buff.slice(0, 16); // Extract the encrypted data after the iv value. const encData = buff.slice(16); // Restores are made const decipher = crypto.createDecipheriv(ALGORITHM, KEY, iv); // Decrypt encrypted data const decData = decipher.update(encData); // Terminate & convert binary back to string return Buffer.concat([decData, decipher.final()]).toString('utf8'); } (() => { const data = "Hello World!"; console.log(`INPUT : ${data}`); // INPUT : Hello World! const encData = encodeBase64(data); console.log(`ENCODE : ${encData}`); // ENCODE : 0XTIPX06EAClUAFNdT+6EDlv+bOrB6plqkGzd0hEvdU= const decData = decodeBase64(encData); console.log(`DECODE : ${decData}`); // DECODE : Hello World! })();
Brief description
encryption
- With
cipher.update(<buffer>)
, the data is encrypted and the encrypted data is returned in binary - Repeat
cipher.update(<buffer>)
if data is long - After all encryption is complete, call
cipher.final()
to get the end of the encrypted data - The concatenation of a series of data becomes the final encrypted data
- With
decoding
- With
decipher.update(<buffer>)
, the encrypted data is decrypted and returned in binary - Get the end of the decoded data with
decipher.final()
. - The concatenation of those data becomes the final decrypted data
- With
About Key Generation
When we say "key," we generally think of something like a key phrase, A key in AES is a 256-bit value, not a string of characters.
However, a string like a key phrase is easier for humans to handle, An example of using a hash function to generate a 256-bit (32-byte) value from a string as a key is given in the Node.js Crypto documentation.
Example
const KEY = crypto.scryptSync("キーフレーズ", "", 32);
Another example would be to use a string of 32 ASCII characters as the key. However, this would limit the value to those assigned to ASCII characters, so the value would be more limited than 256 bits.
Example
const KEY = Buffer.from("01234567890123456789012345678912", 'ascii');
impressions
At first, I was hooked because I thought the key was a string.
Binaries and strings come and go, so you have to follow them carefully.
As far as the encryption/decryption part is concerned, "data before encryption," "data after encryption," "key," and "IV" are all binary, and they are only mathematically operated on, so I think it would be better not to mix other types, but to handle them on a binary basis and return to the original type at the end to avoid confusion.