Encrypt in PHP and decrypt in JS - AES256 - javascript

The function will receive a file uploaded, and will encrypt this one to save in the server, i'm think about using openssl_encrypt.
The encryption type will be AES256.
After when a web-service is requested will return a base 64 encrypted document to be decrypted in the JS side using the crypto-js.
For know, my question is how can i do the encryption process using openssl_encrypt php function?
Encrypt process:
fopen
encrypt
encode base64
fwrite
fclose
Decrypt process:
Decode base 64
Decrypt
Open the pdf document
The processes above, is the idea i have in mind, please correct me if i'm wrong or there is a mistake.
Phase 1:
PHP Code:
After handling the file:
$encryptionMethod = "AES-256-CBC";
$secret = "1234567890##########123456789012"; //must be 32 char length
$iv = substr($secret, 0, 16);
$encryptedMessage = openssl_encrypt($textToEncrypt, $encryptionMethod, $secret,0,$iv);
On jquery to decrypt doesn't work:
var ckey = "1234567890##########123456789012";
var decrypted = CryptoJS.AES.decrypt(data.content, ckey, { iv: "1234567890######" });
The pdf is generated again but i'm not able to open, shows an error message "Acrobat cannot open the file"..
Why am i getting this?

Encrypt/Decrypt working!
PHP SIDE TO ENCRYPT
$encryptionMethod = "AES-256-CBC";
$secret = "1234567890##########123456789012"; //must be 32 char length
$iv = substr($secret, 0, 16);
$encryptedMessage = openssl_encrypt($textToEncrypt, $encryptionMethod, $secret,0,$iv);`
CRYPTOJS TO DECRYPT
var cipherParams = CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Hex.parse(data.toString())});
var decrypted = CryptoJS.AES.decrypt(cipherParams, CryptoJS.enc.Hex.parse(key), { iv: CryptoJS.enc.Hex.parse(iv) });
window.open("data:application/pdf;base64, " + btoa(decrypted.toString(CryptoJS.enc.Utf8)));

Related

CryptoJS aes encrypt function PHP equivalent

I am trying to create a PHP equivalent to this JS code with CryptoJS:
function aesEncrypt (data) {
const key = 'GSTEGSTEjdfheyhdHSHSHSHDHHDHmdjjdn12ndndn5r=';
const iv = '\0';
const cipher = CryptoJS.AES.encrypt(data, CryptoJS.enc.Base64.parse(key), {
iv: CryptoJS.enc.Utf8.parse(iv), // parse the IV
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
})
return cipher.toString()
}
result of the js code : pHjpwiyKq7Rf4dFcBMbm1w==
here is the PHP code I wrote by reading other stackoverflow questions. But it did not return the same result.
$plaintext = "plainText";
$method = 'aes-256-cbc';
$key = base64_encode("GSTEGSTEjdfheyhdHSHSHSHDHHDHmdjjdn12ndndn5r=");
$iv = hex2bin('00000000000000000000000000000000');
$ciphertext = openssl_encrypt(
$plaintext,
$method,
$key,
OPENSSL_RAW_DATA,
$iv
);
$ciphertext = base64_encode($ciphertext);
echo $ciphertext;
result of the PHP code : +YJOMi2vISmEXIjUZls3MA==
In the PHP code, the key must be Base64 decoded and not Base64 encoded:
$key = base64_decode("GSTEGSTEjdfheyhdHSHSHSHDHHDHmdjjdn12ndndn5r=");
With this change the desired ciphertext is created.
Note that the ciphertext is Base64 encoded by default if 0 is passed instead of OPENSSL_RAW_DATA in the fourth parameter of the openssl_encrypt() call. The explicit Base64 encoding of the ciphertext is then not necessary.
Keep in mind that a static IV is insecure. Usually during encryption a random IV is generated, which is passed to the decrypting side along with the ciphertext (typically concatenated).

AES Encryption in PHP and Decryption in Javascript

I have an application in which I am encrypting a json encoded array using AES CBC 128 algorithm and then Decrypting it in javascript(React/Next Js Project). My Encryption in php is as shown in the below code
ENCRYPTION PHP
$plaintext = "message to be encrypted";
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
$ciphertext = base64_encode( $iv.$hmac.$ciphertext_raw );
I am facing problems in decryption of this in Javascript
my code so far is as shown below
const baseenc = CryptoJS.enc.Base64.parse(cipher).toString();
var encrypted = CryptoJS.AES.decrypt(cipher, key, { iv: iv }).toString();
var plaintext = CryptoJS.enc.Latin1.stringify(encrypted);
Can any body please show what is the error or help me in getting the correct output
The following steps must be implemented in the CryptoJS code:
Separate IV, HMAC and ciphertext (after Base64 decoding)
Calculate the HMAC for the ciphertext
Check the authenticity of the ciphertext. The ciphertext is authentic if the received and calculated HMAC are identical.
Perform decryption, only if the ciphertext is authentic
The following code is a possible implementation. As key 0123456789012345 was applied and with the PHP code the used ciphertext was generated:
var ciphertext = 'WqfMfCxKg7U7h5S1mbx7mSHOkkkIrUUpg++mX4ZdWt0I26VfKn7bsi60Oo/SIsWQGyC4dF5z081NvjTXwZGjIpguA0k/QqIM/GDEpCojaro=';
var key = '0123456789012345';
// Convert key and ciphertext into WordArrays
var ciphertextWA = CryptoJS.enc.Base64.parse(ciphertext);
var keyWA = CryptoJS.enc.Utf8.parse(key);
// Separate IV, HMAC and ciphertext
var ivWA = CryptoJS.lib.WordArray.create(ciphertextWA.words.slice(0, 4));
var hmacWA = CryptoJS.lib.WordArray.create(ciphertextWA.words.slice(4, 4 + 8));
var actualCiphertextWA = CryptoJS.lib.WordArray.create(ciphertextWA.words.slice(4 + 8));
// Authenticate
var hmacCalculatedWA = CryptoJS.HmacSHA256(actualCiphertextWA, keyWA);
if(CryptoJS.enc.Base64.stringify(hmacCalculatedWA) === CryptoJS.enc.Base64.stringify(hmacWA)) {
// Decrypt if authentication is successfull
var decryptedMessageWA = CryptoJS.AES.decrypt({ciphertext: actualCiphertextWA}, keyWA, {iv: ivWA});
var decryptedMessage = CryptoJS.enc.Utf8.stringify(decryptedMessageWA);
console.log(decryptedMessage);
} else {
console.log('Authentication failed!');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
Please note that it is better to use different keys for encryption and authentication, see here.

Can't decrypt using CryptoJS (works in Java, Python)

I have a file that I encrypt using AES. Historically, I've had a set of tools (Java, Python) each of which is capable to both encrypt and decrypt these files. However, I've been having problems decrypting these files using CryptoJS.
The encrypted file has IV stored in the first 16 bytes, the rest is payload. During encryption key is formed by using hashing the password string via SHA-1 and using first 32 characters from the hex digest. I've gotten to the point where I can confirm that both IV and key used by CryptoJS is byte-wise identical to the ones used by other tools yet AES.decrypt() produces a buffer that I can't convert back to text.
Here's the decryption code. content and iv are binary strings read directly from file. password is a string with textual password. The code fails trying to convert the result to UTF8 (which I assume is due to the fact that decryption did not succeed).
function string2bytes(s) {
var bytes = [];
for (var i = 0; i < s.length; i++) {
bytes.push(s.charCodeAt(i));
}
return bytes;
}
function decryptData(content, ivx, password) {
// build a key out of text password
var key = CryptoJS.SHA1(password).toString(CryptoJS.enc.Hex).substring(0, 32);
console.log("key0: ", key);
key = string2bytes(key)
console.log(key);
// Convert IV from binary string to WordArray
var iv = CryptoJS.enc.Latin1.parse(ivx);
console.log("IV: ", iv.toString(CryptoJS.enc.Hex));
var decrypted = CryptoJS.AES.decrypt(content, key, { iv: iv });
console.log("raw decrypted: ", decrypted);
console.log("decrypted: ", iv.toString(CryptoJS.enc.Latin1));
console.log("decrypted: ", iv.toString(CryptoJS.enc.Utf8));
}
Any help would be appreciated.
Found a solution by sticking to WordArrays (even for the arguments where a binary string is ostensibly OK): The following function does work. The arguments are as follows:
data is a base64 string with encrypted text
password is a regular string with password
iv is a base64 string with an IV.
so
function decryptData(data, password, iv) {
var data = CryptoJS.enc.Base64.parse(data);
var key = CryptoJS.SHA1(password).toString(CryptoJS.enc.Hex).substring(0, 32);
key = CryptoJS.enc.Base64.parse(btoa(key));
var iv = CryptoJS.enc.Base64.parse(iv);
var decrypted = CryptoJS.AES.decrypt({ciphertext: data}, key, {iv: iv});
return decrypted.toString(CryptoJS.enc.Utf8);
}

Encrypt in python - decrypt in Javascript

I have need to simply encrypt some text in python and being able to decrypt in JavaScrypt.
So far I have in python:
from Crypto import Random
from Crypto.Cipher import AES
import base64
BLOCK_SIZE = 16
key = "1234567890123456" # want to be 16 chars
textToEncrypt = "This is text to encrypt"
def encrypt(message, passphrase):
# passphrase MUST be 16, 24 or 32 bytes long, how can I do that ?
IV = Random.new().read(BLOCK_SIZE)
aes = AES.new(passphrase, AES.MODE_CFB, IV)
return base64.b64encode(aes.encrypt(message))
def decrypt(encrypted, passphrase):
IV = Random.new().read(BLOCK_SIZE)
aes = AES.new(passphrase, AES.MODE_CFB, IV)
return aes.decrypt(base64.b64decode(encrypted))
print encrypt( textToEncrypt, key )
this is producing text: ZF9as5JII5TlqcB5tAd4sxPuBXd5TrgE
in JavaScript:
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
<script>
var decrypted = CryptoJS.AES.decrypt( "ZF9as5JII5TlqcB5tAd4sxPuBXd5TrgE", "1234567890123456");
console.log ( decrypted.toString( CryptoJS.enc.Utf8 ) );
</script>
however it does not produce original string (empty string instead).
What I am doing wrong ?
Is it focusing on AES is a best idea - I will be happy if I have some kind of encryption that will blur data.
There are many problems with your Python code and CryptoJS code:
You use a random IV to encrypt some plaintext in Python. If you want to retrieve that plaintext, you need to use the same IV during decryption. The plaintext cannot be recovered without the IV. Usually the IV is simply prepended to the ciphertext, because it doesn't have to be secret. So you need to read the IV during decryption and not generate a new one.
You use CBC mode in CryptoJS (default) instead of CFB mode. The mode has to be the same. The other tricky part is that CFB mode is parametrized with a segment size. PyCrypto uses by default 8-bit segments (CFB8), but CryptoJS is only implemented for fixed segments of 128-bit (CFB128). Since the PyCrypto version is variable, you need to change that.
The CryptoJS decrypt() function expects as ciphertext either an OpenSSL formatted string or a CipherParams object. Since you don't have an OpenSSL formatted string, you have to convert the ciphertext into an object.
The key for CryptoJS is expected to be a WordArray and not a string.
Use the same padding. PyCrypto doesn't pad the plaintext if CFB8 is used, but padding is needed when CFB128 is used. CryptoJS uses PKCS#7 padding by default, so you only need to implement that padding in python.
Python code (for version 2):
def pad(data):
length = 16 - (len(data) % 16)
return data + chr(length)*length
def unpad(data):
return data[:-ord(data[-1])]
def encrypt(message, passphrase):
IV = Random.new().read(BLOCK_SIZE)
aes = AES.new(passphrase, AES.MODE_CFB, IV, segment_size=128)
return base64.b64encode(IV + aes.encrypt(pad(message)))
def decrypt(encrypted, passphrase):
encrypted = base64.b64decode(encrypted)
IV = encrypted[:BLOCK_SIZE]
aes = AES.new(passphrase, AES.MODE_CFB, IV, segment_size=128)
return unpad(aes.decrypt(encrypted[BLOCK_SIZE:]))
JavaScript code:
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/aes.js"></script>
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/components/mode-cfb-min.js"></script>
<script>
var base64ciphertextFromPython = "...";
var ciphertext = CryptoJS.enc.Base64.parse(base64ciphertextFromPython);
// split iv and ciphertext
var iv = ciphertext.clone();
iv.sigBytes = 16;
iv.clamp();
ciphertext.words.splice(0, 4); // delete 4 words = 16 bytes
ciphertext.sigBytes -= 16;
var key = CryptoJS.enc.Utf8.parse("1234567890123456");
// decryption
var decrypted = CryptoJS.AES.decrypt({ciphertext: ciphertext}, key, {
iv: iv,
mode: CryptoJS.mode.CFB
});
console.log ( decrypted.toString(CryptoJS.enc.Utf8));
</script>
Other considerations:
It seems that you want to use a passphrase as a key. Passphrases are usually human readable, but keys are not. You can derive a key from a passphrase with functions such as PBKDF2, bcrypt or scrypt.
The code above is not fully secure, because it lacks authentication. Unauthenticated ciphertexts may lead to viable attacks and unnoticed data manipulation. Usually the an encrypt-then-MAC scheme is employed with a good MAC function such as HMAC-SHA256.
(1 Year later but I hope this works for someone)
First of all, thanks Artjom B. your post helps me a lot. And Like OP, I have the same same problem Python server endonding and Javascript client decoding. This was my solution:
Python 3.x (Server)
I used an excplicit PKCS7 encode for padding, why? because I want to be sure Im using the same padding enconding and decoding, this is the link where I found it http://programmerin.blogspot.com.co/2011/08/python-padding-with-pkcs7.html .
Then, like Artjom B. said, be sure about your segment size, IV size and AES mode (CBC for me),
This is the code:
def encrypt_val(clear_text):
master_key = '1234567890123456'
encoder = PKCS7Encoder()
raw = encoder.encode(clear_text)
iv = Random.new().read( 16 )
cipher = AES.new( master_key, AES.MODE_CBC, iv, segment_size=128 )
return base64.b64encode( iv + cipher.encrypt( raw ) )
Note than your are enconding on base64 the concatenation of IV and encryption data.
Javascript (client)
function decryptMsg (data) {
master_key = '1234567890123456';
// Decode the base64 data so we can separate iv and crypt text.
var rawData = atob(data);
// Split by 16 because my IV size
var iv = rawData.substring(0, 16);
var crypttext = rawData.substring(16);
//Parsers
crypttext = CryptoJS.enc.Latin1.parse(crypttext);
iv = CryptoJS.enc.Latin1.parse(iv);
key = CryptoJS.enc.Utf8.parse(master_key);
// Decrypt
var plaintextArray = CryptoJS.AES.decrypt(
{ ciphertext: crypttext},
key,
{iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7}
);
// Can be Utf8 too
output_plaintext = CryptoJS.enc.Latin1.stringify(plaintextArray);
console.log("plain text : " + output_plaintext);
}
One of my main problem was keep in mind all kind of encoding and decoding data, for example, I didn't know that the master_key on client side was to be parse with Utf8.
//First pip install pycryptodome -- (pycrypto is obsolete and gives issues)
// pip install pkcs7
from Crypto import Random
from Crypto.Cipher import AES
import base64
from pkcs7 import PKCS7Encoder
from app_settings.views import retrieve_settings # my custom settings
app_secrets = retrieve_settings(file_name='secrets');
def encrypt_data(text_data):
#limit to 16 bytes because my encryption key was too long
#yours could just be 'abcdefghwhatever'
encryption_key = app_secrets['ENCRYPTION_KEY'][:16];
#convert to bytes. same as bytes(encryption_key, 'utf-8')
encryption_key = str.encode(encryption_key);
#pad
encoder = PKCS7Encoder();
raw = encoder.encode(text_data) # Padding
iv = Random.new().read(AES.block_size ) #AES.block_size defaults to 16
# no need to set segment_size=BLAH
cipher = AES.new( encryption_key, AES.MODE_CBC, iv )
encrypted_text = base64.b64encode( iv + cipher.encrypt( str.encode(raw) ) )
return encrypted_text;

Encrypt iOS and Decrypt Node.js AES

I have searched high and low for a solution which and encrypt on Node.js server and Objective-C client, and vise versa using AES (or other if appropriate)
I am relatively new to cryptography, and understanding why my encrypted text is different in each language is beyond my knowledge.
This is what I have so far:
Node.js crypto methods Using this CryptoJS Library - node-cryptojs-aes
var node_cryptojs = require("node-cryptojs-aes");
var CryptoJS = node_cryptojs.CryptoJS;
var textToEncrypt = 'Hello';
var key_clear = 'a16byteslongkey!';
//encrypted + decrypted
var encrypted = CryptoJS.AES.encrypt(clearText, key_clear, { iv: null });
var decrypted = CryptoJS.AES.decrypt(encrypted, key_clear, { iv: null });
//Outputs
console.log("encrypted: " + encrypted); //encrypted: U2FsdGVkX1/ILXOjqIw2Vvz6DzRh1LMHgEQhDm3OunY=
console.log("decrypted: " + decrypted.toString(CryptoJS.enc.Utf8)); // decrypted: Hello
Objective-C crypto methods Using AESCrypt library
NSString* textToEncrypt = #"Hello";
// encrypt
NSString* encryptedText = [AESCrypt encrypt:textToEncrypt password:#"a16byteslongkey!"];
// decrypt
NSString* decryptedText = [AESCrypt decrypt:encryptedText password:#"a16byteslongkey!"];
// output
NSLog(#"Text to encrypt: %#", textToEncrypt); // Text to encrypt: Hello
NSLog(#"Encrypted text: %#", encryptedText); // Encrypted text: wY80MJyxRRJdE+eKw6kaIA==
NSLog(#"Decrypted text: %#", decryptedText); // Decrypted text: Hello
I've been scratching my head for ages and tried everything I can think of. Can show underlying crypto methods from the libraries if required. There is SHAR256 hash applied to the key in AESCrypt library but I have removed this, and think there is some missmatch with the string encoding.
I'm posting this here because there are bound to be others trying to interop Node.js and iOS. Everyone seems to get hung up on keeping everything in the correct structures, buffers, strings etc. I know I did. So here is a step-by-step process to creating a key, creating an iv, encrypting, decrypting and placing in base64 for easy transfer:
JavaScript (Node.js using the CryptoJS module)
// Generate key from password and salt using SHA256 to hash and PDKDF2 to harden
var password = "1234567890123456";
var salt = "gettingsaltyfoo!";
var hash = CryptoJS.SHA256(salt);
var key = CryptoJS.PBKDF2(password, hash, { keySize: 256/32, iterations: 1000 });
console.log("Hash :",hash.toString(CryptoJS.enc.Base64));
console.log("Key :",key.toString(CryptoJS.enc.Base64));
// Generate a random IV
var iv = CryptoJS.lib.WordArray.random(128/8);
console.log("IV :",iv.toString(CryptoJS.enc.Base64));
// Encrypt message into base64
var message = "Hello World!";
var encrypted = CryptoJS.AES.encrypt(message, key, { iv: iv });
var encrypted64 = encrypted.ciphertext.toString(CryptoJS.enc.Base64);
console.log("Encrypted :",encrypted64);
// Decrypt base64 into message again
var cipherParams = CryptoJS.lib.CipherParams.create({ ciphertext: CryptoJS.enc.Base64.parse(encrypted64) });
var decrypted = CryptoJS.AES.decrypt(cipherParams, key, { iv: iv }).toString(CryptoJS.enc.Utf8);
console.log("Decrypted :",decrypted);
iOS SDK using CommonCrypto
// Generate key from password and salt using SHA256 to hash and PDKDF2 to harden
NSString* password = #"1234567890123456";
NSString* salt = #"gettingsaltyfoo!";
NSMutableData* hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
NSMutableData* key = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CC_SHA256(salt.UTF8String, (CC_LONG)strlen(salt.UTF8String), hash.mutableBytes);
CCKeyDerivationPBKDF(kCCPBKDF2, password.UTF8String, strlen(password.UTF8String), hash.bytes, hash.length, kCCPRFHmacAlgSHA1, 1000, key.mutableBytes, key.length);
NSLog(#"Hash : %#",[hash base64EncodedStringWithOptions:0]);
NSLog(#"Key : %#",[key base64EncodedStringWithOptions:0]);
// Generate a random IV (or use the base64 version from node.js)
NSString* iv64 = #"ludWXFqwWeLOkmhutxiwHw=="; // Taken from node.js CryptoJS IV output
NSData* iv = [[NSData alloc] initWithBase64EncodedString:iv64 options:0];
NSLog(#"IV : %#",[iv base64EncodedStringWithOptions:0]);
// Encrypt message into base64
NSData* message = [#"Hello World!" dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData* encrypted = [NSMutableData dataWithLength:message.length + kCCBlockSizeAES128];
size_t bytesEncrypted = 0;
CCCrypt(kCCEncrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
key.bytes,
key.length,
iv.bytes,
message.bytes, message.length,
encrypted.mutableBytes, encrypted.length, &bytesEncrypted);
NSString* encrypted64 = [[NSMutableData dataWithBytes:encrypted.mutableBytes length:bytesEncrypted] base64EncodedStringWithOptions:0];
NSLog(#"Encrypted : %#",encrypted64);
// Decrypt base 64 into message again
NSData* encryptedWithout64 = [[NSData alloc] initWithBase64EncodedString:encrypted64 options:0];
NSMutableData* decrypted = [NSMutableData dataWithLength:encryptedWithout64.length + kCCBlockSizeAES128];
size_t bytesDecrypted = 0;
CCCrypt(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
key.bytes,
key.length,
iv.bytes,
encryptedWithout64.bytes, encryptedWithout64.length,
decrypted.mutableBytes, decrypted.length, &bytesDecrypted);
NSData* outputMessage = [NSMutableData dataWithBytes:decrypted.mutableBytes length:bytesDecrypted];
NSString* outputString = [[NSString alloc] initWithData:outputMessage encoding:NSUTF8StringEncoding];
NSLog(#"Decrypted : %#",outputString);
The output should be the same
/*
Hash : AEIHWLT/cTUfXdYJ+oai6sZ4tXlc4QQcYTbI9If/Moc=
Key : WdHhJ19dSBURBA25HZSpbCJ4KnNEEgwzqjgyTBqa3eg=
IV : ludWXFqwWeLOkmhutxiwHw==
Encrypted : D3JpubesPMgQTiXbaoxAIw==
Decrypted : Hello World!
*/
Hopefully this saves someone else the time I've wasted :)
Are you sure the same key is being used in both libraries? You say you took out the SHA-256 part in AESCrypt, how is the library using the password parameter now? The AES algorithm can only use keys of 16, 24, or 32 bytes in length. Your password is 16 bytes long, but did you change the corresponding parameter to 128 (instead of 256) in the encrypt function?
Do you know how CryptoJS is using the key parameter? Are you sure it's being used directly, or might there be some processing (for example, hashing) before it's passed to the underlying primitive AES encryption function?
What mode of encryption is the CryptoJS library using? Its documentation doesn't say. Given that it asks for an IV, it's probably CBC, but you would have to look at the source to know for sure.
AESCrypt's documentation claims to use CBC mode, but you don't give it an IV anywhere. That must mean that it generates it own somewhere, or always uses a fixed one. (Which half defeats the purpose of CBC mode, but that's another story). So you need to figure out what the IV actually is.
TL;DR: unless you make sure that the same key and key length, the same mode, and the same IV are used across both libraries, then you will have different cipher text.

Categories