Converting Java BouncyCastle to C# Chilkat - java

I have the following JAVA and I am trying to convert into C# using ChilKat (or BouncyCastle) I have a working version in ChilKat, but not sure how to validate Here is the JAVA code:
private SecretKey symmKey = null;
public String encrypt(String strToEncrypt) throws Exception
{
String symmEncryptMode = "DESede";
String encString= null;
KeyGenerator keyGen = KeyGenerator.getInstance(symmEncryptMode);
symmKey = keyGen.generateKey();
byte dataToEncrypt[] = strToEncrypt.getBytes();
Cipher symmCipher = Cipher.getInstance(symmEncryptMode);
symmCipher.init(Cipher.ENCRYPT_MODE, symmKey);
byte[] encrypted = symmCipher.doFinal(dataToEncrypt);
encString= new String(Base64.encode(encrypted));
encString = URLEncoder.encode(encString, "UTF-8");
return(encString);
} //end method create Signature
Here is what I have so far (It returns a value, but I don't know how to validate as this is one of three steps of my encyption process - step 3 works, 1 and 2 are suspect, so I figured I'd ask one at a time...) This uses ChilKat and it returns a value, but I am not sure if it is correct:
private static string EncryptStringSymmetric(string data2Encrypt, ref string passKey)
{
//Init Encryptor
Crypt2 encryptor = new Crypt2();
bool success = encryptor.UnlockComponent("Anything for 30 - day trial");
if (success != true)
{ throw (new Exception("Crypt component unlock failed")); }
//Encryptor Settings
encryptor.CryptAlgorithm = "3des";
encryptor.KeyLength = 192;
encryptor.EncodingMode = "base64";
encryptor.PaddingScheme = 0;
encryptor.Charset = "utf-8";
encryptor.CipherMode = "ecb";
encryptor.RandomizeKey();
passKey = encryptor.GetEncodedKey("base64");
string eStr;
//byte[] bytesToEncrypt = Encoding.ASCII.GetBytes(data2Encrypt);
//eStr = encryptor.EncryptBytesENC(bytesToEncrypt);//??
eStr = encryptor.EncryptStringENC(data2Encrypt);
return eStr;
}

Related

.Net AES encrypt mismatch after decrypt on Java

I have to send the AES encrypted json as content to web server. But after decryption, the content has extra trash symbols appeared at the beggining of the line.
My test method creates the object that is serialized and being send:
[TestMethod]
public void SendModeChangeWorksAsExpected()
{
var snpashot2Send = new ModeChangedReport
{
ControlWorkMode = ModeEnumeration.Stopped,
//Controls
ControlDate = DateTime.Now,
IsSent = false,
SentTime = null,
ReportType = ReportType.ModeChanged,
Line = new Line
{
AgencyId = "a799eb4f-86da-4af1-a221-9ed8b741b5ce"
}
};
//Создаём шифрованное значение
var encryptedString = _agencyReportEncriptingTranslator.ConvertModeChange2CypheredString(snpashot2Send);
//Отправляем в Агентство и получаем результат
var value = _agencyClient.SendModeChangeReport(encryptedString);
}
Here are the serialization and encrypt methods:
public string ConvertModeChange2CypheredString(ModeChangedReport report)
{
if (report == null) throw new ArgumentNullException(nameof(report));
//obj to json
var json = new ModeChangedReportJson
{
LineId = report.Line.AgencyId,
Mode = CreateModeFromIktToUzbekistan(report.ControlWorkMode),
ActionDate = ConvertDateToAgencyString(report.ControlDate)
};
//Serialization
var retString = _agencyJsonSerializer.SerializeReport2Json(json);
//Шифруем сериализованный json
var cypheredValue = _encryptionService.EncryptString(retString);
return cypheredValue;
}
Encrypt method:
public string EncryptString(string plaintext)
{
var plainTextBytes = Encoding.UTF8.GetBytes(plaintext);
var cypheredTextBytes = Encrypt(plainTextBytes);
var converted2Base64Value = Convert.ToBase64String(cypheredTextBytes);
return converted2Base64Value;
}
private byte[] Encrypt(byte[] bytes)
{
#region Args Validation
if (bytes == null || bytes.Length < 1)
{
throw new ArgumentException("Invalid bytes to encrypt");
}
if (_key == null || _key.Length < 1)
{
throw new InvalidOperationException("Invalid encryption key");
}
#endregion
byte[] encrypted;
try
{
using (AesManaged aes = new AesManaged())
{
aes.Key = _key;
aes.IV = _iv;
aes.Padding = PaddingMode.PKCS7;
aes.Mode = CipherMode.CBC;
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, _iv);
using (MemoryStream ms = new MemoryStream())
{
ms.Write(aes.IV, 0, aes.IV.Length);
using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(bytes, 0, bytes.Length);
}
encrypted = ms.ToArray();
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
return encrypted;
}
Http client send method:
public bool SendModeChangeReport(string cypheredValue)
{
var token = GetAccessToken();
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.AuthorizationToken);
client.DefaultRequestHeaders.Add("DEVICE_ID", _agencyAppSettings.DeviceId);
var content2Post = new StringContent(cypheredValue, Encoding.UTF8, "application/json");
using (var response = client.PostAsync(_agencyAppSettings.SendModeChangedReportUrl, content2Post).Result)
{
string tokenResponse = null;
try
{
tokenResponse = response.Content.ReadAsStringAsync().Result;
response.EnsureSuccessStatusCode();
return true;
}
catch (Exception ex)
{
_eventLogManager.LogError("При попытке отправить отчёт о смене режима, произошла ошибка: "
+ $"Код: {response.StatusCode}. Контент: {tokenResponse}. Ошибка: {ex.Message}.");
return false;
}
}
}
}
After decryption on receiving server, the string grows with extra trash characters at the beginning, like G���h R��EQ�Z {"lineid":"a799eb4f-86da-4af1-a221-9ed8b741b5ce"...
The decrypt method of the server (Java):
I think that the problem is the padding difference: PKCS7 on my side, and PKCS5 on server.
How can I solve this problem with the extra chars appear on server side?
Those aren't trash characters, they're the Unicode Replacement Character returned when bytes are decoded into text using the wrong character set.
The very fact you got readable text means decrypting succeeded. It's decoding the bytes into text that failed.
The bug is in the Java code. It's using the String(byte[]) which, according to the docs:
Constructs a new String by decoding the specified array of bytes using the platform's default charset.
That's obviously not UTF8. The String​(byte[] bytes,Charset charset) or String​(byte[] bytes,String charsetName) constructors should be used instead, passing the correct character set, eg :
byte[] decryptedBytes = cipher.doFinal(....);
return new String(decryptedBytes, StandardCharsets.UTF_8);
The hacky alternative is to change the remote server's default character set to UTF8.

Encrypted RequestParams For Secure API

I am learning android and trying to make MY Application secure as much as possible.
I have bought one android app which have below class
public class API {
#SerializedName("sign")
private String sign;
#SerializedName("salt")
private String salt;
#SerializedName("package_name")
private String package_name;
public API(Activity activity) {
String apiKey = "secretkey";
salt = "" + getRandomSalt();
sign = md5(apiKey + salt);
package_name = activity.getApplication().getPackageName();
}
public API(Context context) {
String apiKey = "secretkey";
salt = "" + getRandomSalt();
sign = md5(apiKey + salt);
package_name = context.getApplicationContext().getPackageName();
}
private int getRandomSalt() {
Random random = new Random();
return random.nextInt(900);
}
private String md5(String input) {
try {
// Create MD5 Hash
MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
digest.update(input.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuilder hexString = new StringBuilder();
for (int i = 0; i < messageDigest.length; i++)
hexString.append(String.format("%02x", messageDigest[i]));
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
public static String toBase64(String input) {
byte[] encodeValue = Base64.encode(input.getBytes(), Base64.DEFAULT);
return new String(encodeValue);
}
}
And its using like below
JsonObject jsObj = (JsonObject) new Gson().toJsonTree(new API(Login.this));
jsObj.addProperty("email", sendEmail);
jsObj.addProperty("password", sendPassword);
assert device != null;
jsObj.addProperty("player_id", device.getUserId());
jsObj.addProperty("method_name", "user_login");
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
Call<LoginRP> call = apiService.getLogin(API.toBase64(jsObj.toString()));
I think its securing data with encryption with API class
I am trying to use above method in my application but I am using RequestParams so I am not getting idea how I can use above in my case
RequestParams requestParam=new RequestParams();
requestParam.put("mobile",mobile_number);
requestParam.put("password","password");
AsyncHttpClient client = new AsyncHttpClient();
client.get(BASE_URL+"login.php?",requestParams, new AsyncHttpResponseHandler() {
Let me know if anyone here can help me for solve my issue.
Thanks a lot!
As far as securing data, as long as you are using SSL/TLS, (even with GET), your data is encrypted during transfer.
That being said, the code above has MD5 and Base64 methods to convert the info to a hash. Using those functions, you can have a secret key shared between your app and the server, and use that key to encrypt/decrypt the hash values.
To make the md5 function more secure, you can create a know "salt" value rather than a random one, then update the function like this:
public static String md5(String salt, String plainText)
throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("MD5");
if (salt != null) {
md.update(salt.getBytes());
}
md.update(plainText.getBytes());
byte byteData[] = md.digest();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < byteData.length; i++) {
sb.append(Integer.toString((byteData[i] & 0xff) + 0x100, 16)
.substring(1));
}
return sb.toString();
}
And update the call like this:
requestParam.put("password", md5(md5(SALT_VALUE), md5(password));
Also, as a side-note, with GSON, you do not have to construct your own JSON objects, you can use the actual object reference and call .toJson()

Which version of bcrypt is used in Quarkus and how to generate valid hashes?

We are currently migrating our application from payara to Quarkus and having trouble with the bCrypt mapper.
We cannot properly generate correct hashes because we do not know which version of bCrypt is used. We tried with the example hashes from your github and that works fine, but when we try to use our own generated hashes it doesn't.
We tried using multiple online generators but we cannot find out which version is used in Quarkus. Then we tried to use the wildfly generator code snippet (see below), because we thought that would use the same algorithm as quarkus does, but that also didn't work.
static final Provider ELYTRON_PROVIDER = new WildFlyElytronProvider();
static final String TEST_PASSWORD = "test";
public static void main(String[] args) {
PasswordFactory passwordFactory = null;
try {
passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PROVIDER);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
int iterationCount = 10;
byte[] salt = new byte[BCryptPassword.BCRYPT_SALT_SIZE];
SecureRandom random = new SecureRandom();
random.nextBytes(salt);
IteratedSaltedPasswordAlgorithmSpec iteratedAlgorithmSpec = new IteratedSaltedPasswordAlgorithmSpec(iterationCount, salt);
EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(TEST_PASSWORD.toCharArray(), iteratedAlgorithmSpec);
BCryptPassword original = null;
try {
original = (BCryptPassword) passwordFactory.generatePassword(encryptableSpec);
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
byte[] hash = original.getHash();
Base64.Encoder encoder = Base64.getEncoder();
System.out.println("Encoded Salt = " + encoder.encodeToString(salt));
System.out.println("Encoded Hash = " + encoder.encodeToString(hash));
}
When we switch from bCrypt to clear it works fine as well, which means the database works fine.
Since the example hashes worked fine we do not think the configuration is wrong, but here is our config just in case:
# h2 datasource
#quarkus.datasource.url = jdbc:h2:mem:test
#quarkus.datasource.driver = org.h2.Driver
#quarkus.datasource.username = sa
#quarkus.datasource.password =
# postgres configuration
quarkus.datasource.url = jdbc:postgresql://localhost:5432/postgres
quarkus.datasource.driver = org.postgresql.Driver
quarkus.datasource.username = postgres
quarkus.datasource.password = postgres
#Authorisierung
quarkus.http.auth.basic=true
quarkus.security.jdbc.realm-name=quarkus
quarkus.security.jdbc.enabled=true
quarkus.security.jdbc.principal-query.sql=SELECT n.hash, n.salt, n.iterations FROM nutzer n WHERE n.nutzername=?
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.enabled=true
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.password-index=1
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.salt-index=2
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.iteration-count-index=3
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.hash-encoding=BASE64
quarkus.security.jdbc.principal-query.roles.sql=SELECT nb.berechtigungen_name FROM nutzer n JOIN nutzer_berechtigung nb ON nb.nutzers_nutzername = n.nutzername WHERE n.nutzername=?
quarkus.security.jdbc.principal-query.roles.datasource=permissions
quarkus.security.jdbc.principal-query.roles.attribute-mappings.0.index=1
quarkus.security.jdbc.principal-query.roles.attribute-mappings.0.to=groups
# drop and create the database at startup (use `update` to only update the schema)
quarkus.hibernate-orm.database.generation=drop-and-create
#Always include swagger-ui
quarkus.swagger-ui.always-include=true
We do not get an error message, we just can't authenticate an user.
Are we making a mistake in our config or are we using the wrong algorithm or version of bCrypt?
You can use the example from wildfly github to generate a valid hash and salt:
static final Provider ELYTRON_PROVIDER = new WildFlyElytronPasswordProvider();
static final String TEST_PASSWORD = "myPassword";
public static void main(String[] args) throws Exception {
PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT, ELYTRON_PROVIDER);
int iterationCount = 10;
byte[] salt = new byte[BCryptPassword.BCRYPT_SALT_SIZE];
SecureRandom random = new SecureRandom();
random.nextBytes(salt);
IteratedSaltedPasswordAlgorithmSpec iteratedAlgorithmSpec = new IteratedSaltedPasswordAlgorithmSpec(iterationCount, salt);
EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(TEST_PASSWORD.toCharArray(), iteratedAlgorithmSpec);
BCryptPassword original = (BCryptPassword) passwordFactory.generatePassword(encryptableSpec);
byte[] hash = original.getHash();
Encoder encoder = Base64.getEncoder();
System.out.println("Encoded Salt = " + encoder.encodeToString(salt));
System.out.println("Encoded Hash = " + encoder.encodeToString(hash));
}
Note that WildFlyElytronProvider() is deprecated so far, so you have to use WildFlyElytronPasswordProvider() instead.

C# encrypted data getting truncated using Java decryption code

All,I am posting some encrypted xml data(Using AES-128 ) to another application that uses Java to decrypt.When the Java code decrypts the xml,the start tag of the xml is getting truncated and fails validation.I don't have access to their code base .I can decrypt the same data using C# without any data loss.Please see the code I use to encrypt and Decrypt the data . I have researched this and based on the research ,I added the FlushFinalBlocks() and Close() to the CryptoStream in the encryption logic ,but this doesnt seem to fix the issue.
Encryption Code:
public static string Aes128Encrypt(string plainText)
{
string encodedPayload = null;
string base64Iv = null;
string base64Key = null;
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
using (RijndaelManaged aesAlg = new RijndaelManaged())
{
aesAlg.KeySize = 128;
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.BlockSize = 128;
base64Iv = Convert.ToBase64String(aesAlg.IV);
base64Key = Convert.ToBase64String(aesAlg.Key);
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
csEncrypt.Write(plainBytes, 0, plainBytes.Length);
csEncrypt.FlushFinalBlock();
encodedPayload = Convert.ToBase64String(msEncrypt.ToArray());
csEncrypt.Close();
}
msEncrypt.Flush();
msEncrypt.Close();
}
}
return encodedPayload ;
}
Decryption Code:
public static string Aes128Decrypt(string base64Key, string base64IV, string encodedPayload)
{
string plainText = null;
byte[] key = Convert.FromBase64String(base64Key);
byte[] iv = Convert.FromBase64String(base64IV);
byte[] encryptedBytes = Convert.FromBase64String(encodedPayload);
using (RijndaelManaged aesAlg = new RijndaelManaged())
{
aesAlg.KeySize = 128;
aesAlg.Mode = CipherMode.CBC;
aesAlg.BlockSize = 128;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Key = key;
aesAlg.IV = iv;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(encryptedBytes))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
plainText = srDecrypt.ReadToEnd();
}
}
}
}
return plainText;
}
Testing Code:
string textXml = #"<person>
<firstName>Rennish</firstName>
<lastName>Joseph</lastName>
<accountNumber>12345678910</accountNumber>
<ssn>123456</ssn>
</person>";
Aes128Encrypt(textXml);
string encodedPayload = "4p6uU7SiqB0uCzsrWXMOStP02HM7mKA6QVzcKoNdu3w1+MYLjYVbW/Ig3XPKRRafeu+WKDMuKJJaEREkrZt/Ycvc50wfe2naJ9d0UT5B7Fre1gIsNfZUIK3SF304+WF8zX730mVsluJABKT3JCkk9AkOGCQWPYzcZvH9dojIqGP7V+2j1+IMOPMWWFIitkAi8B7ALxMuMcepzX2/cxHxH7NeID0ytEGUzGfJXSAzQcvBX9dWwUqdMX3Eip5SRPMsotnWWsFTjDuOiZk/q5fuxxWbS6cuYn/64C/vQjEIuheQKn0ZOIDLNPCUavvWD2u6PWNKMNgW/qUIq13W9PQxzIiQxrT7ZqPFJu75C1KdXXUG5lghU7EBAGehHC/5BqFjs9SuYJkV1RrchMEzytrJIQ7Zp4CnOU6Q1rEhFTaMk/s=";
string encodedKey = "2zpVbIxqvjSfJo7zkXzl2A==";
string encodedIV = "5WOQPdmB/BkECmuPdNTaLw==";
Aes128Decrypt(encodedKey, encodedIV, encodedPayload);
Data after encryption at the JAVA application looks like this
<rson>
<firstName>Rennish</firstName>
<lastName>Joseph</lastName>
<accountNumber>12345678910</accountNumber>
<ssn>123456</ssn>
</person>
Interesting problem.
I think the encryption and decryption works fine on both sides.
If part of the encrypted message was lost in transmission you would not be able to decrypt it due to the avalanche effect. So it appears that characters go missing in the plain text.
This might be an encoding issue in the plain text message. The bytes you have encoded and the bytes they decoded are probably the same. The way they are interpreted might not be.
Now there are two options here:
Either <person> becomes <rson> or it becomes rson> and there was a copy-paste mistake.
If the latter case is true then we're missing 3 bytes. This makes me think that the protocol might presume the presence of a byte order marker andsimply removes the first 3 bytes to get rid of it.
If the former case you'd have some very weird encoding issues. As all missing characters appear to be in the ascii range so they shouldn't have these issues.
Easy to test though:
1. Try sending with a byte order marker.
2. Try sending with <XXperson>
3. Try sending some characters with accents and the like.

Decrypting AES encrypted string in C#

I am trying to decrypt an AES encrypted string from Java, in C#. When I decrypt, it returns gibberish and does not match the original plain text, which was encrypted via Java code. Pls guide me on what is going wrong here.
Attached the Java code for encryption and the C# code for decryption. Pls let me know if you need more details.
I tried AesCryptoServiceProvider as well and it did not work either. You can see the code tried in the commented code in C#.
Pls note that I can make changes on my C# code only to match the Java code and can not make any edits to Java side.
Java code for Encryption:
/** encrypt cipher */
private static final Cipher ENCRYPT_CIPHER = generateCipher(Cipher.ENCRYPT_MODE);
private static String ENCRYPT_KEY = "key";
/**
* #param val
* #return encrypted value
* #throws Exception
*/
public String encrypt(final String val) throws Exception {
return new String(Base64.encodeBase64(ENCRYPT_CIPHER.doFinal(val.getBytes()), true)).toString();
}
/**
* #param encrypt
* #return cipher
*/
protected static Cipher generateCipher(final int encrypt) {
try {
final Cipher cipher = Cipher.getInstance("AES");
cipher.init(encrypt, SecretKeyFactory.getInstance("AES").generateSecret(new IBMAESKeySpec(Base64.decodeBase64(ENCRYPT_KEY.getBytes()))));
return cipher;
} catch (final Exception e) {
return null;
}
}
C# code for decryption:
private static String ENCRYPT_KEY = "key";
public String decodeString (String encodedStr)
{
/*using (var aesCryptoProvider = new AesCryptoServiceProvider())
{
aesCryptoProvider.BlockSize = 128;
aesCryptoProvider.KeySize = 256;
aesCryptoProvider.Key = Convert.FromBase64String(ENCRYPT_KEY.ToString());
aesCryptoProvider.Padding = PaddingMode.Zeros;
aesCryptoProvider.Mode = CipherMode.ECB;
using (var decryptor = aesCryptoProvider.CreateDecryptor())
using (var memoryStream = new MemoryStream(Convert.FromBase64String(encodedStr)))
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
using (var streamReader = new StreamReader(cryptoStream, Encoding.UTF8))
{
decodedStr = streamReader.ReadToEnd();
}
}
*/
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.Key = Convert.FromBase64String(ENCRYPT_KEY.ToString()); ;
aesAlg.BlockSize = 128;
aesAlg.KeySize = 256;
aesAlg.Mode = CipherMode.ECB;
aesAlg.Padding = PaddingMode.Zeros;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor();
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(Convert.FromBase64String(encodedStr)))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
decodedStr = srDecrypt.ReadToEnd();
}
}
}
}
}
This is just a quick answer. Haven't done a ton of research into it, but have you checked to see if the endian-ness matches? It looks like C# (.NET) is little-endian, but the JVM is big-endian. I'm not sure if it swaps it for network transmission, however (then it would just match the hardware). Just an idea. If I find anything additional, I'll update my answer.

Categories