I have a node module that can both encrypt and decrypt with AES-256 GCM. Now I am also trying to decrypt with Java whatever the node module encrypts, but I keep getting a AEADBadTagException.
I have tested the node module by itself and can confirm that it works as intended. I know that Java assumes the authentication tag is the last part of the message, so I ensured that the tag is the last thing appended in the node module.
Right now I'm just testing with the word, "hello". This is the encrypted message from node:
Q10blKuyyYozaRf0RVYW7bave8mT5wrJzSdURQQa3lEqEQtgYM3ss825YpCQ70A7hpq5ECPafAxdLMSIBZCxzGbv/Cj4i6W4JCJXuS107rUy0tAAQVQQA2ZhbrQ0gNV9QA==
The salt is not really being used right now because I am trying to keep things simple for testing purposes
Node module:
var crypto = require('crypto');
var encrypt = function(masterkey, plainText) {
// random initialization vector
var iv = crypto.randomBytes(12);
// random salt
var salt = crypto.randomBytes(64);
var key = masterkey;
// AES 256 GCM Mode
var cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
// encrypt the given text
var encrypted = Buffer.concat([cipher.update(plainText, 'utf8'), cipher.final()]);
// extract the auth tag
var tag = cipher.getAuthTag();
return Buffer.concat([salt, iv, encrypted, tag]).toString('base64');
};
var decrypt = function(masterkey, encryptedText) {
// base64 decoding
var bData = new Buffer(encryptedText, 'base64');
// convert data to buffers
var salt = bData.slice(0, 64);
var iv = bData.slice(64, 76);
var tag = bData.slice(bData.length - 16, bData.length);
var text = bData.slice(76, bData.length - 16);
var key = masterkey;
// AES 256 GCM Mode
var decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
// encrypt the given text
var decrypted = decipher.update(text, 'binary', 'utf8') + decipher.final('utf8');
return decrypted;
};
module.exports = {
encrypt: encrypt,
decrypt: decrypt
}
Java Class:
The main method is just there for testing right now and will be removed later.
package decryption;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class DecryptAES256 {
private static String salt;
private static byte[] ivBase64;
private static String base64EncryptedText;
private static String key;
public static void main(String[] args) {
String key = "123456789aabbccddeefffffffffffff";
String sourceText = "Q10blKuyyYozaRf0RVYW7bave8mT5wrJzSdURQQa3lEqEQtgYM3ss825YpCQ70A7hpq5ECPafAxdLMSIBZCxzGbv/Cj4i6W4JCJXuS107rUy0tAAQVQQA2ZhbrQ0gNV9QA==";
System.out.println(decrypt(key, sourceText));
}
public static String decrypt(String masterkey, String encryptedText) {
byte[] parts = encryptedText.getBytes();
salt = new String(Arrays.copyOfRange(parts, 0, 64));
ivBase64 = Arrays.copyOfRange(parts, 64, 76);
ivBase64 = Base64.getDecoder().decode(ivBase64);
base64EncryptedText = new String(Arrays.copyOfRange(parts, 76, parts.length));
key = masterkey;
byte[] decipheredText = decodeAES_256_CBC();
return new String(decipheredText);
}
private static byte[] decodeAES_256_CBC() {
try {
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] text = base64EncryptedText.getBytes();
GCMParameterSpec params = new GCMParameterSpec(128, ivBase64, 0, ivBase64.length);
cipher.init(Cipher.DECRYPT_MODE, skeySpec, params);
return cipher.doFinal(Base64.getDecoder().decode(text));
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed to decrypt");
}
}
}
The exception thrown is normal, you have 2 problems in your Java code:
1- your AES key is not decoded correctly: it is wrapped in hexadecimal representation and you decode it as if it was not. You need to convert it from the hexadecimal representation to bytes, when calling SecretKeySpec().
Replace the following line:
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
By this one:
SecretKeySpec skeySpec = new SecretKeySpec(Hex.decodeHex(key.toCharArray()), "AES");
Note that to get access to the Hex class, you need to import org.apache.commons.codec.binary.Hex in your class file and include the corresponding Apache commons-codec library in your project.
2- you split your base64 cipher text before having converted it to base64, this is not the correct order to do things:
At the start of your decrypt() method, you need to first call Base64.getDecoder().decode() on your cipher text (sourceText) before splitting it into the corresponding fields.
If you want an example of using AES-256-GCM in Java, you can look at the following example I had written some months ago: https://github.com/AlexandreFenyo/kif-idp-client
The source code to encrypt and decrypt is in the following file: https://github.com/AlexandreFenyo/kif-idp-client/blob/master/src/kif/libfc/Tools.java
See the methods named encodeGCM() and decodeGCM().
Those methods are called by the main class here: https://github.com/AlexandreFenyo/kif-idp-client/blob/master/src/kif/libfc/CommandLine.java
Related
I need to convert java code for encryption and decryption using AES/CBC/PKCS5Padding algorithm to dart code.
The java code of AES/CBC/PKCS5Padding encryption and decryption:
package test_Terminal.classes;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
*
* #author jeena
*/
public class IOTEncodingDecoding {
SecretKeySpec secretKeySpec;
IvParameterSpec ivSpec;
String EncryptionKey = "733D3A17-D8A0-454B-AD22-88608FD0C46A";
String saltString = "FA9A4D0F-5523-4EEF-B226-9A3E8F14FEF8";
String algorithm = "AES/CBC/PKCS5Padding";
int encoding_mode;
test_Terminal.classes.general General = new test_Terminal.classes.general();
void setSecretKey() {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec pbeKeySpec = new PBEKeySpec(EncryptionKey.toCharArray(), saltString.getBytes(StandardCharsets.UTF_16LE), 1000, 384);
byte[] derivedData = factory.generateSecret(pbeKeySpec).getEncoded();
byte[] key = new byte[32];
byte[] iv = new byte[16];
System.arraycopy(derivedData, 0, key, 0, key.length);
System.arraycopy(derivedData, key.length, iv, 0, iv.length);
secretKeySpec = new SecretKeySpec(key, "AES");
ivSpec = new IvParameterSpec(iv);
} catch (Exception e) {
General.LogException("setSecretKey", e);
}
}
public String encrypt(String input) {
try {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivSpec);
byte[] cipherText ;
if(encoding_mode==1)
cipherText = cipher.doFinal(input.getBytes(StandardCharsets.UTF_16LE));
else
cipherText = cipher.doFinal(input.getBytes());
return Base64.getEncoder().encodeToString(cipherText);
} catch (Exception e) {
General.LogException("encrypt", e);
}
return "";
}
public String decrypt(String cipherText) {
try {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec);
byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(cipherText));
if(encoding_mode==1)
return new String(plainText, StandardCharsets.UTF_16LE);
else
return new String(plainText);
} catch (Exception e) {
General.LogException("decrypt", e);
General.LogActivity("decrypt", e.getMessage());
}
return "Ticket format error";
}
public void setMode() {
setSecretKey();
}
}
I need to get the following result:
Input(PlainText):C123492349C1CT20230206130645.
Output(Encrypted string):8tyHRaQCsxmmGW2xPBFYx/PALmvHkmjx/TzaXC2rIv0=
This is the dart code that I've got so far for decryption, but I'm getting error.
Uint8List? decrypt(String ciphertext, String password) {
Uint8List rawCipher = base64.decode(ciphertext);
var salt = rawCipher.sublist(0, 0 + 8);
var iv = rawCipher.sublist(8, 8 + 16);
var encrypted = rawCipher.sublist(8 + 16);
Uint8List key = generateKey(password, salt);
print('key => $key');
CBCBlockCipher cipher = CBCBlockCipher(AESEngine());
ParametersWithIV<KeyParameter> params =
ParametersWithIV<KeyParameter>(KeyParameter(key), iv);
PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>
paddingParams =
PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>(
params, null);
PaddedBlockCipherImpl paddingCipher =
PaddedBlockCipherImpl(PKCS7Padding(), cipher);
paddingCipher.init(false, paddingParams);
var val = paddingCipher.process(encrypted);
String res = String.fromCharCodes(val);
debugPrint('res => $res');
return val;
}
Uint8List generateKey(String passphrase, Uint8List salt) {
final derivator = PBKDF2KeyDerivator(HMac(SHA1Digest(), 64))
..init(Pbkdf2Parameters(salt, 1024, 16));
return derivator.process(utf8.encode(passphrase) as Uint8List);
}
I got this code from
The Exception that I'm getting is:
Exception has occurred.
ArgumentError (Invalid argument(s): Input data length must be a multiple of cipher's block size)
I think the values inside rawCipher.sublist() function is wrong. I'm stuck on this problem for few days, please help.
Both codes differ:
Regarding encodings: The Dart code does not consider the UTF-16 LE encoding of the salt. Furthermore, the encoding of the plaintext is unclear. For encoding_mode==1 it is UTF-16LE, otherwise it corresponds to the platform encoding in your environment (which only you know).
Regarding PBKDF2: The Java code derives key and IV from a static salt (note that a static salt is a vulnerability), while the Dart code assumes a concatenation in the order salt|IV|ciphertext during encryption (using a random 8 bytes salt and a random IV).
Also, different iteration counts are used: 1000 in the Java code, 1024 in the Dart code (note that both values are generally much too small for PBKDF2).
The differences can be fixed as follows:
Regarding encodings: In the Dart code, the salt must first be UTF-16 LE encoded: Since the utf package is deprecated, see e.g. here for a UTF-16 LE encoding and here for the decoding. The encoding can be adapted to:
Uint8List encodeUtf16LE(String salt) {
var byteData = ByteData(salt.codeUnits.length * 2);
for (var i = 0; i < salt.codeUnits.length; i += 1) {
byteData.setUint16(i * 2, salt.codeUnits[i], Endian.little);
}
return byteData.buffer.asUint8List();
}
Moreover, from the sample data it can be concluded (by testing) that the plaintext in the Java code has been encoded with UTF-8.
Regarding PBKDF2: In the Dart code, key and IV must be derived from the static salt applied in the Java code.
Also, the parameters from the Java code must be applied (digest: SHA-1, iteration count: 1000, keysize: 32 + 16 = 48 bytes):
Uint8List generateKey(String passphrase, Uint8List salt) {
final derivator = PBKDF2KeyDerivator(HMac(SHA1Digest(), 64))
..init(Pbkdf2Parameters(salt, 1000, 32 + 16));
return derivator.process(utf8.encode(passphrase) as Uint8List);
}
With these changes, key and IV can be derived as follows:
var salt = "FA9A4D0F-5523-4EEF-B226-9A3E8F14FEF8";
var passphrase = "733D3A17-D8A0-454B-AD22-88608FD0C46A";
var saltBytes = encodeUtf16LE(salt);
var keyIv = generateKey(passphrase, saltBytes);
var key = keyIv.sublist(0, 32);
var iv = keyIv.sublist(32, 32 + 16);
The decryption code can be applied unchanged, for decoding use utf8.decode() instead of String.fromCharCodes().
import 'dart:convert';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';
...
var ciphertext = "8tyHRaQCsxmmGW2xPBFYx/PALmvHkmjx/TzaXC2rIv0=";
var encrypted = base64.decode(ciphertext);
var paddingCipher = PaddedBlockCipherImpl(PKCS7Padding(), CBCBlockCipher(AESEngine()))
..init(false, PaddedBlockCipherParameters(ParametersWithIV(KeyParameter(key), iv), null));
var decryptedBytes = paddingCipher.process(encrypted);
var decrypted = utf8.decode(decryptedBytes); // C123492349C1CT20230206130645
I need to decrypt an AES (PKCS#7) encoded string in my Flutter mobile application.
The string is got from a QR Code, which has been generated from a Java application and contains the AES encoded String.
The Java encoding :
import java.security.Security;
import java.nio.charset.StandardCharsets;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class MyClass {
public static void main(String[] args) throws Exception {
String toEncode = "firstname.lastname#mycompany.com;12";
String encoded = pleaseEncodeMe(toEncode);
System.out.println(encoded);
}
private static String pleaseEncodeMe(String plainText) throws Exception {
Security.addProvider(new BouncyCastleProvider());
final String encryptionAlgorithm = "AES/CBC/PKCS7PADDING";
final String encryptionKey = "WHatAnAWEsoMeKey";
final SecretKeySpec keySpecification = new SecretKeySpec(encryptionKey.getBytes(StandardCharsets.UTF_8), encryptionAlgorithm);
final Cipher cipher = Cipher.getInstance(encryptionAlgorithm, "BC");
cipher.init(Cipher.ENCRYPT_MODE, keySpecification);
final byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
return Base64.encodeBase64URLSafeString(encryptedBytes);
}
}
Output : AIRTEuNmSuQtYuysv93w3w83kJJ6sg7kaU7XzA8xrAjOp-lKYPp1brtDAPbhSJmT
The Dart decoding :
void main() {
print(decodeMeOrDie("AIRTEuNmSuQtYuysv93w3w83kJJ6sg7kaU7XzA8xrAjOp-lKYPp1brtDAPbhSJmT"));
}
String decodeMeOrDie(String encryptedString) {
final key = Key.fromUtf8("WHatAnAWEsoMeKey");
final iv = IV.fromLength(16);
final encrypter = Encrypter(AES(key, mode: AESMode.cbc, padding: "PKCS7"));
return encrypter.decrypt64(encryptedString, iv: iv);
}
Output : Y��=X�Rȑ�"Qme#mycompany.com;12
You can see that only a part of the string is decoded.
Two things must be taken into account:
1) For decryption, the IV used for encryption is required.
2) For security reasons, a new IV must be randomly generated for each encryption so that no IV is used more than once with the same key, here.
Therfore, the IV must be passed from the encryption-side to the decryption-side. This doesn't happen automatically, but has to be implemented.
One possibility is to concatenate the byte-arrays of IV and ciphertext. Usually the IV is placed before the ciphertext and the result is Base64-encoded (if required), e.g. in Java:
// Concatenate IV and ciphertext
byte[] iv = ...
byte[] ciphertext = ...
byte[] ivAndCiphertext = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, ivAndCiphertext, 0, iv.length);
System.arraycopy(ciphertext, 0, ivAndCiphertext, iv.length, ciphertext.length);
// If required: Base64-encoding
This data is transmitted to the decryption-side, which separates both parts after Base64-decoding. In the case of AES-CBC, the IV is 16 bytes long, so the first 16 bytes represent the IV and the rest the ciphertext. The IV doesn't need to be encrypted because it isn't secret.
Specifically for your case this means that you have to concatenate IV and ciphertext on the Java-side and to Base64-encode the result. On the Dart-side you have to Base64-decode first and then both parts, IV and ciphertext, can be separated and used for the following decryption.
There are two ways to generate the IV before encryption: Implicit generation by the Cipher-instance as in your example or explicit generation e.g. via SecureRandom. Both alternatives are discussed here. If the IV is generated implicitly (via the Cipher-instance), then this IV must be determined via the Cipher-instance, since it is later required for decryption:
// Determine IV from cipher for later decryption
byte[] iv = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
If the IV is determined explicitly (e.g. using SecureRandom), it must be passed to the Cipher-instance so that it will be used in the running encryption. This is done using an IvParameterSpec.
// Assign IV to cipher so that it is used for current encryption
byte[] iv = ...
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretkeySpec, ivParameterSpec);
A hard-coded key is in general not good practice (except for testing purposes perhaps). However, the topic of key generation/management is outside the scope of this answer. There are already a lot of questions and answers on this subject. If your question is not covered by these answers, please post a new question. A hard-coded IV doesn't occur within the above architecture and should only be used for testing purposes.
If it can help someone, here is the code I ended up with, in dart (it uses the encrypt package) :
/// Decode the specified QR code encrypted string
static String decodeQrCode(String encryptedString) {
try {
// pad the encrypted base64 string with '=' characters until length matches a multiple of 4
final int toPad = encryptedString.length % 4;
if (toPad != 0) {
encryptedString = encryptedString.padRight(encryptedString.length + toPad, "=");
}
// get first 16 bytes which is the initialization vector
final iv = encrypt.IV(Uint8List.fromList(base64Decode(encryptedString).getRange(0, 16).toList()));
// get cipher bytes (without initialization vector)
final encrypt.Encrypted encrypted = encrypt.Encrypted(Uint8List.fromList(
base64Decode(encryptedString).getRange(16, base64Decode(encryptedString).length).toList()));
// decrypt the string using the key and the initialization vector
final key = encrypt.Key.fromUtf8(YOUR_KEY);
final encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7"));
return encrypter.decrypt(encrypted, iv: iv);
} catch (e) {
_log.severe("Error while decoding QR code : $e");
return null;
}
}
I have the following simple encryption code running in node.js:
var crypto = require('crypto');
var encKey = "FOO"; // Not the real key. Assume it works though.
var encrypt = function(str) {
var cipher = crypto.createCipher('aes-256-cbc', encKey);
var crypted = cipher.update(str, 'utf-8', 'hex');
crypted += cipher.final('hex');
return crypted;
};
Which I can also decrypt as below:
var crypto = require('crypto');
var encKey = "FOO"; // Not the real key. Assume it works though.
var decrypt = function(str) {
var decipher = crypto.createDecipher('aes-256-cbc', encKey);
var decrypted = decipher.update(str, 'hex', 'utf-8');
decrypted += decipher.final('utf-8');
return decrypted;
};
This all works fine. Strings are encrypting and decrypting as expected. But now I am faced with task of decrypting encrypted strings from this node.js code, in Java. And that is where things are going wrong and I am not sure why.
For decryption, My Java code looks like this:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.util.Arrays;
private static final String encKey = "FOO";
private static SecretKeySpec secretKey;
private static byte[] key;
public static String decrypt(String str) throws Exception {
String hexDecodedStr = new String(Hex.decodeHex(str.toCharArray()));
setKey(encKey);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(hexDecodedStr.getBytes()));
}
private static void setKey(String myKey) throws Exception {
MessageDigest sha = null;
try {
key = myKey.getBytes("UTF-8");
sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16);
secretKey = new SecretKeySpec(key, "AES");
}
catch (Exception e) {
throw e;
}
}
And it doesn't work. It seems like no matter what I try, I end up with some exception on the cipher.doFinal() call, or the String I get back is totally wrong. I know the node.js code is using aes-256-cbc, while the Java code is using AES/ECB/PKCS5Padding instead of AES/CBC/PKCS5Padding, but when I tried to use AES/CBC/PKCS5Padding, it was requiring an InitVector which I didn't have in node.js so I was unsure of how to proceed. Is node making an InitVector under the hood if not provided with one? Am I missing something totally obvious?
You seems to have the same issue as others OpenSSL encryption failing to decrypt C#
As far I understood the docs, the crypto libeary uses openssl. The openssl creates IV and key from the password using its EVP_BytesToKey function and random salt (not just hash). As dave pointed out, the crypto library uses no salt.
the output of openssl is Salted_{8 bytes salt}{ciphertext} so check what is output of the cipher ( I am unable to do it now)
I wrote a small article how to encrypt properly in Java
I have translate a C# based decrypt function into Java. It works fine and could be used to decrypted the passwords which have been encrypted by C# program.
Here is the source code:
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
public class TestDecrpt {
public static void main(String[] args) throws Exception {
String data = "encrypted data";
String sEncryptionKey = "encryption key";
byte[] rawData = new Base64().decode(data);
byte[] salt = new byte[8];
System.arraycopy(rawData, 0, salt, 0, salt.length);
Rfc2898DeriveBytes keyGen = new Rfc2898DeriveBytes(sEncryptionKey, salt);
byte[] IV = keyGen.getBytes(128 / 8);
byte[] keyByte = keyGen.getBytes(256 / 8);
Key key = new SecretKeySpec(keyByte, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(IV));
int pureDataLength = rawData.length - 8;
byte[] pureData = new byte[pureDataLength];
System.arraycopy(rawData, 8, pureData, 0, pureDataLength);
String plaintext = new String(cipher.doFinal(pureData), "UTF-8").replaceAll("\u0000", "");
System.out.println(plaintext);
}
}
I follow its algorithm to write the encrypt function. And codes is:
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.SecureRandom;
public class testEncrypt {
public static void main(String[] args) throws Exception {
String data = "Welcome2012~1#Welcome2012~1#Welcome2012~1#Welcome2012~1#Welcome2012~1#";
String sEncryptionKey = "encryption key"; # the same key
byte[] rawData = new Base64().decode(data);
SecureRandom random = new SecureRandom();
byte[] salt = new byte[8];
random.nextBytes(salt);
Rfc2898DeriveBytes keyGen = new Rfc2898DeriveBytes(sEncryptionKey, salt);
byte[] IV = keyGen.getBytes(128 / 8);
byte[] keyByte = keyGen.getBytes(256 / 8);
Key key = new SecretKeySpec(keyByte, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(IV));
byte[] out2 = cipher.doFinal(rawData);
byte[] out = new byte[8 + out2.length];
System.arraycopy(salt, 0, out, 0, 8);
System.arraycopy(out2, 0, out, 8, out2.length);
//String outStr=new String(out,"UTF-8");
String outStr = new Base64().encodeToString(out);
System.out.println(outStr);
System.out.print(outStr.length());
}
}
However, the encrypted data could not be decrypted correctly, it always return garbage codes, such as
ꉜ뙧巓妵峩枢펶땝ꉜ뙧巓妵峩枢펶땝ꉜ뙧巓�
Is there something wrong with the encrypt function?
================================================================================
[Update]
After changing the code to
byte[] rawData = data.getBytes("UTF-8");
The data could be encrypted and decrypted successfully.
However, the data which is encrypted in Java could not be correctly descrypted in C#.
Here is the C# version decrypt function:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace Test
{
class Program
{
public static void Main(string[] args)
{
string data="EncryptedData";
string sEncryptionKey="EncryptionKey";
byte[] rawData = Convert.FromBase64String(data);
byte[] salt = new byte[8];
for (int i = 0; i < salt.Length; i++)
salt[i] = rawData[i];
Rfc2898DeriveBytes keyGenerator = new Rfc2898DeriveBytes(sEncryptionKey, salt);
Rijndael aes = Rijndael.Create();
aes.IV = keyGenerator.GetBytes(aes.BlockSize / 8);
aes.Key = keyGenerator.GetBytes(aes.KeySize / 8);
using (MemoryStream memoryStream = new MemoryStream())
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write))
{
cryptoStream.Write(rawData, 8, rawData.Length - 8);
cryptoStream.Close();
byte[] decrypted = memoryStream.ToArray();
Console.Out.WriteLine(Encoding.Unicode.GetString(decrypted));
Console.In.ReadLine();
}
}
}
}
I find that the original code are using "Unicode" as output format,
Encoding.Unicode.GetString(decrypted)
so I change my Java code to "Unicode".
For Decrypt in Java:
String plaintext = new String(cipher.doFinal(pureData), "Unicode");
System.out.println(plaintext);
For Encrypt in Java:
byte[] rawData = data.getBytes("Unicode");
But using the C# code to decrypt the data which has been encrypted by the Java program still meet garbage codes.
How could I fix this issue? Is there any magical trick?
[Last Update]
After using "UTF-16LE" instead of "UTF-8", the issue has gone. It seems that "UTF-16LE" is the Java equivalent to the "Unicode" of C#.
This is the problem:
String data = "Welcome2012~1#Welcome2012~1#Welcome2012~1#Welcome2012~1#Welcome2012~1#";
byte[] rawData = new Base64().decode(data);
That text is not meant to be base64-encoded binary data. It's just text. Why are you trying to decode it as base64 data?
You want:
byte[] rawData = data.getBytes("UTF-8");
That way, when you later write:
String plaintext = new String(cipher.doFinal(pureData), "UTF-8")
.replaceAll("\u0000", "");
you're doing the reverse action. (Admittedly you probably shouldn't need the replaceAll call, but that's a different matter.)
For anything like this, you need to make sure that the steps you take on the way "out" are the reverse of the steps on the way "in". So in the correct code, you have:
Unencrypted text data => unencrypted binary data (encode via UTF-8)
Unencrypted binary data => encrypted binary data (encrypt with AES)
Encrypted binary data => encrypted text data (encode with base64)
So to reverse, we do:
Encrypted text data => encrypted binary data (decode with base64)
Encrypted binary data => unencrypted binary data (decrypt with AES)
Unencrypted binary data => unencrypted text data (decode via UTF-8)
I am trying to translate the following (working) Java code to Ruby.
public static final String PROVIDER = "BC";
public static final int IV_LENGTH = 16;
private static final String HASH_ALGORITHM = "SHA-512";
private static final String PBE_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String SECRET_KEY_ALGORITHM = "AES";
public String decrypt(SecretKey secret, String encrypted) {
Cipher decryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
String ivHex = encrypted.substring(0, IV_LENGTH * 2);
String encryptedHex = encrypted.substring(IV_LENGTH * 2);
IvParameterSpec ivspec = new IvParameterSpec(HexEncoder.toByte(ivHex));
decryptionCipher.init(Cipher.DECRYPT_MODE, secret, ivspec);
byte[] decryptedText = decryptionCipher.doFinal(HexEncoder.toByte(encryptedHex));
String decrypted = new String(decryptedText, "UTF-8");
return decrypted;
}
My (not working) Ruby code is this:
require 'openssl'
require 'digest/sha2'
SECRET = "MY PASSWORD AS RAW TEXT"
IV_LENGHT = 16
encoded = "45D0EC4D910C0A6FF67325FF7362DCBC4613B6F3BFDFE35930D764FB1FE62251"
iv = encoded.slice(0, IV_LENGHT * 2)
e = encoded.slice(IV_LENGHT*2..-1)
binary_iv = iv.unpack('a2'*IV_LENGHT).map{|x| x.hex}.pack('c'*IV_LENGHT)
binary_e = e.unpack('a2'*IV_LENGHT).map{|x| x.hex}.pack('c'*IV_LENGHT)
c = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
c.decrypt
c.key = Digest::SHA256.digest(SECRET).slice(0, IV_LENGHT* 2 )
c.iv = binary_iv
d = c.update(binary_e)
d << c.final
puts "decrypted: #{d}\n"
I have tried the binary and non binary versions, with no luck.
Someone can point to the problem?
Based on the title here, I am going to assume that you want to be able to encrypt a message in Java, and then decrypt that message in Ruby, using password-based AES-CBC encryption.
Now, the openssl standard library in Ruby readily supports password-based key derivation function 2 based on PKCS5. You can greatly simplify your Ruby decryption code if you leverage this in your Java encryption.
Here is how you would encrypt using PBKDF2 in PKCS5 in Java:
// in Java-land
import java.security.AlgorithmParameters;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
...
static String printHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", (b & 0xFF)));
}
return sb.toString();
}
public static Map<String,String> encrypt(String msg, String pwd, byte[] salt)
throws Exception {
Map<String,String> retval = new HashMap<String,String>();
// prepare to use PBKDF2/HMAC+SHA1, since ruby supports this readily
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
// our key is 256 bits, and can be generated knowing the password and salt
KeySpec spec = new PBEKeySpec(pwd.toCharArray(), salt, 1024, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
// given key above, our cippher will be aes-256-cbc in ruby/openssl
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
// generate the intialization vector
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
retval.put("iv", printHex(iv));
byte[] ciphertext = cipher.doFinal(msg.getBytes("UTF-8"));
retval.put("encrypted", printHex(ciphertext));
return retval;
}
public static void main(String[] args) throws Exception {
String msg = "To Ruby, from Java, with love...";
String pwd = "password";
String salt = "8 bytes!"; // in reality, you would use SecureRandom!
System.out.println("password (plaintext): " + pwd);
System.out.println("salt: " + salt);
Map<String,String> m = encrypt(msg, pwd, salt.getBytes());
System.out.println("encrypted: " + m.get("encrypted"));
System.out.println("iv: " + m.get("iv"));
}
Running the above will result in something like the following output.
password (plaintext): password
salt: 8 bytes!
encrypted: 4a39f1a967c728e11c7a5a3fb5d73ad07561f504c9d084d0b1ae600cc1f75137cbb82a4d826c060cb06e2e283449738d
iv: ecbc985b3550edc977a17acc066f2192
Hex strings are used for the encrypted message and initialization vector since you can use OpenSSL to verify the encryption/decryption process (highly recommended).
Now from a Ruby program, you would use the AES-256-CBC cipher, and derive the secret key from the password and salt strings (not byte[] as per Java). Using the output from the above-mentioned Java program, we have:
# from Ruby-land
require 'openssl'
d = OpenSSL::Cipher.new("AES-256-CBC")
d.decrypt
key = OpenSSL::PKCS5.pbkdf2_hmac_sha1("password", "8 bytes!", 1024, d.key_len)
d.key = key
d.iv = "ecbc985b3550edc977a17acc066f2192".scan(/../).map{|b|b.hex}.pack('c*')
data = "4a39f1a967c728e11c7a5a3fb5d73ad07561f504c9d084d0b1ae600cc1f75137cbb82a4d826c060cb06e2e283449738d".scan(/../).map{|b|b.hex}.pack('c*')
d.update(data) << d.final
=> "To Ruby, from Java, with love..."
NOTE: The Ruby part of this code pretty much comes verbatim from the Japanese documentation on the openssl standard library.
I once had a similar problem with CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; and decryption through the openSSL-library in C which I could't solve. I avoided the problem by using "AES/CBC/NoPadding" and by adding a particular padding to the plaintext manually.