I have a system that requires a RSA keypair to be generated in javascript, have the public key then stored in a database at the server side (as a string), then the server side which is in Java will encrypt a string with the stored public key and send it to the client side which will decrypt the string with the private key.
I'm using a browsified version of node-rsa on my client browser.
First at the client i generate a keypair and export the keys, storing them as strings
var NodeRSA = require('node-rsa');
var key = new NodeRSA({b: 1024});
key.exportKey("pkcs8-private");
key.exportKey("pkcs8-public-pem");
The exported private key is stored at the client and the public at the server
Next i used java to encrypt a string with the public key received, so i parse the pkcs8 public key into a Java PublicKey object.
String pubKey = "<Retrieved pkcs8 public key>";
pubKey = pubKey.replaceAll("(-+BEGIN PUBLIC KEY-+\\r?\\n|-+END PUBLIC KEY-+\\r?\\n?)", "");
byte[] keyBytes = Base64.decodeBase64(pubKey);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pk = kf.generatePublic(spec);
And encrypt a text with it
byte[] cipherText;
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pk);
cipherText = cipher.doFinal("Hello World!".getBytes());
return Base64.encodeBase64String(cipherText);
Which works nicely and returns me a Base64 encoded encrypted string like this
WTS1J2f4w5icsUOCtulyHDaBmB5lN7D8mnj0QWMDBkUGiPHkM8nHVx9pd0MtbQAQNasQS2X8kisLMYyEMPasFZtDH0zX1e8lNYaW0xMKsg++ge87f+95nl+TmxDy6S1m7Ce/n0wXno+0MbSv8YsJtsUcAleyyfQX2bxqX8u7Gjs=
Then i try to decrypt it the string at the client side
First i reimport the stored keys in node-rsa
var NodeRSA = require('node-rsa');
var key = new NodeRSA();
key.importKey("<exported private key string>","pkcs8-private");
key.importKey("<exported public key string>","pkcs8-public-pem");
Then i try to decrypt the Base64 encoded encrypted string
key.decrypt("<Base64 Encoded Encrypted>", 'utf-8');
This is where the problem happens, javascript throws this error
Uncaught Error: Error during decryption (probably incorrect key). Original error: Error: Error decoding message, the lHash calculated from the label provided and the lHash in the encrypted data do not match.(…)
However i have tested that if i encrypt and decrypt the text just within javascript, it works just fine. This makes me think that it's some difference between the way i encrypted it at java and how it's done at javascript
Could anyone point out the mistake that I've made here please?
Oh i found the solution. It was a difference in the encryption method.
I just had to initialize Cipher with
Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
instead of
Cipher.getInstance("RSA");
to match node-rsa
Instead of change the encryption mode on my backend, I change it on front.
const rsa = new NodeRSA({ b: 2048 });
rsa.setOptions({ encryptionScheme: 'pkcs1' });
rsa.importKey(
'key',
'pkcs8-public',
);
and keep using Cipher.getInstance("RSA"); to encrypt and decrypt on backend.
Related
I have written a socket server. The server-side is Java and the client-side is in Python. The server uses TCP communication and since TCP is not safe I use asymmetric encryption. The server has a private-public key pair and every time a client connects it sends the public key and requests clients public key.
In order to send the PublicKey object over TCP, I convert it to string with this function.
public String publicKeyToString(PublicKey publicKey){
byte[] pKBytes = publicKey.getEncoded(); // Get the public key to bytes
return Base64.getEncoder().encodeToString(pKBytes); // Convert the public key to String
}
On the other side to create a PublicKey object from string I use this function
public PublicKey publicKeyFromString(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] keyBytes = Base64.getDecoder().decode(key);
KeyFactory kf = KeyFactory.getInstance("RSA");
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
return kf.generatePublic(spec);
}
The above code works very well with java clients. Now I would like to do the same thing in python. I create the private-public key with this block of code
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
client_private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
client_public_key = client_private_key.public_key()
The problem now is that I don't know how to extract a string from the public key so I can send it to the server. Also, the string key will be decoded from public String publicKeyToString(PublicKey publicKey) so it should be encoded with base64.
"Specs":
Java 8 with java.security library
Python 3 with cryptography library
Encryption algorithm RSA
If you have a better library for python I am open to suggestions, java is somewhat fixed because by this point to much code is written.
I'm reverse engineering some code which is decrypting data, hoping I'll be able to encrypt it back and obtain the same data it started with, for reasons that would make this question too long and off-topic.
public void Test() throws Exception {
String pk_enc = //...
String hashStr_64 = //...
byte[] hashStr_encrypted = Base64.decode(hashStr_64);
X509EncodedKeySpec e = new X509EncodedKeySpec(Base64.decode(pk_enc));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKey RSApublicKey = (RSAPublicKey) keyFactory.generatePublic(e);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(2, RSApublicKey); // '2' means decrypt
byte[] hashStr_decrypted = cipher.doFinal(hashStr_encrypted);
String hashStr_result = new String(hashStr_decrypted);
// Now in reverse...
Cipher cipher1 = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
// instantiating a new cipher or using the original one makes no difference
cipher1.init(1, RSApublicKey); // '1' means encrypt
byte[] hashStr_encrypted_reverse = cipher1.doFinal(hashStr_decrypted);
String hashStr_64_reverse = Base64.encode(hashStr_encrypted_reverse);
}
All the code before // Now in reverse... cannot be changed, but that doesn't mean it's impossible to convert hashStr_result back to hashStr_64, right?
However, the code I've wrote after, that should do just that, doesn't work.
hashStr_encrypted_reverse is different from hashStr_encrypted. Why is that and how can I fix it?
Another sign that something went wrong in the encryption is what happens if I try to decrypt again...
// Decrypt again
Cipher cipher2 = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher2.init(2, RSApublicKey);
byte[] hashStr_decrypted_again = cipher.doFinal(hashStr_encrypted_reverse);
This throws:
javax.crypto.BadPaddingException
I don't really care, but maybe it could help answer the question.
Terminology will be confusing. There are 4 RSA operations, best described as: signing, verifying, encrypting, decrypting. Mapping these to a lower-level and using only the language of encryption and decryption, these map as follows:
sign-verify pair
signing -> encrypt with private key
verifying -> decrypt with public key
encrypt-decrypt pair
encrypting -> encrypt with public key
decrypting -> decrypt with private key.
As you can see, each pair of operations has the private key on one side and the public key on the other.
As #JamesKPolk said in his comment, this isn't how RSA works. RSA is an asymmetric encryption algorithm: there are two keys, public and private. A symmetric algorithm (e.g., AES) has a single key, which is used for both encryption and decryption, and that key must be kept safe, except to the sending and receiving parties.
Why asymmetric encryption?
You can encrypt with a public key (typically someone else's key that they've shared with you), and they must use their private key to decrypt it. Anyone can have the public key (that's why it's public), but it cannot be used to read the encrypted message. This is where you are having your problem.
Also, you can encrypt a message with a private key (typically your own), and anyone else can use your public key to decrypt it. This is how digital signatures are implemented: for example, you would encrypt a digest of a document, and anyone can verify your signature if they have your public key, but no one else could have signed it.
I'm working on an android application where I'd like the user to be able to encrypt messages using other's public keys. The system would generate a public/private keypair and then messages can be sent to other users secretly.
I'm creating an Encryption class which will handle the encryption/decryption of messages. Unfortunately I'm having some problems.
In this method, I'd like to pass the user's secret (private key) as well as the message they want to encrypt. I'd like the secret to be user-defined (like "MySecretPassword").
public static void lock(String secret, String textToEncrypt) {
try {
//Convert the public key string into a key
byte[] encodedPublicKey = Base64.decode(secret.getBytes("utf-8"),Base64.DEFAULT);
X509EncodedKeySpec spec = new X509EncodedKeySpec(encodedPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publickey = keyFactory.generatePublic(spec); //Crash Here
PrivateKey privateKey = keyFactory.generatePrivate(spec);
//Encrypt Message
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publickey);
byte[] encryptedBytes = cipher.doFinal(textToEncrypt.getBytes());
Log.d(TAG,"Encrypted: "+new String(encryptedBytes));
} catch (Exception e) {
e.printStackTrace();
}
}
The exception is as follows:
java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0c0740b0:ASN.1 encoding routines:ASN1_get_object:TOO_LONG
What am I missing here? Am I missing something obvious or am I misunderstanding how these tools work? I've used this javascript library for public/private key encryption before and am trying to do something similar here. I'd appreciate it if somebody could point me in the right direction :)
A secret is not a public key.
You encrypt with the public key of the recipient. That value is public, which means that anybody can look it up. You need to get the value of the other party's public key and feed it into your code, not send in your own private key. The proper way to do this does not involve any secrets!
Normally one does not directly encrypt a message with RSA, instead they encrypt an AES key (or other symmetric key) with RSA and use the AES key to encrypt the message. If your messages are really short, you could use RSA directly, but it won't work for long messages.
Here are a couple links showing how to implement RSA on Android:
RSA using SpongyCastle
RSA encryption in Android and Java
Background:
the application that I am working on is supposed to work offline. I have an HTML5 page and the data keyed in by the user is encrypted using crypto-js library.
And I want the encrypted message sent to java webserver and then decrypt it at the server side.
What am doing
I am able to encrypt the message using Crypto-js
<code>
var message = "my message text";
var password = "user password";
var encrypted = CryptoJS.AES.encrypt( message ,password );
console.log(encrypted.toString());
// this prints an encrypted text "D0GBMGzxKXU757RKI8hDuQ=="
</code>
What I would like to do is pass the encrypted text "D0GBMGzxKXU757RKI8hDuQ==
" to a java server side code and get the necrypted message decrypted.
I tried many options to decrypt the crypto-js encrypted message at the java server side.
Please find below my code at the server side that is supposed to do the decryption of the encrypted text.
<code>
public static String decrypt(String keyText,String encryptedText)
{
// generate key
Key key = new SecretKeySpec(keyText.getBytes(), "AES");
Cipher chiper = Cipher.getInstance("AES");
chiper.init(Cipher.DECRYPT_MODE, key);
byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedText);
byte[] decValue = chiper.doFinal(decordedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
}
</code>
I call the java method decrypt from below code
<code>
// performs decryption
public static void main(String[] args) throws Exception
{
String decryptedText = CrypterUtil.decrypt("user password","D0GBMGzxKXU757RKI8hDuQ==");
}
</code>
But i get the following exception when i run the java decrypt code
<code>
Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 13 bytes
at com.sun.crypto.provider.AESCipher.engineGetKeySize(AESCipher.java:372)
at javax.crypto.Cipher.passCryptoPermCheck(Cipher.java:1052)
at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1010)
at javax.crypto.Cipher.implInit(Cipher.java:786)
at javax.crypto.Cipher.chooseProvider(Cipher.java:849)
at javax.crypto.Cipher.init(Cipher.java:1213)
at javax.crypto.Cipher.init(Cipher.java:1153)
at au.gov.daff.pems.model.utils.CrypterUtil.decrypt(CrypterUtil.java:34)
at au.gov.daff.pems.model.utils.CrypterUtil.main(CrypterUtil.java:47)
Process exited with exit code 1.
</code>
Am not sure what am I doing wrong ?... What is the best way to encrypt a message using the crypto-js library so that it can be decripted else where using user keyed in password.
Thanks to Artjom B and Isaac Potoczny-Jones for the prompt response and advice. I am giving the complete solution that worked for me below for the benefit of others.
Java code to do the decryption of the cryptojs encrypted message at the Java server side
public static void main(String args[]) throws Exception{
String password = "Secret Passphrase";
String salt = "222f51f42e744981cf7ce4240eeffc3a";
String iv = "2b69947b95f3a4bb422d1475b7dc90ea";
String encrypted = "CQVXTPM2ecOuZk+9Oy7OyGJ1M6d9rW2D/00Bzn9lkkehNra65nRZUkiCgA3qlpzL";
byte[] saltBytes = hexStringToByteArray(salt);
byte[] ivBytes = hexStringToByteArray(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
SecretKeySpec sKey = (SecretKeySpec) generateKeyFromPassword(password, saltBytes);
System.out.println( decrypt( encrypted , sKey ,ivParameterSpec));
}
public static SecretKey generateKeyFromPassword(String password, byte[] saltBytes) throws GeneralSecurityException {
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), saltBytes, 100, 128);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKey secretKey = keyFactory.generateSecret(keySpec);
return new SecretKeySpec(secretKey.getEncoded(), "AES");
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
public static String decrypt(String encryptedData, SecretKeySpec sKey, IvParameterSpec ivParameterSpec) throws Exception {
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.DECRYPT_MODE, sKey, ivParameterSpec);
byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedData);
byte[] decValue = c.doFinal(decordedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
}
The cryptojs javascript code that can do the encryption and decryption at the client side
function generateKey(){
var salt = CryptoJS.lib.WordArray.random(128/8);
var iv = CryptoJS.lib.WordArray.random(128/8);
console.log('salt '+ salt );
console.log('iv '+ iv );
var key128Bits100Iterations = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 128/32, iterations: 100 });
console.log( 'key128Bits100Iterations '+ key128Bits100Iterations);
var encrypted = CryptoJS.AES.encrypt("Message", key128Bits100Iterations, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
}
function decrypt(){
var salt = CryptoJS.enc.Hex.parse("4acfedc7dc72a9003a0dd721d7642bde");
var iv = CryptoJS.enc.Hex.parse("69135769514102d0eded589ff874cacd");
var encrypted = "PU7jfTmkyvD71ZtISKFcUQ==";
var key = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 128/32, iterations: 100 });
console.log( 'key '+ key);
var decrypt = CryptoJS.AES.decrypt(encrypted, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
var ddd = decrypt.toString(CryptoJS.enc.Utf8);
console.log('ddd '+ddd);
}
You have to understand that a password is not a key. A password usually goes through some hashing function to result in a bit string or byte array which is a key. It cannot be printed, so it is represented as hex or base64.
In JavaScript you use a password, but in Java you assume the same password is the key which it isn't. You could determine how CryptoJS hashes the password to arrive at the key and recreate this in Java, but it seems that it is implemented in such a way that a fresh salt is generated every time something is encrypted with a password and there is no way to change the salt.
If you really want to work will password from the user then you need to derive the key yourself. CryptoJS provides PBKDF2 for this, but it also takes a salt. You can generate one for your application and add it to the code. You would generate it this way once:
CryptoJS.lib.WordArray.random(128/8).toString();
To derive the key everytime you would pass the static salt into the password-based key derivation function (here for AES-256)
var key = CryptoJS.PBKDF2(userPassword,
CryptoJS.enc.Hex.parse(salt),
{ keySize: 256/32, iterations: 1000 });
var iv = CryptoJS.lib.WordArray.random(256/8); // random IV
var encrypted = CryptoJS.AES.encrypt("Message", key, { iv: iv });
On the server you need to convert the hex key string into a byte array. You will also need to tweak the scheme on the server from AES to AES/CBC/PKCS5Padding as it is the default in CryptoJS. Note PKCS5 and PKCS7 are the same for AES.
Also note that you will need to pass the IV from client to server and init it as
chiper.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivBytes));
You can of course recreate the key from the password and the salt on the server using a Java implementation of PBKDF or just save the key for a known password and salt. You can play around with the iterations of the PBKDF what is acceptable for your users.
AES and the related algorithms can be used in many different ways, and when mixing languages, it can always be a little tricky to figure out what modes the client is using and match them to the modes of the server.
The first problem with your Java code is that you cannot use the bytes of a string as an AES key. There are lots of examples on the Internet of people doing this, but it's terribly wrong. Just like #artjom-B showed with the CryptoJS code, you need to use a "Password-based key derivation function" and it needs to also be parametrized exactly the same on the client & server.
Also, the client needs to generate salt and send it along with the crypto text; otherwise, the server cannot generate the same key from the given password. I'm not sure exactly how CryptoJS does this here's something reasonable in Java, and you can tweak the parameters as you learn how cryptoJS works:
public static SecretKey generateKeyFromPassword(String password, byte[] salt) throws GeneralSecurityException {
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 1000, 256);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
return new SecretKeySpec(keyBytes, "AES");
}
With AES CBC, you also need to randomly generate an IV and send that along with the crypto text.
So in summary:
Figure out the AES parameters used by CryptoJS. Not sure what they are, but it sounds like: key size (256), padding (pkcs5), mode (CBC), PBE algorithm (PBKDF2), salt (random), iteration count (100)
Configure your server with the same parameters
Use a PBE key generator, along with a non-secret (but random) salt
Use AES CBC with a non-secret (but random) IV
Send the cipher text, the IV, and the salt to the server
Then on the server side, use the salt, iteration count, and the password to generate the AES key
Then base64 decode and decrypt it
An Android client (4.2.1) application sends a public key via a HttpPost request to a PHP (5.6) API. This API encrypts the data with AES compliant RIJNDAEL_128, then encrypts the key for the AES encryption with the client public key with OpenSSL public encryption and RSA_PKCS1_OAEP_PADDING. It sends this data base64 encoded via XML back to the client android application which shall encrypt the data. I've setup a basic PHP test script which tests the whole process, this works as expected.
Currently I'm working on implementing the decryption in the client Android application but already decrypting the AES-key fails. I have other questions besides this current problem (see at the end).
Here is a text graphical synopsis of what is happening:
client -> public key -> API -> data -> AESencrypt(data), RSAencrypt(AES-key) -> base64encode[AES(data)], base64encode[RSA(AES-key)] -> <xml>base64[AES(data)], base64[RSA(AES-key)]</xml> -> client -> base64[AES(data)], base64[RSA(AES-key)] -> base64decode[AES(data)], base64decode[RSA(AES-key)] -> AESdecrypt(data), RSAdecrypt(AES-key) -> data
I'm encrypting the data with MCRYPT_RIJNDAEL_128 which I read is AES compatible (see PHP doc for mycrypt).
Here is the code:
<?php
$randomBytes = openssl_random_pseudo_bytes(32, $safe);
$randomKey = bin2hex($randomBytes);
$randomKeyPacked = pack('H*', $randomKey);
// test with fixed key:
// $randomKeyPacked = "12345678901234567890123456789012";
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$dataCrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $randomKeyPacked, $data, MCRYPT_MODE_CBC, $iv);
The AES-key coming out of this is encoded with openssl_public_encrypt and the padding setting OPENSSL_PKCS1_OAEP_PADDING. Reading the source code (source of PHP OpenSSL implementation) this is equivalent to RSA_PKCS1_OAEP_PADDING described as
EME-OAEP as defined in PKCS #1 v2.0 with SHA-1, MGF1 and an empty encoding parameter.
in the OpenSSL documentation found here. Afterwards I base64_encode the data to be able to transfer it via an XML string to the client. The code looks like this:
openssl_public_encrypt($randomKeyPacked, $cryptionKeyCrypted, $clientPublicKey, OPENSSL_PKCS1_OAEP_PADDING);
$content = array(
'cryptionKeyCryptedBase64' => base64_encode($cryptionKeyCrypted),
'cryptionIVBase64' => base64_encode($iv),
'dataCryptedBase64' => base64_encode($dataCrypted)
);
// $content gets parsed to a valid xml element here
The client Android application gets the return data via HttpPost request via a BasicResponseHandler. This returned XML string is valid and parsed via Simple to respective java objects. In the the class holding the actual content of the transferred data I currently try to decrypt the data. I decrypt the AES-key with the transformation RSA/ECB/OAEPWithSHA-1AndMGF1Padding which due to this site (only I could find) is a valid string and seems to be the equivalent of the padding I used in PHP. I included the way I generated the private key as it is the same way I generate the public key that was send to the PHP API. Here is that class:
public class Content {
#Element
private String cryptionKeyCryptedBase64;
#Element
private String cryptionIVBase64;
#Element
private String dataCryptedBase64;
#SuppressLint("TrulyRandom")
public String getData() {
String dataDecrypted = null;
try {
PRNGFixes.apply(); // fix TrulyRandom
KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
keygen.initialize(2048);
KeyPair keypair = keygen.generateKeyPair();
PrivateKey privateKey = keypair.getPrivate();
byte[] cryptionKeyCrypted = Base64.decode(cryptionKeyCryptedBase64, Base64.DEFAULT);
//byte[] cryptionIV = Base64.decode(cryptionIVBase64, Base64.DEFAULT);
Cipher cipherRSA = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
cipherRSA.init(Cipher.DECRYPT_MODE, privateKey);
byte[] key = cipherRSA.doFinal(cryptionKeyCrypted);
byte[] dataCrytped = Base64.decode(dataCryptedBase64, Base64.DEFAULT);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipherAES = Cipher.getInstance("AES");
cipherAES.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decryptedAESBytes = cipherAES.doFinal(dataCrytped);
dataDecrypted = new String(decryptedAESBytes, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return dataDecrypted;
}
}
Doing this I currently fail at line
byte[] key = cipherRSA.doFinal(cryptionKeyCrypted);
with Bad padding exceptions for nearly all PHP openssl_public_encrypt padding parameter - Android Cipher transformation string combinations I tried. Using the standard PHP padding parameter by omitting the padding parameter in the openssl_public_encrypt which defaults to OPENSSL_PKCS1_PADDING and a Cipher transformation string of just Cipher.getInstance("RSA") I do not get a bad padding exception. But the encrypted key seems not to be valid as AES decryption fails with
java.security.InvalidKeyException: Key length not 128/192/256 bits.
I tried validating this with a fixed key (see code comment in PHP code above) and I don't get the same key back after decrypting it and transforming it to a string. It seems it is just garbled data although it is 256 bits long if I read the Eclipse ADT debugger correctly.
What might be the correct Cipher transformation string to use as an equivalent for PHP's OPENSSL_PKCS1_OAEP_PADDING. Reading this documentation I need the transformation string in the form "algorithm/mode/padding", I guessed that algorithm = RSA but I couldn't find out how to translate what the OpenSSL (above) documentation states about the padding into a valid cipher transformation string. I.e. what is mode for example?
Unfortunately this Android RSA decryption (fails) / server-side encryption (openssl_public_encrypt) accepted answer did not solve my problem.
Anyway might this solve my problem or does my problem originate elsewhere?
How would I further debug this? What is the correct way to transform the base64 decoded, decrypted key into a human readable form so I can compare it with the key used to encrypt?
I tried with:
String keyString = new String(keyBytes, "UTF-8");
But this doesn't give any human readable text back so I assume either the key is wrong or my method of transforming it.
Also decrypting the AES encrypted data in PHP the IV is needed in the decryption function mcrypt_decrypt. As you can see in the code I send it but it seems in Android this is not needed? Why so?
PS: I hope I provided all needed information, I can add further in the comments.
PPS: For completeness here is the Android client code making the HttpPost request:
#SuppressLint("TrulyRandom")
protected String doInBackground(URI... urls) {
try {
System.setProperty("jsse.enableSNIExtension", "false");
HttpClient httpClient = createHttpClient();
HttpPost httpPost = new HttpPost(urls[0]);
PRNGFixes.apply(); // fix TrulyRandom
KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
keygen.initialize(2048);
KeyPair keypair = keygen.generateKeyPair();
PublicKey publickey = keypair.getPublic();
byte[] publicKeyBytes = publickey.getEncoded();
String pubkeystr = "-----BEGIN PUBLIC KEY-----\n"+Base64.encodeToString(publicKeyBytes,
Base64.DEFAULT)+"-----END PUBLIC KEY-----";
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair("publickey", pubkeystr));
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
// Execute HTTP Post Request
HttpResponse response = httpClient.execute(httpPost);
return new BasicResponseHandler().handleResponse(response);
} catch (Exception e) {
Toast toast = Toast.makeText(asyncResult.getContext(),
"unknown exception occured: " + e.getMessage(),
Toast.LENGTH_SHORT);
toast.show();
return "error";
}
}
You are generating one RSA keypair in doInBackground and telling the host to use the public half of that keypair to encrypt the DEK (data encryption key). You are then generating a completely different RSA keypair in getData and attempting to use the private half of that keypair to decrypt the encrypted DEK. The way public-key encryption works is you encrypt with the public half of a keypair and decrypt with the private half of the same keypair; the public and private halves are mathematically related. You need to save and use at least the private half of the keypair (optionally the keypair with both halves) whose public half you send.
Once you've got the DEK correctly, in order to decrypt CBC-mode data, yes you do need to use the same IV for decryption as was used for encryption. Your receiver needs to put it in an IvParameterSpec and pass that on the Cipher.init(direction,key[,params]) call. Alternatively if you can change the PHP, since you are using a new DEK for each message it is safe to use a fixed IV; easiest is to encrypt with '\0'x16 and allow the Java decrypt to default to all-zero.
Additionally you need to set Base64.decode with the parameter Base64.NO_WRAPas PHP will just put out the base64 delimited by \0. And to that you will also need to use the "AES/CBC/ZeroBytePadding" transformation cipher to decrypt the AES data as the PHP function mycrypt_encrypt will pad the data with zeros.
Here is what the getData function will have to look like:
public String getData() {
String dataDecrypted = null;
try {
byte[] cryptionKeyCrypted = Base64.decode(cryptionKeyCryptedBase64, Base64.NO_WRAP);
byte[] cryptionIV = Base64.decode(cryptionIVBase64, Base64.NO_WRAP);
Cipher cipherRSA = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
// get private key from the pair used to grab the public key to send to the api
cipherRSA.init(Cipher.DECRYPT_MODE, rsaKeyPair.getPrivateKey());
byte[] key = cipherRSA.doFinal(cryptionKeyCrypted);
byte[] dataCrytped = Base64.decode(dataCryptedBase64, Base64.NO_WRAP);
IvParameterSpec ivSpec = new IvParameterSpec(cryptionIV);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipherAES = Cipher.getInstance("AES/CBC/ZeroBytePadding");
cipherAES.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec);
byte[] decryptedAESBytes = cipherAES.doFinal(dataCrytped);
dataDecrypted = new String(decryptedAESBytes, "UTF-8");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return dataDecrypted;
}