Hybrid encryption approach fails - java

Im having trouble with my attempt to create a small programme using hybrid encryption with AES and RSA. It works just fine using only symmetric encryption AES which i have tried, but when i try to implement RSA to wrap the AES encrypted message and key i just cant get it to work.
I have understood that a programme like this can be done using outputstreams or cipherstreams but I´d like to solve it this way if it is possible. In other words id like a user to enter any string into the JOptionInputDialog and get it encrypted with AES key and then the AES key encrpyted with RSA public key and then decrypted with a private RSA key.
I want the answer to display in the same JOptionPane window. In example :
The encrypted text : jfjfjh
The decrypted text : Hello
I have issues right now understanding how to get that string decrypted with the private RSA key. I dont know what i am missing or doing wrong. From any examples ive been googling for the past week and a half i think it looks fine. I must be missing something that is right infront of my eyes but i cant see it since i sit up for too many hours trying to find a different approach ( i think ive changed it a million times but i cant show you all my approaches so this is the one i share with you. Id be really thankful for any kind of help. So here is my code: (Hope you can understand as some words are in my language)
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.swing.JOptionPane;
/**
*
* #author Patricia
*/
public class EncryptionDecryption {
/**
* #param args the command line arguments
* #throws java.lang.Exception
*/
public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException{
//Creating assymmetric keys RSA ( A public Key and Private Key )
KeyPairGenerator gen2 = KeyPairGenerator.getInstance("RSA");
gen2.initialize(1024);
KeyPair keyPair = gen2.genKeyPair();
PrivateKey privatnyckel = keyPair.getPrivate();
PublicKey publiknyckel = keyPair.getPublic();
//Create assymmetric key AES //Create key generator
KeyGenerator gen = KeyGenerator.getInstance("AES"); //Returns keygenerator object that generates key for specified algorithm in this case AES
SecureRandom random = new SecureRandom();
gen.init(random);
// create a key
SecretKey AES = gen.generateKey();
//get the raw key bytes
byte[] symmetriskNyckel =AES.getEncoded(); //Returns copy of the key bytes
//Create cipher based upon AES, encrypt the message with AES key
Cipher cipher = Cipher.getInstance("AES");
//Initialize cipher with secret key AES
cipher.init(Cipher.ENCRYPT_MODE, AES);
// get the text to encrypt with AES key
String inputText1 = JOptionPane.showInputDialog(" Enter the secret message");
//Encrypt the plaintext message you wanna send with the symmetric key AES;
byte[] kryptera = cipher.doFinal(inputText1.getBytes());
//encrypt AES key with RSA
Cipher pipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
pipher.init(Cipher.WRAP_MODE, publiknyckel);
byte[] krypteradAESNyckel = cipher.wrap(AES);
JOptionPane.showMessageDialog(null, "AES key encrypted with RSA public key " + krypteradAESNyckel);
// re-initialise the cipher to be in decrypt mode
Cipher flipher = Cipher.getInstance("RSA");
flipher.init(Cipher.UNWRAP_MODE, privatnyckel );
// decrypt message
byte [] dekryptera = flipher.unwrap(kryptera);
JOptionPane.showMessageDialog
(null, "AES symmetrisk nyckel " +symmetriskNyckel );
// and display the results //
JOptionPane.showMessageDialog(JOptionPane.getRootFrame(),
"Texten krypterad " + new String(kryptera) + "\n"
+ "Text dekrypterat: " + new String(dekryptera));
JOptionPane.showMessageDialog(null, "\n RSA assymmetrisk privat nyckel " + privatnyckel
+ "RSA assymmetrisk publik nyckel" + publiknyckel);
// end example
System.exit(0);
}
}

I think your variable names and comments are fine to start with. When working with something new, write notes to yourself in the code. You can come back later once it works and refactor with rich, meaningful names, and trim the comments for clarity, so that they speak to you six months or a year from now to remind you of what you were thinking of at the time that you wrote this. (And I think Swedish names are just as good as English.)
As JB Nizet points out, you are unwrapping kryptera when you should be unwrapping the symmetric key contained in krypteradAESNyckel. Next, you would decrypt kryptera with the recovered symmetric key. (Your code justs outputs the earlier symmetriskNyckel without having actually unwrapped it freshly from krypteradAESNyckel.)
I also notice that in one case you
Cipher.getInstance("RSA/ECB/PKCS1Padding");
but later you
Cipher.getInstance("RSA");
I would make them consistent, both "RSA/ECB/PKCS1Padding". This attention to detail is just as important as clear variable names and meaningful comments.

Related

How to create javax.crypto.SecretKey based on password and salt

I am not a security expert.
I have a requirement to generate javax.crypto.SecretKey based on password and salt.
In the existing code, we already have logic to generate javax.crypto.SecretKey but not based on password and salt`.
Also, in the existing code we already encrypt and decrypt using the javax.crypto.SecretKey.
There is already lot of data in DB which is encrypted using existing encrypt code and I dont think I can change existing encrypt and decrypt logic.
I am getting below the error when I try to decrypt data using the key generated based on password and salt with existing decrypt code.
key.getAlgorithm(): DESede
encryptedData: [B#31dc339b
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
at com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:975)
at com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1056)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
at com.sun.crypto.provider.DESedeCipher.engineDoFinal(DESedeCipher.java:294)
at javax.crypto.Cipher.doFinal(Cipher.java:2168)
at com.arjun.mytest.PMAdminKeyTest.main(PMAdminKeyTest.java:41)
import java.security.KeyStore;
import java.security.Provider;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class PMAdminKeyTest {
public static void main(String[] args) throws Exception {
// Requirement is to generate Key based on password and salt
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec keySpec = new PBEKeySpec("password".toCharArray(), "salt".getBytes(), 65536, 192);
SecretKey key = new SecretKeySpec(secretKeyFactory.generateSecret(keySpec).getEncoded(), "DESede");
System.out.println("key.getAlgorithm(): " + key.getAlgorithm());
byte[] data = "12345678".getBytes("UTF8");
// Existing encrypt and decrypt code. There is already lot of data in DB
// encrypted in this manner. I dont think I can change this code.
Cipher cipher = Cipher.getInstance(key.getAlgorithm() + "/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedData = cipher.doFinal(data);
System.out.println("encryptedData: " + encryptedData.toString());
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decryptedData = cipher.doFinal(data);
System.out.println("decryptedData: " + decryptedData.toString());
}
}
The only issue I can see is that you pass the unencrypted data to the Cipher in decrypt mode, which won't work. (The cipher obviously cannot decrypt data which is not encrypted without getting odd results.)
So change
byte[] decryptedData = cipher.doFinal(data);
to
byte[] decryptedData = cipher.doFinal(encryptedData);
Then, everything works fine.
Altough I doubt this error exists in your productive code, so if you still have problems on that one, feel free to ask a new question.
You are not decrypting the encrypted data, you are simply trying to decrypt the original data.
Also while printing the data use UTF-8
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class PMAdminKeyTest {
public static void main(String[] args) throws Exception {
// Requirement is to generate Key based on password and salt
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec keySpec = new PBEKeySpec("password".toCharArray(), "salt".getBytes(), 65536, 192);
SecretKey key = new SecretKeySpec(secretKeyFactory.generateSecret(keySpec).getEncoded(), "DESede");
System.out.println("key.getAlgorithm(): " + key.getAlgorithm());
byte[] data = "12345678".getBytes("UTF8");
// Existing encrypt and decrypt code. There is already lot of data in DB
// encrypted in this manner. I dont think I can change this code.
Cipher cipher = Cipher.getInstance(key.getAlgorithm() + "/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedData = cipher.doFinal(data);
System.out.println("encryptedData: " + encryptedData.toString());
cipher.init(Cipher.DECRYPT_MODE, key);
// Notice this
byte[] decryptedData = cipher.doFinal(encryptedData);
// while printing the data use UTF-8
System.out.println("decryptedData: " + new String(decryptedData, "UTF-8"));
}
}

encrypt files with AES , with shared password

I want to encrypt/ decrypt with AES, with shared passwod, my code same as here.
the linked code works fine, but there is no shared passworn in it.
how can I add a shared password to the following implementation?
I need something like
String shared="xxx..";//some password with 16 digits length
Is it possible?
and adding this shared password to the encryption.
It is very important that the key used for AES encryption is not easy to guess so in a lot of implementations the keys are generated randomly. The key itself is a byte array of 16 (128 bit), 24 (192 bit) or 32 (256 bit) byte length and a byte array is not usuable as source for a shared password.
The solution is to encode the byte array into a Base64-encoded string and pass this string to the recepient on a secure way. The recepient decodes the string back to a byte array and further via the SecretKeySpec to a secret key.
The small example shows the way to securly generate a random password with different lengths (the example uses only the 128 bit keylength, encode it and decode it back to a secret key - the orginal SecretKey k is compared to the regenerated SecretKex kReceived.
Just a last notice but it is a security warning: Your encryption method is using the AES ECB mode that is unsecure - please do not use this mode in production (mode is defined here: AES/ECB/PKCS5Padding).
Result:
https://stackoverflow.com/questions/62782129/encrypt-files-with-aes-with-shared-password
sharedKey: UT7PPJwX2fnYTazSOZAhxg==
keySpecReceived equals secretKey: true
Code:
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
public class Main {
public static void main(String[] args) throws NoSuchAlgorithmException {
System.out.println("https://stackoverflow.com/questions/62782129/encrypt-files-with-aes-with-shared-password");
// random key creation taken from https://stackoverflow.com/a/41414233/9114020
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = new SecureRandom();
int keyBitSize = 128; // aes keylength can be 128, 192 or 256 bit
keyGenerator.init(keyBitSize, secureRandom);
SecretKey k = keyGenerator.generateKey();
// encode the key and then base64-encoding
String sharedKey = Base64.getEncoder().encodeToString(k.getEncoded());
System.out.println("sharedKey: " + sharedKey);
// share this key with another party on a secure way
String sharedKeyReceived = sharedKey; // simulates the receiving
byte[] sharedKeyByteReceived = Base64.getDecoder().decode(sharedKeyReceived);
SecretKeySpec kReceived = new SecretKeySpec(sharedKeyByteReceived, "AES");
System.out.println("keySpecReceived equals secretKey: " + kReceived.equals(k));
}
}

RSA and AES for encrypt and decrypt a plainText (verify key)

I want to ask you some question about security on key in java. My code using RSA and AES for encrypt and decrypt a plainText. And I want to use my own password as a private key(AES) for verifies key(RSA). Because RSA are generate public and private key random. (In real case: private key is input by user and public key store in database for verification.)
This code is work find, but I want to know this code is correct or not? Please advice! Thank you.
My process:
1. generate a symmetric key
2. Encrypt the data with the symmetric key
3. Encrypt the symmetric key with rsa
4. send the encrypted key and the data
5. Decrypt the encrypted symmetric key with rsa
6. decrypt the data with the symmetric key
7. done
import java.io.UnsupportedEncodingException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class AES_RSA {
private static byte[] key;
private static SecretKeySpec secretKey;
public static void main(String[] args){
try {
//1. Generate Symmetric Key (AES with 128 bits)
String password = "123456";
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(128); // The AES key size in number of bits
setKey(password);
//2. Encrypt plain text using AES
String plainText = "Please encrypt me urgently...";
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
aesCipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] byteCipherText = aesCipher.doFinal(plainText.getBytes());
//3. Encrypt the key using RSA public key
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair keyPair = kpg.generateKeyPair();
PublicKey puKey = keyPair.getPublic();
PrivateKey prKey = keyPair.getPrivate();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.PUBLIC_KEY, puKey);
byte[] encryptedKey = cipher.doFinal(secretKey.getEncoded()/*Seceret Key From Step 1*/);
//4. Send encrypted data (byteCipherText) + encrypted AES Key (encryptedKey)
//5. On the client side, decrypt symmetric key using RSA private key
cipher.init(Cipher.PRIVATE_KEY, prKey);
byte[] decryptedKey = cipher.doFinal(encryptedKey);
//6. Decrypt the cipher using decrypted symmetric key
SecretKey originalKey = new SecretKeySpec(decryptedKey , 0, decryptedKey.length, "AES");
Cipher aesCipher1 = Cipher.getInstance("AES/ECB/PKCS5PADDING");
aesCipher1.init(Cipher.DECRYPT_MODE, originalKey);
byte[] bytePlainText = aesCipher1.doFinal(byteCipherText);
String plainText1 = new String(bytePlainText);
//7. Done! 'Please encrypt me urgently...'
System.out.println(plainText1);
}catch (Exception e) {}
}
public static void setKey(String myKey)
{
MessageDigest sha = null;
try {
key = myKey.getBytes("UTF-8");
sha = MessageDigest.getInstance("SHA-256");
key = sha.digest(key);
key = Arrays.copyOf(key, 16);
secretKey = new SecretKeySpec(key, "AES");
}
catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
You pseudo-steps are correct, but your description not. For example, you normally keep RSA private key and distribute RSA public key.
But a few suggestions for creating better code.
I suggest to use PKCS5 for creating password-based secret keys rather than a simple hash. Java's PBEKeySpec is a class for generating such secret keys. Here is a small sample code which you can use for your setKey() routine (adjust it as you prefer):
SecretKeyFactory skf = SecretKeyFactory.getInstance("AES");
SecretKey key = skf.generateSecret(new PBEKeySpec(password.getBytes("UTF-8"), salt, 10000));
Never use ECB mode of operation for encrypting data. In worse case, use CBC with randomized IV.
You got something wrong. RSA private key is kept private on server, but RSA public key is distributed freely between clients(normally). You are doing this in the other way.
This is just a suggestion, but I think it is better to use RSA/ECB/OAEPWithSHA-256AndMGF1Padding rather than RSA/ECB/PKCS1Padding for RSA padding. But I think it is not necessary.
In general, you must add a hash or HMAC for authenticating your encrypted data too. But you don't have any authenticating mechanism right now.
Update: Based on design of your mechanism, you cannot securely add an authentication method for detecting active attacks(such as man-in-the-middle). Check comments from Maarten too.
These are the problems that I found. The most important one is just using RSA key-pair in an incorrect way.

(AES Encryption) Code flaws, what should I be carefull with? [Code Provided][Java]

Thank you for taking you time to assist me with this!
THIS POST HAS BEEN EDITED FOR LESS INFORMATION SEE THE EDITED PART
Well I have spend ours of research on this matter and I ended up with a working piece of code..
But Encryption is not a place to make mistakes, and I wanted to ask if my code is actualy secure! It's really important for me because I want to implement it to a program so my code is...
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
public class EncryptFile{
private static final String FILE_IN = "./EncryptFile.java";
private static final String FILE_ENCR = "./EncryptFile_encr.java";
private static final String FILE_DECR = "./EncryptFile_decr.java";
public static void main(String []args){
try
{
Encryption("passwordisnottheactual", Files.readAllBytes(Paths.get(FILE_IN)));
Decryption("passwordisnottheactual");
}catch(Exception e){
System.out.println(e.getMessage());
}
}
private static void Encryption(String Key, byte[] byteArray) throws Exception
{
// Decode the base64 encoded Key
byte[] decodedKey = Base64.getDecoder().decode(Key);
// Rebuild the key using SecretKeySpec
SecretKey secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
// Cipher gets AES Algorithm instance
Cipher AesCipher = Cipher.getInstance("AES");
//Initialize AesCipher with Encryption Mode, Our Key and A ?SecureRandom?
AesCipher.init(Cipher.ENCRYPT_MODE, secretKey, new SecureRandom());
byte[] byteCipherText = AesCipher.doFinal(byteArray);
//Write Bytes To File
Files.write(Paths.get(FILE_ENCR), byteCipherText);
}
private static void Decryption(String Key) throws Exception
{
//Ddecode the base64 encoded string
byte[] decodedKey = Base64.getDecoder().decode(Key);
//Rebuild key using SecretKeySpec
SecretKey secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
//Read All The Bytes From The File
byte[] cipherText = Files.readAllBytes(Paths.get(FILE_ENCR));
//Cipher gets AES Algorithm Instance
Cipher AesCipher = Cipher.getInstance("AES");
//Initialize it in Decrypt mode, with our Key, and a ?SecureRandom?
AesCipher.init(Cipher.DECRYPT_MODE, secretKey, new SecureRandom());
byte[] bytePlainText = AesCipher.doFinal(cipherText);
Files.write(Paths.get(FILE_DECR), bytePlainText);
}
}
EDIT
Possible duplicate of Simple Java AES encrypt/decrypt example – JFPicard
Well it could be but these answers Use IVParameterSpec and I wanted to know if
this line of code is actually secure or if it is bad practice:
AesCipher.init(Cipher.DECRYPT_MODE, secretKey, new SecureRandom());
because I use a new SecureRandom() every time,
and I haven't seen anyone use a SecureRandom object like this.
Encryption key
The password is passes as a string but the Encryption function Base64 decoded it, that is a coding error.
When a password is used the encryption key should be derived from it with the PBKDF2 (aka Rfc2898DeriveBytes) function.
When using key derivation the salt and iteration count needs to be available for decryption, often they are provided in a prefix to the encrypted data.
Encryption mode
No encryption mode is supplied.
Use CBC mode with a random IV.
Just prefix the encrypted data with the IV for use on decryption.
Padding
AES is a block cipher and as such requires the input data size to be a multiple of the block size.
Specify PKCS#7 (née PKCS#5) padding, it will add padding on encryption and remove it on decryption.
On decryption do not return "padding" errors, they can provide a "Padding Oracle" attack.
Explicit
Specify all encryption parameters and sizes.
Do not rely on implementation defaults.
Encryption authentication
Consider if there is a need to know if the data is decrypted correctly.
Versioning
Add a version indicator so that if changes are necessary later there is an compatibility path.
Or consider using RNCryptor which handles all this and more.
Update: (thx Andy for the comment)
If GCM mode is available and interoperability across platforms and libraries is not an issue GCM is arguably a better encryption mode. GCM has authentication and padding build-in making it more robust and an easier secure solution.

Decrypted RSA result not matching the signed message

I used Bouncy Castle Crypto APIs to decrypt a cipher text, and verify the signed message.
The signed message:
signedMessageOther: D0F39F8C8495CA83D2F24536083CCB280CD3A54B
The message decrypted:
01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFF003021300906052B0E03021A05000414D0F39F8C8495CA83D2F2453608
3CCB280CD3A54B
Only the last 20 bytes match the signedMessageOther.
Why?
Here is something about the key:
Algorithm: RSA
Format: X.509
Key Length: 162
The key is 162 bytes -1024 bi, so it is a vaild key.
Here is the code:
import java.security.PublicKey;
import java.security.Security;
import javax.crypto.Cipher;
public class DecyrptTest {
public static void main (String[] args) throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
//msg is some secret msg ,i have deleted.
String msg = "";
byte[] signedMsgBytes = SHA1.sign(msg);
String signedMessageOther=StringTools.bytesToHex(signedMsgBytes);
System.out.println("signedMessageOther: "+signedMessageOther);
String ct="1386cfed01490b9026903722324f80f8a56cc38169b46e15154ce9e7168ff589282855002e195a0c1a96d5fe540a7fa97b01ae24f365f39302e0c1186ee9308d6b94526741f7093dc2678c713bb2b1a8a6942decb35b16725353da523417cb835cea903485b19b63c2c444c8bc6c865ea78c749f10ca70b266f6078192f5c76c";
Cipher cipher = Cipher.getInstance("RSA", "BC");
PublicKey pubKey = KeyUtil.getPubKeyFromFile("res/key.pub");
System.out.println("Algorithm: "+pubKey.getAlgorithm());
System.out.println("Format: "+pubKey.getFormat());
System.out.println("Key Length: "+pubKey.getEncoded().length);
cipher.init(Cipher.DECRYPT_MODE, pubKey);
byte[] ctBytes=StringTools.hexStringToByteArray(ct);
System.out.println(ctBytes.length);
byte[] cipherText2 = cipher.doFinal(ctBytes);
System.out.println("cipher: " + StringTools.bytesToHex(cipherText2));
}
}
You are are trying to drive to work with an internal combustion engine. You need a car.
RSA is a primitive operation that can be pieced with other things to make a useful encryption system. You can't just use it by itself to do the whole job.
I'm not sure if that's really saying the key is 162 bits, but if so, that's a completely useless RSA key size. An RSA key has to be at least 384 bits to be of any use at all. Also, RSA can't sign or encrypt data larger than the key. 162 bits is about 20 bytes. Sound familiar?
You can't use an engine to drive to work. You need a car. RSA is an engine.

Categories

Resources