Communicate with encrypted parameters between Java and Delphi.
If Delphi encrypts them, Java needs to decrypt them.
But if I operate as below, Java will have an error...
How should I change the Java sauce?
[ Delphi source (Encrypt) ]
var
Data: string;
begin
Data := Memo1.Text;
DCP_rijndael1.InitStr(Edt_Password.Text, TDCP_sha256);
DCP_rijndael1.EncryptCBC(Data[1],Data[1],Length(Data));
DCP_rijndael1.Burn;
Memo2.Text := Base64EncodeStr(Data);
end;
[ Delphi source (Decrypt) ]
var
Data: string;
begin
Data := Base64DecodeStr(Memo2.Text);
DCP_rijndael1.InitStr(Edt_Password.Text, TDCP_sha256);
DCP_rijndael1.DecryptCBC(Data[1],Data[1],Length(Data));
DCP_rijndael1.Burn;
Memo3.Text := Data;
end;
[Java source]
public static String Decrypt(String text, String key) throws Exception
{
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] keyBytes= new byte[16];
byte[] b= key.getBytes("UTF-8");
int len= b.length;
if (len > keyBytes.length) len = keyBytes.length;
System.arraycopy(b, 0, keyBytes, 0, len);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(keyBytes);
cipher.init(Cipher.DECRYPT_MODE,keySpec,ivSpec);
sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
byte [] results = cipher.doFinal(decoder.decodeBuffer(text));
return new String(results,"UTF-8");
}
The Delphi code defines that the password should be hashed using SHA-256. (TDCP_sha256).
I don't know how the DCP encryption is implemented but I would assume that the SHA256 hash of the password is used as AES key, hence AES256 is used here.
The Java code however does not make any use of SHA-256 to hash the key as it is called here.
Furthermore on Delphi side you use CBC mode but you don't specify the IV via SetIV method. On Java side you specify the IV using the key which is heavily insecure).
The IV has to be initialized by secure random data. Never use something different!
The common way is to generate a random IV before encryption and then prepend it the encrypted data and read it back before the decryption takes place.
Related
I have a java code working perfectly
public static String encrypt(String message, String sercretKey)
{
String base64EncryptedString = "";
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digestOfPassword = md.digest(sercretKey.getBytes("utf-8"));
byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
byte[] iv = Arrays.copyOf(digestOfPassword, 16);
SecretKey key = new SecretKeySpec(keyBytes, "AES");
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, key, ivParameterSpec);
byte[] plainTextBytes = message.getBytes("utf-8");
byte[] buf = cipher.doFinal(plainTextBytes);
byte[] base64Bytes = Base64.getEncoder().encode(buf);
base64EncryptedString = new String(base64Bytes);
return base64EncryptedString;
}
I have tried using below code to recreate this above code in PHP
function encryptTest($sSecretKey,$sValue)
{
$key = hash('sha256', $sSecretKey,false);
$key = utf8_encode($key);
$key = substr($key, 0, 24);
$iv = substr($key, 0, 16);
$data = $sValue;
$outEnc = openssl_encrypt($data, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($outEnc);
}
But showing different results. What I have missed.
(Same types of questions are available in StackOverflow, but pointing my issues)
There are the following issues:
In the PHP code, the key is currently returned hex encoded. Instead, it must be returned as bytes string. To do this, the third parameter in hash() must be switched from false to true.
In the Java code a 192 bits key is used, i.e. AES-192. Accordingly, in the PHP code "AES-192-CBC" must be applied (and not "AES-256-CBC").
The utf8_encode() call in the PHP code is to be removed, as this corrupts the key.
With these changes, both codes provide the same ciphertext.
Security:
Using SHA256 as key derivation is insecure. Instead apply a dedicated algorithm like Argon2 or PBKDF2. Also, using the key (or a part of it) as IV is insecure as it results in the reuse of key/IV pairs. Instead, a randomly generated IV should be applied for each encryption.
I have used AES encryption in java as below and trying to decrypt in javascript
JAVA :
byte[] input = "data".getBytes();
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] thedigest = md.digest("ENCRYPTION_KEY".getBytes("UTF-8"));
SecretKeySpec skc = new SecretKeySpec(Arrays.copyOf(thedigest, 16), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skc);
byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
ctLength += cipher.doFinal(cipherText, ctLength);
String encryptData = Base64.getUrlEncoder().encodeToString(cipherText);
// Base64.encodeToString(cipherText, Base64.DEFAULT);
System.out.println("encryptString:.......... "+encryptData);
NODEJS:
var crypto = require('crypto');
let keyStr = "ENCRYPTION_KEY";
var text = 'infodba';
var hashPwd =crypto.createHash('sha256').update(keyStr,'utf8').digest();
var key=[] ;
for (i = 0; i < 16; i++) {
key[i] = hashPwd[i];
}
var keyBuffer = new Buffer(hashPwd);
var decipherIV = crypto.createDecipheriv('aes-256-ecb', keyBuffer,'');
var cipherTxtBuffer = new Buffer('encryptedData','hex');
var output = decipherIV.update(cipherTxtBuffer);
output= output+decipherIV.final();
console.log("Dec Output "+output);
While running the node JS code getting error
internal/crypto/cipher.js:174
const ret = this[kHandle].final(); ^
Error: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
at Decipheriv.final (internal/crypto/cipher.js:174:29)
at deciphers (D:\TestJS\index.js:32:30)
The Java code uses only the first 16 bytes of the SHA256 hash as key, so the hash has to be truncated accordingly:
var keyStr = 'ENCRYPTION_KEY';
var hashPwd = crypto.createHash('sha256').update(keyStr, 'utf8').digest();
var keyBuffer = Buffer.from(hashPwd.subarray(0, 16));
A 16 bytes key corresponds to AES-128, so 'aes-128-ecb' has to be used for decryption (and not 'aes-256-ecb'). Furthermore, the ciphertext is Base64url encoded, so it has to be Base64url decoded (and not hex decoded):
var encryptedData = 'dkcvstQcGMQUJ1EJbHs3eY6j_0DjWqYTDGmedmUZwWs=';
var decipherIV = crypto.createDecipheriv('aes-128-ecb', keyBuffer,'');
var output = Buffer.concat([decipherIV.update(encryptedData, 'base64url'), decipherIV.final()]);
output is a buffer and may have to be UTF-8 decoded:
console.log(output.toString('utf8')); // ยง3.1: The fee is $3,000.
With these changes, the NodeJS code decrypts the ciphertext created with the Java code.
Security:
The ECB mode is insecure and should not be used. Nowadays authenticated encryption is usually applied (such as GCM), or at least a mode with an IV (such as CBC).
Using a (fast) digest like SHA256 as key derivation is insecure. More secure is a dedicated key derivation function like Argon2 or PBKDF2.
In the Java code, the Cipher object is instantiated with the algorithm ("AES") alone. This is not robust because then the provider-dependent default values are applied for mode and padding. For SunJCE, these are ECB and PKCS#5 padding, to which the NodeJS code is tailored. For other providers this may differ, so that the NodeJS code would then no longer be compatible. Therefore, it should be fully specified, e.g. "AES/ECB/PKCS5Padding".
Similarly, when encoding the plaintext with getBytes(), the encoding should be specified, otherwise the platform-dependent default encoding is used.
Hello guys,
I'm working on a project that need to encrypt file content in Android and upload it to a server and then decrypt it with NodeJS service.
I have looked over a few projects that based on AES in Java and NodeJS and found something that worked on Java and not on NodeJS.
Here is the code in Java:
public static String encrypt(String encodeKey, String inputFile) throws Exception {
byte[] input = getStringFromFile(inputFile).toString().getBytes("utf-8");
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(encodeKey.getBytes("UTF-8"));
SecretKeySpec skc = new SecretKeySpec(thedigest, "AES/ECB/PKCS5Padding");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skc);
byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
String data = Base64.encodeToString(cipherText, Base64.DEFAULT);
Log.d("Crypto | Length", String.valueOf(ctLength));
Log.d("Crypto | Keypass", encodeKey);
return data;
}
And the code in NodeJS:
var
decipher = crypto.createDecipher('aes-128-ecb', encryption_key),
chunks = [];
chunks.push( decipher.update( new Buffer(fullBuffer, "base64").toString("binary")) );
chunks.push( decipher.final('binary') );
var decrypted = chunks.join("");
As you can see, for each of the files i'm generating a new key-hash for salt (it's the same for decrypt and encrypt);
My problem is that when i'm trying to decrypt it i'm getting this error from NodeJS:
digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
You aren't using the Java Cipher class correctly. You need to call the doFinal method. Since you aren't doing piecemeal encryption anyway you can dispense with the pre-sizing of the ciphertext, eliminate the call to update, and simply call
byte [] cipherText = cipher.doFinal(input);
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
I have a PHP encryption function. I need a java counter part for the same. Due to my limited knowledge in PHP I am unable to understand. Some one knows both the language, kindly help.
PHP code:
function encrypt($decrypted, $keyvalue) {
// Build a 256-bit $key which is a SHA256 hash of $keyvalue.
$key = hash('SHA256', $keyvalue, true);
// Build $iv and $iv_base64. We use a block size of 128 bits (AES compliant) and CBC mode. (Note: ECB mode is inadequate as IV is not used.)
srand(); $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
if (strlen($iv_base64 = rtrim(base64_encode($iv), '=')) != 22) return false;
// Encrypt $decrypted and an MD5 of $decrypted using $key. MD5 is fine to use here because it's just to verify successful decryption.
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $decrypted . md5($decrypted), MCRYPT_MODE_CBC, $iv));
// We're done!
return $iv_base64 . $encrypted;
}
Thanks in advance
Aniruddha
This should do it.
public static byte[] encrypt(byte[] decrypted, byte[] keyvalue) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException{
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] key = sha256.digest(keyvalue);
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] checksum = md5.digest(decrypted);
//The length of the value to encrypt must be a multiple of 16.
byte[] decryptedAndChecksum = new byte[(decrypted.length + md5.getDigestLength() + 15) / 16 * 16];
System.arraycopy(decrypted, 0, decryptedAndChecksum, 0, decrypted.length);
System.arraycopy(checksum, 0, decryptedAndChecksum, decrypted.length, checksum.length);
//The remaining bytes of decryptedAndChecksum stay as 0 (default byte value) because PHP pads with 0's.
SecureRandom rnd = new SecureRandom();
byte[] iv = new byte[16];
rnd.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), ivSpec);
byte[] encrypted = Base64.encodeBase64(cipher.doFinal(decryptedAndChecksum));
byte[] ivBase64 = Base64.encodeBase64String(iv).substring(0, 22).getBytes();
byte[] output = new byte[encrypted.length + ivBase64.length];
System.arraycopy(ivBase64, 0, output, 0, ivBase64.length);
System.arraycopy(encrypted, 0, output, ivBase64.length, encrypted.length);
return output;
}
The equivalent of MCRYPT_RIJNDAEL_128 and MCRYPT_MODE_CBC in java is AES/CBC/NoPadding. You also need a utility for Base64 encoding, the above code uses Base64 from the Apache Codec library.
Also, because the encryption key is 256 bits, you'll need the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files. These can be downloaded from Oracle's website.
Finally, do heed ntoskrnl's warning. This encryption really could be better, don't copy-paste from the PHP manual.