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"));
}
}
Related
The following code is tested for short strings, in that case it decrypts the string nicely.
byte[] ciphertext = Base64.decode(myverylongstring,Base64.DEFAULT);
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(Charset.forName("UTF-8")), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(Charset.forName("UTF-8")));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] decryptedtextByte = cipher.doFinal(ciphertext);
String decryptedtext = new String(decryptedtextByte); //Successfully decrypts the string
System.out.println("decryptedtext: " + decryptedtext);
[...]
} catch (BadPaddingException e) {
e.printStackTrace();
}
But if the string is too large I get fired the exception I mention.
What could I do so it decrypts the string correctly maintaining the same large string to decrypt?
Edit, well technically what's decrypted is a byte[], changing title and adding code to not cause possible confussion.
As you did not show the encryption and we do not know what kind of key you are using there are many possible reasons for failure. As well you did not show the imports so I could just argue what Base64-encoder is in use.
Below you find a simple program that does the en- and decryption a byte array that is much larger than the size you are using.
Edit:
Security warning: The code below uses a stringified key and static IV, uses single part encryption / decryption for a huge byte array, uses the older CBC mode.
Please do not copy below code or use it in production - it is for educational purposes only.
The code does not have any proper exception handling !
It generates a (random filled) byte array with the plaintext and gets the key & iv from strings that are converted to byte[] using the Standard.Charset "UFT-8".
That it's doing the encryption, convert the ciphertext to a Base64 encoded string followed by the decoding to a new ciphertext byte[] and decryption with the same key and iv.
In the end there is simple comparison of the plaintext and decryptedtext byte arrays.
My advice it is to check for any forgotten charset-setting and correct (means identical) usage of key and iv.
code:
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
public class Main {
public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
System.out.println("https://stackoverflow.com/questions/63143007/getting-badpaddingexception-due-to-byte-too-long-to-decipher");
byte[] plaintext = new byte[100000];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(plaintext); // generate a random filled byte array
byte[] key = "12345678901234567890123456789012".getBytes(StandardCharsets.UTF_8);
byte[] iv = "1234567890123456".getBytes(StandardCharsets.UTF_8);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] ciphertextEnc = cipher.doFinal(plaintext);
String ciphertextBase64 = Base64.getEncoder().encodeToString(ciphertextEnc);
byte[] ciphertextDec = Base64.getDecoder().decode(ciphertextBase64);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] decryptedtextByte = cipher.doFinal(ciphertextDec);
System.out.println("decryptedtextByte equals plaintext: " + Arrays.equals(plaintext, decryptedtextByte));
}
}
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.
I am trying to encrypt data in java and decrypt data in ruby.
I found almost same asks, but my case is little bit different.
Encrypt in Ruby and Decrypt in Java - Why is it not working?
AES/CBC encrypt in Java, decrypt in Ruby
My code is ...
Encrypt in java
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import net.sf.json.JSONObject;
import org.apache.commons.codec.binary.Hex;
class Crypt {
public static void main(String[] args) throws Exception {
Map<String, String> node = new HashMap<String, String>();
node.put("timestamp", "1377499097199");
JSONObject jsonObject = JSONObject.fromObject(node);
String json = jsonObject.toString();
System.out.println(json);
//key
String skeyString = "97128424897797a166913557a6f4cc8e";
byte[] skey = Hex.decodeHex(skeyString.toCharArray());
System.out.println("key : " + skeyString);
//iv
String ivString = "84e8c3ea8859a0e293941d1cb00a39c3";
byte[] iv = Hex.decodeHex(ivString.toCharArray());
System.out.println("iv : " + ivString);
//encrypt
SecretKeySpec skeySpec1 = new SecretKeySpec(skey, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec1, new IvParameterSpec(iv));
byte[] encrypted = cipher.doFinal(json.getBytes());
String encryptedString = Hex.encodeHexString(encrypted);
System.out.println("=============>");
System.out.println("encrypted string: " + encryptedString);
}
}
Result is
{"timestamp":"1377499097199"}
key : 97128424897797a166913557a6f4cc8e
iv : 84e8c3ea8859a0e293941d1cb00a39c3
=============>
encrypted string: 395f6c0e8ad27f57c4a5a8975aa633e5b26f288d37ce18c6971779951f3b3527
and I hope to decrypt (encrypted string) in Ruby
Ruby code is ...(got error)
require 'openssl'
key = "97128424897797a166913557a6f4cc8e"
iv = "84e8c3ea8859a0e293941d1cb00a39c3"
encrypted_string = "395f6c0e8ad27f57c4a5a8975aa633e5b26f288d37ce18c6971779951f3b3527"
de_cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
de_cipher.decrypt
de_cipher.key = key
de_cipher.iv = iv
de_cipher.update(encrypted_string) << de_cipher.final
I expected to get
{"timestamp":"1377499097199"}
But it returns error
`final': bad decrypt (OpenSSL::Cipher::CipherError)
I think the problem is cipher.padding and type of key/iv.
But I don't know exactly how to complete ruby code.
Please let me know How to complete this code.
Thank you.
There's two problems with your Ruby code.
First, you're using AES 256 when you should be using AES 128. Java uses AES 128 or 256 based on the size of the key you use, and you're using a 128 bit key.
Second, you need to Hex decode your key, iv, and encrypted_string values in Ruby. OpenSSL Cipher is expecting binary, not hex strings.
require 'openssl';
key = "97128424897797a166913557a6f4cc8e";
iv = "84e8c3ea8859a0e293941d1cb00a39c3";
encrypted_string = "395f6c0e8ad27f57c4a5a8975aa633e5b26f288d37ce18c6971779951f3b3527";
de_cipher = OpenSSL::Cipher::Cipher.new("AES-128-CBC");
de_cipher.decrypt;
de_cipher.key = [key].pack('H*');
de_cipher.iv = [iv].pack('H*');
puts de_cipher.update([encrypted_string].pack('H*')) << de_cipher.final;
Output:
{"timestamp":"1377499097199"}
What am I doing wrong? I expected the Java program to print "private". My goal is to try to write the MessageEncryptor.decrypt ruby method in Java.
Ruby encryption (most code was taken from MessageEncryptor, but modified not to Marshal), but I've extracted it so that it's easier to see what's going on:
require 'openssl'
require 'active_support/base64'
#cipher = 'aes-256-cbc'
d = OpenSSL::Cipher.new(#cipher)
#secret = OpenSSL::PKCS5.pbkdf2_hmac_sha1("password", "some salt", 1024, d.key_len)
cipher = OpenSSL::Cipher::Cipher.new(#cipher)
iv = cipher.random_iv
cipher.encrypt
cipher.key = #secret
cipher.iv = iv
encrypted_data = cipher.update("private")
encrypted_data << cipher.final
puts [encrypted_data, iv].map {|v| ::Base64.strict_encode64(v)}.join("--")
Which printed:
tzFUIVllG2FcYD7xqGPmHQ==--UAPvdm3oN3Hog9ND9HrhEA==
Java code:
package decryptruby;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class DecryptRuby {
public static String decrypt(String encrypted, String pwd, byte[] salt)
throws Exception {
String[] parts = encrypted.split("--");
if (parts.length != 2) return null;
byte[] encryptedData = Base64.decodeBase64(parts[0]);
byte[] iv = Base64.decodeBase64(parts[1]);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(pwd.toCharArray(), salt, 1024, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey aesKey = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, aesKey, new IvParameterSpec(iv));
byte[] result = cipher.doFinal(encryptedData);
return result.toString();
}
public static void main(String[] args) throws Exception {
String encrypted = "tzFUIVllG2FcYD7xqGPmHQ==--UAPvdm3oN3Hog9ND9HrhEA==";
System.out.println("Decrypted: " + decrypt(encrypted, "password", "some salt".getBytes()));
}
}
Which printed
Decrypted: [B#432a0f6c
This is the problem - or at least a problem:
byte[] result = cipher.doFinal(encryptedData);
return result.toString();
You're calling toString() on a byte array. Arrays don't override toString(). That won't give you what you want at all - as you can see. Instead, you need to write something like:
return new String(result, "UTF-8");
... but you need to know what encoding was used to turn the original string into bytes before encryption. It's not clear to me from the Ruby code what encoding is used, but if you can be explicit about it (ideally using UTF-8) it'll make your life a lot easier.
In short, I suspect this problem has nothing to do with encryption at all - it has everything to do with converting text to bytes in Ruby and then converting the same sequence of bytes back into a string in Java.
Of course the encryption may be failing as well but that's a different matter.
Here's the encryption portion of my code. It compiles fine but fails with that exception at runtime:
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
...
byte[] salt = new byte[8];
Random rand = new Random();
rand.nextBytes(salt);
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithSHAAndTwofish-CBC");
SecretKey key = keyFactory.generateSecret(keySpec);
PBEParameterSpec paramSpec = new PBEParameterSpec(salt, 1000);
Cipher cipher = Cipher.getInstance("PBEWithSHAAndTwofish-CBC");
cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
byte[] ciphertext = cipher.doFinal(plaintext);
Does this algorithm not come with Java 1.5? I don't mind using another algorithm, I just don't know what is available. I hope I don't have to use an external library like bouncycastle, because I've struggled for days trying to get it working to no avail (by including it in my .jar application it triggers an "Invalid signature file digest" error).
Looks like you may have to use some other cipher if you do not wish to rely on some external library like BouncyCastle.
See the documentation about Sun crypto providers for more details about what's supported straight out of the box.