RSA encrypt using JSEncrypt and decrypt using BouncyCastle (Java) - java

This might be a duplicate of this answered question, but I can't seem to get the same results. Hoping for some guidance here.
JSEncrypt (client)
let encrypt = new Encrypt.JSEncrypt();
encrypt.setPublicKey(this.publicKey); // retrieved from server
encrypt.encrypt(password);
BouncyCastle (server) - RSA key generation
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(1024);
KeyPair pair = generator.generateKeyPair();
PublicKey pubKey = pair.getPublic();
PrivateKey privKey = pair.getPrivate();
// returned to client
String publicKeyStr = new String(Base64.encodeBase64(pubKey.getEncoded()));
String privateKeyStr = new String(Base64.encodeBase64(privKey.getEncoded()));
BouncyCastle (server) - Decryption
Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// org.apache.commons.codec.binary.Hex
byte[] cipherText = cipher.doFinal(Hex.decodeHex(encrypted.toCharArray()));
decrypted = new String(cipherText, BaseConstant.ENC_UTF8);
Error
org.apache.commons.codec.DecoderException: Illegal hexadecimal character I at index 0
at org.apache.commons.codec.binary.Hex.toDigit(Hex.java:178)
at org.apache.commons.codec.binary.Hex.decodeHex(Hex.java:89)
One thing I noticed is the length of encrypted text by JSEncrypt, which is 172, while encryption at server side produces 256.
The answered question mentioned to use RSA/None/PKCS1Padding, which I had already set. What else could I be missing?

The error occurs in Hex.decodeHex() method, which means that your data is not a Hex encoded string.
JSEncrypt.encrypt() method returns the encrypted data in Base64 (instead of Hex string). In order to decrypt it, you must decode it from base64 format.
So instead of:
byte[] cipherText = cipher.doFinal(Hex.decodeHex(encrypted.toCharArray()));
Do this:
byte[] cipherText = cipher.doFinal(Base64.decodeBase64(encrypted.toCharArray()));

You can also solve this problem just from the client side. See the code below:
let encrypt = new Encrypt.JSEncrypt();
encrypt.setPublicKey(this.publicKey);
encrypt.getKey().encrypt(password);
Just add getKey() after encrypt. It worked for me! I encrypted my password into Hex string using this approach.

Related

AES Encryption in android and decryption in nodejs

I was trying encryption in android and decryption in nodejs server. I generated an AES 128bit key and encrypt it using AES algorithm and then encrypt this generated key using RSA algorithm. Then send both to the server. But while decrypting on the server side, I think the RSA decryption works fine but have a problem in AES decryption.
I'm not getting the string in server side that I encrypted on the client side.
This is the code for the encryption on android side:
String encryptedSecretKey;
String cipherTextString;
// 1. generate secret key using AES
KeyGenerator keyGenerator = null;
keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
// 2. get string which needs to be encrypted
String text = "This is the message to be encrypted";
// 3. encrypt string using secret key
byte[] raw = secretKey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
cipherTextString = Base64.encodeToString(cipher.doFinal(text.getBytes(Charset.forName("UTF-8"))), Base64.DEFAULT);
// 4. get public key
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(Base64.decode(publicKeyString, Base64.DEFAULT));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(publicSpec);
// 5. encrypt secret key using public key
Cipher cipher2 = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
cipher2.init(Cipher.ENCRYPT_MODE, publicKey);
encryptedSecretKey = Base64.encodeToString(cipher2.doFinal(secretKey.getEncoded()), Base64.DEFAULT);
Then send this to the server side.
The code for server side is given below:
var encryptedMessage = req.body.cipherText;
var encryptedAesKey = req.body.secretKey;
//printing those values
console.log("\nEncryptedMessage: \n" + encryptedMessage);
console.log("\nEncrypted key: \n" + encryptedAesKey);
var privateKey = fs.readFileSync('././Keys/privkey_server.pem', "utf8");
var bufferForAesKey = new Buffer(encryptedAesKey, "base64");
var obj = {
key: privateKey
// , padding: constants.RSA_PKCS1_PADDING
// , padding: constants.RSA/ECB/OAEPWithSHA-1
};
var decryptedAes = crypto.privateDecrypt(obj, bufferForAesKey);
console.log("Decrypted AES: " + decryptedAes);
var decryptedAesKeyString = decryptedAes.toString("base64");
console.log("Decrypted AES Key: " + decryptedAesKeyString);
var bufferForAES = new Buffer(decryptedAes, "base64");
//decrypting using AES
var bufferForEncryptedMsg = new Buffer(encryptedMessage, "base64");
var decipher = crypto.createDecipher('aes-128-cbc',bufferForAES);
decipher.setAutoPadding(false);
var dec = decipher.update(bufferForEncryptedMsg,"base64", "utf8");
dec += decipher.final("utf8");
console.log(dec);
Here the final result 'dec' is not giving the correct result but the intermediate results are same in client and server. That means, RSA works fine but have problem in AES.
The output is given below:
EncryptedMessage:
SfosHg+cTrQXYUdF0FuqCJMHgfcP13ckp2L0B9QqOcl8UtWnnl8fLi5lxgR2SKOj
Encrypted key:
C/pa52PZda3xShrPXkHZx8OL6sW4JBEhG/ggNAoHhSVXIGt+iDq/B1ByG5yStBGF3GFJRQT0aGsG
+bZJydP7j9gTivmt99H/bxiZan4CHZnqfGKG1dJCI7ILAYZMCw7JhIcRC8qHMM4LMdF+rxRhENDe
alUfnsLWpcrX9J6fKejJ7EWnWQ1VadKqCDmrJ5xw0lBbsOpwN/vY09+VhF4WkOz8Y3cQGk+FVdz5
tr4L9/jgXlTZdOC2KVBLSH+9pvtHwMWFKKoDSAzvkil4htBjbWTqlBuEINC4I/J/4P3RX2riT5Pv
xHQi/Dv7vdBlo9AEdvWe3Ek8oBleIpmIQHXwQWknPOYghhBAVmACG/tbEQcAtbcmRLruT/XzjPJt
HNBt2HeG9JHYKNoHC3kOuJdnlGe8mv8k0Nzwj04RhEGKSmPIiu/oDgYwS0l96KIlS2ELqBlS5O0L
AJ+RBG7m0WwC9dfrufsuwu0+SPUmg5/ElXRmA3T81lXtQqQbGg8G6r/bAVFGduy4a49s/VWoylx+
/sI079IwyY0IOfwQTVGZRyDC5O1ZBjoYv2+TRo3bjG8GXNQoybkmWkhgotcqVD9mXO67D2NBsFPT
EJnw+1ApSqR7ggIAF+qsMxejFKBICBL/4J8FP4+obA07J1pWiciTRKX+G130qzIBKM08Zdaf/50=
Decrypted AES: %Kp[ϪS�/�W l��9ӊ˽��~��
B�A�
Decrypted AES Key: JUtwW8+qU6Mv/FcgbMbkOdOKy72pun4B490KQrRB4QQ=
T�Ϝ��u��q�
���w�p���u`�̗r[`H0[tW��=��~i-�W
Here the Decrypted AES key is same as the key that we generate in android. But the final output is not giving the desired result. Is there any error in my code??
Neardupe Decrypting strings from node.js in Java? which is the same thing in the opposite direction.
[In Java] I generated an AES 128bit key and encrypt [with] it using AES algorithm and then encrypt this generated key using RSA algorithm.
No you didn't. Your Java code instantiates a KeyGenerator for AES-128, but doesn't use it to generate any key. The key you actually used (and as you say the server correctly decrypted from RSA-OAEP) is 32 bytes, corresponding to AES-256.
But your main problem is that createDecipher takes a password NOT the key. Per the doc
crypto.createDecipher(algorithm, password[, options])
The implementation of crypto.createDecipher() derives keys using the OpenSSL function EVP_BytesToKey with the digest algorithm set to MD5, one iteration, and no salt.
You passed what is actually a key as a password; this results in nodejs using a key that is completely different from the one used in Java and thus getting completely wrong results. You should instead use createDecipheriv which does take the key, and IV (Initialization Vector).
And that is your other problem. To decrypt you must use the same IV as encrypt did, normally by including the IV with the ciphertext sent from the sender to receiver, but you don't. As a result the following (simplified) code cannot decrypt the first 16 bytes of your data, but does the rest.
const crypto = require('crypto');
msg = Buffer.from('SfosHg+cTrQXYUdF0FuqCJMHgfcP13ckp2L0B9QqOcl8UtWnnl8fLi5lxgR2SKOj','base64');
aeskey = Buffer.from('JUtwW8+qU6Mv/FcgbMbkOdOKy72pun4B490KQrRB4QQ=','base64');
dec = crypto.createDecipheriv('aes-256-cbc',aeskey,Buffer.alloc(16)/*this should be the IV*/);
console.log(dec.update(msg,'','latin1')+dec.final('latin1'));
// I used latin1 instead of utf8 because the garbaged first block
// isn't valid UTF-8, and the rest is ASCII which works as either.
->
Y;øï«*M2WÚâeage to be encrypted
// some garbaged chars are control chars and Stack (or browser?)
// may not display them but there really are 16 in total
As an aside, the statement in the doc that 'Initialization vectors [must] be unpredictable and unique ... [but not secret]' is correct for CBC mode, but not some other modes supported by OpenSSL (thus nodejs) and Java. However, that's not a programming Q and thus offtopic here; it belongs on crypto.SX or possibly security.SX where it has already been answered many times.

AES-256-CBC encrypted with PHP and decrypt in Java

I am in a situation where a JSON is encrypted in PHP's openssl_encrypt and needs to be decrypted in JAVA.
$encrypted = "...ENCRYPTED DATA...";
$secretFile = "/path/to/secret/saved/in/text_file";
$secret = base64_decode(file_get_contents($secretFile));
var_dump(strlen($secret)); // prints : int(370)
$iv = substr($encrypted, 0, 16);
$data = substr($encrypted, 16);
$decrypted = openssl_decrypt($data, "aes-256-cbc", $secret, null, $iv);
This $decrypted has correct data which is now decrypted.
Now, the problem is when I try to do same things in Java it doesn't work :(
String path = "/path/to/secret/saved/in/text";
String payload = "...ENCRYPTED DATA...";
StringBuilder output = new StringBuilder();
String iv = payload.substring(0, 16);
byte[] secret = Base64.getDecoder().decode(Files.readAllBytes(Paths.get(path)));
String data = payload.substring(16);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(secret, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(), 0, cipher.getBlockSize());
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); // This line throws exception :
cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
Here it is:
Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 370 bytes
at com.sun.crypto.provider.AESCrypt.init(AESCrypt.java:87)
at com.sun.crypto.provider.CipherBlockChaining.init(CipherBlockChaining.java:91)
at com.sun.crypto.provider.CipherCore.init(CipherCore.java:591)
at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:346)
at javax.crypto.Cipher.init(Cipher.java:1394)
at javax.crypto.Cipher.init(Cipher.java:1327)
at com.sample.App.main(App.java:70)
I have already visited similar question like
AES-256 CBC encrypt in php and decrypt in Java or vice-versa
openssl_encrypt 256 CBC raw_data in java
Unable to exchange data encrypted with AES-256 between Java and PHP
and list continues.... but no luck there
btw, this is how encryption is done in PHP
$secretFile = "/path/to/secret/saved/in/text_file";
$secret = base64_decode(file_get_contents($secretFile));
$iv = bin2hex(openssl_random_pseudo_bytes(8));
$enc = openssl_encrypt($plainText, "aes-256-cbc", $secret, false, $iv);
return $iv.$enc;
and yes, I forgot to mention that my JRE is already at UnlimitedJCEPolicy and I can't change PHP code.
I am totally stuck at this point and can't move forward. Please help out.
EDIT#1
byte[] payload = ....;
byte[] iv = ....;
byte[] secret = ....; // Now 370 bits
byte[] data = Base64.getDecoder().decode(payload);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(Arrays.copyOfRange(secret, 0, 32), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv, 0, cipher.getBlockSize());
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] output = cipher.doFinal(data);
System.out.println(new String(output).trim());
Above snippet seems to be working with openssl_encrypt
EDIT#2
I am not sure if this is correct, but following is what I have done and encryption-decryption on both side are working fine.
Encrypt in PHP, Decrypt in JAVA use AES/CBC/NoPadding
Encrypt in JAVA, Decrypt in PHP use AES/CBC/PKCS5Padding
I won't provide a complete solution, but there are a few differences you should take care of
Encoding:
String iv = payload.substring(0, 16);
String data = payload.substring(16);
are you sure the IV and data are the same in Java and PHP (The IV is string?)? If the data are encrypted, they should be treated as a byte array, not string. Just REALLY make sure they are THE SAME (print hex/base64 in php and java)
For the IV you at the end call iv.getBytes(), but the locale encoding may/will corrupt your values. The String should be use only when it's really string (text). Don't use string for binaries.
Simply treat data and iv as byte[]
Key generation according to the openssl
AES key must have length of 256 bit for aes-256-cbc used. The thing is - openssl by default doesn't use the provided secret as a key (I believe it can, but I don't know how it is to be specified in PHP).
see OpenSSL EVP_BytesToKey issue in Java
and here is the EVP_BytesToKey implementation: https://olabini.com/blog/tag/evp_bytestokey/
you should generate a 256 bit key usging the EVP_BytesToKey function (it's a key derivation function used by openssl).
Edit:
Maarten (in the comments) is right. The key parameter is the key. Seems the PHP function is accepting parameter of any length which is misleading. According to some articles (e.g. http://thefsb.tumblr.com/post/110749271235/using-opensslendecrypt-in-php-instead-of) the key is trucated or padded to necessary length (so seems 370 bit key is truncated to length of 256 bits).
According to your example, I wrote fully working code for PHP and Java:
AesCipher class: https://gist.github.com/demisang/716250080d77a7f65e66f4e813e5a636
Notes:
-By default algo is AES-128-CBC.
-By default init vector is 16 bytes.
-Encoded result = base64(initVector + aes crypt).
-Encoded/Decoded results present as itself object, it gets more helpful and get possibility to check error, get error message and get init vector value after encode/decode operations.
PHP:
$secretKey = '26kozQaKwRuNJ24t';
$text = 'Some text'
$encrypted = AesCipher::encrypt($secretKey, $text);
$decrypted = AesCipher::decrypt($secretKey, $encrypted);
$encrypted->hasError(); // TRUE if operation failed, FALSE otherwise
$encrypted->getData(); // Encoded/Decoded result
$encrypted->getInitVector(); // Get used (random if encode) init vector
// $decrypted->* has identical methods
JAVA:
String secretKey = "26kozQaKwRuNJ24t";
String text = "Some text";
AesCipher encrypted = AesCipher.encrypt(secretKey, text);
AesCipher decrypted = AesCipher.decrypt(secretKey, encrypted);
encrypted.hasError(); // TRUE if operation failed, FALSE otherwise
encrypted.getData(); // Encoded/Decoded result
encrypted.getInitVector(); // Get used (random if encode) init vector
// decrypted.* has identical methods

How to encode a text with rsa/ecb/pkcs1 padding mode with a given public key in java?

I want to encode a string with rsa/ecb/pkcs1 padding mode with a given public key (the public key is a string) in java.
I also want to present the results in UTF-8 Format
how to do it?
i have done this code:
String pub = "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4IJZLsjlx+o4RSvafaAcReoNnzrI0UXu7kZyXPe31ql32X9AvhC6QQIUmLkr1Evm0zP/SgVG9YX3DSqBUgPo04iv1I1/wNKwAf1/uH9EiiqdpczefyxxnzJiKUTcx2/4mA4E4QxCIL5JsZb78WoYZrd2kToW/WD01MnSFiCgSyjGdd812GY2EVzfvlv8kYuti3icMUyitEfHhtw8cAWI6/nVrRPNs0e5NsvtZJ0nfrXsfQDR0C7+ivQK+fQabi8oRGsbTZceAvVlqVE669zoIwIFLcB+eYXTxbka4E7veUMpaF9w//HdwVS2y/2jJiI+16qPStQQPIKQ4Cucoif7/UHfIBuVGVJ5MIVyK7NC7TV/lyoXmyo7ZcnVZnI7rZcw5/qZcqaZ0VCrzvHijwTK7100hOOjiarvRa2OJGXHLIeAUlbrHOXEXS6ah2glPhLDEg6Qzp/lKVSISolal7q73qyhF483P9jXn3hefSLA9J1/1LgeajWvuVkxuw+dy2Tlv7oUpNBkX47/TOho5qttr1y9K3hD5Q87RAJPdBtFdDbY8qUPxoiBsTbUWjVoEjJf2YAsLTJIIi2ZISkbD/VdrtZnS73QSJkJReOMNT9XYNGDJvwNIrRcNGFKlJcX6qq+ozGNsDkrt0ObxAD7YCTjAYQVTlbQOaTu5DbGxGDNCoMCAwEAAQ==";
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] keyBytes = Base64.getDecoder().decode(pub.getBytes("UTF-8"));
PKCS1EncodedKeySpec KeySpec = new PKCS1EncodedKeySpec(keyBytes);
RSAPublicKey publicKey = (RSAPublicKey)keyFactory.generatePublic((java.security.spec.KeySpec) KeySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherData = cipher.doFinal(text.getBytes("UTF-8"));
return cipherData;
But it doesnot work..
it is said that Invalid DER: object is not integer
Assuming you're using a valid RSA key, you'll need to:
Convert your public key from a string to an actual public key object
//This code is incorrect. You'll need bouncy castle for PKCS1
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] keyBytes = Base64().getDecoder.decode(publicKey.getBytes()); //assuming base64 encoded key
PKCS1EncodedKeySpec KeySpec = new PKCS1EncodedKeySpec(keyBytes);
RSAPublicKey publicKey = (RSAPublicKey)keyFactory.generatePublic(KeySpec);
Get the bytes of your plain text
Encrypt using your public key
Encode to a readable format.
Check out this answer for steps 1-3: RSA Encrypt/Decrypt in Java. Remember to use the correct algorithm spec, in your case PKCS1
Chances are your cipher text will not use only UTF-8 characters so you'll probably want to use Base 64 encoded text to display your cipher text. Base 64 is able to display all those wonky characters as ascii values.
Simply use: Base64.getEncoder().encodeToString(cipherTextBytes)

base64 encoding js issue

I am using AES for encrypting and decrypting my password. What I am trying to implement is that that I need to store the encrypted password at the client side in the form of a cookie and then when the client logs in again into my website I need to get that encrypted password from the client side and decrypt it to check it against the unencrypted password provided by the client. The problem I am facing is that while encryption I convert byte array of the encrypted password to string using BASE64.encodeString() in java so that it could be passed to the client side. But when I get the same string from the client side, i.e from the cookie and try to decrypt it, it gives me padding error, i.e. javax.crypto.illegalBlockSizeException : Input length must be multiple of 16 when decrypting with padded cipher .
Why is it happening?
Code for encryption :
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivspec);
byte[] plainBytes = Data.getBytes(UNICODE_FORMAT);
byte[] encrypted = cipher.doFinal(plainBytes);
String encryption = Base64.encodeBase64String(encrypted);
return encryption;
Code for decryption :
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivspec);
byte[] decryptval = Base64.decodeBase64(encryptedData);
byte[] decrypted = cipher.doFinal(decryptval);
return new String(decrypted);
Is the error coming because I am passing the encrypted string to the js to be stored in cookie.?? does JS fiddle with the base64encoded string?
I STRONGLY advise against using a cipher to store/transmit passwords.
A Hash function is a much safer idea. The difference between a Cipher and a Hash is that a Cipher is reversible, whilst a Hash is one way (Plaintext -> Hashtext). Storing your users passwords on the server in a)plaintext or b)encrypted is a big no-no in terms of security.
A Hash on the other hand cannot be reversed; (Theoretically at least)
A simple hash can be done just as easily using the MessageDigest class
Getting a Hash can be pretty simple:
Message Digest md = MessageDigest.getInstance("MD5");
md.digest(input.getBytes());
The client side can then hash the plaintext password to send across to the Server. Then the server can compare hashes to authenticate and return a session token to the user which they can use for the rest of the session without having to transmit passwords all around the place.
Try using the following method to convert bytes to string while encryption -
public static String bytesToString(byte[] bytes) {
HexBinaryAdapter adapter = new HexBinaryAdapter();
String s = adapter.marshal(bytes);
return s;
}
So instead of -
String encryption = Base64.encodeBase64String(encrypted);
Use
String encryption = bytesToString(encrypted);
Similarly, during decryption use this method -
public static byte[] hexToBytes(String hexString) {
HexBinaryAdapter adapter = new HexBinaryAdapter();
byte[] bytes = adapter.unmarshal(hexString);
return bytes;
}
That is -
byte[] decryptval = hexToBytes(encryptedData);

Converting string to byte array

I'm using RSA encryption for converting simpletext to encrypted form.
my plain text is : hello
encrypted text : [B#d7eed7
Now, how to convert encrypted text into simple plain text
i'm using following code
KeyPairGenerator keygenerator = KeyPairGenerator.getInstance("RSA");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
keygenerator.initialize(1024, random);
KeyPair keypair = keygenerator.generateKeyPair();
PrivateKey privateKey = keypair.getPrivate();
PublicKey publicKey = keypair.getPublic();
Cipher cipher = Cipher.getInstance("RSA");
String arrayStr = "[b#d7eed7";
byte ciphertext = arrayStr.getBytes();
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] cleartext1 = cipher.doFinal(ciphertext);
System.out.println("the decrypted cleartext is: " + new String(cleartext1));
i'm getting javax.crypto.BadPaddingException: Data must start with zero
need help !!
The problem is that [B#d7eed7 is not the encrypted text. It simply shows the type and the address of the byte array, not its contents.
For more information, see https://stackoverflow.com/a/5500020/367273
I just looked to the "Related" part on the right side of the screen and... Convert Java string to byte array
to convert string to byte array you can use the following:
String source = "0123456789";
byte[] byteArray = source.getBytes("specify encoding alongside endianess");// e.g "UTF-16LE", "UTF-16"..
For more info you can check here, here and here.
Good luck!

Categories

Resources