I'm trying to create a simple encryption/decryption program, but i'm having problems when decrypting. The way the program works, I get string input from the user, then encrypt using DES, convert to Base64 and give the user the converted secret key. However, when I get the secret key from the user and I try to decrypt, I get either error:
java.security.InvalidKeyException: No installed provider supports this key: (null)
or
javax.crypto.BadPaddingException
I don't know if the fault is at the time of encryption or if it's the decryption. Here's the respective snippets of code:
import javax.xml.bind.DatatypeConverter;
public static boolean encrypt(byte[] text){
Boolean yorn = false;
try{
myDesKey = KeyGenerator.getInstance("DES").generateKey();
//myDeskKey = myDesKey.toString();
Cipher desCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
desCipher.init(Cipher.ENCRYPT_MODE, myDesKey);
//I felt it would be better seeing the secret key as "woatDnBJLAg=" instead of "com.sun.crypto.provider.DESKey#18765"
if (myDesKey != null) {
stringKey = DatatypeConverter.printBase64Binary(myDesKey.getEncoded());
System.out.println("actual secret_key:" + myDesKey);
byte[] encodedKey = DatatypeConverter.parseBase64Binary(stringKey);
myDesKey = new SecretKeySpec(encodedKey, 0, encodedKey.length,
"DES");
System.out.println("after encode & decode secret_key:"
+ DatatypeConverter.printBase64Binary(myDesKey.getEncoded()));
}
textEncrypted = desCipher.doFinal(text);
yorn = true;
JTextArea textArea = new JTextArea(2,50);
textArea.setText("Your encryption key is: " + stringKey + " . Ensure you store it in a safe place" );// + DatatypeConverter.printBase64Binary(myDesKey.getEncoded()));
textArea.setEditable(false);
JOptionPane.showMessageDialog(null, new JScrollPane(textArea), "RESULT", JOptionPane.INFORMATION_MESSAGE);
}catch(Exception e)
{
System.out.println("There has been an error encrypting the file");
yorn = false;
}
return yorn;
Decryption
public static String decrypt(byte[] cipherText, SecretKey key,String key1)
{
String plainText = "";
try{
SecretKey myDesKey = key;
if(key == null){
JOptionPane.showMessageDialog(null, "We were unable to find your decryption key. Please enter your decryption key below: ");
JTextArea textBox = new JTextArea(1,15);
JOptionPane.showMessageDialog(null, new JScrollPane(textBox),"Enter your decryption key ",JOptionPane.PLAIN_MESSAGE);
//myDesKey = textBox.toSecretKey;
}
Cipher desCipher;
desCipher = Cipher.getInstance("DES");
desCipher.init(Cipher.DECRYPT_MODE, myDesKey);
byte[] textDecrypted = desCipher.doFinal(cipherText);
plainText = new String(textDecrypted);
JOptionPane.showMessageDialog(null, plainText, "DECRYPTED MESSAGE", 0);
}catch(Exception e)
{
System.out.println("There has been an error decrypting the file");
System.out.println(e);
}return plainText;
}
}
I know that I'm probably getting the errors because i've combined so many jumbled bits of code from all over stack and I seem to have lost the plot, but any help will be greatly appreciated.
Thanks!!
You're using two different ciphers. Cipher.getInstance("DES") is not fully specified, so it probably defaults to Cipher.getInstance("DES/ECB/PKCS5Padding") which is different from the CBC mode you're using during encryption.
Secondly, since you're using CBC mode, you need to manage the initialization vector (IV), but you don't do this at all. You can either provide the IV as a third parameter for Cipher#init or let it automatically be generated as you currently do, but then you have to send the IV (desCipher.getIV()) along with the ciphertext to the receiver. The IV doesn't have to be secret. A common way is to prepend it to the ciphertext and slice it off before decryption.
Lastly, the key you're using during decryption is not the same key that you've used during encryption, because you've re-encoded the key after encryption, but didn't decode it back.
Related
Following code was working fine but on a Samsung SM-G950F, Android OS 8.0.0, it crashes:
private String encrypt(String value) {
try {
final byte[] bytes = value != null ? value.getBytes(UTF8) : new byte[0];
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(SEKRIT));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID).getBytes(UTF8), 20));
return new String(Base64.encode(pbeCipher.doFinal(bytes), Base64.NO_WRAP), UTF8);
} catch (Exception e) {
return value;
}
}
private String decrypt(String value) {
try {
final byte[] bytes = value != null ? Base64.decode(value, Base64.DEFAULT) : new byte[0];
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(SEKRIT));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID).getBytes(UTF8), 20));
return new String(pbeCipher.doFinal(bytes), UTF8);
} catch (Exception e) {
return value;
}
}
here is the full error message:
Caused by java.lang.RuntimeException: javax.crypto.BadPaddingException: pad block corrupted
at com.edaf.utils.ObscuredSharedPreferences.decrypt(SourceFile:185)
at com.edaf.utils.ObscuredSharedPreferences.getString(SourceFile:134)
at com.edaf.utils.Utils.getFromGlobals(SourceFile:145)
I changed return value on crash but I am sure it wont help
That piece of code was designed to fail if encryption was performed on one device and decryption is performed on a different device. If Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID) returns a different string then the salt parameter of the PBEParameterSpec constructor will be different.
Once the salt is different, then a different key will be calculated from the password, and your decryption is likely to fail with a padding related exception. The salt is used to create a different value even if passwords are identical (using only the ID is rather stupid though, because using the same ID will result in identical passwords, even in different applications).
If the decryption doesn't fail then you'll get a garbage plaintext by the way.
There are endless issues within the code of the question, for instance using single DES and a 20 iteration count and non-authenticated encryption. I could post a page long review, but instead I urge you to read a book on modern crypto, and revise your protocol ASAP.
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
Hello i'm new to Java and i have the following problem: i'm trying to encrypt the password of a user using the blowfish algorithm, but when i try to decrypt it back to check the authentication it fails to decrypt it for some reason.
public static String encryptBlowFish(String to_encrypt, String salt){
String dbpassword = null;
try{
SecretKeySpec skeySpec = new SecretKeySpec( salt.getBytes(), "Blowfish" );
// Instantiate the cipher.
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
//byte[] encrypted = cipher.doFinal( URLEncoder.encode(data).getBytes() );
byte[] encrypted = cipher.doFinal( to_encrypt.getBytes() );
dbpassword = new String(encrypted);
} catch (Exception e) {
System.out.println("Exception while encrypting");
e.printStackTrace();
dbpassword = null;
} finally {
return dbpassword;
}
}
public static String decryptBlowFish(String to_decrypt, String salt){
String dbpassword = null;
try{
SecretKeySpec skeySpec = new SecretKeySpec( salt.getBytes(), "Blowfish" );
// Instantiate the cipher.
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
//byte[] encrypted = cipher.doFinal( URLEncoder.encode(data).getBytes() );
byte[] encrypted = cipher.doFinal( to_decrypt.getBytes() );
dbpassword = new String(encrypted);
} catch (Exception e) {
System.out.println("Exception while decrypting");
e.printStackTrace();
dbpassword = null;
} finally {
return dbpassword;
}
}
When i call the decrypt function it gives me the following error:
java.security.InvalidKeyException: Parameters missing
Any ideas? Thank you
you are doing multiple things wrong here:
you are converting an encrypted value to a string. not all bytes are valid strings. store bytes directly in a database as a binary blob, not as a string (or convert it to hex or base64 first).
you are confusing salt and key. the thing you are calling salt in your code is actually a private key. you don't appear to have a real salt, at all.
you are encrypting a password. that means you need to store a key somewhere (you cannot store it in the database or anyone that steals the database will be able to decrypt the passwords). instead, you should use a hash.
even then, you should not store passwords in this way. not even if you use a salt correctly. these days it is too easy to crack simply hashed passwords, even when salted. instead, use the bcrypt library or PBKDF2.
here are instructions for doing this correctly. note that if you follow those instructions it is ok to store the password as a string (it has been converted correctly for you).
First of all I wanna say thank you...
I wrote a program which one is doing encryption and decryption with Enum.
Enum has AES,BlowFish,DESede. My program will support these 3 encryption algorithm.
Then I wanted to Generate a SecretKey with SecretKeyFactory.But I think,I made a mistake to generate a key. (Obviously I loose myself in code.I have no idea about what can I do...)
My Code is below. This program's purpose is;
Users will write encryption and decryption method parameters. (Text,Encryption Algorithm)
Algorithm type will choose in Enum type. (Enum has 3 algorithm format)
According to the entered Encryption Type,program will encrypt entered text.
I know my code is really terrible. It has lots of unnecessary declaration and logical mistakes.
Code is working fine sometimes,sometimes will crash.
EDIT = Question is my code doesnt work always. Sometimes gives error. Error is = javax.crypto.BadPaddingException: Given final block not properly padded
Thank you for answering.
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class SymetricAlgorithms {
private static enum algorithms { //Enum declaration 3 encryption types here
AES, BlowFish, DESede;
}
private static String data = "HOWCANISOLVETHIS"; //this will be encrypt and decrypt
public static void main(String[] args) throws Throwable {
SecretKey kgen = GenerateKey(); // Create a key.
String encrypText = encrypt(kgen, data, algorithms.AES); //encrypt method calling here.
String decrypText = dencypt(kgen, encrypText, algorithms.AES);//decrypt method calling here.
System.out.println("plaintext = " + data + " key = " + kgen
+ "\nEncryptedText = " + encrypText
+ "\nDecryptedText = " + decrypText);
}
public static String dencypt(SecretKey inKey, String text, algorithms eValue)throws Throwable {//decryption
try {
byte[] text2 = text.getBytes(); //convert from parameters TEXT to Bytes
Cipher cipher = Cipher.getInstance("AES"); //Cipher initialize and choose encryption method (AES)
cipher.init(Cipher.DECRYPT_MODE, inKey); //cipher process
byte plainTextByte[] = new byte[20]; //Creating byte array
plainTextByte =cipher.doFinal(text2);//using byte array to assign ciphers result
System.out.println(plainTextByte);
return new String(plainTextByte);
} catch (Exception e) {
System.err.println("Data Cant Decrypted !");
e.printStackTrace();
}
return null;
}
public static String encrypt(SecretKey inKey, String text, algorithms eValue)
throws Throwable {
try {
Cipher cipher = null; //cipher declaration
switch (eValue) {//Enum. 3 types here and control structure for Users choosing encryption type is acceptable
case AES:cipher = Cipher.getInstance("AES");
break;
case BlowFish:Cipher cipher2 = Cipher.getInstance("BlowFish");
cipher = cipher2;
break;
case DESede:Cipher cipher3 = Cipher.getInstance("DESede");
cipher=cipher3;
break;
default:
System.out.println("Unexpectable value input.");
break;
}
System.out.println(inKey);
//Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, inKey);
byte[] ciphertext = cipher.doFinal(text.getBytes("UTF-8"));//cipher result is assign to byte array
System.out.println(ciphertext);
return new String(ciphertext);
} catch (Exception e) {
System.err.println("Unexpectable algorithm type !");
e.printStackTrace();
}
return null;
}
public static SecretKey GenerateKey() throws Throwable {//Generate a key for using crypt
//could sb explain these? =D I loose myself. I combined codes from finding internet...Failed...
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecureRandom prng = SecureRandom.getInstance("SHA1PRNG");
byte bytes[] = new byte[20];
prng.nextBytes(bytes);
String passwordTemp = prng.toString();
String saltTemp = passwordTemp;
char[] password = passwordTemp.toCharArray();
byte[] salt = saltTemp.getBytes();
KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
return secret;
} catch (Exception e) {
System.err.println("Key cant be generated !");
e.printStackTrace();
}
return null;
}
}
The theme of the problem is misunderstanding of the relationship between Strings and bytes. At the end of the encrypt method, what do you think these two lines do:
byte[] ciphertext = cipher.doFinal(...
return new String(ciphertext);
The last line takes the encrypted bytes, which could be almost anything, and attempts to interpret those bytes as encoding some characters of a string. Using what encoding? String constructor with no character encoding argument uses system default encoding, which depends on JVM/OS/Locale. Lets say it is UTF-8. Are you guaranteed that there will actually be some character for the encrypted bytes? Answer: NO. Will you get the same bytes back, when you take the resulting string and call .getBytes("UTF-8"). Answer: No, there are mutliple byte sequences encoding the same characters, thus new String(bytes, "UTF-8").getBytes("UTF-8") is not guaranteed to return the bytes you started with.
In summary, don't attempt to interpret arbitrary bytes as a string. Make your encrypt method return byte[], and your decryp method take an array of bytes to decode-- then it will work.
It is not necessary to make your program work, but if you must represent the encrypted bytes as a string, consider base64 encoding, or hexadecimal encoding -- these encodings uniquely map every possible byte (or sequence of bytes) to a string.
UPDATE: here is a more concise generateKey() method. It allows you to pass the password in as an argument.
public static SecretKey generateKey(String password) {
try {
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
byte saltBytes[] = new byte[20];
secureRandom.nextBytes(saltBytes);
KeySpec spec = new PBEKeySpec(password.toCharArray(), saltBytes, 65536, 128);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKey secretKey = factory.generateSecret(spec);
return new SecretKeySpec(secretKey.getEncoded(), "AES");
} catch (Exception e) {
throw new IllegalStateException("Key cant be generated !");
}
}
I'm writing a small application for transferring files, more or less as a way to learn more of the programmatic encryption underpinnings. The idea is to generate an RSA keypair, exchange public keys, and send the AES iv and key over for further decryption. I want to encrypt the AES key with the receivers RSA public key, like so:
// encode the SecretKeySpec
private byte[] EncryptSecretKey ()
{
Cipher cipher = null;
byte[] key = null;
try
{
cipher = Cipher.getInstance("RSA/ECB/NOPADDING");
// contact.getPublicKey returns a public key of type Key
cipher.init(Cipher.ENCRYPT_MODE, contact.getPublicKey() );
// skey is the SecretKey used to encrypt the AES data
key = cipher.doFinal(skey.getEncoded());
}
catch(Exception e )
{
System.out.println ( "exception encoding key: " + e.getMessage() );
e.printStackTrace();
}
return key;
}
I then write out the key value to the receiver, and decrypt it like so:
private SecretKey decryptAESKey(byte[] data )
{
SecretKey key = null;
PrivateKey privKey = null;
Cipher cipher = null;
System.out.println ( "Data as hex: " + utility.asHex(data) );
System.out.println ( "data length: " + data.length );
try
{
// assume this loads our private key
privKey = (PrivateKey)utility.loadLocalKey("private.key", false);
cipher = Cipher.getInstance("RSA/ECB/NOPADDING");
cipher.init(Cipher.DECRYPT_MODE, privKey );
key = new SecretKeySpec(cipher.doFinal(data), "AES");
System.out.println ( "Key decrypted, length is " + key.getEncoded().length );
System.out.println ( "data: " + utility.asHex(key.getEncoded()));
}
catch(Exception e)
{
System.out.println ( "exception decrypting the aes key: " + e.getMessage() );
e.printStackTrace();
return null;
}
return key;
}
In console, on the other side, I get this as output:
read_bytes for key: 16
data length: 16
Data as hex: <hex string>
Key decrypted, length is 256
java.security.InvalidKeyException: Invalid AES key length: 256 bytes
Furthermore, if I create a byte array of size 16 and put the cipher.doFinal(data) output into it, the array is seemingly resized to 256 bytes (.length says so, at least). Why would this be, and further, what am I doing incorrectly?
edit
I solved this, and thought I'd post the issue in case someone runs into this. The problem, it turns out, was the RSA/ECB/NOPADDING. For some odd reason it was screwing up my creation of the SecretKey when I transferred it over to the client. It might have something to do with how I'm generating keypairs (i'm using getInstance("RSA") for that), but I'm not entirely sure.
As owlstead mentioned, you cannot just use "raw" RSA without padding for encryption/decryption. For one it is very insecure, and for another, the Java libraries do not even support it. Below is the working code for the encryption/decryption of the AES key using RSA keypairs.
private byte[] EncryptSecretKey ()
{
Cipher cipher = null;
byte[] key = null;
try
{
// initialize the cipher with the user's public key
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, contact.getPublicKey() );
key = cipher.doFinal(skey.getEncoded());
}
catch(Exception e )
{
System.out.println ( "exception encoding key: " + e.getMessage() );
e.printStackTrace();
}
return key;
}
Decryption of the AES key looks like this:
private SecretKey decryptAESKey(byte[] data )
{
SecretKey key = null;
PrivateKey privKey = null;
Cipher cipher = null;
try
{
// this is OUR private key
privKey = (PrivateKey)utility.loadLocalKey(
ConfigFrame.privateKeyLocation, false);
// initialize the cipher...
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privKey );
// generate the aes key!
key = new SecretKeySpec ( cipher.doFinal(data), "AES" );
}
catch(Exception e)
{
System.out.println ( "exception decrypting the aes key: "
+ e.getMessage() );
return null;
}
return key;
}
You cannot just use "raw" RSA to encrypt data without any padding. You need some kind of padding scheme, if only for security reasons. Normally "RSA/ECB/PKCS1Padding" is used. This can encrypt data up to 11 bytes less than the key size. It makes sure that the data fits in the modulus, and adds at least 8 bytes of random data to make sure that encrypting e.g. the word "Yes" twice does not result in two identical cipher texts. Finally, it makes sure you can find out the size in octets of the encrypted data, so you can simply encrypt the 16, 24 or 32 bytes that make up the AES key.
One thing to remember is that with RSA and actually many other
algorithms including AES usually, the "useful" data that you supply
isn't literally the data that's encrypted. Usually, some extra data
needs to be included, for example, indicating the actual length of the
data in some way, data for any integrity checking... To the user, the
number of input bytes doesn't necessarily equal the number of bytes
following encryption.
Comment Source
To get the right key size you can use an HashAlgorithm on the key (SHA), which will give you a fixed output-size. Otherwise you can just use the first 16bytes as key and ignore the rest? Good luck.