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.
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.
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.
I'm currently running into an issue where our decryption portion of our C# site is having trouble with the padding with the encrypted string from java. The .Net code throws this error "Padding is invalid and cannot be removed". The _signKey and _encKey are both 64 bytes.
public String encryptString(String plainText) {
byte[] ciphertext;
byte[] iv = new byte[16];
byte[] plainBytes = plainText.getBytes(StandardCharsets.UTF_8);
String _signKey = "****************************************************************";
String _encKey = "****************************************************************";
try {
Mac sha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec shaKS = new SecretKeySpec(_signKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha256.init(shaKS);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);
byte[] sessionKey = sha256.doFinal((_encKey + iv).getBytes(StandardCharsets.UTF_8));
// Perform Encryption
SecretKeySpec eks = new SecretKeySpec(sessionKey, "AES");
cipher.init(Cipher.ENCRYPT_MODE, eks, ivParams);
ciphertext = cipher.doFinal(plainBytes);
System.out.println("ciphertext= " + new String(ciphertext));
// Perform HMAC using SHA-256 on ciphertext
SecretKeySpec hks = new SecretKeySpec(_signKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(hks);
ByteArrayOutputStream outputStream2 = new ByteArrayOutputStream();
outputStream2.write(iv);
outputStream2.write(ciphertext);
outputStream2.flush();
outputStream2.write(mac.doFinal(outputStream2.toByteArray()));
return Base64.encodeBase64String(outputStream2.toByteArray());
} catch (Exception e) {
e.printStackTrace();
}
return plainText;
}
Does does encrypt the string properly as far as I can tell. We cannot change any code on the .Net side to decrypt this because this is being used today.
public static string DecryptString(string ciphertext)
{
using (HMACSHA256 sha256 = new HMACSHA256(Encoding.UTF8.GetBytes(_signKey)))
{
// Convert message to bytes
byte[] encBytes = Convert.FromBase64String(ciphertext);
// Get arrays for comparing HMAC tags
byte[] sentTag = new byte[sha256.HashSize / 8];
byte[] calcTag = sha256.ComputeHash(encBytes, 0, (encBytes.Length - sentTag.Length));
// If message length is too small return null
if (encBytes.Length < sentTag.Length + _ivLength) { return null; }
// Copy tag from end of encrypted message
Array.Copy(encBytes, (encBytes.Length - sentTag.Length), sentTag, 0, sentTag.Length);
// Compare tags with constant time comparison, return null if no match
int compare = 0;
for (int i = 0; i < sentTag.Length; i++) { compare |= sentTag[i] ^ calcTag[i]; }
if (compare != 0) { return null; }
using (AesCryptoServiceProvider csp = new AesCryptoServiceProvider())
{
// Set parameters
csp.BlockSize = _blockBits;
csp.KeySize = _keyBits;
csp.Mode = CipherMode.CBC;
csp.Padding = PaddingMode.PKCS7;
// Copy init vector from message
var iv = new byte[_ivLength];
Array.Copy(encBytes, 0, iv, 0, iv.Length);
// Derive session key
byte[] sessionKey = sha256.ComputeHash(Encoding.UTF8.GetBytes(_encKey + iv));
// Decrypt message
using (ICryptoTransform decrypt = csp.CreateDecryptor(sessionKey, iv))
{
return Encoding.UTF8.GetString(decrypt.TransformFinalBlock(encBytes, iv.Length, encBytes.Length - iv.Length - sentTag.Length));
}
}
}
}
If there is anything that sticks out it would be appreciated for the reply.
I didn't read all your code, but this line in Java:
byte[] sessionKey = sha256.doFinal((_encKey + iv).getBytes(StandardCharsets.UTF_8));
does nothing useful or sensible. The "+" operator does string concatenation, but iv is a byte[], not a String. So java uses iv.toString(), which simply returns a String containing something like [B#1188e820 which is meaningless in this context.
Refer four java code and DotNet code:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); //Java
csp.Padding = PaddingMode.PKCS7; //.Net
You are essentially using different padding, that is the probable source of error; however, there is an alternate view, Refer this great post and this for general fundamentals on padding
The cipher suites supported by deafult Oracle JVM implementation are here
If you notice it does not have 'AES/CBC/PKCS7Padding', a PKCS#7 padding implementation is available in sun.security package, refer this, otherwise you could use Bouncy Castle packages. It would be recommendable to use Bouncy Castle as com.sun package are generally considered unsupported.
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.
is there a way to decrypt files that have been encrypted using
openssl -des3 enc command.
Exactly how does openssl use the password and salt to make the key?
OpenSSL's enc utility uses a non-standard (and low quality) key derivation algorithm for passwords. The following code shows how the enc utility generates the key and initialization vector, given salt and a password. Note that enc stores the "salt" value in the encrypted file when the -salt option is specified (and that is critical for security).
public InputStream decrypt(InputStream is, byte[] password)
throws GeneralSecurityException, IOException
{
/* Parse the "salt" value from the stream. */
byte[] header = new byte[16];
for (int idx = 0; idx < header.length;) {
int n = is.read(header, idx, header.length - idx);
if (n < 0)
throw new EOFException("File header truncated.");
idx += n;
}
String magic = new String(header, 0, 8, "US-ASCII");
if (!"Salted__".equals(magic))
throw new IOException("Expected salt in header.");
/* Compute the key and IV with OpenSSL's non-standard method. */
SecretKey secret;
IvParameterSpec iv;
byte[] digest = new byte[32];
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(password);
md5.update(header, 8, 8);
md5.digest(digest, 0, 16);
md5.update(digest, 0, 16);
md5.update(password);
md5.update(header, 8, 8);
md5.digest(digest, 16, 16);
iv = new IvParameterSpec(digest, 24, 8);
DESedeKeySpec keySpec = new DESedeKeySpec(digest);
SecretKeyFactory factory = SecretKeyFactory.getInstance("DESede");
secret = factory.generateSecret(keySpec);
}
finally {
Arrays.fill(digest, (byte) 0);
}
/* Initialize the cipher. */
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, iv);
return new CipherInputStream(is, cipher);
}
This key and IV generation are described in the EVP_BytesToKey(3) documentation. The enc command uses 1 as the iteration count (which is a bad idea, and noted as a bug in the man page for my version of enc), and MD5 as the digest algorithm—a "broken" algorithm.
It is not clear how a OpenSSL converts text password to bytes. I'm guessing it uses the default platform character encoding. So, if you are stuck with a String password (not good, since it can't be "zero-ized"), you can just call password.getBytes() to convert it to a byte[].
If you can, use something like Java 6's Console or Swing's JPasswordField to get a password. These return an array, so you can "delete" the password from memory when you are done with it: Arrays.fill(password, '\0');
Thank you, Erickson, for your post. It helped me tremendously trying to recreate openssl's password to key and IV routine.
I ended up with something slightly different, probably because I need to decrypt blowfish-encrypted data rather than DES. See below.
Also I've discovered that openssl will stop reading passwords when it encounters bytes 00, 0a, or 0d. Generally I think that openssl only reads password characters between bytes 11 and 127. So for the example below, I have code that precedes this that truncates the password if it contains 00, 0a or 0d.
/* Compute the key and IV with OpenSSL's non-standard method. */
final byte[] digest = new byte[32];
final MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(password, 0);
// append the salt
md5.update(salt);
// run the digest and output 16 bytes to the first 16 bytes to the digest array. Digest is reset
md5.digest(digest, 0, 16);
// write the first 16 bytes from the digest array back to the buffer
md5.update(digest, 0, 16);
// append the password
md5.update(password, 0);
// append the salt
md5.update(salt);
// run the digest and output 16 bytes to the last 16 bytes of the digest array
md5.digest(digest, 16, 16);
key = Arrays.copyOfRange(digest, 0, 16);
iv = Arrays.copyOfRange(digest, 16, 24);
This code above can be replaced with 3 lines using org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator. It becomes
final OpenSSLPBEParametersGenerator generator = new OpenSSLPBEParametersGenerator();
generator.init(password, salt);
final ParametersWithIV ivParam = (ParametersWithIV)generator.generateDerivedParameters(16, 8);
final KeyParameter keyParameter = (KeyParameter)ivParam.getParameters();