how to convert array byte[] to key? - java

I have information security project about encrypting file using AES. and the using key in this algorithm is also encrypted using RSA algorithm and public key,
the problem is: after encrypting the random key it returns array byte[], how this array byte converted into key so I can encrypt the file?
NOTE [public_Key is generated from user using JPasswordField
and this is the challenge I faced from my course project]
public void AESEncryption(File file) throws FileNotFoundException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
String data;
SecretKey random_key;
int key_size=128;
Scanner myReader = new Scanner(file);
while (myReader.hasNextLine()) {
data = myReader.nextLine();
}
// create GenerateKey object to access public key
// GenerateKey is my personal class and contain public key
GenerateKey key = new GenerateKey();
// convert public key to string
String public_Key = key.PublicKey.getText();
// convert string public key to secret key
byte[] decodedKey = Base64.getDecoder().decode(public_Key);
SecretKey originalKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
// generate random key
KeyGenerator g = KeyGenerator.getInstance("AES");
// give it size
g.init(key_size);
random_key = g.generateKey();
// encrypt the random key with RSA and public key
byte[] random_byteKey = random_key.getEncoded();
Cipher cipher_Key = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher_Key.init(Cipher.ENCRYPT_MODE, originalKey);
byte[] encrypted_key = cipher_Key.doFinal(random_byteKey); //RSA key
// after generating RSA key we will Encrypt file using RSA key
byte[] byte_message = data.getBytes();
Cipher cipherTxt = Cipher.getInstance("AES/GCM/NoPadding");
// the problem in here
cipherTxt.init(Cipher.ENCRYPT_MODE, encrypted_key);
byte[] encByte = cipherTxt.doFinal(byte_message);
}

You are not understanding what you need to do. First you generate a random AES key that is used solely for the data encryption. Then you encrypt that key with RSA using the trusted RSA public key which is part of the key pair of the receiver. So you never have to convert either the public key or the RSA ciphertext to a symmetric key.
As an aside, instead of using Cipher#doFinal() you should use Cipher#wrap() , which takes a symmetric key. That way you don't have to encode them to a byte array. It may also be more secure if a hardware module is used, for instance, depending on the Cipher implementation.
I'd strongly suggest you generate separate methods for these separate steps as well as for the file handling.
In the end, you'll need something more akin to this:
public static void hybridEncrypt(RSAPublicKey publicKey, File in, File out) throws IOException, InvalidKeyException {
int key_size=128;
try {
KeyGenerator g = KeyGenerator.getInstance("AES");
g.init(key_size);
SecretKey dataKey = g.generateKey();
// encrypt the random data key with the RSA public key
Cipher cipher_Key = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher_Key.init(Cipher.WRAP_MODE, publicKey);
byte[] encryptedKey = cipher_Key.wrap(dataKey);
Cipher cipherTxt = Cipher.getInstance("AES/GCM/NoPadding");
cipherTxt.init(Cipher.ENCRYPT_MODE, dataKey);
byte[] message = Files.readAllBytes(in.toPath());
byte[] encryptedMessage = cipherTxt.doFinal(message);
out.createNewFile();
Files.write(out.toPath(), encryptedKey);
Files.write(out.toPath(), encryptedMessage, StandardOpenOption.APPEND);
} catch(NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException e) {
throw new RuntimeException("RSA or AES/GCM not available", e);
} catch (BadPaddingException e) {
throw new RuntimeException("Padding failed for NoPadding", e);
}
}
public static void main(String[] args) throws Exception {
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
kpGen.initialize(3072);
KeyPair keyPairReceiver = kpGen.generateKeyPair();
RSAPublicKey publicKeyReceiver = (RSAPublicKey) keyPairReceiver.getPublic();
hybridEncrypt(publicKeyReceiver, new File("plain.txt"), new File("bla.bin"));
}
Beware that this is still not best practice code, for instance it uses the old PKCS#1 encryption instead of OAEP. Don't copy paste this guys - with encryption you need to understand what you are doing, and preferably use a well vetted high level library.

Related

Java RSA decryption javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes

Small RSA decryption question with Java please.
I want to meet in a secret location with a friend of mine.
As we do not want anyone to eavesdrop on the secret location, I generated a RSA key pair, a private key and a public key. Note, I generated both as Strings.
String privateKeyString = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC6cXloNrocJ8s[...]LABviZm5AFCQWfke4LZo5mOS10";
String publicKeyString = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB[...]EatUOuyQMt2Vwx4uV+d/A3DP6PtMGBKpF8St4iGwIDAQAB";
I then shared publicly my public key to my friend, who wrote this:
private static String encryptSecretLocation(String secretLocation) {
try {
String publicKeyString = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB[...]EatUOuyQMt2Vwx4uV+d/A3DP6PtMGBKpF8St4iGwIDAQAB"; //public key same as above
byte[] buffer = Base64.getDecoder().decode(publicKeyString);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
PublicKey publicKey = keyFactory.generatePublic(keySpec);
Cipher encryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] secretMessageBytes = secretLocation.getBytes(StandardCharsets.UTF_8);
byte[] encryptedMessageBytes = encryptCipher.doFinal(secretMessageBytes);
return Base64.getEncoder().encodeToString(encryptedMessageBytes);
} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) {
return "bad location";
}
}
And indeed, he managed to get some gibberish, which he sent me back
"OixtTJRXe2nDRWDBqSs9m4wN[...]17/MKpw=="
via a nice letter:
Hey, meet me at (decrypt this)
"OixtTJRXe2nDRWDBqSs9m4wN[...]17/MKpw=="
I will wait for you there tomorrow at noon! Please come alone!
I have the private key which I did not share with anyone, and was hoping to compute back the secret location with this piece of code, this is what I tried.
public static void main(String[] args) throws Exception {
String privateKeyString = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC6cXloNrocJ8s[...]LABviZm5AFCQWfke4LZo5mOS10";
String publicKeyString = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB[...]EatUOuyQMt2Vwx4uV+d/A3DP6PtMGBKpF8St4iGwIDAQAB";
byte[] buffer1 = Base64.getDecoder().decode(privateKeyString);
PKCS8EncodedKeySpec keySpec1 = new PKCS8EncodedKeySpec(buffer1);
KeyFactory keyFactory1 = KeyFactory.getInstance("RSA");
PrivateKey privateKey = (RSAPrivateKey) keyFactory1.generatePrivate(keySpec1);
Cipher decryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedMessageBytes = decryptCipher.doFinal("OixtTJRXe2nDRWDBqSs9m4wN[...]17/MKpw==".getBytes(StandardCharsets.UTF_8));
String decryptedMessage = new String(decryptedMessageBytes, StandardCharsets.UTF_8);
System.out.println(decryptedMessage);
}
Unfortunately, all I am getting is :
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes
at java.base/com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:347)
at java.base/com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:392)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202)
May I ask what is the issue, and how to compute the secret location of our meeting back please?
Thank you
P.S. Tried to make the question interesting, hope you liked it!
As Topaco mentioned (all credits to him) when decrypting, the ciphertext must be Base64 decoded and not UTF8 encoded:
In my case, it was UTF8 encoded.
Base64.getDecoder().decode("OixtTJRXe2nDRWDBqSs9m4wN[...]17/MKpw==") worked.

How do I decrypt a public PEM key encrypted byte array using a matching DER private key?

So I am creating a basic socket program where I want to send an encrypted string in C to a Java program. My C program encrypts the string with a public PEM key. I have converted the matching private PEM key to a DER key and now want to decrypt the string that was sent to my Java program. How do I do this?
At the moment I am getting an IllegalBlockSizeException stating that "Data must not be longer than 256 bytes" when trying to run the code as it stands.
This is what I have at the moment:
C client program...
//Get our public key from the publickey file created by server
FILE *publicKeyFile = fopen("publicKey.pem", "rb");
RSA *rsa = RSA_new();
rsa = PEM_read_RSA_PUBKEY(publicKeyFile, &rsa, NULL, NULL);
if(rsa == NULL) {
printf("Error with public key...\n");
}
else {
//if the public key is correct we will encrypt the message
RSA_public_encrypt(2048, sigMessage, sigMessage, rsa, RSA_PKCS1_PADDING);
}
Java decryption...
public static String decrypt(byte[] encryptedMessage) {
try {
Cipher rsa;
rsa = Cipher.getInstance("RSA");
PrivateKey ourKey = getKey("resources/privateKey.der");
rsa.init(Cipher.DECRYPT_MODE, ourKey);
byte[] utf8 = rsa.doFinal(encryptedMessage);
return new String(utf8, "UTF8");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static PrivateKey getKey(String filePath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
File f = new File(filePath);
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int) f.length()];
dis.readFully(keyBytes);
dis.close();
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
}
You can't encrypt 2,048 bytes with RSA unless you have an RSA key of over 16K. You probably have a 2048 bit RSA key, which limits you to under 256 bytes. Check out the openssl man page for RSA_public_encrypt:
flen must be less than RSA_size(rsa) - 11 for the PKCS #1 v1.5 based
padding modes, less than RSA_size(rsa) - 41 for RSA_PKCS1_OAEP_PADDING
and exactly RSA_size(rsa) for RSA_NO_PADDING. The random number
generator must be seeded prior to calling RSA_public_encrypt().
And RSA_size:
RSA_size() returns the RSA modulus size in bytes. It can be used to
determine how much memory must be allocated for an RSA encrypted
value.
You should not encrypt a full 256 bytes with a 2048 bit key, though. You want to always use random padding with RSA encryption, and choose OAEP over v1.5.

Decrypting byte array from Java in C++ using RSA with java generated keys

I need to decrypt file in C++. What I have is byte array and pair of keys generated in Java using KeyPairGenerator from java.security;
Java Code:
public void generateKeys() {
try {
final KeyPairGenerator pairGenerator = KeyPairGenerator.getInstance(algorithmName);
pairGenerator.initialize(1024); //1024 - keysize
final KeyPair keyPair = pairGenerator.generateKeyPair();
savePublicKeyIntoFile(keyPair);
savePrivateKeyIntoFile(keyPair);
} catch (Exception e) {
System.err.println("Class EncryptionTool.generateKeys() ");
e.printStackTrace();
}
}
public static String encrypt() throws Exception {
// Encrypt the string using the public key
ObjectInputStream inputStream = null;
inputStream = new ObjectInputStream(new FileInputStream(publicKeyFilepath));
final PublicKey publicKey = (PublicKey) inputStream.readObject();
// get an RSA cipher object and print the provider
final Cipher cipher = Cipher.getInstance(algorithmName);
// encrypt the plain text using the public key
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherText = null;
**cipherText = cipher.doFinal( loadPassword() );**
return changeByteArrayToString(cipherText);
}
I have generated keys saved in files and this cipherText array in C++.
What should I use to decrypt this ?

Java AES encryption/decryption procedure and usage of Initialization Vector

I want to learn the basics of AES encryption so I started to make a very simple Java program. The program loads a text file in to a String and asks for a key from the user. The program then uses AES to encrypt the text creating a new text file with the encrypted text. The program prints the Initialization Vector (IV) to the user.
The program also has the decryption function. The user specifies the encrypted text file along with the Initialization Vector and the key to decrypt it back to the original text in a new text file.
However I think I'm doing something wrong. Is it normal procedure in AES encryption that the user needs to have both key and IV to decrypt the file? I have browsed through the internet and almost in every example, the encrypted data can be decrypted by the user specifying only the key but in my case the user needs to have both the key and the IV. The program is working fine but I think it isn't efficient.
So should I use a constant, known IV which is used in all the encryptions and decryptions or what? Also some tutorials are using "salt", what is it and should I use it?
Here are my encrypt and decrypt methods:
public String encrypt(String stringToEncrypt, String userKey)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
// User gives string key which is formatted to 16 byte and to a secret
// key
byte[] key = userKey.getBytes();
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16);
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
// Cipher initialization
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
// Encryption and encoding
String encryptedData = new BASE64Encoder().encode(cipher
.doFinal(stringToEncrypt.getBytes()));
// IV is printed to user
System.out.println("\nENCRYPTION IV: \n"
+ new BASE64Encoder().encode(cipher.getIV()) + "\n");
// Function returns encrypted string which can be writed to text file
return encryptedData;
}
public String decrypt(String stringToDecrypt, String userKey, String userIv)
throws NoSuchAlgorithmException, IOException,
NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException,
BadPaddingException {
// User gives the same string key which was used for encryption
byte[] key = userKey.getBytes();
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16);
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
// Decode string iv to byte
byte[] iv = new BASE64Decoder().decodeBuffer(userIv);
// Cipher initialization
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
// Decryption and decoding
String decryptedData = new String(cipher.doFinal(new BASE64Decoder()
.decodeBuffer(stringToDecrypt)));
// Function returns decrypted string which can be writed to text file
return decryptedData;
}
UPDATE
I updated my code now to use "PBKDF2WithHmacSHA256" algorithm with salt and etc. I also combined the Initialization Vector (IV) byte array to the cipher text byte array as prefix so I can split them in decrypt method and get the IV there (That's working fine).
However there's now an issue with the key, because I'm generating new encrypted key also in decryption method which of course is a wrong key for encrypted data. I want to be able to close the program so I can't store the key as a class variable. It's very hard to explain the issue but I hope you understand the problem...
public static byte[] getEncryptedPassword(String password, byte[] salt,
int iterations, int derivedKeyLength)
throws NoSuchAlgorithmException, InvalidKeySpecException {
KeySpec mKeySpec = new PBEKeySpec(password.toCharArray(), salt,
iterations, derivedKeyLength);
SecretKeyFactory mSecretKeyFactory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA256");
return mSecretKeyFactory.generateSecret(mKeySpec).getEncoded();
}
public String encrypt(String dataToEncrypt, String key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidParameterSpecException, IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException {
byte[] mEncryptedPassword = getEncryptedPassword(key, generateSalt(),
16384, 128);
SecretKeySpec mSecretKeySpec = new SecretKeySpec(mEncryptedPassword, "AES");
Cipher mCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
mCipher.init(Cipher.ENCRYPT_MODE, mSecretKeySpec);
byte[] ivBytes = mCipher.getIV();
byte[] encryptedTextBytes = mCipher.doFinal(dataToEncrypt.getBytes());
byte[] combined = new byte[ivBytes.length+encryptedTextBytes.length];
System.arraycopy(ivBytes, 0, combined, 0, ivBytes.length);
System.arraycopy(encryptedTextBytes, 0, combined, ivBytes.length, encryptedTextBytes.length);
return Base64.getEncoder().encodeToString(combined);
}
public String decrypt(String dataToDecrypt, String key) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
byte[] encryptedCombinedBytes = Base64.getDecoder().decode(dataToDecrypt);
byte[] mEncryptedPassword = getEncryptedPassword(key, generateSalt(),
16384, 128);
byte[] ivbytes = Arrays.copyOfRange(encryptedCombinedBytes,0,16);
SecretKeySpec mSecretKeySpec = new SecretKeySpec(mEncryptedPassword, "AES");
Cipher mCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
mCipher.init(Cipher.DECRYPT_MODE, mSecretKeySpec, new IvParameterSpec(ivbytes));
byte[] encryptedTextBytes = Arrays.copyOfRange(encryptedCombinedBytes, 16, encryptedCombinedBytes.length);
System.out.println(encryptedTextBytes.length);
byte[] decryptedTextBytes = mCipher.doFinal(encryptedTextBytes);
return Base64.getEncoder().encodeToString(decryptedTextBytes);
}
public byte[] generateSalt() {
SecureRandom random = new SecureRandom();
byte saltBytes[] = new byte[16];
random.nextBytes(saltBytes);
return saltBytes;
}}
I hope somebody knows how to make this better. Thanks!
Just save the IV in the file before the encrypted data.
You should never use the same IV more than once (it's ok-ish, if you roll a new IV every time, and it just so happens that you roll the same twice, so you don't have to store and check that). Using the same IV many times poses a great security risk, as encrypting the same content twice reveals that it's - in fact - the same content.
Storing IV alongside the encrypted data is a common, and secure procedure, as it's role is to introduce "randomness" to the encryption scheme, and it shouldn't be secret, just securely (and in some schemes randomly) generated.

Data differs after encryption and consecutive decryption with AES

I want to encrypt and decrypt integers with AES but can't get it going.
To test the basic cryptographic process I wrote a simple method that takes input data, encrypts and decrypts it with the same parameters and returns the result.
Here is my failing JUnit test case that checks whether the input and the output data are equal.
#Test
public void test4() throws UnsupportedEncodingException {
Random random = new Random();
SecretKey secretKey = Tools.generateKey("secretKey".getBytes("UTF-8"));
byte[] initializationVector = Tools.intToByteArray(random.nextInt());
// ensuring that the initialization vector has the correct length
byte[] ivHash = Tools.hashMD5(initializationVector);
int value = random.nextInt();
byte[] input = Tools.intToByteArray(value);
byte[] received = Tools.enDeCrypt(input, secretKey, ivHash);
assertEquals(data.hashCode(), received.hashCode());
}
Method generateKey:
public static SecretKeySpec generateKey(byte[] secretKey) {
try {
// 256 bit key length
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(secretKey);
byte[] key = md.digest();
return new SecretKeySpec(key, "AES");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
Method for int -> byte[] conversion:
public static byte[] intToByteArray(int a) {
// block size is 128 bit, thus I append zeros
byte[] intByte = ByteBuffer.allocate(4).putInt(a).array();
byte[] longerIntByte = new byte[16];
for (int i = 0; i < 4; i++) {
longerIntByte[i] = intByte[i];
}
for (int i = 4; i < longerIntByte.length; i++) {
longerIntByte[i] = 0;
}
return longerIntByte;
}
Here is the code for encryption and decryption:
public static byte[] enDeCrypt(byte[] data, SecretKey secretKey,
byte[] initialisationVector) {
try {
IvParameterSpec ivSpec = new IvParameterSpec(initialisationVector);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
byte[] encrypted = cipher.doFinal(data);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
} catch (NoSuchAlgorithmException | NoSuchPaddingException
| InvalidKeyException | InvalidAlgorithmParameterException
| IllegalBlockSizeException | BadPaddingException e) {
throw new RuntimeException(e);
}
}
assertEquals(data.hashCode(), received.hashCode()) is very unlikely to pass unless data and received refer to the same object (since byte arrays inherit the identity hash code method from Object). I don't see where data comes from, but that is probably not the case here. You should use Arrays.equals(data, received).
Apart from that, there are various cryptographic issues here:
Random is not "random enough" for cryptographic purposes; you should use SecureRandom.
Key derivation using plain SHA-256 is dubious. You should consider using a key derivation algorithm that is specifically designed for this, like PBKDF2.
AES with 256-bit keys is not always better than 128-bit keys. Check this page. In this case it's completely bogus since passphrases rarely even reach 128 bits of entropy.
Random IVs – good, but why jump through hoops when you could just directly use SecureRandom.nextBytes(). Hashing the IV doesn't add anything useful.
There's no reason to do manual zero padding when you could instead let the library handle it. Just specify PKCS5Padding instead of NoPadding.

Categories