I've the task to migrate a php code to Java, but I'm not able to decrypt the information as these functions do. I've read many similar messages, but no solution solve the problem.
static function pkcs7_unpad($data) {
return substr($data, 0, -ord($data[strlen($data) - 1]));
}
static function decrypt($enc_name) {
$encryption_key = 'secret_key';
$iv = 'inizialization_vector';
$name = Encryptation::pkcs7_unpad(openssl_decrypt(
$enc_name,
'AES-256-CBC',
$encryption_key,
0,
$iv
));
return $name;
}
UPDATE
Update with encryption code:
static function pkcs7_pad($data, $size) {
$length = $size - strlen($data) % $size;
return $data . str_repeat(chr($length), $length);
}
static function encrypt($name) {
$encryption_key = 'secret_key';
$iv = 'inizialization_vector';
$enc_name = openssl_encrypt(
Encryptation::pkcs7_pad($name, 16), // padded data
'AES-256-CBC', // cipher and mode
$encryption_key, // secret key
0, // options (not used)
$iv // initialisation vector
);
return $enc_name;
}
The following Java code focus on the encryption-/decryption-part without a sophisticated exception handling etc. You should adapt it to your requirements. Since the PHP-code is the reference, the double padding has to be taken into account. Analogous to the PHP-methods the default and a custom PKCS7-padding are used in the Java-methods (another approach would be to use no default padding (i.e. AES/CBC/NoPadding) but a completely custom padding).
Custom PKCS7-Padding:
private static byte[] addPKCS7Padding(byte[] data, int size) {
byte pad = (byte)(size - (data.length % size));
byte[] output = new byte[data.length + pad];
System.arraycopy(data, 0, output, 0, data.length);
for (int i = data.length; i < output.length; i++)
output[i] = (byte)pad;
return output;
}
Unpadding:
private static byte[] removePKCS7Padding(byte[] data, int size) {
byte pad = data[data.length - 1];
byte[] output = new byte[data.length - pad];
System.arraycopy(data, 0, output, 0, data.length - pad);
return output;
}
A possible counterpart for the PHP encrypt-method is:
public static String encrypt(byte[] plainData) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] plainDataPKCS7Padded = addPKCS7Padding(plainData, 16);
byte[] encryptedData = cipher.doFinal(plainDataPKCS7Padded);
return Base64.getEncoder().encodeToString(encryptedData);
}
A possible counterpart for the PHP decrypt-method is:
public static byte[] decrypt(String encodedAndEncryptedData) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] encryptedData = Base64.getDecoder().decode(encodedAndEncryptedData);
byte[] decryptedData = cipher.doFinal(encryptedData);
return removePKCS7Padding(decryptedData, 16);
}
Test:
public class Cryptography {
private static byte[] key = "01234567890123456789012345678901".getBytes(StandardCharsets.UTF_8);
private static byte[] iv = "0123456789012345".getBytes(StandardCharsets.UTF_8);
public static void main(String[] args) throws Exception {
String plainText = "This is a plain text which needs to be encrypted...";
String encrypedData = encrypt(plainText.getBytes(StandardCharsets.UTF_8));
System.out.println(encrypedData);
byte[] decryptedData = decrypt(encrypedData);
String decryptedText = new String(decryptedData, StandardCharsets.UTF_8);
System.out.println(decryptedText);
}
...
}
Output:
cPF/JWAwp8G9xkUhHIMHLaS8WVfJM2UCxf2bphgOuJ6JVBmMFWAc5rwZzS/hNpAUx3+94UEEwXso2v/LkXVeXJmmfSgIaIvc9oDtGDUoQVo=
This is a plain text which needs to be encrypted...
which is equal to the output of the PHP-methods when the same plain text, key and IV are used.
EDIT:
Since AES-256-CBC is used in the PHP-methods, the key-length is 32 byte. The number denotes the key-length in bits (a 16 byte key would be needed for e.g. AES-128-CBC). The PHP-openssl-methods automatically pad too short keys with 0x0-values. In Java this doesn't happen automatically, but has to be implemented explicitly, e.g. with
private static byte[] padKey(byte[] key) {
byte[] paddedKey = new byte[32];
System.arraycopy(key, 0, paddedKey, 0, key.length);
return paddedKey;
}
For a test replace
private static byte[] key = "01234567890123456789012345678901".getBytes(StandardCharsets.UTF_8);
with
private static byte[] shortLengthKey = "012345678".getBytes(StandardCharsets.UTF_8);
private static byte[] key = padKey(shortLengthKey);
where shortLengthKey represents your 9 byte key.
The output is for both, PHP and Java:
CLPqFawQclb9PPOzcKowWBCi2gsaBJPXpl+kbGD8xYmTgL3WOtGUKpAskhXxhU4SKFZheEWiz+xkUEzLsKht3YzL9ZkSTuYtYwaZ0BjuWLM=
This is a plain text which needs to be encrypted...
Related
I am using one encryption/decryption method used in java and applying it in php . I have almost reached to the end but there is one mistake which I found that my decrypted string is not what i was expecting ,
This is my java code
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
public static String encrypt(String message, String key) throws GeneralSecurityException, UnsupportedEncodingException {
if (message == null || key == null) {
throw new IllegalArgumentException("text to be encrypted and key should not be null");
}
Cipher cipher = Cipher.getInstance(ALGORITHM);
byte[] messageArr = message.getBytes();
byte[] keyparam = key.getBytes();
SecretKeySpec keySpec = new SecretKeySpec(keyparam, "AES");
byte[] ivParams = new byte[16];
byte[] encoded = new byte[messageArr.length + 16];
System.arraycopy(ivParams, 0, encoded, 0, 16);
System.arraycopy(messageArr, 0, encoded, 16, messageArr.length);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(ivParams));
byte[] encryptedBytes = cipher.doFinal(encoded);
encryptedBytes = Base64.getEncoder().encode(encryptedBytes);
return new String(encryptedBytes);
}
public static String decrypt(String encryptedStr, String key) throws GeneralSecurityException, UnsupportedEncodingException {
if (encryptedStr == null || key == null) {
throw new IllegalArgumentException("text to be decrypted and key should not be null");
}
Cipher cipher = Cipher.getInstance(ALGORITHM);
byte[] keyparam = key.getBytes();
SecretKeySpec keySpec = new SecretKeySpec(keyparam, "AES");
byte[] encoded = encryptedStr.getBytes();
encoded = Base64.getDecoder().decode(encoded);
byte[] decodedEncrypted = new byte[encoded.length - 16];
System.arraycopy(encoded, 16, decodedEncrypted, 0, encoded.length - 16);
byte[] ivParams = new byte[16];
System.arraycopy(encoded, 0, ivParams, 0, ivParams.length);
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(ivParams));
byte[] decryptedBytes = cipher.doFinal(decodedEncrypted);
return new String(decryptedBytes);
}
now i have done this in php like this
$encKey = 'encryptionKey';
$cipher = "aes-256-cbc";
$data = 'some data';
$encryption_key = openssl_random_pseudo_bytes(32);
$iv_size = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($iv_size);
$ciphertext_raw = openssl_encrypt(json_encode($data), 'aes-256-cbc', $encKey, $options = OPENSSL_RAW_DATA, $iv);
//$ciphertext_raw = openssl_encrypt(json_encode($data), $cipher, $encKey, $options=OPENSSL_RAW_DATA, $iv);
$encrypted_data = base64_encode($iv . $ciphertext_raw);
print_r($encrypted_data);
now the problem is when i encrypt one string in php and decrypt it in java code it add some escape sequence but when i encrypt that same string in java and decrypt it in java it is proper and accurate and i am not sure why this is happening ... may be i am doing some thing wrong
as I have to do this encryption in php using java code i am not sure that is this process going in right way or there is some error .
this is the string which i am encrypting
String str = "vendor_id=INT_GTW&format=json&msg_code=KBEX99&data={\"header\":{\"msg_code\":\"KBEX99\",\"source\":\"INSTANTPAY\",\"channel\":\"CONBNK\",\"txn_ref_number\":\"INSTANTPAY_CONBNK_00001\",\"txn_datetime\":\"1498118309808\",\"ip\":\"1\",\"device_id\":\"XYWZPQR123\",\"api_version\":\"1.0.0\"},\"detail\":{\"entity\":\"INSTANTPAY\",\"intent\":\"REG\",\"user_identifier\":\"INSTANTPAY28423928\",\"crn\":\"105683710\",\"p1\":\"105683710\",\"p2\":\"\",\"p3\":\"INSTANTPAY\",\"p4\":\"\",\"p5\":\"\",\"p6\":\"\",\"p7\":\"\",\"p8\":\"\",\"p9\":\"\",\"p10\":\"\",\"p11\":\"\",\"p12\":\"\",\"p13\":\"\",\"p14\":\"\",\"p15\":\"\",\"p16\":\"\",\"p17\":\"\",\"p18\":\"\",\"p19\":\"\",\"p20\":\"\"}}";
and this is decrypted string
vendor_id=INT_GTW&format=json&msg_code=KBEX99&data={"header":{"msg_code":"KBEX99","source":"INSTANTPAY","channel":"CONBNK","txn_ref_number":"INSTANTPAY_CONBNK_00001","txn_datetime":"1498118309808","ip":"1","device_id":"XYWZPQR123","api_version":"1.0.0"},"detail":{"entity":"INSTANTPAY","intent":"REG","user_identifier":"INSTANTPAY28423928","crn":"105683710","p1":"105683710","p2":"","p3":"INSTANTPAY","p4":"","p5":"","p6":"","p7":"","p8":"","p9":"","p10":"","p11":"","p12":"","p13":"","p14":"","p15":"","p16":"","p17":"","p18":"","p19":"","p20":""}}
but when i am doing it in php and decrypting
"vendor_id=INT_GTW&format=json&msg_code=KBEX99&data={\"header\":{\"msg_code\":\"KBEX99\",\"source\":\"INSTANTPAY\",\"channel\":\"CONBNK\",\"txn_ref_number\":\"INSTANTPAY_CONBNK_00001\",\"txn_datetime\":\"1498118309808\",\"ip\":\"1\",\"device_id\":\"XYWZPQR123\",\"api_version\":\"1.0.0\"},\"detail\":{\"entity\":\"INSTANTPAY\",\"intent\":\"REG\",\"user_identifier\":\"INSTANTPAY28423928\",\"crn\":\"105683710\",\"p1\":\"105683710\",\"p2\":\"\",\"p3\":\"INSTANTPAY\",\"p4\":\"\",\"p5\":\"\",\"p6\":\"\",\"p7\":\"\",\"p8\":\"\",\"p9\":\"\",\"p10\":\"\",\"p11\":\"\",\"p12\":\"\",\"p13\":\"\",\"p14\":\"\",\"p15\":\"\",\"p16\":\"\",\"p17\":\"\",\"p18\":\"\",\"p19\":\"\",\"p20\":\"\"}}"
i am getting this
So any encryption decryption expert please help me in this ...
Thank you !!
I want to encrypt the data in flutter using the AES cbc-128 algorithm. below is the java code for that i want to achieve the same functionality as below but in dart. i have tried
cryptography
dependency in flutter but the problem with that is that i want to use my own key in the algorithm as below in the java code. if you know any method for achieving this please let me know.
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);
BASE64Decoder decoder = new BASE64Decoder();
byte[] results = cipher.doFinal(decoder.decodeBuffer(text));
return new String(results, "UTF-8");
}
public static String Encrypt(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.ENCRYPT_MODE, keySpec, ivSpec);
byte[] results = cipher.doFinal(text.getBytes("UTF-8"));
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(results);
}
Test Case
For the following input
plainText="This is plain text";
key="sahtojetrout2";
i want the encrypted result to be
encryptedText="8FmSMnDsFJVyNUXunhJLSmhFnRq89fl5DyTp0wdYfgk=";
which Topaco has written in an online editor you can check out that here Java Code. In flutter i have tried the program given at the Flutter site
You can do AES CBC-128 encryption in flutter with the help of crypt library. It supports the AES cbc encryption. The following sample code accepts key-string and plain-text as arguments and encrypts it as you have mentioned. You can pass your own key here. For AES-128, you need 128 bit key or 16 character string.
import 'package:encrypt/encrypt.dart';
void main() {
final key = "Your16CharacterK";
final plainText = "lorem ipsum example example";
Encrypted encrypted = encrypt(key, plainText);
String decryptedText = decrypt(key, encrypted);
print(decryptedText);
}
String decrypt(String keyString, Encrypted encryptedData) {
final key = Key.fromUtf8(keyString);
final encrypter = Encrypter(AES(key, mode: AESMode.cbc));
final initVector = IV.fromUtf8(keyString.substring(0, 16));
return encrypter.decrypt(encryptedData, iv: initVector);
}
Encrypted encrypt(String keyString, String plainText) {
final key = Key.fromUtf8(keyString);
final encrypter = Encrypter(AES(key, mode: AESMode.cbc));
final initVector = IV.fromUtf8(keyString.substring(0, 16));
Encrypted encryptedData = encrypter.encrypt(plainText, iv: initVector);
return encryptedData;
}
In the above example, the IV is created from the key itself to keep the code easy to read. Use random data for IV for better security. Referred article for flutter encryption.
I have been trying to figure this out for days now. Encryption method works fine, but during the decryption tests I am getting the exception below. Especially I am using: AES/GCM/NoPadding . As far as I know T_LEN should be IV_LENGTH*8 as a byte array representation. The error truly shows at ExampleCryptografer.java decryption method: byte[] decryptedText = cipher.doFinal(decoded);
javax.crypto.AEADBadTagException: Tag mismatch!
at java.base/com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:623)
at java.base/com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1116)
at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1053)
at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202)
at com.example.ExampleCryptografer.decrypt(ExampleCryptografer.java:61)
at com.example.ExampleCryptograferTest.decrypt_givenEncryptedExample_ShouldSucceed(ExampleCryptograferTest.java:21)
This is how my tests looks like:
public class ExampleCryptographerTest {
private ExampleCryptographer objectUnderTest = new ExampleCryptographer("knownKeyForTest=");
#Test
public void decrypt_givenEncryptedExample_ShouldSucceed() {
String example = "afasfdafafa=";
String encodedExample = objectUnderTest.encrypt(example);
String result = objectUnderTest.decrypt(encodedExample);
assertThat(result).isNotNull();
assertThat(result.length()).isEqualTo(48);
}
#Test
public void encrypt_givenExample_ShouldSucceed() {
String example = "afasfdafafa=";
String result = objectUnderTest.encrypt(example);
assertThat(result).isNotNull();
assertThat(result.length()).isEqualTo(48);
}
#Test
public void decrypt_givenEncryptedExampleWithOtherKey_ShouldFail() {
String example = "afasfdafafa=";
String encodedExample = new ExampleCryptographer("otherKeyForTest=").encrypt(example);
Throwable throwable = catchThrowable(() -> objectUnderTest.decrypt(encodedExample));
assertThat(throwable)
.isInstanceOf(IllegalArgumentException.class);
}
#Test(expected = InvalidKeyException.class)
public void encrypt_givenInvalidKey_ShouldFail() {
new ExampleCryptographer("invalid").encrypt("test");
}
}
and finally the actual code:
public class ExampleCryptographer {
private static final String ALGORITHM = "AES";
private final Key key;
private static final int T_LEN = 96;
private static final int IV_LENGTH = 12;
private final Base64 base64 = new Base64(76, null, true);
#SneakyThrows
public ExampleCryptographer(#Value("${myKey}") String secretKey) {
this.key = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
}
#SneakyThrows
public String encrypt(#NonNull String text) {
byte[] iv = new byte[IV_LENGTH];
(new SecureRandom()).nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec ivSpec = new GCMParameterSpec(T_LEN, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] ciphertext = cipher.doFinal(text.getBytes(UTF_8));
byte[] encrypted = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, encrypted, 0, iv.length);
System.arraycopy(ciphertext, 0, encrypted, iv.length, ciphertext.length);
return base64.encodeAsString(encrypted);
}
#SneakyThrows
public String decrypt(#NonNull String encryptedText) {
byte[] decoded = base64.decode(encryptedText);
byte[] iv = Arrays.copyOfRange(decoded, 0, IV_LENGTH);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec ivSpec = new GCMParameterSpec(T_LEN, iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decryptedText = cipher.doFinal(decoded);
return new String(decryptedText);
}
}
Can anyone help? I have been reading many about, and still cannot find anything wrong.
T_LEN is the size OF THE AUTHENTICATION TAG in bits. It should be large enough that the risk of (successful) forgery does not exceed that acceptable to your data owners, but is not related to the IV in any way. If you don't have an analysis that less is sufficient, and you aren't in a resource-constrained environment (and JavaSE never is), just use the max of 128.
Your main problem is in encrypt you reasonably concatenate IV+ciphertext (which for Java includes the tag), but in decrypt you use the first bytes as the IV and the whole buffer as the ciphertext when it should be Arrays.copyOfRange(decoded,IV_LENGTH,decoded.length).
Also, AES key must be exactly 16, 24, or 32 bytes and should be random bits, which cannot be reliably represented directly in a Java String. Generally you should use byte[] and if you need to pass or store it as a string encode to (and decode from) hex or base64.
Finally, on encrypt you encode with getBytes() as UTF-8, but on decrypt you decode with new String using the default encoding, which varies from one JVM to another and often depends on the environment and often isn't UTF-8 in which case this may return 'mojibake' (effectively garbage) not the data you encrypted.
Oh, and AEADBadTagException is not a subclass of IllegalArgumentException.
I don't know why an error is coming up.
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
I understand that this error occurs when the incorrect key is used during the decryption. However, if you look at the test results result below, you can see that both C# and Java are the same (Key, IV, Salt is Base64 encoded).
C# Test Result
Java Test Result
It's the same!(Key, IV, Salt)
But the current BadpaddingException error is generated. What could be the problem?
I am attaching my source file.
C# (Encryption)
class AES {
private readonly static string keyStr = "This is Key";
private readonly static string vector = "This is Vector";
public static Rfc2898DeriveBytes MakeKey(string password){
byte[] keyBytes = System.Text.Encoding.UTF8.GetBytes(password);
byte[] saltBytes = SHA512.Create().ComputeHash(keyBytes);
Rfc2898DeriveBytes result = new Rfc2898DeriveBytes(keyBytes, saltBytes, 65536);
return result;
}
public static Rfc2898DeriveBytes MakeVector(string vector){
byte[] vectorBytes = System.Text.Encoding.UTF8.GetBytes(vector);
byte[] saltBytes = SHA512.Create().ComputeHash(vectorBytes);
Rfc2898DeriveBytes result = new Rfc2898DeriveBytes(vectorBytes, saltBytes, 65536);
return result;
}
public static void Encrypt(String inputFile, String outputFile) {
using (RijndaelManaged aes = new RijndaelManaged()){
//Create Key and Vector
Rfc2898DeriveBytes key = AES.MakeKey(AES.keyStr);
Rfc2898DeriveBytes vector = AES.MakeVector(AES.vector);
//AES256
aes.BlockSize = 128;
aes.KeySize = 256;
// It is equal in java
// Cipher _Cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = key.GetBytes(32); //256bit key
aes.IV = vector.GetBytes(16); //128bit block size
//processing Encrypt
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
byte[] encrypted;
using (MemoryStream msEncrypt = new MemoryStream()) {
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) {
byte[] inputBytes = File.ReadAllBytes(inputFile);
csEncrypt.Write(inputBytes, 0, inputBytes.Length);
}
encrypted = msEncrypt.ToArray();
}
string encodedString = Convert.ToBase64String(encrypted);
File.WriteAllText(outputFile, encodedString);
}
}
}
Java (Decryption)
public class AES256File {
private static final String algorithm = "AES";
private static final String blockNPadding = algorithm+"/CBC/PKCS5Padding";
private static final String password = "This is Key";
private static final String IV = "This is Vector";
private static IvParameterSpec ivSpec;
private static Key keySpec;
public static void MakeKey(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException{
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
MessageDigest digest = MessageDigest.getInstance("SHA-512");
byte[] keyBytes = password.getBytes("UTF-8");
// C# : byte[] saltBytes = SHA512.Create().ComputeHash(keyBytes);
byte[] saltBytes = digest.digest(keyBytes);
//256bit
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), saltBytes, 65536, 256);
Key secretKey = factory.generateSecret(pbeKeySpec);
byte[] key = new byte[32];
System.arraycopy(secretKey.getEncoded(), 0, key, 0, 32);
SecretKeySpec secret = new SecretKeySpec(key, "AES");
setKeySpec(secret);
}
public static void MakeVector(String IV) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException{
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
MessageDigest digest = MessageDigest.getInstance("SHA-512");
byte[] vectorBytes = IV.getBytes("UTF-8");
byte[] saltBytes = digest.digest(vectorBytes);
// 128bit
PBEKeySpec pbeKeySpec = new PBEKeySpec(IV.toCharArray(), saltBytes, 65536, 128);
Key secretIV = factory.generateSecret(pbeKeySpec);
byte[] iv = new byte[16];
System.arraycopy(secretIV.getEncoded(), 0, iv, 0, 16);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
setIvSpec(ivSpec);
}
public void decrypt(File source, File dest) throws Exception {
Cipher c = Cipher.getInstance(blockNPadding);
c.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
fileProcessing(source, dest, c);
}
public void fileProcessing(File source, File dest, Cipher c) throws Exception{
InputStream input = null;
OutputStream output = null;
try{
input = new BufferedInputStream(new FileInputStream(source));
output = new BufferedOutputStream(new FileOutputStream(dest));
byte[] buffer = new byte[input.available()];
int read = -1;
while((read = input.read(buffer)) != -1){
output.write(c.update(buffer, 0, read));
}
byte[] deryptedBytes = c.doFinal(buffer); // -----------------------> Error!! Showing!
byte[] decodedBytes = Base64.getDecoder().decode(deryptedBytes);
String decodeString = new String(decodedBytes, "UTF-8");
decodedBytes = decodeString.getBytes(StandardCharsets.UTF_8);
output.write(decodedBytes);
}finally{
if(output != null){
try{output.close();}catch(IOException e){}
}
if(input != null){
try{input.close();}catch(IOException e){}
}
}
}
I have verified as below.
Verification Key and IV in C#
//Key Verification
var salt = Convert.ToBase64String(saltBytes);
Console.Write("Salt Result : ");
Console.WriteLine(salt);
var result_test = Convert.ToBase64String(result.GetBytes(32));
Console.Write("Key Test Result: ");
Console.WriteLine(result_test);
//IV Verification (Salt is Using same code)
var result_test = Convert.ToBase64String(result.GetBytes(16));
Console.Write("IV Test Result: ");
Console.WriteLine(result_test);
Verification Key and IV in Java
//Key Verification
/* print Salt */
String base64 = Base64.getEncoder().encodeToString(saltBytes);
System.out.println("Salt Result : " + base64);
/* print Key */
String result_test = Base64.getEncoder().encodeToString(key);
System.out.println("Key Test Result : " + result_test);
/* print generated Key */
System.out.println("Secret Key Result : " + Base64.getEncoder().encodeToString(secret.getEncoded()));
//IV Verification (Salt is Using same code)
/* print IV */
String result_test = Base64.getEncoder().encodeToString(iv);
System.out.println("IV Test Result : " + result_test);
/* print generated IV */
System.out.println("IV Result : " + Base64.getEncoder().encodeToString(ivSpec.getIV()));
Updated
c# .netframework 4.5 / Java8 modified what #Topaco said and confirmed that it worked well.
I want to say thank you very much to #Topaco and #Gusto2, and I'm going to make changes to the parts that have been modified in security, just as #Gusto2 said!
1) In the C# Encrypt-method the plain text is encrypted first and then Base64-encoded. Thus, in the decryption process the data must be Base64-decoded first and then decrypted. Currently this is handled in the wrong order i.e. the data are decrypted first and then decoded. Therefore, in the Java fileProcessing-method replace
while((read = input.read(buffer)) != -1){
output.write(c.update(buffer, 0, read));
}
with
while((read = input.read(buffer)) != -1) {
byte[] bufferEncoded = buffer;
if (read != buffer.length) {
bufferEncoded = Arrays.copyOf(buffer, read);
}
byte[] bufferDecoded = Base64.getDecoder().decode(bufferEncoded);
output.write(c.update(bufferDecoded));
}
2) It's not necessary to pass buffer (or bufferDecoded) to the doFinal-method, since that was already done in the update-method. Thus,
byte[] deryptedBytes = c.doFinal(buffer);
must be replaced with
output.write(c.doFinal());
3) Since the Base64-decoding is already done in 1) in the try-block all lines following the doFinal-statement have to be removed. Overall, this results in
try {
input = new BufferedInputStream(new FileInputStream(source));
output = new BufferedOutputStream(new FileOutputStream(dest));
byte[] buffer = new byte[input.available()];
int read = -1;
while((read = input.read(buffer)) != -1) {
byte[] bufferEncoded = buffer;
if (read != buffer.length) {
bufferEncoded = Arrays.copyOf(buffer, read);
}
byte[] bufferDecoded = Base64.getDecoder().decode(bufferEncoded);
output.write(c.update(bufferDecoded));
}
output.write(c.doFinal());
}
4) The size of the buffer has to be a multiple of 4 in order to ensure a proper Base64-decoding. Thus, it's more reliable to replace
byte[] buffer = new byte[input.available()];
with
byte[] buffer = new byte[4 * (input.available() / 4)];
As long as the data are read in one chunk (which is not guaranteed, see e.g. https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/InputStream.html#available()) there is no problem. However, if the data are read in several chunks it's important to read a multiple of 4 bytes, otherwise the Base64-decoding will fail. That can be easily proved by using a buffer size which isn't a multiple of 4. This point must also be considered if the buffer size is explicitly defined with regard to larger files.
while((read = input.read(buffer)) != -1){
output.write(c.update(buffer, 0, read));
}
byte[] deryptedBytes = c.doFinal(buffer)
you are decrypting the input to a file, then you are using the same cipher instance to decrypt the the last read chunk (again) into a separate array not to the file
quick fix:
while((read = input.read(buffer)) != -1){
output.write(c.update(buffer, 0, read));
}
output.write(c.doFinal()); // write the padded block
if you want to create and print a decrypted String, you need to create a new Cipher instance (or maybe it will be enough to reinitialize the instance, I am not sure) assuming the buffer contains the whole input
c.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
// assuming the buffer contains the whole input again
byte[] deryptedBytes = c.doFinal(buffer); // decrypting the whole file again
correct approach:
IV is used to securely reuse the same encryption key for multiple encryptions. So if your key is not random, you should generate new random IV for each encryption (and pass the IV along the ciphertext, most often prepended). Otherwise the encryption is not semantically secure and you may create opening for the two pad attack. So deriving IV from the key may not be very secure.
I advice to use any MAC (authentication code) passed along the ciphertext to ensure integrity (e.g. HMAC)
you are still reading all the file input fully into memory, what would not work for REALLY LARGE files. You may initialize the buffer to an arbitrary length (a few MB?) and process the input file as chunked
I have a sample code,which encrypt and decrypt a string using AES-GCM-256.
I am unable to understand,how authentication tag is being generated on encrypter side and how is that being used on decrypter side.
Actually here i am not generating authentication tag either on encrypter side nor validating decrypter side,so is it being done internally by library itself.
private static String encrypt(String s, byte[] k) throws Exception {
SecureRandom r = SecureRandom.getInstance("SHA1PRNG");
// Generate 128 bit IV for Encryption
byte[] iv = new byte[12]; r.nextBytes(iv);
SecretKeySpec eks = new SecretKeySpec(k, "AES");
Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
// Generated Authentication Tag should be 128 bits
c.init(Cipher.ENCRYPT_MODE, eks, new GCMParameterSpec(128, iv));
byte[] es = c.doFinal(s.getBytes(StandardCharsets.UTF_8));
// Construct Output as "IV + CIPHERTEXT"
byte[] os = new byte[12 + es.length];
System.arraycopy(iv, 0, os, 0, 12);
System.arraycopy(es, 0, os, 12, es.length);
// Return a Base64 Encoded String
return Base64.getEncoder().encodeToString(os);
}
private static String decrypt(String eos, byte[] k) throws Exception {
// Recover our Byte Array by Base64 Decoding
byte[] os = Base64.getDecoder().decode(eos);
// Check Minimum Length (IV (12) + TAG (16))
if (os.length > 28) {
byte[] iv = Arrays.copyOfRange(os, 0, 12);
byte[] es = Arrays.copyOfRange(os, 12, os.length);
// Perform Decryption
SecretKeySpec dks = new SecretKeySpec(k, "AES");
Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
c.init(Cipher.DECRYPT_MODE, dks, new GCMParameterSpec(128, iv));
// Return our Decrypted String
return new String(c.doFinal(es), StandardCharsets.UTF_8);
}
throw new Exception();
}