In my program, I'm trying to encrypt some plaintext with RSA using the following code:
static String RSAEncrypt(String pubkey, String plain){
return encrypt(pubkey,plain,"RSA");
}
static String encrypt(String stringKey, String plain, String algo){
String enc="failed";
try{
byte[] byteKey = new BASE64Decoder().decodeBuffer(stringKey);
Key key = new SecretKeySpec(byteKey,algo);
byte[] data = plain.getBytes();
Cipher c = Cipher.getInstance(algo);
c.init(Cipher.ENCRYPT_MODE, key);
byte[] encVal = c.doFinal(data);
enc = new BASE64Encoder().encode(encVal);
}catch(Exception e){e.printStackTrace();}
return enc;
}
However, when it runs, it shows the following error:
java.security.InvalidKeyException: No installed provider supports this key: javax.crypto.spec.SecretKeySpec
at javax.crypto.Cipher.chooseProvider(Cipher.java:877)
at javax.crypto.Cipher.init(Cipher.java:1212)
at javax.crypto.Cipher.init(Cipher.java:1152)
at Crypto.encrypt(Crypto.java:37)
at Crypto.RSAEncrypt(Crypto.java:62)
I have tried changing it to RSA/None/PKCS1Padding and RSA/ECB/PKCS1Padding to no avail.. I know that installing BouncyCastle may help but I'd like to avoid it (I'd like to avoid more dependencies and I've been having some issues installing it anyway). Thanks in advance for any ideas.
As was said in the comments, SecretKeySpec is for symmetric algorithms only. You mentioned that you got your byte[] containing the key by calling getEncoded.
There are two possibilities and two resulting formats:
Encoding of an RSA PrivateKey
Calling PrivateKey#getEncoded on an instance of an RSA private key will result in a PKCS#8 encoding for private keys, and it can be restored with the help of PKCS8EncodedKeySpec.
Encoding of an RSA PublicKey
PublicKey#getEncoded on an RSA public key results in the generic X.509 public key encoding, and can be restored with X509EncodedKeySpec.
Example Usage
byte[] data = "test".getBytes("UTF8");
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(512);
KeyPair keyPair = kpg.genKeyPair();
byte[] pk = keyPair.getPublic().getEncoded();
X509EncodedKeySpec spec = new X509EncodedKeySpec(pk);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(spec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] encrypted = cipher.doFinal(data);
byte[] priv = keyPair.getPrivate().getEncoded();
PKCS8EncodedKeySpec spec2 = new PKCS8EncodedKeySpec(priv);
PrivateKey privKey = keyFactory.generatePrivate(spec2);
cipher.init(Cipher.DECRYPT_MODE, privKey);
byte[] plain = cipher.doFinal(encrypted);
System.out.println(new String(plain, "UTF8")); //=> "test"
Related
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));
}
I encrypt and decrypt with RSA 2048 keys. But for additional security I need to use passphrase for RSAPrivateKey with a AES 128 method.
I can generate this keys, but I don't know how to use them in JAVA.
In my code I initialize private (witout passphrase) key (public is the same):
String PRIVATE_KEY_FILE_RSA = "src/pri.der";
File privKeyFile = new File(PRIVATE_KEY_FILE_RSA);
// read private key DER file
DataInputStream dis = new DataInputStream(new FileInputStream(privKeyFile));
byte[] privKeyBytes = new byte[(int) privKeyFile.length()];
dis.read(privKeyBytes);
dis.close();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// decode private key
PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privKeyBytes);
RSAPrivateKey privKey =(RSAPrivatKey) keyFactory.generatePublic(pubSpec);
And use:
Algorithm algorithm = Algorithm.RSA256(pubKey, privKey);
...
I need for any information or examples how to enter there passphrase.
This solution is better for me.
UPDATE
If the link is not working, look for not-yet-commons-ssl.
I used for not-yet-commons-ssl-0.3.11.jar.
For example:
//path to private key file
String PRIVATE_KEY_FILE_RSA = "C:\\Users\\Adey";
FileInputStream in = new FileInputStream(PRIVATE_KEY_FILE_RSA);
// passphrase - the key to decode private key
String passphrase = "somepass";
PKCS8Key pkcs8 = new PKCS8Key(in, passphrase.toCharArray());
byte[] decrypted = pkcs8.getDecryptedBytes();
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decrypted);
RSAPrivateKey privKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(spec);
You have to use PBE (Password Based Encrytion) to protect private key with a password. After some research that I made for you the following code sample may help you:
//Generating keypairs
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
// extract the encoded private key, this is an unencrypted PKCS#8 private key
byte[] encodedprivkey = keyPair.getPrivate().getEncoded();
// We must use a PasswordBasedEncryption algorithm in order to encrypt the private key, you may use any common algorithm supported by openssl, you can check them in the openssl documentation http://www.openssl.org/docs/apps/pkcs8.html
String MYPBEALG = "PBEWithSHA1AndDESede";
String password = "pleaseChangeit!";
int count = 20;// hash iteration count
SecureRandom random = new SecureRandom();
byte[] salt = new byte[8];
random.nextBytes(salt);
// Create PBE parameter set
PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count);
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory keyFac = SecretKeyFactory.getInstance(MYPBEALG);
SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
Cipher pbeCipher = Cipher.getInstance(MYPBEALG);
// Initialize PBE Cipher with key and parameters
pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
// Encrypt the encoded Private Key with the PBE key
byte[] ciphertext = pbeCipher.doFinal(encodedprivkey);
// Now construct PKCS #8 EncryptedPrivateKeyInfo object
AlgorithmParameters algparms = AlgorithmParameters.getInstance(MYPBEALG);
algparms.init(pbeParamSpec);
EncryptedPrivateKeyInfo encinfo = new EncryptedPrivateKeyInfo(algparms, ciphertext);
// and here we have it! a DER encoded PKCS#8 encrypted key!
byte[] encryptedPkcs8 = encinfo.getEncoded();
See also Java Cryptography Architecture (JCA) Reference Guide
My knowledge of encryption is very basic, so apologies for any ignorance on my part.
Within an Android app I am currently trying to mimic the execution of this command using the SpongyCastle library and standard java.security libs:
echo 'test' | openssl rsautl -encrypt -pubin -inkey test.pub | base64 > encrypted_file
It should be noted that the reading/writing to and from files in the command are not going to be implemented and I have my public key (i.e. test.pub) as a Base64 encoded string base64key in my code.
I have attempted the following but am certain it does not work:
static {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
}
//...more code here
byte[] pka = Base64.decode(base64key);
X509EncodedKeySpec x = new X509EncodedKeySpec(pka);
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(x);
byte[] testToByte = "test".getBytes("UTF8");
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherText = cipher.doFinal(testToByte);
String encrypted = Base64.encode((new String(cipherText, "UTF8").toString().getBytes()))
I know this is way off, but am not sure where to turn. Any help would be appreciated.
This was eventually solved using the following methods:
private void stripHeaders(){
public_key = public_key.replace("-----BEGIN PUBLIC KEY-----", "");
public_key = public_key.replace("-----END PUBLIC KEY-----", "");
}
public byte[] encryptWithPublicKey(String encrypt) throws Exception {
byte[] message = encrypt.getBytes("UTF-8");
stripHeaders();
PublicKey apiPublicKey= getRSAPublicKeyFromString();
Cipher rsaCipher = Cipher.getInstance("RSA/None/PKCS1Padding", "SC");
rsaCipher.init(Cipher.ENCRYPT_MODE, apiPublicKey);
return rsaCipher.doFinal(message);
}
private PublicKey getRSAPublicKeyFromString() throws Exception{
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "SC");
byte[] publicKeyBytes = Base64.decode(public_key.getBytes("UTF-8"), Base64.DEFAULT);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKeyBytes);
return keyFactory.generatePublic(x509KeySpec);
}
I have a java program that encrypts file content with a random-generated key.
That key is encrpyted with RSA and saved into a text file.
Now, I have a java program that given the file and the keystore where the RSA key is stored, needs to first decrypt the encryped key and then with the key to decrypt the file.
Here's what I have so far:
// Fetch the other public key and decrypt the file encryption key
java.security.cert.Certificate cert2 = keystore.getCertificate("keyForSeckeyDecrypt");
Key secKeyPublicKey = cert2.getPublicKey();
Cipher cipher = Cipher.getInstance(secKeyPublicKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, secKeyPublicKey);
keyFileFis = new FileInputStream(keyFile);
byte[] encryptedKey = new byte[128];
keyFileFis.read(encryptedKey);
byte[] realFileKey = cipher.doFinal(encryptedKey, 0, encryptedKey.length);
Key realKey = // THE PROBLEM!!!;
keyFileFis.close();
In short, I get the encrypted key from the key text file and decrypt it, now I have the decrypted key as a byte array, how would I make it a Key variable again?
I've generated the key this way:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
Key secKey = keyGen.generateKey();
cipher.init(Cipher.ENCRYPT_MODE, secKey);
And encrypted it this way:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair kp = kpg.genKeyPair();
PrivateKey privateKey = kp.getPrivate();
Cipher keyCipher = Cipher.getInstance("RSA");
keyCipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] encryptedKey = keyCipher.doFinal(secKey.getEncoded());
FileOutputStream keyStream = new FileOutputStream("key.txt");
keyStream.write(encryptedKey);
keyStream.close();
I haven't tried it but from clicking through the API SecretKeySpec could be what you are looking for.
SecretKeySpec(byte[] key, String algorithm)
It can be used to construct a SecretKey from a byte array, without having to go through a (provider-based) SecretKeyFactory.
This class is only useful for raw secret keys that can be represented as a byte array and have no key parameters associated with them, e.g., DES or Triple DES keys.
If I get it right, this should work..
Key privateKey = keyStore.getKey("youralias", "password".toCharArray());
PublicKey publicKey = keyStore.getCertificate("youralias").getPublicKey();
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
Key secKey = keyGen.generateKey();
Cipher keyCipher = Cipher.getInstance("RSA");
keyCipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] encryptedKey = keyCipher.doFinal(secKey.getEncoded());
// Write & Read to/from file!
Cipher decryptCipher = Cipher.getInstance("RSA");
decryptCipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] decryptedKey = decryptCipher.doFinal(encryptedKey);
boolean equals = Arrays.equals(secKey.getEncoded(), new SecretKeySpec(decryptedKey, "AES").getEncoded());
System.out.println(equals?"Successfull!":"Failed!");
Basically, I'm trying to have an encrypted data flow between Java client and a c# server.
Before jumping into the deep water of having a multi platform encryption working, I'm trying to make a simple encryption app but I'm stuck at the very beginning.
I have the following simple code:
String text = "hello";
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.genKeyPair();
Key publicKey = kp.getPublic();
Key privateKey = kp.getPrivate();
Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherData = cipher.doFinal(text.getBytes());
cipher = Cipher.getInstance("RSA/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] textData = cipher.doFinal(text.getBytes());
String decrypted = new String(textData);
System.out.println(decrypted);
No exception is thrown but I don't get the original "hello" text after the decryption.
Any ideas?
10x a lot
This looks fishy:
byte[] textData = cipher.doFinal(text.getBytes());
Did you mean:
byte[] textData = cipher.doFinal(cipherData);