Generate AES key from RSA encoded AES key - java

What I am trying to do is to encrypt a string with AES, encrypt the AES key getEncoded() value with RSA, then decrypt that AES getEncoded() value so that I get my original string. The public key is loaded from the users certificate, and the private key from file.
The code is given below.
public class Main {
public static void main(String[] args) throws Exception {
String myString = "My Message";
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
SecretKey secretKey = keyGenerator.generateKey();
byte[] initializationVector = new byte[128 / 8];//16
SecureRandom prng = new SecureRandom();
prng.nextBytes(initializationVector);
Cipher AESCipherForEncryption = Cipher.getInstance("AES/CBC/PKCS5PADDING");
AESCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(initializationVector));
byte[] byteVersionOfMyMessage = myString.getBytes();
byte[] byteVersionOfCipherText = AESCipherForEncryption.doFinal(byteVersionOfMyMessage);
String cipherText = new BASE64Encoder().encode(byteVersionOfCipherText);
InputStream in1 = new FileInputStream("user.crt");
CertificateFactory cf1 = CertificateFactory.getInstance("X509");
Certificate c1 = cf1.generateCertificate(in1);
X509Certificate toSendcert = (X509Certificate) c1;
PublicKey publicKey = toSendcert.getPublicKey();
String cipherTextRSA = encryptRSA(publicKey, new String(secretKey.getEncoded()));
String decypheredRSA = decryptRSA(getPrivateKey("user.pk8", "RSA"), cipherTextRSA);
System.out.println(cipherTextRSA);
System.out.println(decypheredRSA);
SecretKey originalKey = new SecretKeySpec(new String(decypheredRSA.getBytes("UTF-8")).getBytes(), 0, new String(decypheredRSA.getBytes("UTF-8")).getBytes().length, "AES");
Cipher AESCipherForDecryption = Cipher.getInstance("AES/CBC/PKCS5PADDING");
AESCipherForDecryption.init(Cipher.DECRYPT_MODE, originalKey, new IvParameterSpec(initializationVector));
byte[] byteVersionOfDecriptedText = AESCipherForDecryption.doFinal(new BASE64Decoder().decodeBuffer(cipherText));
String decMessage = new String(byteVersionOfDecriptedText);
System.out.println(decMessage);
}
public static String encryptRSA(PublicKey pubKey, String message) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
Base64.Encoder encoder = Base64.getEncoder();
String encryptedString = encoder.encodeToString(cipher.doFinal(message.getBytes("UTF-8")));
return encryptedString;
}
public static PrivateKey getPrivateKey(String filename, String algorithm) throws Exception {
File f = new File(filename);
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int) f.length()];
dis.readFully(keyBytes);
dis.close();
String temp = new String(keyBytes);
String privKeyPEM = temp.replace("-----BEGIN PRIVATE KEY-----", "");
privKeyPEM = privKeyPEM.replace("-----END PRIVATE KEY-----", "");
privKeyPEM = privKeyPEM.replace("\n", "");
byte[] decoded = Base64.getDecoder().decode(privKeyPEM);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
KeyFactory kf = KeyFactory.getInstance(algorithm);
return kf.generatePrivate(spec);
}
public static String decryptRSA(PrivateKey prKey, String encrypted) throws Exception {
Base64.Decoder decoder = Base64.getDecoder();
byte[] input = decoder.decode(encrypted);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, prKey);
return new String(cipher.doFinal(input));
}
The error that I keep getting is:
Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 28 bytes
at com.sun.crypto.provider.AESCipher.engineGetKeySize(AESCipher.java:509)
at javax.crypto.Cipher.passCryptoPermCheck(Cipher.java:1067)
at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1038)
at javax.crypto.Cipher.implInit(Cipher.java:805)
at javax.crypto.Cipher.chooseProvider(Cipher.java:864)
at javax.crypto.Cipher.init(Cipher.java:1396)
at javax.crypto.Cipher.init(Cipher.java:1327)
at com.company.Main.main(Main.java:79)
If I don't encrypt and decrypt the secretKey.getEncoded() value, and just use AES without RSA it works properly. Also working with RSA, if I just encrypt some string with a public key, and decrypt it with a private it works. My question would be: "How could I properly encrypt and decrypt the secretKey.getEncoded() value with RSA, so that I can properly encrypt and decrypt myString?".

new String(secretKey.getEncoded())
This won't work as AES keys contain random bytes, and not every byte is a character representative. The problem with the standard string conversion in Java is that it drops unknown characters and bytes instead of generating an exception during encoding / decoding.
RSA operates on bytes, you should not turn the key into string and then back again into bytes as the transformation may be lossy (e.g. dropping 4 of the 32 bytes).
Alternatively - and probably even better - you may want to try the wrapping modes of cipher instead. This should be compatible with some hardware solutions out there. In that case you don't even have to call getEncoded.
OAEP encryption and authenticated encryption modes such as GCM should be preferred over PKCS#1 padding (the default for the Sun providers) and CBC mode encryption.

Related

Use RSA encrypted AES-Key for encoding Text

First, please excuse my bad english, this is not my native language.
i am trying to encode and then decode some text of different lengths.
I tried to encode some text with RSA algorithm in java. In the progress, I found out that RSA-encoding is limited to a specific size of bytes, so this approach doesn't fit my specs as my text could be large.
As I want to stay with a public and a private key only, I came up with the idea to use a 'hard-coded' AES key which will be encoded with RSA public key and than use this to encode the data. (see code)
//harcoded secretkey base64 encoded
private static final String secretkey_base64 = "xtnN6Pove5AovbLXtGRJKw==";
//this is how RSA key are generated
public static KeyPair genKeys() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance( "RSA" );
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
return kp;
}
//here text should be encoded
public static String encodeText(String plainText, PublicKey publicKey) throws Exception
{
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherText = encryptCipher.doFinal(CryptoUtil.secretkey_base64.getBytes(UTF_8));
String encodedSecKey = Base64.getEncoder().encodeToString(cipherText);
SecretKeySpec k = new SecretKeySpec(Base64.getDecoder().decode(encodedSecKey), "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.ENCRYPT_MODE, k);
byte[] byteCipherText = aesCipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(byteCipherText);
}
Obviously this doesn't work because the RSA encoded AES key is too long for AES encryption. Throws "Invalid AES key length: 256 bytes"
Does anyone have an idea how I could get the encryption running with this particular approach ?
Is this even possible, or do I have to use a different approach?
Thanks in advance.
//edit - Solved thanks to #ewramner
// here is the working solution:
public static String encodeText(String plainText, PublicKey publicKey) throws Exception
{
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(128); // The AES key size in number of bits
SecretKey secKey = generator.generateKey();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.PUBLIC_KEY, publicKey);
byte[] encSecKey = cipher.doFinal(secKey.getEncoded());
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.ENCRYPT_MODE, secKey);
byte[] byteCipherText = aesCipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(encSecKey) + "#" + Base64.getEncoder().encodeToString(byteCipherText);
}
public static String decodeText(String cipherText, PrivateKey privateKey) throws Exception
{
String[] split = cipherText.split("#");
String encSecKey = split[0];
String encText = split[1];
byte[] bytesSecKey = Base64.getDecoder().decode(encSecKey);
byte[] bytesText = Base64.getDecoder().decode(encText);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.PRIVATE_KEY, privateKey);
byte[] decSecKey = cipher.doFinal(bytesSecKey);
SecretKey secKey = new SecretKeySpec(decSecKey , 0, decSecKey .length, "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
return new String(aesCipher.doFinal(bytesText));
}

Android String Encryption/Decryption

I want to encrypt and decrypt a String from an EditText using AndroidKeyStore. My problem is that at the decrypt process is get a BadPaddingException.
Key generator code:
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT).
setBlockModes(KeyProperties.BLOCK_MODE_GCM).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE).build();
keyGenerator.init(keyGenParameterSpec);
keyGenerator.generateKey();
Encryption code:
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry(ALIAS, null);
SecretKey secretKey = secretKeyEntry.getSecretKey();
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
cipherIV = cipher.getIV();
plainText.setText(new String(cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8));
Decryption code:
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
final KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry(ALIAS, null);
final SecretKey secretKey = secretKeyEntry.getSecretKey();
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
final GCMParameterSpec spec = new GCMParameterSpec(128, cipherIV);
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
byte[] decrypted = cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8));
plainText.setText(new String(decrypted, StandardCharsets.UTF_8));
This line is in error:
plainText.setText(new String(cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8));
If we break it apart, we have something like
byte [] cipherBytes = cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8));
plainText.setText(new String(cipherBytes, StandardCharsets.UTF_8);
The problem is that cipherBytes is a sequence of arbitrary bytes rather than the characters of a string. The String constructor will silently replace invalid characters with something else, a process which corrupts the data.
If you want to display the cipher bytes or otherwise send it to a character oriented channel you must encode it. Typically encodings are base64 or hex. To decrypt the String you must then decode it to bytes first and then decrypt it.
Example:
byte [] cipherBytes = cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8));
plainText.setText(Base64.encodeToString(cipherBytes, Base64.DEFAULT));
And on decrypt:
byte[] cipherBytes = Base64.decode(plainText.getText().toString(), Base64.DEFAULT);
byte[] decrypted = cipher.doFinal(cipherBytes);
byte[] decrypted = cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8));
This line may not work well because of the call of getBytes(StandardCharsets.UTF_8). If your EditText is hex representation, try to convert it to string and then invoke getBytes(). E.g.
public static byte[] convertHexStringToByteArray(String hexString) {
int l = hexString.length();
byte[] data = new byte[l/2];
for (int i = 0; i < l; i += 2) {
data[i/2] = (byte)((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i+1), 16));
}
return data;
}

RSA Encrypting in c# and Decrypting data in java

I'm trying to encrypt data in my C# client with public RSA key from the Java server. To do this I generated KeyPair in Java
KeyPairGenerator.java
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
Key pub = kp.getPublic();
Key pvt = kp.getPrivate();
String outFile = "rsa_key";
FileOutputStream outPvt = new FileOutputStream(outFile + ".key");
outPvt.write(pvt.getEncoded());
outPvt.close();
FileOutputStream outPub = new FileOutputStream(outFile + ".pub");
outPub.write(pub.getEncoded());
outPub.close();
It gave me two files (rsa_key.key and rsa_key.pub) and then I encoded public key with Base64 so C# could read it:
PublicKeyBase64.java
String keyFile2 = "rsa_key.pub";
Path path2 = Paths.get(keyFile2);
byte[] bytes2 = Files.readAllBytes(path2);
X509EncodedKeySpec ks2 = new X509EncodedKeySpec(bytes2);
KeyFactory kf2 = KeyFactory.getInstance("RSA");
PublicKey pub = kf2.generatePublic(ks2);
Base64.Encoder encoder = Base64.getEncoder();
String outFile = "en_rsa_key";
Writer out = new FileWriter(outFile + ".pub");
out.write(encoder.encodeToString(pub.getEncoded()));
out.close();
Then I created my C# class to encrypt data
Encrypter.cs
class Encrypter
{
private const string PATH = "..\\..\\key\\en_rsa_key.pub";
private string PublicKey;
public Encrypter()
{
PublicKey = File.ReadAllText(PATH);
Console.WriteLine(PublicKey.Length);
}
public string encryptData(string dataToEncrypt)
{
Asn1Object obj = Asn1Object.FromByteArray(Convert.FromBase64String(PublicKey));
DerSequence publicKeySequence = (DerSequence)obj;
DerBitString encodedPublicKey = (DerBitString)publicKeySequence[1];
DerSequence publicKey = (DerSequence)Asn1Object.FromByteArray(encodedPublicKey.GetBytes());
DerInteger modulus = (DerInteger)publicKey[0];
DerInteger exponent = (DerInteger)publicKey[1];
RsaKeyParameters keyParameters = new RsaKeyParameters(false, modulus.PositiveValue, exponent.PositiveValue);
RSAParameters parameters = DotNetUtilities.ToRSAParameters(keyParameters);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(parameters);
//Console.WriteLine(dataToEncrypt);
byte[] encryptedData = rsa.Encrypt(Encoding.UTF8.GetBytes(dataToEncrypt), true);
//Console.WriteLine(Convert.ToBase64String(encryptedData));
return Convert.ToBase64String(encryptedData);
}
}
And at last my Decrypter class
public class Decrypter {
private static final String PATH = "I:\\rsa_key.key";
private File privateKeyFile = new File(PATH);
private RSAPrivateKey privateKey;
public Decrypter() throws Exception {
DataInputStream dis = new DataInputStream(new FileInputStream(privateKeyFile));
byte[] privateKeyBytes = new byte[(int)privateKeyFile.length()];
dis.read(privateKeyBytes);
dis.close();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privateKeyBytes);
privateKey = (RSAPrivateKey) keyFactory.generatePrivate(privSpec);
}
public String decrypt(String data) throws Exception{
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return new String(cipher.doFinal(data.getBytes()));
}
}
I send encrypted data in C# via WebSocket and receive it without any problem because data while sending and receiving is the same. The problem is that data length is 344 characters (bytes) so when I decrypt my data it shows me and error: javax.crypto.IllegalBlockSizeException: Data must not be longer than 245 bytes
I'm using 2048 bit key so it's 256B - 11B for padding that's why it's 245B. But the problem is that it always generates 344B and it doesn't depend on message to encode length. For example encoding "A" gives 344B and "Hello world" also 344B.
In this topic people say to use symmetric key, Encrypt the data with the symmetric key and then Encrypt the symmetric key with rsa. But it won't help because any encrypted string in my code has 344B size, so I won't be able to decrypt encrypted symmetric key.
What's wrong with my code?

Java - Encrypt String with existing public key file

I've been researching this for the past 4-5 hours now and can't seem to find an answer that actually works despite finding 'answers' that used everything from a few methods to an entire ~100 line class. I can't imagine that there isn't some simple function to do such a trivial thing :P
I have a pre-existing set of public / private keys (actually, two sets - one generated by ssh-keygen and another by openssl so .. whatever format works is cool).
All I am after is a simple java equivalent to what I write in python like -
key_object = someModule.KeyObject(nameOfPublicKeyFile)
def encrypt (SomePlainText) :
return someOtherModule.encrypt(key_object, SomePlainText)
Any help would be awesome!
These openssl commands in the shell create an RSA key pair and write the public and private keys to DER formatted files.
Here, the private key file is not password-protected (-nocrypt) to keep things simple.
$ openssl genrsa -out keypair.pem 2048
Generating RSA private key, 2048 bit long modulus
............+++
................................+++
e is 65537 (0x10001)
$ openssl rsa -in keypair.pem -outform DER -pubout -out public.der
writing RSA key
$ openssl pkcs8 -topk8 -nocrypt -in keypair.pem -outform DER -out private.der
Now that you have the DER files, you can read them in Java and use KeySpec and KeyFactory to create PublicKey and PrivateKey objects.
public byte[] readFileBytes(String filename) throws IOException
{
Path path = Paths.get(filename);
return Files.readAllBytes(path);
}
public PublicKey readPublicKey(String filename) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException
{
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(readFileBytes(filename));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(publicSpec);
}
public PrivateKey readPrivateKey(String filename) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException
{
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(readFileBytes(filename));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
With the public and private keys, you can encrypt and decrypt small amounts of data (that fit within your RSA modulus.) I recommend OAEP padding.
public byte[] encrypt(PublicKey key, byte[] plaintext) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException
{
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(plaintext);
}
public byte[] decrypt(PrivateKey key, byte[] ciphertext) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException
{
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(ciphertext);
}
Here it is tied together with a simple encryption and decryption:
public void Hello()
{
try
{
PublicKey publicKey = readPublicKey("public.der");
PrivateKey privateKey = readPrivateKey("private.der");
byte[] message = "Hello World".getBytes("UTF8");
byte[] secret = encrypt(publicKey, message);
byte[] recovered_message = decrypt(privateKey, secret);
System.out.println(new String(recovered_message, "UTF8"));
}
catch (Exception e)
{
e.printStackTrace();
}
}
I would like to share a piece of code.. well actually a whole class which can do what you require if you customize it to your own needs. I have used this in one of my application where I used to encrypt/decrypt a file with the generated public/private keys. Same can be applied to Strings as well.
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.io.*;
import java.util.*;
/**
* This class encrypts and decrypts a file using CipherStreams
* and a 256-bit Rijndael key. The key is then encrypted using
* a 1024-bit RSA key, which is password-encrypted.
*/
public class FileEncryptorRSA {
/**
* When files are encrypted, this will be appended to the end
* of the filename.
*/
private static final String ENCRYPTED_FILENAME_SUFFIX=".encrypted";
/**
* When files are decrypted, this will be appended to the end
* of the filename.
*/
private static final String DECRYPTED_FILENAME_SUFFIX=".decrypted";
/**
* Number of times the password will be hashed with MD5
* when transforming it into a TripleDES key.
*/
private static final int ITERATIONS = 1000;
/**
* FileEncryptor is started with one of three options:
*
* -c: create key pair and write it to 2 files
* -e: encrypt a file, given as an argument
* -d: decrypt a file, given as an argument
*/
public static void main (String[] args)
throws Exception {
if ((args.length < 1) || (args.length > 2)) {
usage();
} else if ("-c".equals(args[0])) {
createKey();
} else if ("-e".equals(args[0])) {
encrypt(args[1]);
} else if ("-d".equals(args[0])) {
decrypt(args[1]);
} else {
usage();
}
}
private static void usage() {
System.err.println("Usage: java FileEncryptor -c|-e|-d [filename]");
System.exit(1);
}
/**
* Creates a 1024 bit RSA key and stores it to
* the filesystem as two files.
*/
private static void createKey()
throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Password to encrypt the private key: ");
String password = in.readLine();
System.out.println("Generating an RSA keypair...");
// Create an RSA key
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
System.out.println("Done generating the keypair.\n");
// Now we need to write the public key out to a file
System.out.print("Public key filename: ");
String publicKeyFilename = in.readLine();
// Get the encoded form of the public key so we can
// use it again in the future. This is X.509 by default.
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
// Write the encoded public key out to the filesystem
FileOutputStream fos = new FileOutputStream(publicKeyFilename);
fos.write(publicKeyBytes);
fos.close();
// Now we need to do the same thing with the private key,
// but we need to password encrypt it as well.
System.out.print("Private key filename: ");
String privateKeyFilename = in.readLine();
// Get the encoded form. This is PKCS#8 by default.
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
// Here we actually encrypt the private key
byte[] encryptedPrivateKeyBytes =
passwordEncrypt(password.toCharArray(),privateKeyBytes);
fos = new FileOutputStream(privateKeyFilename);
fos.write(encryptedPrivateKeyBytes);
fos.close();
}
/**
* Encrypt the given file with a session key encrypted with an
* RSA public key which will be read in from the filesystem.
*/
private static void encrypt(String fileInput)
throws Exception {
BufferedReader in = new BufferedReader
(new InputStreamReader(System.in));
System.out.print("Public Key to encrypt with: ");
String publicKeyFilename = in.readLine();
// Load the public key bytes
FileInputStream fis = new FileInputStream(publicKeyFilename);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int theByte = 0;
while ((theByte = fis.read()) != -1)
{
baos.write(theByte);
}
fis.close();
byte[] keyBytes = baos.toByteArray();
baos.close();
// Turn the encoded key into a real RSA public key.
// Public keys are encoded in X.509.
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
// Open up an output file for the output of the encryption
String fileOutput = fileInput + ENCRYPTED_FILENAME_SUFFIX;
DataOutputStream output = new DataOutputStream
(new FileOutputStream(fileOutput));
// Create a cipher using that key to initialize it
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
// Now create a new 256 bit Rijndael key to encrypt the file itself.
// This will be the session key.
KeyGenerator rijndaelKeyGenerator = KeyGenerator.getInstance("Rijndael");
rijndaelKeyGenerator.init(256);
System.out.println("Generating session key...");
Key rijndaelKey = rijndaelKeyGenerator.generateKey();
System.out.println("Done generating key.");
// Encrypt the Rijndael key with the RSA cipher
// and write it to the beginning of the file.
byte[] encodedKeyBytes= rsaCipher.doFinal(rijndaelKey.getEncoded());
output.writeInt(encodedKeyBytes.length);
output.write(encodedKeyBytes);
// Now we need an Initialization Vector for the symmetric cipher in CBC mode
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16];
random.nextBytes(iv);
// Write the IV out to the file.
output.write(iv);
IvParameterSpec spec = new IvParameterSpec(iv);
// Create the cipher for encrypting the file itself.
Cipher symmetricCipher = Cipher.getInstance("Rijndael/CBC/PKCS5Padding");
symmetricCipher.init(Cipher.ENCRYPT_MODE, rijndaelKey, spec);
CipherOutputStream cos = new CipherOutputStream(output, symmetricCipher);
System.out.println("Encrypting the file...");
FileInputStream input = new FileInputStream(fileInput);
theByte = 0;
while ((theByte = input.read()) != -1)
{
cos.write(theByte);
}
input.close();
cos.close();
System.out.println("File encrypted.");
return;
}
/**
* Decrypt the given file.
* Start by getting the RSA private key
* and decrypting the session key embedded
* in the file. Then decrypt the file with
* that session key.
*/
private static void decrypt(String fileInput)
throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Private Key to decrypt with: ");
String privateKeyFilename = in.readLine();
System.out.print("Password for the private key: ");
String password = in.readLine();
// Load the private key bytes
FileInputStream fis = new FileInputStream(privateKeyFilename);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int theByte = 0;
while ((theByte = fis.read()) != -1)
{
baos.write(theByte);
}
fis.close();
byte[] keyBytes = baos.toByteArray();
baos.close();
keyBytes = passwordDecrypt(password.toCharArray(), keyBytes);
// Turn the encoded key into a real RSA private key.
// Private keys are encoded in PKCS#8.
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
// Create a cipher using that key to initialize it
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
// Read in the encrypted bytes of the session key
DataInputStream dis = new DataInputStream(new FileInputStream(fileInput));
byte[] encryptedKeyBytes = new byte[dis.readInt()];
dis.readFully(encryptedKeyBytes);
// Decrypt the session key bytes.
rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] rijndaelKeyBytes = rsaCipher.doFinal(encryptedKeyBytes);
// Transform the key bytes into an actual key.
SecretKey rijndaelKey = new SecretKeySpec(rijndaelKeyBytes, "Rijndael");
// Read in the Initialization Vector from the file.
byte[] iv = new byte[16];
dis.read(iv);
IvParameterSpec spec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("Rijndael/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, rijndaelKey, spec);
CipherInputStream cis = new CipherInputStream(dis, cipher);
System.out.println("Decrypting the file...");
FileOutputStream fos = new FileOutputStream(fileInput + DECRYPTED_FILENAME_SUFFIX);
// Read through the file, decrypting each byte.
theByte = 0;
while ((theByte = cis.read()) != -1)
{
fos.write(theByte);
}
cis.close();
fos.close();
System.out.println("Done.");
return;
}
/**
* Utility method to encrypt a byte array with a given password.
* Salt will be the first 8 bytes of the byte array returned.
*/
private static byte[] passwordEncrypt(char[] password, byte[] plaintext) throws Exception {
// Create the salt.
byte[] salt = new byte[8];
Random random = new Random();
random.nextBytes(salt);
// Create a PBE key and cipher.
PBEKeySpec keySpec = new PBEKeySpec(password);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithSHAAndTwofish-CBC");
SecretKey key = keyFactory.generateSecret(keySpec);
PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATIONS);
Cipher cipher = Cipher.getInstance("PBEWithSHAAndTwofish-CBC");
cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
// Encrypt the array
byte[] ciphertext = cipher.doFinal(plaintext);
// Write out the salt, then the ciphertext and return it.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(salt);
baos.write(ciphertext);
return baos.toByteArray();
}
/**
* Utility method to decrypt a byte array with a given password.
* Salt will be the first 8 bytes in the array passed in.
*/
private static byte[] passwordDecrypt(char[] password, byte[] ciphertext) throws Exception {
// Read in the salt.
byte[] salt = new byte[8];
ByteArrayInputStream bais = new ByteArrayInputStream(ciphertext);
bais.read(salt,0,8);
// The remaining bytes are the actual ciphertext.
byte[] remainingCiphertext = new byte[ciphertext.length-8];
bais.read(remainingCiphertext,0,ciphertext.length-8);
// Create a PBE cipher to decrypt the byte array.
PBEKeySpec keySpec = new PBEKeySpec(password);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithSHAAndTwofish-CBC");
SecretKey key = keyFactory.generateSecret(keySpec);
PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATIONS);
Cipher cipher = Cipher.getInstance("PBEWithSHAAndTwofish-CBC");
// Perform the actual decryption.
cipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
return cipher.doFinal(remainingCiphertext);
}
}
EDIT:
You will require to change Java policy of you JVM to Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction for using this code.
All related information regarding change in JAVA Policy can be found HERE
Here is a good example:
http://javadigest.wordpress.com/2012/08/26/rsa-encryption-example/
and there are many more. (Google for "java encrypt RSA example" if this link breaks.)
I can't seem to find an answer that actually works
Try the one linked above. If it doesn't work, please follow up with an edit or comment to say what is going wrong.
I can't imagine that there isn't some simple function to do such a trivial thing. :P
Well sorry, but your imagination must be broken :-)
In fact, it is not a trivial thing. And it is made more difficult by the fact that Java is trying to support a wide range of crypto functionality and crypto technology stacks using a single unified API.

RSA Encryption / Decryption using Java

I am doing a simple program to encrypt/decrypt using RSA algorithm in Java. I create a cipher object as follows:
//Create a Cipher object
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/NoPadding");
I do the encryption by calling the encrypt function:
String cipher=encrypt(textByte, pair, rsaCipher);
System.out.println("The Encryption using RSA Algorithm : "+cipher);
And the decryption as:
//Decryption
String plain=decrypt(Base64.decodeBase64(cipher),pair, rsaCipher);
System.out.println("The Decryption using RSA Algorithm : "+plain);
When I display the output, the decryption output returns a long space before the original text:
However, when I edit the code for creating the Cipher object to be:
//Create a Cipher object
Cipher rsaCipher = Cipher.getInstance("RSA");
i.e, removed the operation mode and padding arguments, the problem get resolved and the output becomes:
Where is the problem. In the first case (when the space appears), I specified NoPadding? Why the spaces appears in the decrypted message ? Even if I used padding, I expect this should not happen.
EDIT:
This is the encrypt and decrypt methods:
public static String encrypt(byte[] textBytes, KeyPair pair, Cipher rsaCipher) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException
{
//get the public key
PublicKey pk=pair.getPublic();
//Initialize the cipher for encryption. Use the public key.
rsaCipher.init(Cipher.ENCRYPT_MODE, pk);
//Perform the encryption using doFinal
byte[] encByte = rsaCipher.doFinal(textBytes);
// converts to base64 for easier display.
byte[] base64Cipher = Base64.encodeBase64(encByte);
return new String(base64Cipher);
}//end encrypt
public static String decrypt(byte[] cipherBytes, KeyPair pair, Cipher rsaCipher) throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException
{
//get the public key
PrivateKey pvk=pair.getPrivate();
//Create a Cipher object
//Cipher rsaCipher = Cipher.getInstance("RSA/ECB/NoPadding");
//Initialize the cipher for encryption. Use the public key.
rsaCipher.init(Cipher.DECRYPT_MODE, pvk);
//Perform the encryption using doFinal
byte[] decByte = rsaCipher.doFinal(cipherBytes);
return new String(decByte);
}//end decrypt
Your problem is indeed with the padding. Some kind of padding, either PKCS#1 1.5 or OAEP padding in practice, is required for secure RSA functionality. Furthermore, it is required to find the start and end of the encrypted plain text.
The modular exponentiation of RSA is performed using large integers. The results of these operations are then represented as octet strings. These octet strings are basically big endian, unsigned, fixed length representation of an integer. These integers are left padded with 00 valued bytes (this is called the I2OS primitive in the RSA standard). So what you are seeing is the result of the modular exponentiation, with the 00 padding still in place.
Long story short, always use a padding scheme. Nowadays, OAEP would be preferable. Use it together with hybrid encryption scheme, or use a higher level container format such as CMS or PGP.
//This is a complete encryption and decryption module using
//Algorithm: JWEAlgorithm.RSA_OAEP_256
//Encryption Method: A128CBC_HS256
public static String encrypt(String text) throws Exception {
// Set the plain text
Payload payload = new Payload(text);
// Create the header
JWEHeader header = new JWEHeader(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A128CBC_HS256);
// Create the JWE object and encrypt it
JWEObject jweObject = new JWEObject(header, payload);
jweObject.encrypt(new RSAEncrypter(getPublicKey()));
// Serialise to compact JOSE form...
String jweString = jweObject.serialize();
LOG.info("Generated Encrypted Key : {}", jweString);
return jweString;
}
public static String decrypt(String text) throws Exception {
// Parse into JWE object...
JWEObject jweObject = JWEObject.parse(text);
jweObject.decrypt(new RSADecrypter(getPrivateKey()));
// Get the plain text
Payload payload = jweObject.getPayload();
System.out.println(payload.toString());
return payload.toString();
}
private static RSAPublicKey getPublicKey() throws Exception {
String filename = "/home/vaibhav/Setups/cert/pub.der";
File f = new File(filename);
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int)f.length()];
dis.readFully(keyBytes);
dis.close();
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return (RSAPublicKey) kf.generatePublic(spec);
}
private static RSAPrivateKey getPrivateKey() throws Exception {
String filename = "/home/vaibhav/Setups/cert/private.pkcs8";
File f = new File(filename);
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int)f.length()];
dis.readFully(keyBytes);
dis.close();
PKCS8EncodedKeySpec spec1 = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) kf.generatePrivate(spec1);
}

Categories

Resources