rsa js pidCrypt encryption java decryption - java

I am trying to encrypt some simple message with pidCrypt in browser, then decrypt it with java crypto on the server. But I kept getting the error message:
javax.crypto.IllegalBlockSizeException: Data must not be longer than 128 bytes
Keys:
keys are generated with openssl genrsa (sslery).
The private key then converted to pkcs#8 der format and read into java code
JS code snippet:
/*---* ENCRYPT: RSA 1024 bit ---------*/
// public key
var params = certParser(publickey);
var key = pidCryptUtil.decodeBase64(params.b64);
// new RSA instance
var rsa = new pidCrypt.RSA();
/* RSA encryption
* get the modulus and exponent from certificate (ASN1 parsing)
* pem(Array of Bytes)
*/
// ASN1 parsing
var asn = pidCrypt.ASN1.decode(pidCryptUtil.toByteArray(key));
var tree = asn.toHexTree();
// setting the public key for encryption with retrieved ASN.1 tree
rsa.setPublicKeyFromASN(tree);
/*** encrypt */
var crypted = rsa.encrypt(plaintext);
//var crypted64 = pidCryptUtil.encodeBase64(crypted);
java code snippet:
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, myPrivKey);
byte[] descryptedData = cipher.doFinal(cyphertext.getBytes());

Related

AES Encryption done in OpenSSL is decrypted successfully but fails when encrypted in Java

I have used the below OpenSSL code to do an AES encryption which is decrypting successfully in the Tax website
openssl rand 48 > 48byterandomvalue.bin
hexdump /bare 48byterandomvalue.bin > 48byterandomvalue.txt
set /a counter=0
for /f "tokens=* delims= " %%i in (48byterandomvalue.txt) do (
set /a counter=!counter!+1
set var=%%i
if "!counter!"=="1" (set aes1=%%i)
if "!counter!"=="2" (set aes2=%%i)
if "!counter!"=="3" (set iv=%%i)
)
set result1=%aes1:~0,50%
set result1=%result1: =%
set result2=%aes2:~0,50%
set result2=%result2: =%
set aeskey=%result1%%result2%
set initvector=%iv:~0,50%
set initvector=%initvector: =%
openssl aes-256-cbc -e -in PAYLOAD.zip -out PAYLOAD -K %aeskey% -iv %initvector%
openssl rsautl -encrypt -certin -inkey test_public.cer -in
48byterandomvalue.bin -out 000000.00000.TA.840_Key
But I wanted to do the same this in Java as part of migration, so i used the javax.crypto and java.security libraries but the decryption is failing when I upload the file on the Tax website
//creating the random AES-256 secret key
SecureRandom srandom = new SecureRandom();
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey secretKey = keyGen.generateKey();
byte[] aesKeyb = secretKey.getEncoded();
//creating the initialization vector
byte[] iv = new byte[128/8];
srandom.nextBytes(iv);
IvParameterSpec ivspec = new IvParameterSpec(iv);
byte[] encoded = Files.readAllBytes(Paths.get(filePath));
str = new String(encoded, StandardCharsets.US_ASCII);
//fetching the Public Key from certificate
FileInputStream fin = new FileInputStream("test_public.cer");
CertificateFactory f = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate)f.generateCertificate(fin);
PublicKey pk = certificate.getPublicKey();
//encrypting the AES Key with Public Key
Cipher RSACipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
RSACipher.init(Cipher.ENCRYPT_MODE, pk);
byte[] RSAEncrypted = RSACipher.doFinal(aesKeyb);
FileOutputStream out = new FileOutputStream("000000.00000.TA.840_Key");
out.write(RSAEncrypted);
out.write(iv);
out.close();
Also, the AES key generated in java is different from the one generated via openssl. Can you guys please help.
EDIT 1:
Below is the code for AES Encrpytion used:
Cipher AESCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AESCipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec);
byte[] AESEncrypted = AESCipher.doFinal(str.getBytes("UTF-8"));
String encryptedStr = new String(AESEncrypted);
The data that script and Java-code encrypt with RSA differ:
The script generates a random 48-bytes-sequence and stores it in the file 48byterandomvalue.bin. The first 32 bytes are used as AES key, the last 16 bytes as IV. Key and IV are used to encrypt the file PAYLOAD.zip with AES-256 in CBC-mode and store it as file PAYLOAD. The file 48byterandomvalue.bin is encrypted with RSA and stored as file 000000.00000.TA.840_Key.
In the Java-code, a random 32-bytes AES key and a random 16-bytes IV are generated. Both are used to perform the encryption with AES-256 in CBC-mode. The AES key is encrypted with RSA, concatenated with the unencrypted IV and the result is stored in the file 000000.00000.TA.840_Key.
The content of the file 000000.00000.TA.840_Key is different for script and Java-code. For the Java-code to generate the file 000000.00000.TA.840_Key with the script-logic, the unencrypted AES key must be concatenated with the unencrypted IV and this result must be encrypted with RSA:
...
//byte[] aesKeyb byte-array with random 32-bytes key
//byte[] iv byte-array with random 16-bytes iv
byte[] key_iv = new byte[aesKeyb.length + iv.length];
System.arraycopy(aesKeyb, 0, key_iv, 0, aesKeyb.length);
System.arraycopy(iv, 0, key_iv, aesKeyb.length, iv.length);
...
byte[] RSAEncrypted = RSACipher.doFinal(key_iv);
FileOutputStream out = new FileOutputStream("000000.00000.TA.840_Key");
out.write(RSAEncrypted);
out.close();
...
Note: The IV doesn't have to be secret and therefore doesn't need to be encrypted. The encryption is only necessary to generate the result of the script in the Java-code.
Another problem concerns the conversion of arbitrary binary data into strings. This generally leads to corrupted data if the encoding is unsuitable (e.g. ASCII or UTF8). Therefore
...
byte[] encoded = Files.readAllBytes(Paths.get(filePath));
str = new String(encoded, StandardCharsets.US_ASCII); // Doesn't work: ASCII (7-bit) unsuitable for arbitrary bytes, *
...
byte[] AESEncrypted = AESCipher.doFinal(str.getBytes("UTF-8")); // Doesn't work: UTF-8 unsuitable for arbitrary bytes and additionally different from *
String encryptedStr = new String(AESEncrypted); // Doesn't work: UTF-8 unsuitable for arbitrary bytes
...
should be replaced by
...
byte[] encoded = Files.readAllBytes(Paths.get(filePath));
...
byte[] AESEncrypted = AESCipher.doFinal(encoded);
FileOutputStream out = new FileOutputStream("PAYLOAD");
out.write(AESEncrypted);
out.close();
...
A suitable encoding to store arbitrary data in a string is e.g. Base64, but this isn't necessary in this case, because Base64-encoding isn't used in the script either.
Try these changes. If other issues occur, it would be best to test AES encryption, RSA encryption, and key_iv-generation separately. This makes it easier to isolate bugs.

How to extract the enc encrypted data using AES 256 CBC in JAVA or SCALA?

I need help on AES 256 Decryption using JAVA or SCALA. I am able to extract using openssl.
Base 64 encoded Key: 5UX8IBWruBk1QmMZlZ1ESYmZRiC9w1DsrPpLIP9QF+Q=
Base 64 encoded payload:
U2FsdGVkX19XO99r3f7LgNbPTW8tKexv26mCPUYTMTTiwSSayFvB/QraYJkfzKjEB+tisqzzrU9aZu/tQ5CIVrHHwkUxpyjKEjx3N5q+Ba3weNK/NthpcCsNw5GQxl3NWGoDPe2IFXHMpvpy9xb2mbMnPtwr3m4nF3JzRD6Ft34Q7bHmmTCDkh5kEF9Hx+nbeiLURqLJ1S5YeGq7xhZqalimuQPwT7cr3MPkWPGyZVtNtrKJfIRStoMqP9F2qvm6.
OpenSSL decryption command:
# extract the json from payload
$ openssl enc -d -aes-256-cbc -salt -in data.payload -out output.json -pass file:./key.otkey
input key.otkey mentioned above and data.payload file contain base64 decoded string mentioned above.
I am able to decode using openssl and output :
{"fields":["NSN","store_busn_dt","all_net_sales_amt","all_net_sales_qty","dt_net_sale_qty","brfst_net_sale_qty"],"data":[[38099,"2018-04-01",7675.900000000001,998,752,262]]}
But i can't able to extract using Scala code:
import java.nio.charset.StandardCharsets
import java.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
import javax.crypto.spec.IvParameterSpec
import javax.xml.bind.DatatypeConverter
object AesDecryption extends App {
val key: String = "5UX8IBWruBk1QmMZlZ1ESYmZRiC9w1DsrPpLIP9QF+Q="
val keyOut = Base64.getDecoder.decode(key)
val otKey: SecretKeySpec = new SecretKeySpec(keyOut, "AES")
val payload: String = "U2FsdGVkX19XO99r3f7LgNbPTW8tKexv26mCPUYTMTTiwSSayFvB/QraYJkfzKjEB+tisqzzrU9aZu/tQ5CIVrHHwkUxpyjKEjx3N5q+Ba3weNK/NthpcCsNw5GQxl3NWGoDPe2IFXHMpvpy9xb2mbMnPtwr3m4nF3JzRD6Ft34Q7bHmmTCDkh5kEF9Hx+nbeiLURqLJ1S5YeGq7xhZqalimuQPwT7cr3MPkWPGyZVtNtrKJfIRStoMqP9F2qvm6"
val encryptedData: Array[Char] = payload.toCharArray
//Base64.getDecoder.decode(payload)
val encData: Array[Byte] = DatatypeConverter.parseBase64Binary(new String(encryptedData))
println(new String(encData, StandardCharsets.ISO_8859_1))
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
// Generating IV.// Generating IV.
val ivSize = 16
val iv = new Array[Byte](ivSize)
// Extract IV.// Extract IV.
System.arraycopy(encData, 0, iv, 0, iv.length)
val ivParameterSpec = new IvParameterSpec(iv)
// extract data
cipher.init(Cipher.DECRYPT_MODE, otKey, ivParameterSpec)
val output: Array[Byte] = cipher.doFinal(encData)
// print result
println(new String(output, StandardCharsets.ISO_8859_1))
}
Output :
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:989)
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)
Excepted output above mentioned json. Thanks in advance.!
I think you're using your initialization vector (IV) wrong.
In your code you derive your ivParameterSpec (IV) from encData (in turn derived from payload i.e. encrypted data)
You don't specify it explicitly during encryption and based on openssl docs:
If not explicitly given it will be derived from the password. See key derivation for details.
So, my advice here is to either:
get IV you need to use from key file you use for encryption with openssl
specify IV explicitly during encryption (openssl -iv <IV here>) and reuse it during decryption
UPDATE: Also, check out this answer for code example (includes using proper IV).
Finally i am able to decrypt the data for give encrypted file and -pass key using AES 256 bit CBC decoder.
$ echo -n "U2FsdGVkX19XO99r3f7LgNbPTW8tKexv26mCPUYTMTTiwSSayFvB/QraYJkfzKjE
B+tisqzzrU9aZu/tQ5CIVrHHwkUxpyjKEjx3N5q+Ba3weNK/NthpcCsNw5GQxl3N
WGoDPe2IFXHMpvpy9xb2mbMnPtwr3m4nF3JzRD6Ft34Q7bHmmTCDkh5kEF9Hx+nb
eiLURqLJ1S5YeGq7xhZqalimuQPwT7cr3MPkWPGyZVtNtrKJfIRStoMqP9F2qvm6
" | openssl enc -aes-256-cbc -salt -base64 -pass pass:5UX8IBWruBk1QmMZlZ1ESYmZRiC9w1DsrPpLIP9QF+Q= -d
Scala Code:
def aesDecryption(secret: String, data: String): String = {
val decoded: Array[Byte] = Base64.getDecoder.decode(data)
val salt = decoded.slice(8,16)
val encryptedData = decoded.slice(16, decoded.length)
val (key, iv) = getKeyIv(secret.getBytes(), salt)
val sKeySpec = new SecretKeySpec(key, "AES")
val ivSpec = new IvParameterSpec(iv)
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.DECRYPT_MODE, sKeySpec, ivSpec)
new String(cipher.doFinal(encryptedData)) }
private def getKeyIv(password: Array[Byte], salt: Array[Byte]): (Array[Byte], Array[Byte]) = {
val md5: MessageDigest = MessageDigest.getInstance("MD5")
// first digest
md5.update(password)
md5.update(salt)
val hash1 = md5.digest()
// second digest
md5.reset()
md5.update(hash1)
md5.update(password)
md5.update(salt)
val hash2 = md5.digest()
// third digest
md5.reset()
md5.update(hash2)
md5.update(password)
md5.update(salt)
val hash3 = md5.digest()
val key = hash1.take(16) ++ hash2.take(16)
val iv = hash3.take(16)
(key, iv)
}
aesDecryption(secret, encryptedData) using this functional call, we can able to decrypt the data.
Reference: https://github.com/chmduquesne/minibackup/blob/master/samples/OpensslAES.java

Using public private key combination for symmetric encryption with Java 8

I'm trying to use an asymmetric private and public key combination to generate a symmetric key for encrypting and decrypting some text, but, I'm stuck unable to use the generated key as it is 128bytes in size and this is unacceptable for the AES encryption. I'd like to solve this problem using just the JRE (no external libraries). Do you have a solution?
I've included my example code below, there's a comment indicating the line I get the exception thrown.
(encryptCipher.init(Cipher.ENCRYPT_MODE, tomSecretKeySpec, iv);)
I read about KDF hashing, but Java doesn't seem to have an obvious way of invoking this on my 128byte key. Also, Im not sure this is the right answer since my understanding is that the longer the key, the more secure the encryption (for a given algorithm). Perhaps I need to switch from using AES/CBC/PKCS5Padding, but none of the other algorithms included with the JDK as standard seem to support the 128byte key either.
public void demoSymmetricEncryption() throws NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException {
String keyAlgorithm = "DiffieHellman";
String keyAgreementAlgorithm = "DiffieHellman";
String keySpecAlgorithm = "AES";
String cipherAlgorithm = "AES/CBC/PKCS5Padding";
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance(keyAlgorithm);
keyGenerator.initialize(1024, new SecureRandom());
KeyPair tomKeyPair = keyGenerator.generateKeyPair();
PrivateKey tomPrivateKey = tomKeyPair.getPrivate();
PublicKey tomPublicKey = tomKeyPair.getPublic();
KeyPair steveKeyPair = keyGenerator.generateKeyPair();
PrivateKey stevePrivateKey = steveKeyPair.getPrivate();
PublicKey stevePublicKey = steveKeyPair.getPublic();
int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");
System.out.println("Limited encryption policy files installed : " + (maxKeyLen == 128)); // returns false
KeyAgreement tomKeyAgreement = KeyAgreement.getInstance(keyAgreementAlgorithm);
keyGenerator.initialize(1024, new SecureRandom());
tomKeyAgreement.init(tomPrivateKey);
tomKeyAgreement.doPhase(stevePublicKey, true);
byte[] tomSecret = tomKeyAgreement.generateSecret();
SecretKeySpec tomSecretKeySpec = new SecretKeySpec(tomSecret, keySpecAlgorithm);
KeyAgreement steveKeyAgreement = KeyAgreement.getInstance(keyAgreementAlgorithm);
steveKeyAgreement.init(stevePrivateKey);
steveKeyAgreement.doPhase(tomPublicKey, true);
byte[] steveSecret = steveKeyAgreement.generateSecret();
SecretKeySpec steveSecretKeySpec = new SecretKeySpec(steveSecret, keySpecAlgorithm);
System.out.println("Secret Keys are identical : " + steveSecretKeySpec.equals(tomSecretKeySpec)); // returns true
String initVector = "RandomInitVector";
Cipher encryptCipher = Cipher.getInstance(cipherAlgorithm);
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
// fails because AES key is 128 bytes not 128 bits in length - think I need to use KDF hash to shrink it appropriately.
encryptCipher.init(Cipher.ENCRYPT_MODE, tomSecretKeySpec, iv);
// Attempt to use the cipher
byte[] encryptedData = encryptCipher.doFinal("Hello".getBytes());
Cipher decryptCipher = Cipher.getInstance(cipherAlgorithm);
iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
decryptCipher.init(Cipher.DECRYPT_MODE, steveSecretKeySpec, iv);
byte[] decryptedData = decryptCipher.doFinal(encryptedData);
System.out.println("Decrypted Data : " + new String(decryptedData));
}
The output from the program is as follows:
Limited encryption policy files installed : false
Secret Keys are identical : true
Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 128 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:582)
at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:339)
at javax.crypto.Cipher.implInit(Cipher.java:806)
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 crypto.SymetricEncryptionTest.demoSymmetricEncryption(SymetricEncryptionTest.java:76)
at crypto.SymetricEncryptionTest.main(SymetricEncryptionTest.java:29)
The error is: * Invalid AES key length: 128 bytes*
Valid AES key sizes are 128-bits, 192-bits and 256-bits or in bytes: 16-bytes, 24-bytes and 32-bytes.
Use an AES key size that is valid.
The general method of generation a symmetric key is just to get the bytes from a cryptographic PRNG. For Java see Class SecureRandom.
For key derivation use PBKDF2, see Class SecretKeyFactory and Java Cryptography Architecture Standard Algorithm Name Documentation "PBKDF2WithHmacSHA1" (Constructs secret keys using the Password-Based Key Derivation Function function).
For an example see OWASP Hashing Java but use "PBKDF2WithHmacSHA1" as the algorithm.
The reason the code wasn't working was that I was using incompatible algorithms. The corrections are as follows:
Replace lines:
String keyAlgorithm = "DiffieHellman";
String keyAgreementAlgorithm = "DiffieHellman";
with
String keyAlgorithm = "EC";
String keyAgreementAlgorithm = "ECDH";
int keySize = 128;
and replace lines
keyGenerator.initialize(1024, new SecureRandom());
with
keyGenerator.initialize(keySize, new SecureRandom());
Program now produces output:
Limited encryption policy files installed : false
Secret Keys are identical : true
Decrypted Data : Hello
Technically, you probably also want to Base64 encode the encrypted output and then decode it again prior to the decode as below:
String encryptedData = Base64.encode(encryptCipher.doFinal("Hello".getBytes()));
byte[] decryptedData = decryptCipher.doFinal(Base64.decode(encryptedData));

RSA Java encryption and Node.js decryption is not working

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.

How to encrypt a message at client side using crypto-js library and decrypt it at the Java server

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

Categories