Below is my java code which I am trying to move to NodeJS.
public static String decryptDataWithKey(String keyString, String base64String) throws Exception {
//Algorithm
String AES_ALGORITHM = "AES";
//Key from keystring
MessageDigest digester = MessageDigest.getInstance("MD5");
digester.update(keyString.getBytes());
byte[] password = digester.digest();
Key key = new SecretKeySpec(password, AES_ALGORITHM); // what is the equivalent of this line in javascript ?
//Create decipher
Cipher c = Cipher.getInstance(AES_ALGORITHM);
c.init(Cipher.DECRYPT_MODE, key);
//Get bytes of enc data
byte[] decodedValue = new BASE64Decoder().decodeBuffer(base64String);
// Do decrypt
byte[] decValue = c.doFinal(decodedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
}
I ended up writing below method, but getting exception - Bad Decrypt
function decryptDataWithKey(keyString, base64String){
//Algorithm
let algorithm = 'aes-128-ecb';
//Key from keystring
let key = crypto.createHash('md5').update(keyString).digest();
//Create decipher
let decipher = crypto.createDecipher(algorithm,key);
//Get bytes of enc data
let cipher = new Buffer(base64String, 'base64');
// Do decrypt
let decrypted = decipher.update(cipher, 'base64', 'utf-8');
decrypted += decipher.final('utf-8'); // throwing exception : digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
return decrypted;
}
Please guide me what I am missing here. I am trying to avoid the jar dependency in my nodejs project. I believe this should be achievable using crypto.
Or Should I go for crypto-js ?
Related
I am looking for settings/parameters of CryptoKit which will allow me to share data between iOS App and a Java Application. The flow would be something like below:
- Use CryptoKit to encrypt a text using a fixed key and random initialization vector (IV).
- In the Java application use standard javax libraries to perform the decryption using the same fixed key. The random IV will be transported/shared with the application along with the encrypted text.
Similarly, the reverse is also required, where text is encrypted using JavaX libraries using a fixed key and random IV. The random IV and encrypted text is shared with the iOS app where it should use CryptoKit to decrypt it.
Below is the code for Encrypt and Decrypt in Java
public static byte[] encrypt(byte[] plaintext, byte[] key, byte[] IV) throws Exception
{
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("AES_256/GCM/NoPadding");
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
// Create GCMParameterSpec
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, IV);
// Initialize Cipher for ENCRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
// Perform Encryption
byte[] cipherText = cipher.doFinal(plaintext);
return cipherText;
}
public static String decrypt(byte[] cipherText, byte[] key, byte[] IV) throws Exception
{
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("AES_256/GCM/NoPadding");
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
// Create GCMParameterSpec
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, IV);
// Initialize Cipher for DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
// Perform Decryption
byte[] decryptedText = cipher.doFinal(cipherText);
return new String(decryptedText);
}
The CryptoKit commands as below:
let mykey = SymmetricKey(data: passhash)
let myiv = try AES.GCM.Nonce()
let mySealedBox = try AES.GCM.seal(source.data(using: .utf8)!, using: mykey, nonce: myiv)
let myNewSealedBox = try AES.GCM.SealedBox(nonce: myiv, ciphertext: mySealedBox.ciphertext, tag: mySealedBox.tag)
let myText = try String(decoding: AES.GCM.open(myNewSealedBox, using: mykey), as: UTF8.self)
Below are the steps to generate an encrypted text in Java:
int GCM_IV_LENGTH = 12;
//Generate Key
MessageDigest md = MessageDigest.getInstance("SHA265");
byte[] key = md.digest("pass".getBytes(StandardCharsets.UTF_8));
// Generate IV
SecureRandom sr = new SecureRandom(pass.getBytes(StandardCharsets.UTF_8));
byte[] IV = new byte[GCM_IV_LENGTH];
sr.nextBytes(IV);
//Encrypt
byte[] cipherText = encrypt("Text to encrypt".getBytes(), key, IV);
//Base64 Encoded CipherText
String cipherTextBase64 = Base64.getEncoder().encodeToString(cipherText);
To Decrypt this in SWIFT CryptoKit, I first need to create a sealed box with this CipherText however, the CryptoKit API to create a sealed box requires the following:
Nonce/IV (Available above)
CipherText (Available above)
Tag (NO IDEA FROM WHERE TO GET THIS????)
AES.GCM.SealedBox(nonce: , ciphertext: , tag: )
The other way, lets first encrypt data in CryptoKit
let mykey = SymmetricKey(data: SHA256.hash(data: "12345".data(using: .utf8)!))
let myiv = AES.GCM.Nonce()
let mySealedBox = try AES.GCM.seal("Text to encrypt".data(using: .utf8)!, using: mykey, nonce: myiv)
let cipherText = mySealedBox.cipherText.base64EncodedString()
let iv = myiv.withUnsafeBytes{
return Data(Array($0)).base64EncodedString()
}
If i pass this IV and CipherText to Java Decrypt function along with key (SHA265 hash of "12345" string), i get a TAG mismatch error.
This is the final set of code in SWIFT:
let pass = “Password”
let data = “Text to encrypt”.data(using: .utf8)!
let key = SymmetricKey(data: SHA256.hash(data: pass.datat(using: .utf8)!))
let iv = AES.GCM.Nonce()
let mySealedBox = try AES.GCM.seal(data, using: key, nonce: iv)
dataToShare = mySealedBox.combined?.base64EncodedData()
Write this data to a file (I am using google APIs to write this data to a file on google drive)
Read this data from the file in java and pass it to the functions as defined in the question using the below code:
byte[] iv = Base64.getDecoder().decode(text.substring(0,16));
cipher[] = Base64.getDecoder().decode(text.substring(16));
byte[] key = md.digest(pass.getBytes(StandardCharsets.UTF_8));
String plainText = decrypt(cipher, key, iv);
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.
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);
I have code in PHP
$res = strtoupper(bin2hex(mcrypt_encrypt(MCRYPT_3DES, $this->hex2str($key), $this->hex2str($data), MCRYPT_MODE_ECB)));
public function hex2str($data) {
$len = strlen($data);
$res = pack("H" . $len, $data);
return $res;
}
I try to create in java version.
Java code :
private String doEncrypt3DES(String key, String data) throws Exception{
SecretKey secretKey;
byte[] keyValue;
Cipher c;
keyValue = Hex.decodeHex(key.toCharArray());
DESedeKeySpec keySpec = new DESedeKeySpec(keyValue);
secretKey = SecretKeyFactory.getInstance("DESede").generateSecret(keySpec);
// Create the cipher
c = Cipher.getInstance("DESede/ECB/NoPadding");
c.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] text = data.getBytes("utf-8"); // Base64.decodeBase64(data);
byte[] textEncrypt = c.doFinal(text);
String hex = bytesToHex(textEncrypt);
return hex;
}
But they got different result. Can you help me to fix java code?
data : CED0CF172E8AC451B39FC746C5339F29
key : 436C6561724B657944657632536E724D436C6561724B6579
Use hex decode and not utf-8 decode to data.
Here is the situation:
The encrypted text is done in JAVA (which we have no JAVA background at all)
The method is 3DES
The padded is PKCS#5
Base 64
The decryption will be in C#, and here is the code:
public static string DecryptString(string Message, string Passphrase)
{
byte[] Results;
UTF8Encoding UTF8 = new UTF8Encoding();
MD5CryptoServiceProvider HashProvider = new MD5CryptoServiceProvider();
byte[] TDESKey = HashProvider.ComputeHash(UTF8.GetBytes(Passphrase));
TripleDESCryptoServiceProvider TDESAlgorithm = new TripleDESCryptoServiceProvider();
TDESAlgorithm.Key = TDESKey;
TDESAlgorithm.Mode = CipherMode.ECB;
TDESAlgorithm.Padding = PaddingMode.PKCS7;
byte[] DataToDecrypt = Convert.FromBase64String(Message);
try
{
ICryptoTransform Decryptor = TDESAlgorithm.CreateDecryptor();
Results = Decryptor.TransformFinalBlock(DataToDecrypt, 0, DataToDecrypt.Length);
}
finally
{
TDESAlgorithm.Clear();
HashProvider.Clear();
}
return UTF8.GetString(Results);
}
However, when tried to decrypt, got the error message: BAD DATA
Where am I missing here?
Thanks in advance.
Added, and here's how the encryption works:
<cffunction name="getToken" returntype="String" output="false">
<cfscript>
plainText = getPlainText();
rawSecretKey = CreateObject("java","sun.misc.BASE64Decoder").decodeBuffer(variables.encryptionKey);
secretKeySpec = CreateObject("java","javax.crypto.spec.SecretKeySpec").init(rawSecretKey,"DESEDE");
cipher = CreateObject("java","javax.crypto.Cipher").getInstance("DESEDE");
cipher.init(Cipher.ENCRYPT_MODE, secretkeySpec);
encrypted = cipher.doFinal(plainText.getBytes()); // a byte array (a binary in CF)
return URLEncodedFormat(ToString(ToBase64(encrypted)));
</cfscript>
</cffunction>
Update:
This issue has been resolved. The problem was that the key needed to be converted from Base64.
The answer:
Instead of:
byte[] TDESKey = HashProvider.ComputeHash(UTF8.GetBytes(Passphrase));
Do this:
byte[] TDESKey = Convert.FromBase64String(Passphrase);
That solves this issue.