In a digital world, where data is transmitted everywhere, concerns about where this data ends up are growing fast. Many companies offer services without encryption, leaving sensitive data exposed to unnecessary risks. Every year, we witness personal data leaks, and that is why at SuperViz, we have done work to guarantee the security and privacy of our users' data.
After much study and being responsible for implementing it internally in the company, I would like to share this article to help reduce such incidents. At the end of the article, you will be able to create encryptions using symmetric keys and using asymmetric keys.
Before diving into encryption, it's extremely valuable to know the difference between asymmetric and symmetric keys.
In summary, the main difference between symmetric and asymmetric encryption lies in how the keys are generated and used to encrypt and decrypt data, as well as in the relative efficiency and security of each approach.
In this article we will use the AES-256-CBC
encryption, with CBC
being the acronym for Cipher Block Chaining.
CBC is a way of encrypting information. Imagine you have a message that you want to keep secure, with CBC it works like this:
Knowing how CBC works, let's create our first message encrypted with AES-256-CBC
! Imagine that we are going to create a function called encrypt
, it will be responsible for creating the symmetric key and performing the encryption, it will have the following parameters: salt
, passphrase
, text
.
The passphrase
will be required to use in encryption to decrypt the content. Additionally, we will need a salt
, which is a random text used as a basis for encrypting the data. This salt
will increase the security of the encryption, making it more difficult for third parties to decipher protected content.
In the first line of the function, we will generate the symmetric key. This key will be the only key used to both encrypt and decrypt the content. It will be shared between the sender and the recipient.
import * as crypto from 'crypto'
const salt = randomBytes(32).toString('hex');
const passphrase = 'password';
const encrypt = (passphrase, salt, text) => {
const key = scryptSync(passphrase, salt, 32)
...
}
In line 7, we create our symmetric key. In the next line, we will need to generate the Initialization Vector
. The Initialization Vector (IV) is like a secret ingredient in encryption, it is a random value that joins with the key to scramble the data, which makes encryption more secure and difficult to break.
const iv = crypto.randomBytes(16)
Now let's encrypt the message using our key
and iv
.
key
and the iv
;utf-8
to hex
;const cipher = crypto.createCipheriv('aes-256-cbc', key, iv)
let encrypted = cipher.update(text, 'utf8', 'hex')
encrypted += cipher.final('hex')
We are approaching the end, but a challenge arises. To decrypt the message content, we need the IV (Initialization Vector), however, it is crucial that each IV is unique and random. Storing the IV alongside the encrypted message would compromise security. A commonly adopted solution is to merge the IV with the encrypted message in strategic locations, keeping its position confidential.
const part1 = encrypted.slice(0, 17)
const part2 = encrypted.slice(17)
return `${part1}${iv.toString('hex')}${part2}`
It is important to point out that, in parts 1 and 2, our encrypted text contains only 32 characters.
And in the end, our encryption function looked like this:
import * as crypto from 'crypto'
const salt = crypto.randomBytes(32).toString('hex');
const passphrase = 'password';
const encrypt = (passphrase, salt, text) => {
const key = crypto.scryptSync(passphrase, salt, 32)
const iv = crypto.randomBytes(16)
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv)
let encrypted = cipher.update(text, 'utf8', 'hex')
encrypted += cipher.final('hex')
const part1 = encrypted.slice(0, 17)
const part2 = encrypted.slice(17)
return `${part1}${iv.toString('hex')}${part2}`
}
Now that we have our content encrypted at some point, we will need to decrypt the content. Some steps will be like what we did when encrypting the content. First let's recover our key
.
import * as crypto from 'crypto'
const salt = crypto.randomBytes(32).toString('hex');
const passphrase = 'password';
const decrypt = (passphrase, salt, text) => {
const key = crypto.scryptSync(passphrase, salt, 32)
...
}
Next, to define the positions that we place when encrypting our content, which in the case of this article we are using position 17:
const ivPosition = {
start: 17,
end: 17 + 32
}
const iv = Buffer.from(text.slice(ivPosition.start, ivPosition.end), 'hex')
Having the IV in hand, just get the encrypted content:
const part1: string = text.slice(0, ivPosition.start)
const part2: string = text.slice(ivPosition.end)
const encryptedText = `${part1}${part2}`
Now we have both the encrypted content and the IV, we can then begin the process of deciphering the content. The process is similar to encryption:
key
and the iv
;hex
to utf-8
;const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv)
let decrypted = decipher.update(encryptedText, 'hex', 'utf8')
decrypted += decipher.final('utf8')
return decrypted
In the end, we have a code similar to this:
import * as crypto from 'crypto'
const salt = crypto.randomBytes(32).toString('hex');
const passphrase = 'password';
const decrypt = (passphrase, salt, text) => {
const key = crypto.scryptSync(passphrase, salt, 32)
const ivPosition = {
start: 17,
end: 17 + 32
}
const iv = Buffer.from(text.slice(ivPosition.start, ivPosition.end), 'hex')
const part1: string = text.slice(0, ivPosition.start)
const part2: string = text.slice(ivPosition.end)
const encryptedText = `${part1}${part2}`
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv)
let decrypted = decipher.update(encryptedText, 'hex', 'utf8')
decrypted += decipher.final('utf8')
return decrypted
}
Asymmetric keys, or public key cryptography, have transformed the way we protect sensitive information online. This guarantees extra security, although it is slower and requires more resources.
NodeJS supports a variety of asymmetric key types to meet diverse encryption needs. These include:
This variety of options allows developers to choose the algorithm best suited to their specific needs, balancing security, performance, and efficiency.
For this article, we will use the implementation of the RSA algorithm, given its wide use. In the initial stage, it is crucial to generate the asymmetric keys: the Public Key and the Private Key. To do this, it is essential to specify the types and formats of these keys. We will opt for the PEM (Privacy-Enhanced Mail) format for both keys, thus ensuring a consistent and secure approach.
generateKeyPair(passphrase: string): KeyPair {
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
cipher: 'aes-256-cbc',
passphrase: passphrase
}
})
return {
publicKey,
privateKey
}
}
Firstly, it is important to understand the generateKeyPairSync
function. It is responsible for generating a pair of keys: one public and one private.
When generating these keys, some parameters are essential:
modulusLength
: This parameter defines the length of the module. In the case of RSA, the minimum length is 4096;publicKeyEncoding
: This object establishes the type and format of the public key. The type is defined as spki
(Subject Public Key Info), a standard format for public keys. The format is pem
, which is a base64
encoding type;privateKeyEncoding
: This object defines the type, format, cipher, and passphrase of the private key. The type is defined as pkcs8
, also a standard format for private keys. The format is pem
, similar to the public key. The cipher is defined as aes-256-cbc
, an encryption algorithm as we saw earlier. The secret phrase, our passphrase
, is the password used to encrypt the private key.In NodeJS, there are two types of public key (pkcs1
and spki
) and two types of private key (pkcs1
and pkcs8
) when using RSA.
Here, we chose the spki/pkcs8
format. To add an extra layer of security, we also use the AES-256-CBC
(Cipher Block Chaining) algorithm as mentioned previously.
As a result, the generateKeyPairSync
function returns an object containing the generated public and private keys.
PKCS is the acronym for "Public Key Cryptography Standards", which are a set of standards developed to assist the implementation of public key cryptography. Among these standards, we have PKCS1 and PKCS8, which describe formats for encoding private keys.
PKCS1 is the first PKCS standard, used specifically for encoding RSA keys. It details the process of encoding an RSA private key, which is relatively simple as an RSA key only requires a few integers.
In contrast, PKCS8 is a more comprehensive standard used to encode any type of private key. It can be used not only for RSA keys but also for other types of keys such as DSA or EC (Elliptic Curve). Thus, PKCS8 is a more general format compared to PKCS1 as it includes information about the key type in the private key data structure.
In short, the difference between PKCS1 and PKCS8 is that PKCS1 is specific to RSA keys, while PKCS8 can be used to encode any type of private key.
With our Public Key at your disposal, it is very easy to encrypt any message.
text
and publicKey
;publicEncrypt
function, which will be used to encrypt the text;Buffer.from(text, 'utf8')
to convert the text into a UTF-8 encoded buffer;toString('base64')
to convert the buffer to base64.
const encrypt = (text, publicKey) => {
return publicEncrypt(publicKey, Buffer.from(text, 'utf8')).toString('base64');
}
With this, we have our encrypted message ready to be stored in a database or something similar.
To decrypt content, we only need our Private Key and the passphrase specified when creating the asymmetric keys.
text
and PrivateKey
;privateDecrypt
function, which will be used to decrypt the text;PrivateKey
and Passphrase
to the function;Buffer.from(text, 'base64')
to convert the text to a base64 buffer;.toString('utf8')
to convert all content to utf8
const decrypt = (encryptedText, privateKey) => {
return privateDecrypt({
key: privateKey,
passphrase
}, Buffer.from(encryptedText, 'base64')).toString('utf8');
}
After these steps, we have our original message back. With this, we complete the full cycle of encryption and decryption using asymmetric keys.
In conclusion, encryption plays a crucial role in protecting sensitive data and information in today's digital era.
I hope that, through this article, you have gained a deeper understanding of the differences and applications between symmetric and asymmetric encryption. Furthermore, with the code examples provided, you should be able to implement your own encryption and decryption functions in NodeJS.
Remember, data security is an important responsibility and encryption is one of the most effective tools we can use to ensure that security.
Источник: dev.to
Наш сайт является информационным посредником. Сообщить о нарушении авторских прав.
security node