Public/private key to encrypt AES session key - java

I've been working on an encryption/decryption program. I am using AES.
What I am struggling with is, removing the string password and create a AES session key. This AES session key is what I actually want to use to encrypt and decrypt the data with. Then use the private or public key to encrypt this AES session key and stored locally (for example purposes).
Below is the coding which is currently fully working to encrypt and decrypt data with a plain text string:
Coding so far:
// password to encrypt the file
String password = "p#sswordDataNoW";
// salt is used for encoding
byte[] salt = new byte[8];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(salt);
FileOutputStream saltOutFile = new FileOutputStream("salt.enc");
saltOutFile.write(salt);
saltOutFile.close();
SecretKeyFactory factory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 65536,
128);
SecretKey secretKey = factory.generateSecret(keySpec);
SecretKey secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
//padding AES encryption
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
// iv adds randomness to the text and just makes the mechanism more
// secure
// used while initializing the cipher
// file to store the iv
FileOutputStream ivOutFile = new FileOutputStream("iv.enc");
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
ivOutFile.write(iv);
ivOutFile.close();
//file encryption
byte[] input = new byte[64];
int bytesRead;
while ((bytesRead = inFile.read(input)) != -1) {
byte[] output = cipher.update(input, 0, bytesRead);
if (output != null)
outFile.write(output);
}
byte[] output = cipher.doFinal();
if (output != null)
outFile.write(output);
inFile.close();
outFile.flush();
outFile.close();
Please could someone help me out, in terms of changing the string passwordto a session key that is generated. Then encrypt this session key with either the public or private key and stored on the local system.
I have tried many different ways and every time it has no worked out.

An AES key consists of either 16, 24 or 32 bytes and is supposed to look like random noise. The user is never going to see it. So, you can generate it securely:
SecureRandom r = new SecureRandom();
byte[] aesKey = new byte[16];
r.nextBytes(aesKey);
Here I generated a 16 byte key. You should use 24 or 32 byte for higher security, but only if you installed the Unlimited Strength policy files for your JRE/JDK.
Now, the remaining encryption is easy:
SecretKey secret = new SecretKeySpec(aesKey, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
// everything else as before
Separately from that you can encrypt aesKey with your public key. If you "encrypt" with a private key, you're not protecting the AES key from spying on. "Encrypting with a private key" is called signing and that data is not confidential.

Related

How to set padding for signed key encryption in java? [duplicate]

I am using rsa key to encrypt a long string which I will send to my server(will encrypt it with server's public key and my private key) But it throws an exception like javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes
I feel that I have not understood the working of rsa properly till now(using the inbuilt libraries are the cause for this). Can some one please explain why this exception is being thrown. Is it not at all possible to send long string encrypted?
The RSA algorithm can only encrypt data that has a maximum byte length
of the RSA key length in bits divided with eight minus eleven padding
bytes, i.e. number of maximum bytes = key length in bits / 8 - 11.
So basicly you divide the key length with 8 -11(if you have padding). For example if you have a 2048bit key you can encrypt 2048/8 = 256 bytes (- 11 bytes if you have padding). So, either use a larger key or you encrypt the data with a symmetric key, and encrypt that key with rsa (which is the recommended approach).
That will require you to:
generate a symmetric key
Encrypt the data with the symmetric key
Encrypt the symmetric key with rsa
send the encrypted key and the data
Decrypt the encrypted symmetric key with rsa
decrypt the data with the symmetric key
done :)
Based on #John Snow answer, I did an example
Generate Symmetric Key (AES with 128 bits)
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(128); // The AES key size in number of bits
SecretKey secKey = generator.generateKey();
Encrypt plain text using AES
String plainText = "Please encrypt me urgently..."
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.ENCRYPT_MODE, secKey);
byte[] byteCipherText = aesCipher.doFinal(plainText.getBytes());
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(secKey.getEncoded()/*Seceret Key From Step 1*/);
Send encrypted data (byteCipherText) + encrypted AES Key (encryptedKey)
On the client side, decrypt symmetric key using RSA private key
cipher.init(Cipher.PRIVATE_KEY, prKey);
byte[] decryptedKey = cipher.doFinal(encryptedKey);
Decrypt the cipher text using decrypted symmetric key
//Convert bytes to AES SecertKey
SecretKey originalKey = new SecretKeySpec(decryptedKey , 0, decryptedKey .length, "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, originalKey);
byte[] bytePlainText = aesCipher.doFinal(byteCipherText);
String plainText = new String(bytePlainText);`
You should not use RSA on your secret data directly. You should only ever use RSA on pseudo-random or completely random data, such as session keys or message authentication codes.
You've gotten the problem at 256 bytes -- that is because you're probably working with 2048 bit keys. The keys are able to encrypt any integer in the range 0 to 2^2048 - 1 into the same range, and that means your data must be 256 bytes or smaller.
If you intend to encrypt more than this, please use one RSA encryption to encrypt a session key for a symmetric algorithm, and use that to encrypt your data.
To follow on from John Snow's answer above I created a simple random-symmetric-crypt library that you can use to simply encrypt any length data using a private key.
You can find the library at GitHub - random-symmetric-crypto
final RandomSymmetricCipher cipher = new RandomSymmetricCipher();
// Encrypt the data and the random symmetric key.
final CryptoPacket cryptoPacket = cipher.encrypt(inputData, PRIVATE_KEY_BASE64);
// Convert the CryptoPacket into a Base64 String that can be readily reconstituted at the other end.
final CryptoPacketConverter cryptoPacketConverter = new CryptoPacketConverter();
final String base64EncryptedData = cryptoPacketConverter.convert(cryptoPacket);
System.out.println("Base64EncryptedData=" + base64EncryptedData);
// Decrypt the Base64 encoded (and encrypted) String.
final byte[] outputData = cipher.decrypt(base64EncryptedData, PUBLIC_KEY_BASE64);
I went through the same problem, this is how I solved it.
AES can encrypt data as a standalone algorithm and can also do it with the help of RSA algorithm. Using AES standalone algorithm combined with RSA algorithm in the same block code(function) will cause increase in Data size affecting the AES key Size. You shouldn't do as shown with the code:
//encryption without using RSA KEY both can't run at the same time.
byte[] bytes = Files.readAllBytes(Paths.get(pvtKeyFile));
ks = new PKCS8EncodedKeySpec(bytes);
kf = KeyFactory.getInstance("RSA");
pvt = kf.generatePrivate(ks);
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pvt);
processFiles(cipher, localFile, localFile + ".enc");
System.out.println("The encrypted files have been created successfully.");
//encryption using RSA.
byte[] bytes = Files.readAllBytes(Paths.get(pvtKeyFile));
ks = new PKCS8EncodedKeySpec(bytes);
kf = KeyFactory.getInstance("RSA");
pvt = kf.generatePrivate(ks);
kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
skey = kgen.generateKey();
byte[] iv = new byte[128/8];
srandom.nextBytes(iv);
ivspec = new IvParameterSpec(iv);
try (FileOutputStream out = new FileOutputStream(localFile + ".enc")) {
{
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pvt);
byte[] b = cipher.doFinal(skey.getEncoded());
out.write(b);
System.err.println("AES Key Length: " + b.length);
}
out.write(iv);
System.err.println("IV Length: " + iv.length);
ciphers = Cipher.getInstance("AES/CBC/PKCS5Padding");
ciphers.init(Cipher.ENCRYPT_MODE, skey, ivspec);
System.out.println("The encrypted files have been created successfully.");
try (FileInputStream in = new FileInputStream(localFile)) {
processFile(ciphers, in, out);
}
}
You can't do as shown above, it will cause error during the decryption process. if you are to use AES standalone algorithm use it in one block of code without including the RSA algorithm and the vice versa is true, as show below.
//encryption without using RSA KEY both can't run at the same time.
byte[] bytes = Files.readAllBytes(Paths.get(pvtKeyFile));
ks = new PKCS8EncodedKeySpec(bytes);
kf = KeyFactory.getInstance("RSA");
pvt = kf.generatePrivate(ks);
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pvt);
processFiles(cipher, localFile, localFile + ".enc");
System.out.println("The encrypted files have been created successfully.");
OR
You can only use the RSA Algorithm as shown:
byte[] bytes = Files.readAllBytes(Paths.get(pvtKeyFile));
ks = new PKCS8EncodedKeySpec(bytes);
kf = KeyFactory.getInstance("RSA");
pvt = kf.generatePrivate(ks);
kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
skey = kgen.generateKey();
byte[] iv = new byte[128/8];
srandom.nextBytes(iv);
ivspec = new IvParameterSpec(iv);
try (FileOutputStream out = new FileOutputStream(localFile + ".enc")) {
{
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pvt);
byte[] b = cipher.doFinal(skey.getEncoded());
out.write(b);
System.err.println("AES Key Length: " + b.length);
}
out.write(iv);
System.err.println("IV Length: " + iv.length);
ciphers = Cipher.getInstance("AES/CBC/PKCS5Padding");
ciphers.init(Cipher.ENCRYPT_MODE, skey, ivspec);
System.out.println("The encrypted files have been created successfully.");
try (FileInputStream in = new FileInputStream(localFile)) {
processFile(ciphers, in, out);
}
}
Thanks I hope it will help someone regards.
you need split your data by the publicKey
int keyLength = publicKey.getModulus().bitLength() / 16;
String[] datas = splitString(data, keyLength - 11);
String mi = ""//the data after encrypted;
for (String s : datas) {
mi += bcd2Str(cipher.doFinal(s.getBytes()));
}
return mi;
public static String bcd2Str(byte[] bytes) {
char temp[] = new char[bytes.length * 2], val;
for (int i = 0; i < bytes.length; i++) {
val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f);
temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + '0');
val = (char) (bytes[i] & 0x0f);
temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + '0');
}
return new String(temp);
}

iOS CryptoKit in Java

I am looking for settings/parameters of CryptoKit which will allow me to share data between iOS App and a Java Application. The flow would be something like below:
- Use CryptoKit to encrypt a text using a fixed key and random initialization vector (IV).
- In the Java application use standard javax libraries to perform the decryption using the same fixed key. The random IV will be transported/shared with the application along with the encrypted text.
Similarly, the reverse is also required, where text is encrypted using JavaX libraries using a fixed key and random IV. The random IV and encrypted text is shared with the iOS app where it should use CryptoKit to decrypt it.
Below is the code for Encrypt and Decrypt in Java
public static byte[] encrypt(byte[] plaintext, byte[] key, byte[] IV) throws Exception
{
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("AES_256/GCM/NoPadding");
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
// Create GCMParameterSpec
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, IV);
// Initialize Cipher for ENCRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
// Perform Encryption
byte[] cipherText = cipher.doFinal(plaintext);
return cipherText;
}
public static String decrypt(byte[] cipherText, byte[] key, byte[] IV) throws Exception
{
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("AES_256/GCM/NoPadding");
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
// Create GCMParameterSpec
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, IV);
// Initialize Cipher for DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
// Perform Decryption
byte[] decryptedText = cipher.doFinal(cipherText);
return new String(decryptedText);
}
The CryptoKit commands as below:
let mykey = SymmetricKey(data: passhash)
let myiv = try AES.GCM.Nonce()
let mySealedBox = try AES.GCM.seal(source.data(using: .utf8)!, using: mykey, nonce: myiv)
let myNewSealedBox = try AES.GCM.SealedBox(nonce: myiv, ciphertext: mySealedBox.ciphertext, tag: mySealedBox.tag)
let myText = try String(decoding: AES.GCM.open(myNewSealedBox, using: mykey), as: UTF8.self)
Below are the steps to generate an encrypted text in Java:
int GCM_IV_LENGTH = 12;
//Generate Key
MessageDigest md = MessageDigest.getInstance("SHA265");
byte[] key = md.digest("pass".getBytes(StandardCharsets.UTF_8));
// Generate IV
SecureRandom sr = new SecureRandom(pass.getBytes(StandardCharsets.UTF_8));
byte[] IV = new byte[GCM_IV_LENGTH];
sr.nextBytes(IV);
//Encrypt
byte[] cipherText = encrypt("Text to encrypt".getBytes(), key, IV);
//Base64 Encoded CipherText
String cipherTextBase64 = Base64.getEncoder().encodeToString(cipherText);
To Decrypt this in SWIFT CryptoKit, I first need to create a sealed box with this CipherText however, the CryptoKit API to create a sealed box requires the following:
Nonce/IV (Available above)
CipherText (Available above)
Tag (NO IDEA FROM WHERE TO GET THIS????)
AES.GCM.SealedBox(nonce: , ciphertext: , tag: )
The other way, lets first encrypt data in CryptoKit
let mykey = SymmetricKey(data: SHA256.hash(data: "12345".data(using: .utf8)!))
let myiv = AES.GCM.Nonce()
let mySealedBox = try AES.GCM.seal("Text to encrypt".data(using: .utf8)!, using: mykey, nonce: myiv)
let cipherText = mySealedBox.cipherText.base64EncodedString()
let iv = myiv.withUnsafeBytes{
return Data(Array($0)).base64EncodedString()
}
If i pass this IV and CipherText to Java Decrypt function along with key (SHA265 hash of "12345" string), i get a TAG mismatch error.
This is the final set of code in SWIFT:
let pass = “Password”
let data = “Text to encrypt”.data(using: .utf8)!
let key = SymmetricKey(data: SHA256.hash(data: pass.datat(using: .utf8)!))
let iv = AES.GCM.Nonce()
let mySealedBox = try AES.GCM.seal(data, using: key, nonce: iv)
dataToShare = mySealedBox.combined?.base64EncodedData()
Write this data to a file (I am using google APIs to write this data to a file on google drive)
Read this data from the file in java and pass it to the functions as defined in the question using the below code:
byte[] iv = Base64.getDecoder().decode(text.substring(0,16));
cipher[] = Base64.getDecoder().decode(text.substring(16));
byte[] key = md.digest(pass.getBytes(StandardCharsets.UTF_8));
String plainText = decrypt(cipher, key, iv);

How to properly recreate SecretKey from string

I'm trying to make an encryption-decryption app. I've got two classes - one with functions to generate the key, encrypt and decrypt, second one for JavaFX GUI. In the GUI class I've got 4 textareas: 1st to write text to encrypt, 2nd for encrypted text, 3rd for the key (String encodedKey = Base64.getEncoder().encodeToString(klucz.getEncoded());) and 4th for decrypted text.
The problem is, I am not able to decrypt the text. I'm trying to recreate the SecretKey like this:
String encodedKey = textAreaKey.getText();
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
SecretKey klucz = new SecretKeySpec(decodedKey, "DESede");
When I encrypt the key looks like this: com.sun.crypto.provider.DESedeKey#4f964d80 and when I try to recreate it: javax.crypto.spec.SecretKeySpec#4f964d80 and I'm getting javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8 when decrypting with padded cipher
Here is my 1st class:
public class Encryption {
public static SecretKey generateKey() throws NoSuchAlgorithmException {
Security.addProvider(new com.sun.crypto.provider.SunJCE());
KeyGenerator keygen = KeyGenerator.getInstance("DESede");
keygen.init(168);
SecretKey klucz = keygen.generateKey();
return klucz;
}
static byte[] encrypt(byte[] plainTextByte, SecretKey klucz)
throws Exception {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, klucz);
byte[] encryptedBytes = cipher.doFinal(plainTextByte);
return encryptedBytes;
}
static byte[] decrypt(byte[] encryptedBytes, SecretKey klucz)
throws Exception {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, klucz);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return decryptedBytes;
}
}
edit
btnEncrypt.setOnAction((ActionEvent event) -> {
try {
String plainText = textAreaToEncrypt.getText();
SecretKey klucz = Encryption.generateKey();
byte[] plainTextByte = plainText.getBytes();
byte[] encryptedBytes = Encryption.encrypt(plainTextByte, klucz);
String encryptedText = Base64.getEncoder().encodeToString(encryptedBytes);
textAreaEncryptedText.setText(encryptedText);
byte[] byteKey = klucz.getEncoded();
String stringKey = Base64.getEncoder().encodeToString(byteKey);
textAreaKey.setTextstringKey
} catch (Exception ex) {
ex.printStackTrace();
}
});
btnDecrypt.setOnAction((ActionEvent event) -> {
try {
String stringKey = textAreaKey.getText();
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
SecretKey klucz2 = new SecretKeySpec(decodedKey, "DESede");
String encryptedText = textAreaEncryptedText.getText();
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText.getBytes());
byte[] decryptedBytes = Encryption.decrypt(encryptedBytes, klucz2;
String decryptedText = Base64.getEncoder().encodeToString(decryptedBytes);
textAreaDecryptedText.setText(decryptedText);
} catch (Exception ex) {
ex.printStackTrace();
}
});
One of your problems is here:
String encryptedText = new String(encryptedBytes, "UTF8");
Generally, many byte sequences in cipher text are not valid UTF-8–encoded characters. When you try to create a String, this malformed sequences will be replaced with the "replacement character", and then information from the the cipher text is irretrievably lost. When you convert the String back to bytes and try to decrypt it, the corrupt cipher text raises an error.
If you need to represent the cipher text as a character string, use base-64 encoding, just as you do for the key.
The other principal problem is that you are aren't specifying the full transformation. You should specify the "mode" and "padding" of the cipher explicitly, like "DESede/ECB/PKCS5Padding".
The correct mode will depend on your assignment. ECB is generally not secure, but more secure modes add a bit of complexity that may be outside the scope of your assignment. Study your instructions and clarify the requirements with your teacher if necessary.
There are two main issues:
You should not use user entered password as a key (there are difference between them). The key must have specific size depending on the cipher (16 or 24 bytes for 3des)
Direct 3DES (DESede) is a block cipher encrypting 8 bytes at once. To encrypt multiple blocks, there are some methods defined how to do that properly. It is calls Block cipher mode.
For proper encryption you need to take care of a few more things
Creating a key from the password
Let's assume you want to use DESede (3des). The key must have fixed size - 16 or 24 bytes. To properly generate a key from password you should use PBKDF. Some people are sensitive to "must use", however neglecting this step really compromises the encryption security mainly using user-entered passwords.
For 3DES you can use :
int keySize = 16*8;
int iterations = 800000;
char[] password = "password".toCharArray();
SecureRandom random = new SecureRandom();
byte[] salt = random.generateSeed(8);
SecretKeyFactory secKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(password, salt, iterations, keySize);
SecretKey pbeSecretKey = secKeyFactory.generateSecret(spec);
SecretKey desSecret = new SecretKeySpec(pbeSecretKey.getEncoded(), "DESede");
// iv needs to have block size
// we will use the salt for simplification
IvParameterSpec ivParam = new IvParameterSpec(salt);
Cipher cipher = Cipher.getInstance("DESEde/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, desSecret, ivParam);
System.out.println("salt: "+Base64.getEncoder().encodeToString(salt));
System.out.println(cipher.getIV().length+" iv: "+Base64.getEncoder().encodeToString(cipher.getIV()));
byte[] ciphertext = cipher.doFinal("plaintext input".getBytes());
System.out.println("encrypted: "+Base64.getEncoder().encodeToString(ciphertext));
if you can ensure that your password has good entropy (is long and random enough) you may be good with a simple hash
MessageDigest dgst = MessageDigest.getInstance("sha-1");
byte[] hash = dgst.digest("some long, complex and random password".getBytes());
byte[] keyBytes = new byte[keySize/8];
System.arraycopy(hash, 0, keyBytes, 0, keySize/8);
SecretKey desSecret = new SecretKeySpec(keyBytes, "DESede");
The salt serves to randomize the output and should be used.
The output of the encryption should be salt | cipthertext | tag (not necessarily in this order, but you will need all of these for proper encryption).
To decrypt the output, you will need to split the output to salt, ciphertext and the tag.
I see zero vectors ( static salt or iv ) very often in examples from StackOverflow, but in many cases it may lead to broken ciphers revelaling key or plaintext.
The initialization vector iv is needed for block chain modes (encrypting longer input than a single block), we could use the salt from the key as well
when having the same size ( 8 bytes in our case). For really secure solution the password salt should be longer.
The tag is an authentication tag, to ensure that nobody has manipulated with the ciphertext. You could use HMAC of the plaintext or ciphertext. It is important you should use different key for HMAC than for encryption. However - I believe in your case your homework will be ok even without the hmac tag

getting a IllegalBlockSizeException: Data must not be longer than 256 bytes when using rsa

I am using rsa key to encrypt a long string which I will send to my server(will encrypt it with server's public key and my private key) But it throws an exception like javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes
I feel that I have not understood the working of rsa properly till now(using the inbuilt libraries are the cause for this). Can some one please explain why this exception is being thrown. Is it not at all possible to send long string encrypted?
The RSA algorithm can only encrypt data that has a maximum byte length
of the RSA key length in bits divided with eight minus eleven padding
bytes, i.e. number of maximum bytes = key length in bits / 8 - 11.
So basicly you divide the key length with 8 -11(if you have padding). For example if you have a 2048bit key you can encrypt 2048/8 = 256 bytes (- 11 bytes if you have padding). So, either use a larger key or you encrypt the data with a symmetric key, and encrypt that key with rsa (which is the recommended approach).
That will require you to:
generate a symmetric key
Encrypt the data with the symmetric key
Encrypt the symmetric key with rsa
send the encrypted key and the data
Decrypt the encrypted symmetric key with rsa
decrypt the data with the symmetric key
done :)
Based on #John Snow answer, I did an example
Generate Symmetric Key (AES with 128 bits)
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(128); // The AES key size in number of bits
SecretKey secKey = generator.generateKey();
Encrypt plain text using AES
String plainText = "Please encrypt me urgently..."
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.ENCRYPT_MODE, secKey);
byte[] byteCipherText = aesCipher.doFinal(plainText.getBytes());
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(secKey.getEncoded()/*Seceret Key From Step 1*/);
Send encrypted data (byteCipherText) + encrypted AES Key (encryptedKey)
On the client side, decrypt symmetric key using RSA private key
cipher.init(Cipher.PRIVATE_KEY, prKey);
byte[] decryptedKey = cipher.doFinal(encryptedKey);
Decrypt the cipher text using decrypted symmetric key
//Convert bytes to AES SecertKey
SecretKey originalKey = new SecretKeySpec(decryptedKey , 0, decryptedKey .length, "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, originalKey);
byte[] bytePlainText = aesCipher.doFinal(byteCipherText);
String plainText = new String(bytePlainText);`
You should not use RSA on your secret data directly. You should only ever use RSA on pseudo-random or completely random data, such as session keys or message authentication codes.
You've gotten the problem at 256 bytes -- that is because you're probably working with 2048 bit keys. The keys are able to encrypt any integer in the range 0 to 2^2048 - 1 into the same range, and that means your data must be 256 bytes or smaller.
If you intend to encrypt more than this, please use one RSA encryption to encrypt a session key for a symmetric algorithm, and use that to encrypt your data.
To follow on from John Snow's answer above I created a simple random-symmetric-crypt library that you can use to simply encrypt any length data using a private key.
You can find the library at GitHub - random-symmetric-crypto
final RandomSymmetricCipher cipher = new RandomSymmetricCipher();
// Encrypt the data and the random symmetric key.
final CryptoPacket cryptoPacket = cipher.encrypt(inputData, PRIVATE_KEY_BASE64);
// Convert the CryptoPacket into a Base64 String that can be readily reconstituted at the other end.
final CryptoPacketConverter cryptoPacketConverter = new CryptoPacketConverter();
final String base64EncryptedData = cryptoPacketConverter.convert(cryptoPacket);
System.out.println("Base64EncryptedData=" + base64EncryptedData);
// Decrypt the Base64 encoded (and encrypted) String.
final byte[] outputData = cipher.decrypt(base64EncryptedData, PUBLIC_KEY_BASE64);
I went through the same problem, this is how I solved it.
AES can encrypt data as a standalone algorithm and can also do it with the help of RSA algorithm. Using AES standalone algorithm combined with RSA algorithm in the same block code(function) will cause increase in Data size affecting the AES key Size. You shouldn't do as shown with the code:
//encryption without using RSA KEY both can't run at the same time.
byte[] bytes = Files.readAllBytes(Paths.get(pvtKeyFile));
ks = new PKCS8EncodedKeySpec(bytes);
kf = KeyFactory.getInstance("RSA");
pvt = kf.generatePrivate(ks);
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pvt);
processFiles(cipher, localFile, localFile + ".enc");
System.out.println("The encrypted files have been created successfully.");
//encryption using RSA.
byte[] bytes = Files.readAllBytes(Paths.get(pvtKeyFile));
ks = new PKCS8EncodedKeySpec(bytes);
kf = KeyFactory.getInstance("RSA");
pvt = kf.generatePrivate(ks);
kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
skey = kgen.generateKey();
byte[] iv = new byte[128/8];
srandom.nextBytes(iv);
ivspec = new IvParameterSpec(iv);
try (FileOutputStream out = new FileOutputStream(localFile + ".enc")) {
{
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pvt);
byte[] b = cipher.doFinal(skey.getEncoded());
out.write(b);
System.err.println("AES Key Length: " + b.length);
}
out.write(iv);
System.err.println("IV Length: " + iv.length);
ciphers = Cipher.getInstance("AES/CBC/PKCS5Padding");
ciphers.init(Cipher.ENCRYPT_MODE, skey, ivspec);
System.out.println("The encrypted files have been created successfully.");
try (FileInputStream in = new FileInputStream(localFile)) {
processFile(ciphers, in, out);
}
}
You can't do as shown above, it will cause error during the decryption process. if you are to use AES standalone algorithm use it in one block of code without including the RSA algorithm and the vice versa is true, as show below.
//encryption without using RSA KEY both can't run at the same time.
byte[] bytes = Files.readAllBytes(Paths.get(pvtKeyFile));
ks = new PKCS8EncodedKeySpec(bytes);
kf = KeyFactory.getInstance("RSA");
pvt = kf.generatePrivate(ks);
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pvt);
processFiles(cipher, localFile, localFile + ".enc");
System.out.println("The encrypted files have been created successfully.");
OR
You can only use the RSA Algorithm as shown:
byte[] bytes = Files.readAllBytes(Paths.get(pvtKeyFile));
ks = new PKCS8EncodedKeySpec(bytes);
kf = KeyFactory.getInstance("RSA");
pvt = kf.generatePrivate(ks);
kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
skey = kgen.generateKey();
byte[] iv = new byte[128/8];
srandom.nextBytes(iv);
ivspec = new IvParameterSpec(iv);
try (FileOutputStream out = new FileOutputStream(localFile + ".enc")) {
{
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pvt);
byte[] b = cipher.doFinal(skey.getEncoded());
out.write(b);
System.err.println("AES Key Length: " + b.length);
}
out.write(iv);
System.err.println("IV Length: " + iv.length);
ciphers = Cipher.getInstance("AES/CBC/PKCS5Padding");
ciphers.init(Cipher.ENCRYPT_MODE, skey, ivspec);
System.out.println("The encrypted files have been created successfully.");
try (FileInputStream in = new FileInputStream(localFile)) {
processFile(ciphers, in, out);
}
}
Thanks I hope it will help someone regards.
you need split your data by the publicKey
int keyLength = publicKey.getModulus().bitLength() / 16;
String[] datas = splitString(data, keyLength - 11);
String mi = ""//the data after encrypted;
for (String s : datas) {
mi += bcd2Str(cipher.doFinal(s.getBytes()));
}
return mi;
public static String bcd2Str(byte[] bytes) {
char temp[] = new char[bytes.length * 2], val;
for (int i = 0; i < bytes.length; i++) {
val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f);
temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + '0');
val = (char) (bytes[i] & 0x0f);
temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + '0');
}
return new String(temp);
}

Java encryption by client and decryption by server, using PBKDF2WithHmacSHA1 and AES/CBC/PKCS5Padding

I'm going for secure confidentiality as long as the private key stays secret, and I get following error in my app when decrypting: javax.crypto.BadPaddingException: Given final block not properly padded
The code:
// Encryption, client side
byte[] plainData = "hello plaintext!".getBytes("UTF-8");
byte[] salt = new byte[64];
new SecureRandom().nextBytes(salt);
KeySpec spec = new javax.crypto.spec.PBEKeySpec("password".toCharArray(), salt, 1024, 256);
SecretKey sk = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(spec);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sk.getEncoded(), "AES"));
byte[] iv = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
byte[] ciphertext = cipher.doFinal(plainData);
System.out.println("ciphertext: "+new String(ciphertext, "UTF-8")); // cipher
// Decryption, server side
KeySpec spec2 = new javax.crypto.spec.PBEKeySpec("password".toCharArray(), salt, 1024, 256);
SecretKey sk2 = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(spec2);
Cipher cipher2 = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher2.init(Cipher.DECRYPT_MODE, new SecretKeySpec(sk2.getEncoded(), "AES"), new IvParameterSpec(iv)); // Get the same IV value from client/encryptor aswell, still random
String plaintext = new String(cipher2.doFinal(ciphertext), "UTF-8");
System.out.println("decrypted plaintext: "+plaintext); // plain
Is it the randomness of salt that causing the problem?
I can decrypt it fine when I make use of the object references on the client side, but I need my own instances on the server.
Great thanks in advance for correcting my error(s)!
*EDIT: * Code updated and corrected
Just from quickly looking at your code I can see that you are creating a different salt at the client and server side. In order for the server side to be able to decrypt that salt and the key have to be the same.
Now I'm not a Java developer but all the other code to me looks ok but like I said if you are creating a different salt at each end the decryption is not going to work.

Categories