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);
}
Related
As per given steps, I have performed encryption but am getting an error.
Generate a 16-digit random number (session key). Say RANDOMNO.
RANDOMNO = 1111222233334444
Encrypt RANDOMNO using RSA/ECB/PKCS1Padding and encode using Base64. Say ENCR_KEY.
ENCR_KEY = B64Encode(RSA/ECB/PKCS1Encryption(RANDOMNO,ICICIPubKey.cer))
Perform AES/CBC/PKCS5Padding encryption on request payload using RANDOMNO as key and ivinitialization vector. Say ENCR_DATA.
ENCR_DATA = B64Encode(AES/CBC/PKCS5Padding(REQUEST_DATA, RANDOMNO, IV))
Now the client may choose to send IV in request from one of the two options below.
Send Base64 Encoded IV in “iv” tag.
public byte[] generateRandomBytes() {
SecureRandom ng=new SecureRandom();
byte[] randomBytes=new byte[16];
ng.nextBytes(randomBytes);
return randomBytes;
}
//new method for encryption -we need to check
public String encryptRandomKeyWithCertificate(byte[] randomNumber) throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, FileNotFoundException, CertificateException {
//step2: encrypt the random number with certificate
FileInputStream fin = new FileInputStream("D:\\cedge_uat\\ICICIUATpubliccert.cer");
CertificateFactory f = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) f.generateCertificate(fin);
PublicKey publicKey = certificate.getPublicKey();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherData = cipher.doFinal(randomNumber);
String encodedData = Base64.getEncoder().encodeToString(cipherData);
return encodedData;
}
public String encryptRequestWithKey(String text, byte[] randomNumber) throws Exception {
//step3: encrypt the requestString with randomkeyEncrypted
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
BASE64Decoder decoder = new BASE64Decoder();
SecretKeySpec keySpec = new SecretKeySpec(randomNumber, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(new byte[16]);
cipher.init(Cipher.ENCRYPT_MODE, keySpec,ivSpec);
byte[] cipherData = cipher.doFinal(text.getBytes());
// BASE64Encoder encoder = new BASE64Encoder();
//return encoder.encode(cipherData).replaceAll("[\r\n]+", "");
String encodedData = Base64.getEncoder().encodeToString(cipherData);
return encodedData;
}
public static void main(String[] args) throws IOException {
String requestString = "CORP_USER=";
byte[] randomNumber;
String encryptedKey;
try {
randomNumber=encryption.generateRandomBytes();
encryptedKey = encryption.encryptRandomKeyWithCertificate(randomNumber);
String encryptedData =encryption.encryptRequestWithKey(requestString,randomNumber);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
According to the Wikipedia page on AES encryption, an AES key can be 128, 192 or 256 bits; i.e. 16, 24 or 32 bytes.
You are supplying a key whose size depends on the "random" number string you are generating. It looks like it will be between 1 and 19 digits plus a possible sign. When you call getBytes() on that string, you will get a byte array with anywhere between 1 and 20 bytes. That is typically NOT one of the acceptable key sizes for AES.
What you should do is use SecureRandom.nextBytes(byte[]) and supply a byte array of one of the three acceptable key sizes for AES.
I wrote a simple Encryption and Decryption helper class for my android app to encrypt and store Strings securely.
It consists of a single static public method to encrypt, then it calls a private static method to decrypt the encrypted message and returns it. I wrote the method this way to check if the message is intact after encryption/decryption.
I wrote a simple JUnit test with a String and called AssertEquals on the String before and after sending it to the Crypto encryption method.
I get this following errors from running the test:
javax.crypto.AEADBadTagException: Tag mismatch!
The error stack:
at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:571)
at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1046)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:983)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:845)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at util.Crypto.decrypt(Crypto.java:94)
at util.Crypto.encrypt(Crypto.java:64)
at com.example.ali.meappley.CryptoTest.encryptAndDecryptTest(CryptoTest.java:29)
I'm new to cryptography, but I read different stackoverflow replies and couldn't find anything of help. Some users suggested calling cipher.update(someByteArray) before calling cipher.doFinal(someByteArray) but I couldnt manage to get it working. Any suggestions?
This is my helper class
public class Crypto {
//public methods
//public static encrypt method
public static String encrypt(String messageToEncrypt, #Nullable byte[] associatedData) throws NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidAlgorithmParameterException,
InvalidKeyException,
BadPaddingException,
IllegalBlockSizeException {
byte[] plainBytes = messageToEncrypt.getBytes();
/////////////////////////////////////////////////////////////////
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = new SecretKeySpec(key, "AES");
byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
if (associatedData != null) {
cipher.updateAAD(associatedData);
}
byte[] cipherText = cipher.doFinal(plainBytes);
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + iv.length + cipherText.length);
byteBuffer.putInt(iv.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
Arrays.fill(key,(byte) 0); //overwrite the content of key with zeros
///////////////////////////////////////////////////////////////////
byte[] decrypted = decrypt(cipherMessage, null, key);
return decrypted.toString();
}
//public static decrypt method
private static byte[] decrypt(byte[] cipherMessage, #Nullable byte[] associatedData, byte[] key) throws NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidAlgorithmParameterException,
InvalidKeyException,
BadPaddingException,
IllegalBlockSizeException {
ByteBuffer byteBuffer = ByteBuffer.wrap(cipherMessage);
int ivLength = byteBuffer.getInt();
if(ivLength < 12 || ivLength >= 16) { // check input parameter
throw new IllegalArgumentException("invalid iv length");
}
byte[] iv = new byte[ivLength];
byteBuffer.get(iv);
byte[] cipherText = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherText);
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, iv));
if (associatedData != null) {
cipher.updateAAD(associatedData);
}
cipher.update(cipherText);
byte[] plainText= cipher.doFinal(cipherText);
return plainText;
}
There are a few issues with your code:
1) In your encrypt-method remove the following line (or shift it behind the decrypt-call).
Arrays.fill(key, (byte) 0); // overwrite the content of key with zeros
Otherwise the key for encryption and decryption differ.
2) In your encrypt-method also pass the associatedData in your decrypt-call i.e. replace
byte[] decrypted = decrypt(cipherMessage, null, key);
with
byte[] decrypted = decrypt(cipherMessage, associatedData, key);
The associatedData passed for encryption and decryption have to match for validity. For the purpose of the associatedData see e.g. https://crypto.stackexchange.com/questions/6711/how-to-use-gcm-mode-and-associated-data-properly
3) In your decrypt-method remove the line
cipher.update(cipherText);
For the purpose of the update-method see e.g. What does cipher.update do in java?
All three issues give rise to an AEADBadTagException.
4) I suspect for testing purposes your encrypt-method returns decrypted.toString() which however only gives you the object's class and hashcode. It would make more sense to return e.g. new String(decrypted).
I am trying to decrypt a String with a known key in Java using standard Cipher API.
The encrypted String comes from a Web Service using the standard CommonCrypto Library which responds with some statistics as encrypted strings at regular intervals.
The specs are AES/CBC/PKCS7Padding with KeySize = 32 Bytes and BlockSize = 16 Bytes, and Encoding UTF-8 (raw) & Base64. I intend to write a Java client that can request these statistics, decrypt them and store them for later analyses.
Question 1. Does the CommonCrypto automatically pad keys with extra chars if the key is short? For instance less than 16 Bytes or 32 Bytes.
Question 2. What encoding measures should I take to ensure an identical encryption/decryption on both ends?
Example Strings and Key
String message = "mQp9sp8ri1E0V1Xfso1d5g==Mrf3wtaqUjASlZmUO+BI8MrWsrZSC0MxxMocswfYnqSn/VKB9luv6E8887eCxpLNNAOMB0YXv6OS7rFDFdlvC53pCHo3cVZiLJFqgWN/eNiC9p4RMxyFCcOzWrwKzT5P8sy55DwE25DNJkvMthSaxK5zcP1OdLgBiZFOSxYRsX4rBk7VP7p5xr2uTGjRL+jmGgB9u3TmeCNCr8NxGLNt6g==";
String userKey = "123456789";
private static String decrypt (String message, String userKey) throws UnsupportedEncodingException,
NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidKeyException,
ShortBufferException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException, NoSuchProviderException {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
if (message.length() >= 48) {
ivFromEncryptedString = message.substring(0, Math.min(message.length(), 24));
messageFromEncryptedString = message.substring(24, message.length());
System.out.println(ivFromEncryptedString);
System.out.println(messageFromEncryptedString);
byte[] data = decodeBase64(messageFromEncryptedString);
byte[] ivData = decodeBase64(ivFromEncryptedString);
paddedKey = padShortKeys(userKey);
byte[] keyBytes = paddedKey.getBytes(CHARSET);
MessageDigest sha = MessageDigest.getInstance("SHA-256"); //key
keyBytes = sha.digest(keyBytes);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivData);
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
byte [] encrypted = new byte[cipher.getOutputSize(data.length)];
int ctLength = cipher.update(data, 0, data.length, encrypted, 0);
ctLength += cipher.doFinal(encrypted, ctLength);
} catch (Exception e) {
System.out.println(e);
} finally {
return encrypted;
}
}
return null;
}
private static String encodeBase64(byte [] in){
return Base64.getEncoder().encodeToString(in);
}
private static byte[] decodeBase64(String str) throws UnsupportedEncodingException {
return DatatypeConverter.parseBase64Binary(str);
}
Also with the current code status I am getting placehoder characters instead of the desired result.
Thanks in advance folks. :)
CommonCrypto is unclear, which implementation are you using? Apple, Apache, Java Class Cipher or another, please supply a link to it.
Never assume an encryption will pad the key or IV, they should always be provided in the exact length, there is no standard for such padding. If they need padding (they shouldn't) do it yourself.
Typically if encrypted data needs to be expressed as a character string Base64 encoding is used.
As James states, for one-shot encryption just use doFinal(ByteBuffer input, ByteBuffer output) which
encrypts or decrypts data in a single-part operation.
Note: A 9 digit key only has about 33-bits of security which is not close to sufficient. Simple using a hash function is insufficient for deriving an encryption key from a password, instead PBKDF2 or Argon2 should be used.
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.
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.